Repository: flutter/plugins Branch: main Commit: 557d3284ac9d Files: 3782 Total size: 12.8 MB Directory structure: gitextract_ziolw3rl/ ├── .ci/ │ ├── Dockerfile │ ├── flutter_master.version │ ├── flutter_stable.version │ ├── scripts/ │ │ ├── build_all_plugins.sh │ │ ├── build_examples_win32.sh │ │ ├── create_all_plugins_app.sh │ │ ├── create_simulator.sh │ │ ├── drive_examples_win32.sh │ │ ├── native_test_win32.sh │ │ └── prepare_tool.sh │ └── targets/ │ ├── ios_build_all_plugins.yaml │ ├── ios_platform_tests.yaml │ ├── macos_build_all_plugins.yaml │ ├── macos_lint_podspecs.yaml │ ├── macos_platform_tests.yaml │ ├── windows_build_all_plugins.yaml │ └── windows_build_and_platform_tests.yaml ├── .ci.yaml ├── .cirrus.yml ├── .clang-format ├── .gitattributes ├── .github/ │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ ├── labeler.yml │ ├── post_merge_labeler.yml │ └── workflows/ │ ├── pull_request_label.yml │ ├── release.yml │ └── scorecards-analysis.yml ├── .gitignore ├── .gitmodules ├── .opensource/ │ └── project.json ├── AUTHORS ├── CODEOWNERS ├── CONTRIBUTING.md ├── FlutterFire.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── packages/ │ ├── camera/ │ │ ├── camera/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── cameraexample/ │ │ │ │ │ │ │ └── FlutterActivityTest.java │ │ │ │ │ │ └── main/ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ └── res/ │ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ └── values/ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── build.excerpt.yaml │ │ │ │ ├── integration_test/ │ │ │ │ │ └── camera_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ │ └── README.md │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ ├── lib/ │ │ │ │ │ ├── main.dart │ │ │ │ │ └── readme_full_example.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ ├── test/ │ │ │ │ │ └── main_test.dart │ │ │ │ ├── test_driver/ │ │ │ │ │ └── integration_test.dart │ │ │ │ └── web/ │ │ │ │ ├── index.html │ │ │ │ └── manifest.json │ │ │ ├── lib/ │ │ │ │ ├── camera.dart │ │ │ │ └── src/ │ │ │ │ ├── camera_controller.dart │ │ │ │ ├── camera_image.dart │ │ │ │ └── camera_preview.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── camera_image_stream_test.dart │ │ │ ├── camera_image_test.dart │ │ │ ├── camera_preview_test.dart │ │ │ ├── camera_test.dart │ │ │ └── camera_value_test.dart │ │ ├── camera_android/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── android/ │ │ │ │ ├── build.gradle │ │ │ │ ├── lint-baseline.xml │ │ │ │ ├── settings.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ └── java/ │ │ │ │ │ └── io/ │ │ │ │ │ └── flutter/ │ │ │ │ │ └── plugins/ │ │ │ │ │ └── camera/ │ │ │ │ │ ├── Camera.java │ │ │ │ │ ├── CameraCaptureCallback.java │ │ │ │ │ ├── CameraPermissions.java │ │ │ │ │ ├── CameraPlugin.java │ │ │ │ │ ├── CameraProperties.java │ │ │ │ │ ├── CameraRegionUtils.java │ │ │ │ │ ├── CameraState.java │ │ │ │ │ ├── CameraUtils.java │ │ │ │ │ ├── DartMessenger.java │ │ │ │ │ ├── ImageSaver.java │ │ │ │ │ ├── MethodCallHandlerImpl.java │ │ │ │ │ ├── features/ │ │ │ │ │ │ ├── CameraFeature.java │ │ │ │ │ │ ├── CameraFeatureFactory.java │ │ │ │ │ │ ├── CameraFeatureFactoryImpl.java │ │ │ │ │ │ ├── CameraFeatures.java │ │ │ │ │ │ ├── Point.java │ │ │ │ │ │ ├── autofocus/ │ │ │ │ │ │ │ ├── AutoFocusFeature.java │ │ │ │ │ │ │ └── FocusMode.java │ │ │ │ │ │ ├── exposurelock/ │ │ │ │ │ │ │ ├── ExposureLockFeature.java │ │ │ │ │ │ │ └── ExposureMode.java │ │ │ │ │ │ ├── exposureoffset/ │ │ │ │ │ │ │ └── ExposureOffsetFeature.java │ │ │ │ │ │ ├── exposurepoint/ │ │ │ │ │ │ │ └── ExposurePointFeature.java │ │ │ │ │ │ ├── flash/ │ │ │ │ │ │ │ ├── FlashFeature.java │ │ │ │ │ │ │ └── FlashMode.java │ │ │ │ │ │ ├── focuspoint/ │ │ │ │ │ │ │ └── FocusPointFeature.java │ │ │ │ │ │ ├── fpsrange/ │ │ │ │ │ │ │ └── FpsRangeFeature.java │ │ │ │ │ │ ├── noisereduction/ │ │ │ │ │ │ │ ├── NoiseReductionFeature.java │ │ │ │ │ │ │ └── NoiseReductionMode.java │ │ │ │ │ │ ├── resolution/ │ │ │ │ │ │ │ ├── ResolutionFeature.java │ │ │ │ │ │ │ └── ResolutionPreset.java │ │ │ │ │ │ ├── sensororientation/ │ │ │ │ │ │ │ ├── DeviceOrientationManager.java │ │ │ │ │ │ │ └── SensorOrientationFeature.java │ │ │ │ │ │ └── zoomlevel/ │ │ │ │ │ │ ├── ZoomLevelFeature.java │ │ │ │ │ │ └── ZoomUtils.java │ │ │ │ │ ├── media/ │ │ │ │ │ │ └── MediaRecorderBuilder.java │ │ │ │ │ └── types/ │ │ │ │ │ ├── CameraCaptureProperties.java │ │ │ │ │ ├── CaptureTimeoutsWrapper.java │ │ │ │ │ ├── ExposureMode.java │ │ │ │ │ ├── FlashMode.java │ │ │ │ │ ├── FocusMode.java │ │ │ │ │ ├── ResolutionPreset.java │ │ │ │ │ └── Timeout.java │ │ │ │ └── test/ │ │ │ │ ├── java/ │ │ │ │ │ └── io/ │ │ │ │ │ └── flutter/ │ │ │ │ │ └── plugins/ │ │ │ │ │ └── camera/ │ │ │ │ │ ├── CameraCaptureCallbackStatesTest.java │ │ │ │ │ ├── CameraCaptureCallbackTest.java │ │ │ │ │ ├── CameraPermissionsTest.java │ │ │ │ │ ├── CameraPropertiesImplTest.java │ │ │ │ │ ├── CameraRegionUtils_convertPointToMeteringRectangleTest.java │ │ │ │ │ ├── CameraRegionUtils_getCameraBoundariesTest.java │ │ │ │ │ ├── CameraTest.java │ │ │ │ │ ├── CameraTest_getRecordingProfileTest.java │ │ │ │ │ ├── CameraUtilsTest.java │ │ │ │ │ ├── DartMessengerTest.java │ │ │ │ │ ├── ImageSaverTests.java │ │ │ │ │ ├── MethodCallHandlerImplTest.java │ │ │ │ │ ├── features/ │ │ │ │ │ │ ├── autofocus/ │ │ │ │ │ │ │ ├── AutoFocusFeatureTest.java │ │ │ │ │ │ │ └── FocusModeTest.java │ │ │ │ │ │ ├── exposurelock/ │ │ │ │ │ │ │ ├── ExposureLockFeatureTest.java │ │ │ │ │ │ │ └── ExposureModeTest.java │ │ │ │ │ │ ├── exposureoffset/ │ │ │ │ │ │ │ └── ExposureOffsetFeatureTest.java │ │ │ │ │ │ ├── exposurepoint/ │ │ │ │ │ │ │ └── ExposurePointFeatureTest.java │ │ │ │ │ │ ├── flash/ │ │ │ │ │ │ │ └── FlashFeatureTest.java │ │ │ │ │ │ ├── focuspoint/ │ │ │ │ │ │ │ └── FocusPointFeatureTest.java │ │ │ │ │ │ ├── fpsrange/ │ │ │ │ │ │ │ ├── FpsRangeFeaturePixel4aTest.java │ │ │ │ │ │ │ └── FpsRangeFeatureTest.java │ │ │ │ │ │ ├── noisereduction/ │ │ │ │ │ │ │ └── NoiseReductionFeatureTest.java │ │ │ │ │ │ ├── resolution/ │ │ │ │ │ │ │ └── ResolutionFeatureTest.java │ │ │ │ │ │ ├── sensororientation/ │ │ │ │ │ │ │ ├── DeviceOrientationManagerTest.java │ │ │ │ │ │ │ └── SensorOrientationFeatureTest.java │ │ │ │ │ │ └── zoomlevel/ │ │ │ │ │ │ ├── ZoomLevelFeatureTest.java │ │ │ │ │ │ └── ZoomUtilsTest.java │ │ │ │ │ ├── media/ │ │ │ │ │ │ └── MediaRecorderBuilderTest.java │ │ │ │ │ ├── types/ │ │ │ │ │ │ ├── ExposureModeTest.java │ │ │ │ │ │ ├── FlashModeTest.java │ │ │ │ │ │ └── FocusModeTest.java │ │ │ │ │ └── utils/ │ │ │ │ │ └── TestUtils.java │ │ │ │ └── resources/ │ │ │ │ └── robolectric.properties │ │ │ ├── example/ │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── cameraexample/ │ │ │ │ │ │ │ └── FlutterActivityTest.java │ │ │ │ │ │ └── main/ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ └── res/ │ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ └── values/ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── integration_test/ │ │ │ │ │ └── camera_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ ├── camera_controller.dart │ │ │ │ │ ├── camera_preview.dart │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── lib/ │ │ │ │ ├── camera_android.dart │ │ │ │ └── src/ │ │ │ │ ├── android_camera.dart │ │ │ │ ├── type_conversion.dart │ │ │ │ └── utils.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── android_camera_test.dart │ │ │ ├── method_channel_mock.dart │ │ │ ├── type_conversion_test.dart │ │ │ └── utils_test.dart │ │ ├── camera_android_camerax/ │ │ │ ├── .metadata │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── android/ │ │ │ │ ├── build.gradle │ │ │ │ ├── settings.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ └── java/ │ │ │ │ │ └── io/ │ │ │ │ │ └── flutter/ │ │ │ │ │ └── plugins/ │ │ │ │ │ └── camerax/ │ │ │ │ │ ├── CameraAndroidCameraxPlugin.java │ │ │ │ │ ├── CameraFlutterApiImpl.java │ │ │ │ │ ├── CameraInfoFlutterApiImpl.java │ │ │ │ │ ├── CameraInfoHostApiImpl.java │ │ │ │ │ ├── CameraPermissionsManager.java │ │ │ │ │ ├── CameraSelectorFlutterApiImpl.java │ │ │ │ │ ├── CameraSelectorHostApiImpl.java │ │ │ │ │ ├── CameraXProxy.java │ │ │ │ │ ├── DeviceOrientationManager.java │ │ │ │ │ ├── GeneratedCameraXLibrary.java │ │ │ │ │ ├── InstanceManager.java │ │ │ │ │ ├── JavaObjectHostApiImpl.java │ │ │ │ │ ├── PreviewHostApiImpl.java │ │ │ │ │ ├── ProcessCameraProviderFlutterApiImpl.java │ │ │ │ │ ├── ProcessCameraProviderHostApiImpl.java │ │ │ │ │ ├── SystemServicesFlutterApiImpl.java │ │ │ │ │ └── SystemServicesHostApiImpl.java │ │ │ │ └── test/ │ │ │ │ └── java/ │ │ │ │ └── io/ │ │ │ │ └── flutter/ │ │ │ │ └── plugins/ │ │ │ │ └── camerax/ │ │ │ │ ├── CameraInfoTest.java │ │ │ │ ├── CameraPermissionsManagerTest.java │ │ │ │ ├── CameraSelectorTest.java │ │ │ │ ├── CameraTest.java │ │ │ │ ├── DeviceOrientationManagerTest.java │ │ │ │ ├── InstanceManagerTest.java │ │ │ │ ├── JavaObjectHostApiTest.java │ │ │ │ ├── PreviewTest.java │ │ │ │ ├── ProcessCameraProviderTest.java │ │ │ │ └── SystemServicesTest.java │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── cameraxexample/ │ │ │ │ │ │ │ └── MainActivityTest.java │ │ │ │ │ │ ├── debug/ │ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ │ ├── main/ │ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ │ └── cameraexample/ │ │ │ │ │ │ │ │ └── MainActivity.java │ │ │ │ │ │ │ └── res/ │ │ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ │ ├── drawable-v21/ │ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ │ ├── values/ │ │ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ │ │ └── values-night/ │ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ │ └── profile/ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── integration_test/ │ │ │ │ │ └── integration_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ ├── camera_controller.dart │ │ │ │ │ ├── camera_image.dart │ │ │ │ │ ├── camera_preview.dart │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ ├── test/ │ │ │ │ │ └── widget_test.dart │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── lib/ │ │ │ │ ├── camera_android_camerax.dart │ │ │ │ └── src/ │ │ │ │ ├── android_camera_camerax.dart │ │ │ │ ├── android_camera_camerax_flutter_api_impls.dart │ │ │ │ ├── camera.dart │ │ │ │ ├── camera_info.dart │ │ │ │ ├── camera_selector.dart │ │ │ │ ├── camerax_library.g.dart │ │ │ │ ├── instance_manager.dart │ │ │ │ ├── java_object.dart │ │ │ │ ├── preview.dart │ │ │ │ ├── process_camera_provider.dart │ │ │ │ ├── surface.dart │ │ │ │ ├── system_services.dart │ │ │ │ └── use_case.dart │ │ │ ├── pigeons/ │ │ │ │ └── camerax_library.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── android_camera_camerax_test.dart │ │ │ ├── android_camera_camerax_test.mocks.dart │ │ │ ├── camera_info_test.dart │ │ │ ├── camera_info_test.mocks.dart │ │ │ ├── camera_selector_test.dart │ │ │ ├── camera_selector_test.mocks.dart │ │ │ ├── camera_test.dart │ │ │ ├── instance_manager_test.dart │ │ │ ├── preview_test.dart │ │ │ ├── preview_test.mocks.dart │ │ │ ├── process_camera_provider_test.dart │ │ │ ├── process_camera_provider_test.mocks.dart │ │ │ ├── system_services_test.dart │ │ │ ├── system_services_test.mocks.dart │ │ │ └── test_camerax_library.g.dart │ │ ├── camera_avfoundation/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── integration_test/ │ │ │ │ │ └── camera_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ │ └── README.md │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ └── RunnerTests/ │ │ │ │ │ ├── AvailableCamerasTest.m │ │ │ │ │ ├── CameraCaptureSessionQueueRaceConditionTests.m │ │ │ │ │ ├── CameraExposureTests.m │ │ │ │ │ ├── CameraFocusTests.m │ │ │ │ │ ├── CameraMethodChannelTests.m │ │ │ │ │ ├── CameraOrientationTests.m │ │ │ │ │ ├── CameraPermissionTests.m │ │ │ │ │ ├── CameraPreviewPauseTests.m │ │ │ │ │ ├── CameraPropertiesTests.m │ │ │ │ │ ├── CameraTestUtils.h │ │ │ │ │ ├── CameraTestUtils.m │ │ │ │ │ ├── CameraUtilTests.m │ │ │ │ │ ├── FLTCamPhotoCaptureTests.m │ │ │ │ │ ├── FLTCamSampleBufferTests.m │ │ │ │ │ ├── FLTSavePhotoDelegateTests.m │ │ │ │ │ ├── Info.plist │ │ │ │ │ ├── MockFLTThreadSafeFlutterResult.h │ │ │ │ │ ├── MockFLTThreadSafeFlutterResult.m │ │ │ │ │ ├── QueueUtilsTests.m │ │ │ │ │ ├── StreamingTest.m │ │ │ │ │ ├── ThreadSafeEventChannelTests.m │ │ │ │ │ ├── ThreadSafeFlutterResultTests.m │ │ │ │ │ ├── ThreadSafeMethodChannelTests.m │ │ │ │ │ └── ThreadSafeTextureRegistryTests.m │ │ │ │ ├── lib/ │ │ │ │ │ ├── camera_controller.dart │ │ │ │ │ ├── camera_preview.dart │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── ios/ │ │ │ │ ├── Assets/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── Classes/ │ │ │ │ │ ├── CameraPermissionUtils.h │ │ │ │ │ ├── CameraPermissionUtils.m │ │ │ │ │ ├── CameraPlugin.h │ │ │ │ │ ├── CameraPlugin.m │ │ │ │ │ ├── CameraPlugin.modulemap │ │ │ │ │ ├── CameraPlugin_Test.h │ │ │ │ │ ├── CameraProperties.h │ │ │ │ │ ├── CameraProperties.m │ │ │ │ │ ├── FLTCam.h │ │ │ │ │ ├── FLTCam.m │ │ │ │ │ ├── FLTCam_Test.h │ │ │ │ │ ├── FLTSavePhotoDelegate.h │ │ │ │ │ ├── FLTSavePhotoDelegate.m │ │ │ │ │ ├── FLTSavePhotoDelegate_Test.h │ │ │ │ │ ├── FLTThreadSafeEventChannel.h │ │ │ │ │ ├── FLTThreadSafeEventChannel.m │ │ │ │ │ ├── FLTThreadSafeFlutterResult.h │ │ │ │ │ ├── FLTThreadSafeFlutterResult.m │ │ │ │ │ ├── FLTThreadSafeMethodChannel.h │ │ │ │ │ ├── FLTThreadSafeMethodChannel.m │ │ │ │ │ ├── FLTThreadSafeTextureRegistry.h │ │ │ │ │ ├── FLTThreadSafeTextureRegistry.m │ │ │ │ │ ├── QueueUtils.h │ │ │ │ │ ├── QueueUtils.m │ │ │ │ │ └── camera_avfoundation-umbrella.h │ │ │ │ └── camera_avfoundation.podspec │ │ │ ├── lib/ │ │ │ │ ├── camera_avfoundation.dart │ │ │ │ └── src/ │ │ │ │ ├── avfoundation_camera.dart │ │ │ │ ├── type_conversion.dart │ │ │ │ └── utils.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── avfoundation_camera_test.dart │ │ │ ├── method_channel_mock.dart │ │ │ ├── type_conversion_test.dart │ │ │ └── utils_test.dart │ │ ├── camera_platform_interface/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── lib/ │ │ │ │ ├── camera_platform_interface.dart │ │ │ │ └── src/ │ │ │ │ ├── events/ │ │ │ │ │ ├── camera_event.dart │ │ │ │ │ └── device_event.dart │ │ │ │ ├── method_channel/ │ │ │ │ │ ├── method_channel_camera.dart │ │ │ │ │ └── type_conversion.dart │ │ │ │ ├── platform_interface/ │ │ │ │ │ └── camera_platform.dart │ │ │ │ ├── types/ │ │ │ │ │ ├── camera_description.dart │ │ │ │ │ ├── camera_exception.dart │ │ │ │ │ ├── camera_image_data.dart │ │ │ │ │ ├── exposure_mode.dart │ │ │ │ │ ├── flash_mode.dart │ │ │ │ │ ├── focus_mode.dart │ │ │ │ │ ├── image_format_group.dart │ │ │ │ │ ├── resolution_preset.dart │ │ │ │ │ ├── types.dart │ │ │ │ │ └── video_capture_options.dart │ │ │ │ └── utils/ │ │ │ │ └── utils.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── camera_platform_interface_test.dart │ │ │ ├── events/ │ │ │ │ ├── camera_event_test.dart │ │ │ │ └── device_event_test.dart │ │ │ ├── method_channel/ │ │ │ │ ├── method_channel_camera_test.dart │ │ │ │ └── type_conversion_test.dart │ │ │ ├── types/ │ │ │ │ ├── camera_description_test.dart │ │ │ │ ├── camera_exception_test.dart │ │ │ │ ├── camera_image_data_test.dart │ │ │ │ ├── exposure_mode_test.dart │ │ │ │ ├── flash_mode_test.dart │ │ │ │ ├── focus_mode_test.dart │ │ │ │ ├── image_group_test.dart │ │ │ │ └── resolution_preset_test.dart │ │ │ └── utils/ │ │ │ ├── method_channel_mock.dart │ │ │ └── utils_test.dart │ │ ├── camera_web/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── integration_test/ │ │ │ │ │ ├── camera_error_code_test.dart │ │ │ │ │ ├── camera_metadata_test.dart │ │ │ │ │ ├── camera_options_test.dart │ │ │ │ │ ├── camera_service_test.dart │ │ │ │ │ ├── camera_test.dart │ │ │ │ │ ├── camera_web_exception_test.dart │ │ │ │ │ ├── camera_web_test.dart │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ ├── helpers.dart │ │ │ │ │ │ └── mocks.dart │ │ │ │ │ └── zoom_level_capability_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ ├── run_test.sh │ │ │ │ ├── test_driver/ │ │ │ │ │ └── integration_test.dart │ │ │ │ └── web/ │ │ │ │ └── index.html │ │ │ ├── lib/ │ │ │ │ ├── camera_web.dart │ │ │ │ └── src/ │ │ │ │ ├── camera.dart │ │ │ │ ├── camera_service.dart │ │ │ │ ├── camera_web.dart │ │ │ │ ├── shims/ │ │ │ │ │ ├── dart_js_util.dart │ │ │ │ │ ├── dart_ui.dart │ │ │ │ │ ├── dart_ui_fake.dart │ │ │ │ │ └── dart_ui_real.dart │ │ │ │ └── types/ │ │ │ │ ├── camera_error_code.dart │ │ │ │ ├── camera_metadata.dart │ │ │ │ ├── camera_options.dart │ │ │ │ ├── camera_web_exception.dart │ │ │ │ ├── media_device_kind.dart │ │ │ │ ├── orientation_type.dart │ │ │ │ ├── types.dart │ │ │ │ └── zoom_level_capability.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── README.md │ │ │ └── more_tests_exist_elsewhere_test.dart │ │ └── camera_windows/ │ │ ├── .gitignore │ │ ├── .metadata │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── example/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── README.md │ │ │ ├── integration_test/ │ │ │ │ └── camera_test.dart │ │ │ ├── lib/ │ │ │ │ └── main.dart │ │ │ ├── pubspec.yaml │ │ │ ├── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ └── windows/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ ├── flutter/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ └── generated_plugins.cmake │ │ │ └── runner/ │ │ │ ├── CMakeLists.txt │ │ │ ├── Runner.rc │ │ │ ├── flutter_window.cpp │ │ │ ├── flutter_window.h │ │ │ ├── main.cpp │ │ │ ├── resource.h │ │ │ ├── runner.exe.manifest │ │ │ ├── utils.cpp │ │ │ ├── utils.h │ │ │ ├── win32_window.cpp │ │ │ └── win32_window.h │ │ ├── lib/ │ │ │ └── camera_windows.dart │ │ ├── pubspec.yaml │ │ ├── test/ │ │ │ ├── camera_windows_test.dart │ │ │ └── utils/ │ │ │ └── method_channel_mock.dart │ │ └── windows/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── camera.cpp │ │ ├── camera.h │ │ ├── camera_plugin.cpp │ │ ├── camera_plugin.h │ │ ├── camera_windows.cpp │ │ ├── capture_controller.cpp │ │ ├── capture_controller.h │ │ ├── capture_controller_listener.h │ │ ├── capture_device_info.cpp │ │ ├── capture_device_info.h │ │ ├── capture_engine_listener.cpp │ │ ├── capture_engine_listener.h │ │ ├── com_heap_ptr.h │ │ ├── include/ │ │ │ └── camera_windows/ │ │ │ └── camera_windows.h │ │ ├── photo_handler.cpp │ │ ├── photo_handler.h │ │ ├── preview_handler.cpp │ │ ├── preview_handler.h │ │ ├── record_handler.cpp │ │ ├── record_handler.h │ │ ├── string_utils.cpp │ │ ├── string_utils.h │ │ ├── test/ │ │ │ ├── camera_plugin_test.cpp │ │ │ ├── camera_test.cpp │ │ │ ├── capture_controller_test.cpp │ │ │ └── mocks.h │ │ ├── texture_handler.cpp │ │ └── texture_handler.h │ ├── e2e/ │ │ └── README.md │ ├── espresso/ │ │ ├── .gitignore │ │ ├── .metadata │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── android/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── lint-baseline.xml │ │ │ ├── settings.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── AndroidManifest.xml │ │ │ └── java/ │ │ │ ├── androidx/ │ │ │ │ └── test/ │ │ │ │ └── espresso/ │ │ │ │ └── flutter/ │ │ │ │ ├── EspressoFlutter.java │ │ │ │ ├── action/ │ │ │ │ │ ├── ActionUtil.java │ │ │ │ │ ├── ClickAction.java │ │ │ │ │ ├── FlutterActions.java │ │ │ │ │ ├── FlutterScrollToAction.java │ │ │ │ │ ├── FlutterTypeTextAction.java │ │ │ │ │ ├── FlutterViewAction.java │ │ │ │ │ ├── SyntheticClickAction.java │ │ │ │ │ ├── WaitUntilIdleAction.java │ │ │ │ │ ├── WidgetCoordinatesCalculator.java │ │ │ │ │ └── WidgetInfoFetcher.java │ │ │ │ ├── api/ │ │ │ │ │ ├── FlutterAction.java │ │ │ │ │ ├── FlutterTestingProtocol.java │ │ │ │ │ ├── SyntheticAction.java │ │ │ │ │ ├── WidgetAction.java │ │ │ │ │ ├── WidgetAssertion.java │ │ │ │ │ └── WidgetMatcher.java │ │ │ │ ├── assertion/ │ │ │ │ │ ├── FlutterAssertions.java │ │ │ │ │ └── FlutterViewAssertion.java │ │ │ │ ├── common/ │ │ │ │ │ ├── Constants.java │ │ │ │ │ └── Duration.java │ │ │ │ ├── exception/ │ │ │ │ │ ├── AmbiguousWidgetMatcherException.java │ │ │ │ │ ├── InvalidFlutterViewException.java │ │ │ │ │ └── NoMatchingWidgetException.java │ │ │ │ ├── internal/ │ │ │ │ │ ├── idgenerator/ │ │ │ │ │ │ ├── IdException.java │ │ │ │ │ │ ├── IdGenerator.java │ │ │ │ │ │ └── IdGenerators.java │ │ │ │ │ ├── jsonrpc/ │ │ │ │ │ │ ├── JsonRpcClient.java │ │ │ │ │ │ └── message/ │ │ │ │ │ │ ├── ErrorObject.java │ │ │ │ │ │ ├── JsonRpcRequest.java │ │ │ │ │ │ └── JsonRpcResponse.java │ │ │ │ │ └── protocol/ │ │ │ │ │ └── impl/ │ │ │ │ │ ├── DartVmService.java │ │ │ │ │ ├── DartVmServiceUtil.java │ │ │ │ │ ├── FlutterProtocolException.java │ │ │ │ │ ├── GetOffsetAction.java │ │ │ │ │ ├── GetOffsetResponse.java │ │ │ │ │ ├── GetVmResponse.java │ │ │ │ │ ├── GetWidgetDiagnosticsAction.java │ │ │ │ │ ├── GetWidgetDiagnosticsResponse.java │ │ │ │ │ ├── NoPendingFrameCondition.java │ │ │ │ │ ├── NoPendingPlatformMessagesCondition.java │ │ │ │ │ ├── NoTransientCallbacksCondition.java │ │ │ │ │ ├── WaitCondition.java │ │ │ │ │ ├── WaitForConditionAction.java │ │ │ │ │ └── WidgetInfoFactory.java │ │ │ │ ├── matcher/ │ │ │ │ │ ├── FlutterMatchers.java │ │ │ │ │ ├── IsDescendantOfMatcher.java │ │ │ │ │ ├── IsExistingMatcher.java │ │ │ │ │ ├── WithTextMatcher.java │ │ │ │ │ ├── WithTooltipMatcher.java │ │ │ │ │ ├── WithTypeMatcher.java │ │ │ │ │ └── WithValueKeyMatcher.java │ │ │ │ └── model/ │ │ │ │ ├── WidgetInfo.java │ │ │ │ └── WidgetInfoBuilder.java │ │ │ └── com/ │ │ │ └── example/ │ │ │ └── espresso/ │ │ │ └── EspressoPlugin.java │ │ ├── example/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── README.md │ │ │ ├── android/ │ │ │ │ ├── .gitignore │ │ │ │ ├── app/ │ │ │ │ │ ├── build.gradle │ │ │ │ │ └── src/ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ └── example/ │ │ │ │ │ │ └── MainActivityTest.java │ │ │ │ │ ├── debug/ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ ├── main/ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ └── com/ │ │ │ │ │ │ │ └── example/ │ │ │ │ │ │ │ └── espresso_example/ │ │ │ │ │ │ │ └── MainActivity.java │ │ │ │ │ │ └── res/ │ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ └── values/ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── profile/ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ ├── build.gradle │ │ │ │ ├── gradle/ │ │ │ │ │ └── wrapper/ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ ├── gradle.properties │ │ │ │ └── settings.gradle │ │ │ ├── lib/ │ │ │ │ └── main.dart │ │ │ ├── pubspec.yaml │ │ │ └── test_driver/ │ │ │ └── example.dart │ │ └── pubspec.yaml │ ├── file_selector/ │ │ ├── file_selector/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── .gitignore │ │ │ │ ├── .metadata │ │ │ │ ├── README.md │ │ │ │ ├── build.excerpt.yaml │ │ │ │ ├── ios/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ │ └── README.md │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── Runner-Bridging-Header.h │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ │ ├── lib/ │ │ │ │ │ ├── get_directory_page.dart │ │ │ │ │ ├── home_page.dart │ │ │ │ │ ├── main.dart │ │ │ │ │ ├── open_image_page.dart │ │ │ │ │ ├── open_multiple_images_page.dart │ │ │ │ │ ├── open_text_page.dart │ │ │ │ │ ├── readme_standalone_excerpts.dart │ │ │ │ │ └── save_text_page.dart │ │ │ │ ├── linux/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ ├── flutter/ │ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ │ └── generated_plugins.cmake │ │ │ │ │ ├── main.cc │ │ │ │ │ ├── my_application.cc │ │ │ │ │ └── my_application.h │ │ │ │ ├── macos/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── Flutter-Debug.xcconfig │ │ │ │ │ │ └── Flutter-Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ └── MainMenu.xib │ │ │ │ │ │ ├── Configs/ │ │ │ │ │ │ │ ├── AppInfo.xcconfig │ │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ │ ├── Release.xcconfig │ │ │ │ │ │ │ └── Warnings.xcconfig │ │ │ │ │ │ ├── DebugProfile.entitlements │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ ├── MainFlutterWindow.swift │ │ │ │ │ │ └── Release.entitlements │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ ├── pubspec.yaml │ │ │ │ ├── web/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── manifest.json │ │ │ │ └── windows/ │ │ │ │ ├── .gitignore │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── flutter/ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ └── generated_plugins.cmake │ │ │ │ └── runner/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── Runner.rc │ │ │ │ ├── flutter_window.cpp │ │ │ │ ├── flutter_window.h │ │ │ │ ├── main.cpp │ │ │ │ ├── resource.h │ │ │ │ ├── runner.exe.manifest │ │ │ │ ├── utils.cpp │ │ │ │ ├── utils.h │ │ │ │ ├── win32_window.cpp │ │ │ │ └── win32_window.h │ │ │ ├── lib/ │ │ │ │ └── file_selector.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── file_selector_test.dart │ │ ├── file_selector_ios/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── .gitignore │ │ │ │ ├── .metadata │ │ │ │ ├── README.md │ │ │ │ ├── ios/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ │ └── README.md │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── Runner-Bridging-Header.h │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ │ │ └── RunnerTests/ │ │ │ │ │ └── FileSelectorTests.m │ │ │ │ ├── lib/ │ │ │ │ │ ├── home_page.dart │ │ │ │ │ ├── main.dart │ │ │ │ │ ├── open_image_page.dart │ │ │ │ │ ├── open_multiple_images_page.dart │ │ │ │ │ └── open_text_page.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── ios/ │ │ │ │ ├── .gitignore │ │ │ │ ├── Assets/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── Classes/ │ │ │ │ │ ├── FFSFileSelectorPlugin.h │ │ │ │ │ ├── FFSFileSelectorPlugin.m │ │ │ │ │ ├── FFSFileSelectorPlugin_Test.h │ │ │ │ │ ├── FileSelectorPlugin.modulemap │ │ │ │ │ ├── file_selector_ios-umbrella.h │ │ │ │ │ ├── messages.g.h │ │ │ │ │ └── messages.g.m │ │ │ │ └── file_selector_ios.podspec │ │ │ ├── lib/ │ │ │ │ ├── file_selector_ios.dart │ │ │ │ └── src/ │ │ │ │ └── messages.g.dart │ │ │ ├── pigeons/ │ │ │ │ ├── copyright.txt │ │ │ │ └── messages.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── file_selector_ios_test.dart │ │ │ ├── file_selector_ios_test.mocks.dart │ │ │ └── test_api.g.dart │ │ ├── file_selector_linux/ │ │ │ ├── .gitignore │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── .gitignore │ │ │ │ ├── .metadata │ │ │ │ ├── README.md │ │ │ │ ├── lib/ │ │ │ │ │ ├── get_directory_page.dart │ │ │ │ │ ├── get_multiple_directories_page.dart │ │ │ │ │ ├── home_page.dart │ │ │ │ │ ├── main.dart │ │ │ │ │ ├── open_image_page.dart │ │ │ │ │ ├── open_multiple_images_page.dart │ │ │ │ │ ├── open_text_page.dart │ │ │ │ │ └── save_text_page.dart │ │ │ │ ├── linux/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ ├── flutter/ │ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ │ └── generated_plugins.cmake │ │ │ │ │ ├── main.cc │ │ │ │ │ ├── my_application.cc │ │ │ │ │ └── my_application.h │ │ │ │ └── pubspec.yaml │ │ │ ├── lib/ │ │ │ │ └── file_selector_linux.dart │ │ │ ├── linux/ │ │ │ │ ├── .gitignore │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── file_selector_plugin.cc │ │ │ │ ├── file_selector_plugin_private.h │ │ │ │ ├── include/ │ │ │ │ │ └── file_selector_linux/ │ │ │ │ │ └── file_selector_plugin.h │ │ │ │ └── test/ │ │ │ │ ├── file_selector_plugin_test.cc │ │ │ │ └── test_main.cc │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── file_selector_linux_test.dart │ │ ├── file_selector_macos/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── .gitignore │ │ │ │ ├── .metadata │ │ │ │ ├── README.md │ │ │ │ ├── lib/ │ │ │ │ │ ├── get_directory_page.dart │ │ │ │ │ ├── home_page.dart │ │ │ │ │ ├── main.dart │ │ │ │ │ ├── open_image_page.dart │ │ │ │ │ ├── open_multiple_images_page.dart │ │ │ │ │ ├── open_text_page.dart │ │ │ │ │ └── save_text_page.dart │ │ │ │ ├── macos/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── Flutter-Debug.xcconfig │ │ │ │ │ │ └── Flutter-Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ └── MainMenu.xib │ │ │ │ │ │ ├── Configs/ │ │ │ │ │ │ │ ├── AppInfo.xcconfig │ │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ │ ├── Release.xcconfig │ │ │ │ │ │ │ └── Warnings.xcconfig │ │ │ │ │ │ ├── DebugProfile.entitlements │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ ├── MainFlutterWindow.swift │ │ │ │ │ │ └── Release.entitlements │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ └── RunnerTests/ │ │ │ │ │ ├── Info.plist │ │ │ │ │ └── RunnerTests.swift │ │ │ │ └── pubspec.yaml │ │ │ ├── lib/ │ │ │ │ ├── file_selector_macos.dart │ │ │ │ └── src/ │ │ │ │ └── messages.g.dart │ │ │ ├── macos/ │ │ │ │ ├── Classes/ │ │ │ │ │ ├── FileSelectorPlugin.swift │ │ │ │ │ └── messages.g.swift │ │ │ │ └── file_selector_macos.podspec │ │ │ ├── pigeons/ │ │ │ │ ├── copyright.txt │ │ │ │ └── messages.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── file_selector_macos_test.dart │ │ │ ├── file_selector_macos_test.mocks.dart │ │ │ └── messages_test.g.dart │ │ ├── file_selector_platform_interface/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── lib/ │ │ │ │ ├── file_selector_platform_interface.dart │ │ │ │ └── src/ │ │ │ │ ├── method_channel/ │ │ │ │ │ └── method_channel_file_selector.dart │ │ │ │ ├── platform_interface/ │ │ │ │ │ └── file_selector_interface.dart │ │ │ │ ├── types/ │ │ │ │ │ ├── types.dart │ │ │ │ │ └── x_type_group/ │ │ │ │ │ └── x_type_group.dart │ │ │ │ └── web_helpers/ │ │ │ │ └── web_helpers.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── file_selector_platform_interface_test.dart │ │ │ ├── method_channel_file_selector_test.dart │ │ │ └── x_type_group_test.dart │ │ ├── file_selector_web/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── integration_test/ │ │ │ │ │ ├── dom_helper_test.dart │ │ │ │ │ └── file_selector_web_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ ├── run_test.sh │ │ │ │ ├── test_driver/ │ │ │ │ │ └── integration_test.dart │ │ │ │ └── web/ │ │ │ │ └── index.html │ │ │ ├── lib/ │ │ │ │ ├── file_selector_web.dart │ │ │ │ └── src/ │ │ │ │ ├── dom_helper.dart │ │ │ │ └── utils.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── more_tests_exist_elsewhere_test.dart │ │ │ └── utils_test.dart │ │ └── file_selector_windows/ │ │ ├── .gitignore │ │ ├── .metadata │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── example/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── README.md │ │ │ ├── lib/ │ │ │ │ ├── get_directory_page.dart │ │ │ │ ├── home_page.dart │ │ │ │ ├── main.dart │ │ │ │ ├── open_image_page.dart │ │ │ │ ├── open_multiple_images_page.dart │ │ │ │ ├── open_text_page.dart │ │ │ │ └── save_text_page.dart │ │ │ ├── pubspec.yaml │ │ │ └── windows/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ ├── flutter/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ └── generated_plugins.cmake │ │ │ └── runner/ │ │ │ ├── CMakeLists.txt │ │ │ ├── Runner.rc │ │ │ ├── flutter_window.cpp │ │ │ ├── flutter_window.h │ │ │ ├── main.cpp │ │ │ ├── resource.h │ │ │ ├── runner.exe.manifest │ │ │ ├── utils.cpp │ │ │ ├── utils.h │ │ │ ├── win32_window.cpp │ │ │ └── win32_window.h │ │ ├── lib/ │ │ │ ├── file_selector_windows.dart │ │ │ └── src/ │ │ │ └── messages.g.dart │ │ ├── pigeons/ │ │ │ ├── copyright.txt │ │ │ └── messages.dart │ │ ├── pubspec.yaml │ │ ├── test/ │ │ │ ├── file_selector_windows_test.dart │ │ │ ├── file_selector_windows_test.mocks.dart │ │ │ └── test_api.g.dart │ │ └── windows/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── file_dialog_controller.cpp │ │ ├── file_dialog_controller.h │ │ ├── file_selector_plugin.cpp │ │ ├── file_selector_plugin.h │ │ ├── file_selector_windows.cpp │ │ ├── include/ │ │ │ └── file_selector_windows/ │ │ │ └── file_selector_windows.h │ │ ├── messages.g.cpp │ │ ├── messages.g.h │ │ ├── string_utils.cpp │ │ ├── string_utils.h │ │ └── test/ │ │ ├── file_selector_plugin_test.cpp │ │ ├── test_file_dialog_controller.cpp │ │ ├── test_file_dialog_controller.h │ │ ├── test_main.cpp │ │ ├── test_utils.cpp │ │ └── test_utils.h │ ├── flutter_plugin_android_lifecycle/ │ │ ├── .gitignore │ │ ├── .metadata │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── android/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ ├── proguard.txt │ │ │ ├── settings.gradle │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java/ │ │ │ │ └── io/ │ │ │ │ └── flutter/ │ │ │ │ ├── embedding/ │ │ │ │ │ └── engine/ │ │ │ │ │ └── plugins/ │ │ │ │ │ └── lifecycle/ │ │ │ │ │ └── FlutterLifecycleAdapter.java │ │ │ │ └── plugins/ │ │ │ │ └── flutter_plugin_android_lifecycle/ │ │ │ │ └── FlutterAndroidLifecyclePlugin.java │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── io/ │ │ │ └── flutter/ │ │ │ └── embedding/ │ │ │ └── engine/ │ │ │ └── plugins/ │ │ │ └── lifecycle/ │ │ │ └── FlutterLifecycleAdapterTest.java │ │ ├── example/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── android/ │ │ │ │ ├── .gitignore │ │ │ │ ├── app/ │ │ │ │ │ ├── build.gradle │ │ │ │ │ └── src/ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ └── flutter_plugin_android_lifecycle/ │ │ │ │ │ │ └── MainActivityTest.java │ │ │ │ │ ├── debug/ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ ├── main/ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ └── flutter_plugin_android_lifecycle_example/ │ │ │ │ │ │ │ └── MainActivity.java │ │ │ │ │ │ └── res/ │ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ └── values/ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── profile/ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ ├── build.gradle │ │ │ │ ├── gradle/ │ │ │ │ │ └── wrapper/ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ ├── gradle.properties │ │ │ │ └── settings.gradle │ │ │ ├── integration_test/ │ │ │ │ └── flutter_plugin_android_lifecycle_test.dart │ │ │ ├── lib/ │ │ │ │ └── main.dart │ │ │ └── pubspec.yaml │ │ └── pubspec.yaml │ ├── google_maps_flutter/ │ │ ├── google_maps_flutter/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── .metadata │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── googlemapsexample/ │ │ │ │ │ │ │ └── MainActivityTest.java │ │ │ │ │ │ ├── debug/ │ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ └── googlemapsexample/ │ │ │ │ │ │ │ └── GoogleMapsTestActivity.java │ │ │ │ │ │ └── main/ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ └── res/ │ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ └── values/ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── assets/ │ │ │ │ │ └── night_mode.json │ │ │ │ ├── build.excerpt.yaml │ │ │ │ ├── integration_test/ │ │ │ │ │ └── google_maps_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ │ └── README.md │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ ├── lib/ │ │ │ │ │ ├── animate_camera.dart │ │ │ │ │ ├── lite_mode.dart │ │ │ │ │ ├── main.dart │ │ │ │ │ ├── map_click.dart │ │ │ │ │ ├── map_coordinates.dart │ │ │ │ │ ├── map_ui.dart │ │ │ │ │ ├── marker_icons.dart │ │ │ │ │ ├── move_camera.dart │ │ │ │ │ ├── padding.dart │ │ │ │ │ ├── page.dart │ │ │ │ │ ├── place_circle.dart │ │ │ │ │ ├── place_marker.dart │ │ │ │ │ ├── place_polygon.dart │ │ │ │ │ ├── place_polyline.dart │ │ │ │ │ ├── readme_sample.dart │ │ │ │ │ ├── scrolling_map.dart │ │ │ │ │ ├── snapshot.dart │ │ │ │ │ └── tile_overlay.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── lib/ │ │ │ │ ├── google_maps_flutter.dart │ │ │ │ └── src/ │ │ │ │ ├── controller.dart │ │ │ │ └── google_map.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── circle_updates_test.dart │ │ │ ├── fake_maps_controllers.dart │ │ │ ├── google_map_test.dart │ │ │ ├── map_creation_test.dart │ │ │ ├── marker_updates_test.dart │ │ │ ├── polygon_updates_test.dart │ │ │ ├── polyline_updates_test.dart │ │ │ └── tile_overlay_updates_test.dart │ │ ├── google_maps_flutter_android/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── android/ │ │ │ │ ├── build.gradle │ │ │ │ ├── settings.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ └── java/ │ │ │ │ │ └── io/ │ │ │ │ │ └── flutter/ │ │ │ │ │ └── plugins/ │ │ │ │ │ └── googlemaps/ │ │ │ │ │ ├── CircleBuilder.java │ │ │ │ │ ├── CircleController.java │ │ │ │ │ ├── CircleOptionsSink.java │ │ │ │ │ ├── CirclesController.java │ │ │ │ │ ├── Convert.java │ │ │ │ │ ├── GoogleMapBuilder.java │ │ │ │ │ ├── GoogleMapController.java │ │ │ │ │ ├── GoogleMapFactory.java │ │ │ │ │ ├── GoogleMapInitializer.java │ │ │ │ │ ├── GoogleMapListener.java │ │ │ │ │ ├── GoogleMapOptionsSink.java │ │ │ │ │ ├── GoogleMapsPlugin.java │ │ │ │ │ ├── LifecycleProvider.java │ │ │ │ │ ├── MarkerBuilder.java │ │ │ │ │ ├── MarkerController.java │ │ │ │ │ ├── MarkerOptionsSink.java │ │ │ │ │ ├── MarkersController.java │ │ │ │ │ ├── PolygonBuilder.java │ │ │ │ │ ├── PolygonController.java │ │ │ │ │ ├── PolygonOptionsSink.java │ │ │ │ │ ├── PolygonsController.java │ │ │ │ │ ├── PolylineBuilder.java │ │ │ │ │ ├── PolylineController.java │ │ │ │ │ ├── PolylineOptionsSink.java │ │ │ │ │ ├── PolylinesController.java │ │ │ │ │ ├── TileOverlayBuilder.java │ │ │ │ │ ├── TileOverlayController.java │ │ │ │ │ ├── TileOverlaySink.java │ │ │ │ │ ├── TileOverlaysController.java │ │ │ │ │ └── TileProviderController.java │ │ │ │ └── test/ │ │ │ │ ├── java/ │ │ │ │ │ └── io/ │ │ │ │ │ └── flutter/ │ │ │ │ │ └── plugins/ │ │ │ │ │ └── googlemaps/ │ │ │ │ │ ├── CircleBuilderTest.java │ │ │ │ │ ├── CircleControllerTest.java │ │ │ │ │ ├── ConvertTest.java │ │ │ │ │ ├── GoogleMapControllerTest.java │ │ │ │ │ ├── GoogleMapInitializerTest.java │ │ │ │ │ ├── MarkersControllerTest.java │ │ │ │ │ ├── PolygonBuilderTest.java │ │ │ │ │ ├── PolygonControllerTest.java │ │ │ │ │ ├── PolylineBuilderTest.java │ │ │ │ │ └── PolylineControllerTest.java │ │ │ │ └── resources/ │ │ │ │ └── mockito-extensions/ │ │ │ │ └── org.mockito.plugins.MockMaker │ │ │ ├── example/ │ │ │ │ ├── .metadata │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── googlemapsexample/ │ │ │ │ │ │ │ ├── GoogleMapsTest.java │ │ │ │ │ │ │ └── MainActivityTest.java │ │ │ │ │ │ ├── debug/ │ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ └── googlemapsexample/ │ │ │ │ │ │ │ └── GoogleMapsTestActivity.java │ │ │ │ │ │ └── main/ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ └── res/ │ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ └── values/ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── assets/ │ │ │ │ │ └── night_mode.json │ │ │ │ ├── build.excerpt.yaml │ │ │ │ ├── integration_test/ │ │ │ │ │ ├── google_maps_tests.dart │ │ │ │ │ ├── latest_renderer_test.dart │ │ │ │ │ └── legacy_renderer_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ ├── animate_camera.dart │ │ │ │ │ ├── example_google_map.dart │ │ │ │ │ ├── lite_mode.dart │ │ │ │ │ ├── main.dart │ │ │ │ │ ├── map_click.dart │ │ │ │ │ ├── map_coordinates.dart │ │ │ │ │ ├── map_ui.dart │ │ │ │ │ ├── marker_icons.dart │ │ │ │ │ ├── move_camera.dart │ │ │ │ │ ├── padding.dart │ │ │ │ │ ├── page.dart │ │ │ │ │ ├── place_circle.dart │ │ │ │ │ ├── place_marker.dart │ │ │ │ │ ├── place_polygon.dart │ │ │ │ │ ├── place_polyline.dart │ │ │ │ │ ├── readme_excerpts.dart │ │ │ │ │ ├── scrolling_map.dart │ │ │ │ │ ├── snapshot.dart │ │ │ │ │ └── tile_overlay.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── lib/ │ │ │ │ ├── google_maps_flutter_android.dart │ │ │ │ └── src/ │ │ │ │ ├── google_map_inspector_android.dart │ │ │ │ └── google_maps_flutter_android.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── google_maps_flutter_android_test.dart │ │ ├── google_maps_flutter_ios/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── .metadata │ │ │ │ ├── README.md │ │ │ │ ├── assets/ │ │ │ │ │ └── night_mode.json │ │ │ │ ├── integration_test/ │ │ │ │ │ └── google_maps_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ │ └── README.md │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ ├── RunnerTests/ │ │ │ │ │ │ ├── FLTGoogleMapJSONConversionsConversionTests.m │ │ │ │ │ │ ├── GoogleMapsTests.m │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ ├── PartiallyMockedMapView.h │ │ │ │ │ │ └── PartiallyMockedMapView.m │ │ │ │ │ └── RunnerUITests/ │ │ │ │ │ ├── GoogleMapsUITests.m │ │ │ │ │ └── Info.plist │ │ │ │ ├── lib/ │ │ │ │ │ ├── animate_camera.dart │ │ │ │ │ ├── example_google_map.dart │ │ │ │ │ ├── lite_mode.dart │ │ │ │ │ ├── main.dart │ │ │ │ │ ├── map_click.dart │ │ │ │ │ ├── map_coordinates.dart │ │ │ │ │ ├── map_ui.dart │ │ │ │ │ ├── marker_icons.dart │ │ │ │ │ ├── move_camera.dart │ │ │ │ │ ├── padding.dart │ │ │ │ │ ├── page.dart │ │ │ │ │ ├── place_circle.dart │ │ │ │ │ ├── place_marker.dart │ │ │ │ │ ├── place_polygon.dart │ │ │ │ │ ├── place_polyline.dart │ │ │ │ │ ├── scrolling_map.dart │ │ │ │ │ ├── snapshot.dart │ │ │ │ │ └── tile_overlay.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── ios/ │ │ │ │ ├── Assets/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── Classes/ │ │ │ │ │ ├── FLTGoogleMapJSONConversions.h │ │ │ │ │ ├── FLTGoogleMapJSONConversions.m │ │ │ │ │ ├── FLTGoogleMapTileOverlayController.h │ │ │ │ │ ├── FLTGoogleMapTileOverlayController.m │ │ │ │ │ ├── FLTGoogleMapsPlugin.h │ │ │ │ │ ├── FLTGoogleMapsPlugin.m │ │ │ │ │ ├── GoogleMapCircleController.h │ │ │ │ │ ├── GoogleMapCircleController.m │ │ │ │ │ ├── GoogleMapController.h │ │ │ │ │ ├── GoogleMapController.m │ │ │ │ │ ├── GoogleMapController_Test.h │ │ │ │ │ ├── GoogleMapMarkerController.h │ │ │ │ │ ├── GoogleMapMarkerController.m │ │ │ │ │ ├── GoogleMapPolygonController.h │ │ │ │ │ ├── GoogleMapPolygonController.m │ │ │ │ │ ├── GoogleMapPolylineController.h │ │ │ │ │ ├── GoogleMapPolylineController.m │ │ │ │ │ ├── google_maps_flutter_ios-umbrella.h │ │ │ │ │ └── google_maps_flutter_ios.modulemap │ │ │ │ └── google_maps_flutter_ios.podspec │ │ │ ├── lib/ │ │ │ │ ├── google_maps_flutter_ios.dart │ │ │ │ └── src/ │ │ │ │ ├── google_map_inspector_ios.dart │ │ │ │ └── google_maps_flutter_ios.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── google_maps_flutter_ios_test.dart │ │ ├── google_maps_flutter_platform_interface/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── lib/ │ │ │ │ ├── google_maps_flutter_platform_interface.dart │ │ │ │ └── src/ │ │ │ │ ├── events/ │ │ │ │ │ └── map_event.dart │ │ │ │ ├── method_channel/ │ │ │ │ │ └── method_channel_google_maps_flutter.dart │ │ │ │ ├── platform_interface/ │ │ │ │ │ ├── google_maps_flutter_platform.dart │ │ │ │ │ └── google_maps_inspector_platform.dart │ │ │ │ └── types/ │ │ │ │ ├── bitmap.dart │ │ │ │ ├── callbacks.dart │ │ │ │ ├── camera.dart │ │ │ │ ├── cap.dart │ │ │ │ ├── circle.dart │ │ │ │ ├── circle_updates.dart │ │ │ │ ├── joint_type.dart │ │ │ │ ├── location.dart │ │ │ │ ├── map_configuration.dart │ │ │ │ ├── map_objects.dart │ │ │ │ ├── map_widget_configuration.dart │ │ │ │ ├── maps_object.dart │ │ │ │ ├── maps_object_updates.dart │ │ │ │ ├── marker.dart │ │ │ │ ├── marker_updates.dart │ │ │ │ ├── pattern_item.dart │ │ │ │ ├── polygon.dart │ │ │ │ ├── polygon_updates.dart │ │ │ │ ├── polyline.dart │ │ │ │ ├── polyline_updates.dart │ │ │ │ ├── screen_coordinate.dart │ │ │ │ ├── tile.dart │ │ │ │ ├── tile_overlay.dart │ │ │ │ ├── tile_overlay_updates.dart │ │ │ │ ├── tile_provider.dart │ │ │ │ ├── types.dart │ │ │ │ ├── ui.dart │ │ │ │ └── utils/ │ │ │ │ ├── circle.dart │ │ │ │ ├── map_configuration_serialization.dart │ │ │ │ ├── maps_object.dart │ │ │ │ ├── marker.dart │ │ │ │ ├── polygon.dart │ │ │ │ ├── polyline.dart │ │ │ │ └── tile_overlay.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── method_channel/ │ │ │ │ └── method_channel_google_maps_flutter_test.dart │ │ │ ├── platform_interface/ │ │ │ │ ├── google_maps_flutter_platform_test.dart │ │ │ │ └── google_maps_inspector_platform_test.dart │ │ │ ├── types/ │ │ │ │ ├── bitmap_test.dart │ │ │ │ ├── camera_test.dart │ │ │ │ ├── location_test.dart │ │ │ │ ├── map_configuration_test.dart │ │ │ │ ├── maps_object_test.dart │ │ │ │ ├── maps_object_updates_test.dart │ │ │ │ ├── marker_test.dart │ │ │ │ ├── test_maps_object.dart │ │ │ │ ├── tile_overlay_test.dart │ │ │ │ ├── tile_overlay_updates_test.dart │ │ │ │ └── tile_test.dart │ │ │ └── utils/ │ │ │ └── map_configuration_serialization_test.dart │ │ └── google_maps_flutter_web/ │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── example/ │ │ │ ├── README.md │ │ │ ├── build.yaml │ │ │ ├── integration_test/ │ │ │ │ ├── google_maps_controller_test.dart │ │ │ │ ├── google_maps_controller_test.mocks.dart │ │ │ │ ├── google_maps_plugin_test.dart │ │ │ │ ├── google_maps_plugin_test.mocks.dart │ │ │ │ ├── marker_test.dart │ │ │ │ ├── markers_test.dart │ │ │ │ ├── projection_test.dart │ │ │ │ ├── resources/ │ │ │ │ │ └── icon_image_base64.dart │ │ │ │ ├── shape_test.dart │ │ │ │ └── shapes_test.dart │ │ │ ├── lib/ │ │ │ │ └── main.dart │ │ │ ├── pubspec.yaml │ │ │ ├── regen_mocks.sh │ │ │ ├── run_test.sh │ │ │ ├── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ └── web/ │ │ │ └── index.html │ │ ├── lib/ │ │ │ ├── google_maps_flutter_web.dart │ │ │ └── src/ │ │ │ ├── circle.dart │ │ │ ├── circles.dart │ │ │ ├── convert.dart │ │ │ ├── google_maps_controller.dart │ │ │ ├── google_maps_flutter_web.dart │ │ │ ├── marker.dart │ │ │ ├── markers.dart │ │ │ ├── polygon.dart │ │ │ ├── polygons.dart │ │ │ ├── polyline.dart │ │ │ ├── polylines.dart │ │ │ ├── shims/ │ │ │ │ ├── dart_ui.dart │ │ │ │ ├── dart_ui_fake.dart │ │ │ │ └── dart_ui_real.dart │ │ │ ├── third_party/ │ │ │ │ └── to_screen_location/ │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ └── to_screen_location.dart │ │ │ └── types.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ ├── README.md │ │ └── tests_exist_elsewhere_test.dart │ ├── google_sign_in/ │ │ ├── google_sign_in/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── google-services.json │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── googlesigninexample/ │ │ │ │ │ │ │ └── FlutterActivityTest.java │ │ │ │ │ │ ├── debug/ │ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ │ └── main/ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── .gitignore │ │ │ │ │ │ │ └── googlesigninexample/ │ │ │ │ │ │ │ └── GoogleSignInTestActivity.java │ │ │ │ │ │ └── res/ │ │ │ │ │ │ └── values/ │ │ │ │ │ │ └── strings.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── integration_test/ │ │ │ │ │ └── google_sign_in_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── GoogleSignInPluginTest/ │ │ │ │ │ │ └── Info.plist │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── GoogleService-Info.plist │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ ├── test_driver/ │ │ │ │ │ └── integration_test.dart │ │ │ │ └── web/ │ │ │ │ └── index.html │ │ │ ├── lib/ │ │ │ │ ├── google_sign_in.dart │ │ │ │ ├── src/ │ │ │ │ │ ├── common.dart │ │ │ │ │ └── fife.dart │ │ │ │ ├── testing.dart │ │ │ │ └── widgets.dart │ │ │ ├── pubspec.yaml │ │ │ ├── resources/ │ │ │ │ └── README.md │ │ │ └── test/ │ │ │ ├── fife_test.dart │ │ │ ├── google_sign_in_test.dart │ │ │ ├── google_sign_in_test.mocks.dart │ │ │ └── widgets_test.dart │ │ ├── google_sign_in_android/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── android/ │ │ │ │ ├── build.gradle │ │ │ │ ├── settings.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ └── java/ │ │ │ │ │ └── io/ │ │ │ │ │ └── flutter/ │ │ │ │ │ └── plugins/ │ │ │ │ │ └── googlesignin/ │ │ │ │ │ ├── BackgroundTaskRunner.java │ │ │ │ │ ├── Executors.java │ │ │ │ │ ├── GoogleSignInPlugin.java │ │ │ │ │ └── GoogleSignInWrapper.java │ │ │ │ └── test/ │ │ │ │ └── java/ │ │ │ │ └── io/ │ │ │ │ └── flutter/ │ │ │ │ └── plugins/ │ │ │ │ └── googlesignin/ │ │ │ │ └── GoogleSignInTest.java │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── google-services.json │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── googlesigninexample/ │ │ │ │ │ │ │ ├── FlutterActivityTest.java │ │ │ │ │ │ │ └── GoogleSignInTest.java │ │ │ │ │ │ ├── debug/ │ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ │ └── main/ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── .gitignore │ │ │ │ │ │ │ └── googlesigninexample/ │ │ │ │ │ │ │ └── GoogleSignInTestActivity.java │ │ │ │ │ │ └── res/ │ │ │ │ │ │ └── values/ │ │ │ │ │ │ └── strings.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── integration_test/ │ │ │ │ │ └── google_sign_in_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── lib/ │ │ │ │ ├── google_sign_in_android.dart │ │ │ │ └── src/ │ │ │ │ └── utils.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── google_sign_in_android_test.dart │ │ ├── google_sign_in_ios/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── integration_test/ │ │ │ │ │ └── google_sign_in_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── GoogleSignInPluginTest/ │ │ │ │ │ │ └── Info.plist │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── GoogleService-Info.plist │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ ├── RunnerTests/ │ │ │ │ │ │ ├── GoogleSignInTests.m │ │ │ │ │ │ └── Info.plist │ │ │ │ │ └── RunnerUITests/ │ │ │ │ │ ├── GoogleSignInUITests.m │ │ │ │ │ └── Info.plist │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── ios/ │ │ │ │ ├── Assets/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── Classes/ │ │ │ │ │ ├── FLTGoogleSignInPlugin.h │ │ │ │ │ ├── FLTGoogleSignInPlugin.m │ │ │ │ │ ├── FLTGoogleSignInPlugin.modulemap │ │ │ │ │ ├── FLTGoogleSignInPlugin_Test.h │ │ │ │ │ └── google_sign_in_ios-umbrella.h │ │ │ │ └── google_sign_in_ios.podspec │ │ │ ├── lib/ │ │ │ │ ├── google_sign_in_ios.dart │ │ │ │ └── src/ │ │ │ │ └── utils.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── google_sign_in_ios_test.dart │ │ ├── google_sign_in_platform_interface/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── lib/ │ │ │ │ ├── google_sign_in_platform_interface.dart │ │ │ │ └── src/ │ │ │ │ ├── method_channel_google_sign_in.dart │ │ │ │ ├── types.dart │ │ │ │ └── utils.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── google_sign_in_platform_interface_test.dart │ │ │ └── method_channel_google_sign_in_test.dart │ │ └── google_sign_in_web/ │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── example/ │ │ │ ├── README.md │ │ │ ├── build.yaml │ │ │ ├── integration_test/ │ │ │ │ ├── google_sign_in_web_test.dart │ │ │ │ ├── google_sign_in_web_test.mocks.dart │ │ │ │ ├── people_test.dart │ │ │ │ ├── src/ │ │ │ │ │ ├── dom.dart │ │ │ │ │ ├── jsify_as.dart │ │ │ │ │ ├── jwt_examples.dart │ │ │ │ │ └── person.dart │ │ │ │ └── utils_test.dart │ │ │ ├── lib/ │ │ │ │ └── main.dart │ │ │ ├── pubspec.yaml │ │ │ ├── regen_mocks.sh │ │ │ ├── run_test.sh │ │ │ ├── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ └── web/ │ │ │ └── index.html │ │ ├── lib/ │ │ │ ├── google_sign_in_web.dart │ │ │ └── src/ │ │ │ ├── gis_client.dart │ │ │ ├── people.dart │ │ │ └── utils.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ ├── README.md │ │ └── tests_exist_elsewhere_test.dart │ ├── image_picker/ │ │ ├── image_picker/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── imagepickerexample/ │ │ │ │ │ │ │ └── FlutterActivityTest.java │ │ │ │ │ │ ├── debug/ │ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ │ └── main/ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ └── java/ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ ├── .gitignore │ │ │ │ │ │ └── imagepickerexample/ │ │ │ │ │ │ └── ImagePickerTestActivity.java │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── integration_test/ │ │ │ │ │ └── image_picker_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── .gitignore │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ ├── Runner.xcscheme │ │ │ │ │ │ └── RunnerUITests.xcscheme │ │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ └── image_picker_exampleTests/ │ │ │ │ │ └── Info.plist │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ ├── test_driver/ │ │ │ │ │ └── integration_test.dart │ │ │ │ └── web/ │ │ │ │ ├── index.html │ │ │ │ └── manifest.json │ │ │ ├── lib/ │ │ │ │ └── image_picker.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── image_picker_deprecated_test.dart │ │ │ ├── image_picker_test.dart │ │ │ └── image_picker_test.mocks.dart │ │ ├── image_picker_android/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── android/ │ │ │ │ ├── build.gradle │ │ │ │ ├── settings.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ ├── java/ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ └── imagepicker/ │ │ │ │ │ │ ├── ExifDataCopier.java │ │ │ │ │ │ ├── FileUtils.java │ │ │ │ │ │ ├── ImagePickerCache.java │ │ │ │ │ │ ├── ImagePickerDelegate.java │ │ │ │ │ │ ├── ImagePickerFileProvider.java │ │ │ │ │ │ ├── ImagePickerPlugin.java │ │ │ │ │ │ ├── ImagePickerUtils.java │ │ │ │ │ │ └── ImageResizer.java │ │ │ │ │ └── res/ │ │ │ │ │ └── xml/ │ │ │ │ │ └── flutter_image_picker_file_paths.xml │ │ │ │ └── test/ │ │ │ │ ├── java/ │ │ │ │ │ └── io/ │ │ │ │ │ └── flutter/ │ │ │ │ │ └── plugins/ │ │ │ │ │ └── imagepicker/ │ │ │ │ │ ├── FileUtilTest.java │ │ │ │ │ ├── ImagePickerCacheTest.java │ │ │ │ │ ├── ImagePickerDelegateTest.java │ │ │ │ │ ├── ImagePickerPluginTest.java │ │ │ │ │ └── ImageResizerTest.java │ │ │ │ └── resources/ │ │ │ │ └── mockito-extensions/ │ │ │ │ └── org.mockito.plugins.MockMaker │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── imagepickerexample/ │ │ │ │ │ │ │ ├── FlutterActivityTest.java │ │ │ │ │ │ │ ├── ImagePickerPickTest.java │ │ │ │ │ │ │ └── ImagePickerTest.java │ │ │ │ │ │ ├── debug/ │ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ │ └── main/ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ └── java/ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ ├── .gitignore │ │ │ │ │ │ └── imagepickerexample/ │ │ │ │ │ │ ├── DriverExtensionActivity.java │ │ │ │ │ │ ├── DummyContentProvider.java │ │ │ │ │ │ └── ImagePickerTestActivity.java │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── integration_test/ │ │ │ │ │ └── image_picker_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── lib/ │ │ │ │ └── image_picker_android.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── image_picker_android_test.dart │ │ ├── image_picker_for_web/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── integration_test/ │ │ │ │ │ ├── image_picker_for_web_test.dart │ │ │ │ │ └── image_resizer_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ ├── run_test.sh │ │ │ │ ├── test_driver/ │ │ │ │ │ └── integration_test.dart │ │ │ │ └── web/ │ │ │ │ └── index.html │ │ │ ├── lib/ │ │ │ │ ├── image_picker_for_web.dart │ │ │ │ └── src/ │ │ │ │ ├── image_resizer.dart │ │ │ │ └── image_resizer_utils.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── README.md │ │ │ ├── image_resizer_utils_test.dart │ │ │ └── tests_exist_elsewhere_test.dart │ │ ├── image_picker_ios/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── integration_test/ │ │ │ │ │ └── image_picker_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── .gitignore │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ ├── Runner.xcscheme │ │ │ │ │ │ └── RunnerUITests.xcscheme │ │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ ├── RunnerTests/ │ │ │ │ │ │ ├── ImagePickerPluginTests.m │ │ │ │ │ │ ├── ImagePickerTestImages.h │ │ │ │ │ │ ├── ImagePickerTestImages.m │ │ │ │ │ │ ├── ImageUtilTests.m │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ ├── MetaDataUtilTests.m │ │ │ │ │ │ ├── PhotoAssetUtilTests.m │ │ │ │ │ │ └── PickerSaveImageToPathOperationTests.m │ │ │ │ │ ├── RunnerUITests/ │ │ │ │ │ │ ├── ImagePickerFromGalleryUITests.m │ │ │ │ │ │ ├── ImagePickerFromLimitedGalleryUITests.m │ │ │ │ │ │ └── Info.plist │ │ │ │ │ ├── TestImages/ │ │ │ │ │ │ ├── heicImage.heic │ │ │ │ │ │ ├── icnsImage.icns │ │ │ │ │ │ ├── proRawImage.dng │ │ │ │ │ │ └── tiffImage.tiff │ │ │ │ │ └── image_picker_exampleTests/ │ │ │ │ │ └── Info.plist │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── ios/ │ │ │ │ ├── Assets/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── Classes/ │ │ │ │ │ ├── FLTImagePickerImageUtil.h │ │ │ │ │ ├── FLTImagePickerImageUtil.m │ │ │ │ │ ├── FLTImagePickerMetaDataUtil.h │ │ │ │ │ ├── FLTImagePickerMetaDataUtil.m │ │ │ │ │ ├── FLTImagePickerPhotoAssetUtil.h │ │ │ │ │ ├── FLTImagePickerPhotoAssetUtil.m │ │ │ │ │ ├── FLTImagePickerPlugin.h │ │ │ │ │ ├── FLTImagePickerPlugin.m │ │ │ │ │ ├── FLTImagePickerPlugin_Test.h │ │ │ │ │ ├── FLTPHPickerSaveImageToPathOperation.h │ │ │ │ │ ├── FLTPHPickerSaveImageToPathOperation.m │ │ │ │ │ ├── ImagePickerPlugin.modulemap │ │ │ │ │ ├── image_picker_ios-umbrella.h │ │ │ │ │ ├── messages.g.h │ │ │ │ │ └── messages.g.m │ │ │ │ └── image_picker_ios.podspec │ │ │ ├── lib/ │ │ │ │ ├── image_picker_ios.dart │ │ │ │ └── src/ │ │ │ │ └── messages.g.dart │ │ │ ├── pigeons/ │ │ │ │ ├── copyright.txt │ │ │ │ └── messages.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── image_picker_ios_test.dart │ │ │ └── test_api.g.dart │ │ ├── image_picker_platform_interface/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── lib/ │ │ │ │ ├── image_picker_platform_interface.dart │ │ │ │ └── src/ │ │ │ │ ├── method_channel/ │ │ │ │ │ └── method_channel_image_picker.dart │ │ │ │ ├── platform_interface/ │ │ │ │ │ └── image_picker_platform.dart │ │ │ │ └── types/ │ │ │ │ ├── camera_device.dart │ │ │ │ ├── image_options.dart │ │ │ │ ├── image_picker_options.dart │ │ │ │ ├── image_source.dart │ │ │ │ ├── lost_data_response.dart │ │ │ │ ├── multi_image_picker_options.dart │ │ │ │ ├── picked_file/ │ │ │ │ │ ├── base.dart │ │ │ │ │ ├── html.dart │ │ │ │ │ ├── io.dart │ │ │ │ │ ├── lost_data.dart │ │ │ │ │ ├── picked_file.dart │ │ │ │ │ └── unsupported.dart │ │ │ │ ├── retrieve_type.dart │ │ │ │ └── types.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── assets/ │ │ │ │ └── hello.txt │ │ │ ├── new_method_channel_image_picker_test.dart │ │ │ ├── picked_file_html_test.dart │ │ │ └── picked_file_io_test.dart │ │ └── image_picker_windows/ │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── example/ │ │ │ ├── README.md │ │ │ ├── lib/ │ │ │ │ └── main.dart │ │ │ ├── pubspec.yaml │ │ │ └── windows/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ ├── flutter/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ └── generated_plugins.cmake │ │ │ └── runner/ │ │ │ ├── CMakeLists.txt │ │ │ ├── Runner.rc │ │ │ ├── flutter_window.cpp │ │ │ ├── flutter_window.h │ │ │ ├── main.cpp │ │ │ ├── resource.h │ │ │ ├── runner.exe.manifest │ │ │ ├── utils.cpp │ │ │ ├── utils.h │ │ │ ├── win32_window.cpp │ │ │ └── win32_window.h │ │ ├── lib/ │ │ │ └── image_picker_windows.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ ├── image_picker_windows_test.dart │ │ └── image_picker_windows_test.mocks.dart │ ├── in_app_purchase/ │ │ ├── in_app_purchase/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── .metadata │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── inapppurchaseexample/ │ │ │ │ │ │ │ └── FlutterActivityTest.java │ │ │ │ │ │ └── main/ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ └── res/ │ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ └── values/ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ ├── keystore.example.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── integration_test/ │ │ │ │ │ └── in_app_purchase_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ │ └── README.md │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Configuration.storekit │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ ├── lib/ │ │ │ │ │ ├── consumable_store.dart │ │ │ │ │ └── main.dart │ │ │ │ ├── macos/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── Flutter-Debug.xcconfig │ │ │ │ │ │ └── Flutter-Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ └── MainMenu.xib │ │ │ │ │ │ ├── Configs/ │ │ │ │ │ │ │ ├── AppInfo.xcconfig │ │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ │ ├── Release.xcconfig │ │ │ │ │ │ │ └── Warnings.xcconfig │ │ │ │ │ │ ├── DebugProfile.entitlements │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ ├── MainFlutterWindow.swift │ │ │ │ │ │ └── Release.entitlements │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── lib/ │ │ │ │ └── in_app_purchase.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── in_app_purchase_test.dart │ │ ├── in_app_purchase_android/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── android/ │ │ │ │ ├── build.gradle │ │ │ │ ├── settings.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ └── java/ │ │ │ │ │ └── io/ │ │ │ │ │ └── flutter/ │ │ │ │ │ └── plugins/ │ │ │ │ │ └── inapppurchase/ │ │ │ │ │ ├── BillingClientFactory.java │ │ │ │ │ ├── BillingClientFactoryImpl.java │ │ │ │ │ ├── InAppPurchasePlugin.java │ │ │ │ │ ├── MethodCallHandlerImpl.java │ │ │ │ │ ├── PluginPurchaseListener.java │ │ │ │ │ └── Translator.java │ │ │ │ └── test/ │ │ │ │ └── java/ │ │ │ │ ├── android/ │ │ │ │ │ ├── text/ │ │ │ │ │ │ └── TextUtils.java │ │ │ │ │ └── util/ │ │ │ │ │ └── Log.java │ │ │ │ └── io/ │ │ │ │ └── flutter/ │ │ │ │ └── plugins/ │ │ │ │ └── inapppurchase/ │ │ │ │ ├── InAppPurchasePluginTest.java │ │ │ │ ├── MethodCallHandlerTest.java │ │ │ │ └── TranslatorTest.java │ │ │ ├── build.yaml │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── inapppurchaseexample/ │ │ │ │ │ │ │ └── FlutterActivityTest.java │ │ │ │ │ │ ├── main/ │ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ │ └── res/ │ │ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ │ └── values/ │ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ │ └── test/ │ │ │ │ │ │ └── resources/ │ │ │ │ │ │ └── mockito-extensions/ │ │ │ │ │ │ └── org.mockito.plugins.MockMaker │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ ├── keystore.example.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── integration_test/ │ │ │ │ │ └── in_app_purchase_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ ├── consumable_store.dart │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── test/ │ │ │ │ └── integration_test.dart │ │ │ ├── lib/ │ │ │ │ ├── billing_client_wrappers.dart │ │ │ │ ├── in_app_purchase_android.dart │ │ │ │ └── src/ │ │ │ │ ├── billing_client_wrappers/ │ │ │ │ │ ├── README.md │ │ │ │ │ ├── billing_client_wrapper.dart │ │ │ │ │ ├── billing_client_wrapper.g.dart │ │ │ │ │ ├── purchase_wrapper.dart │ │ │ │ │ ├── purchase_wrapper.g.dart │ │ │ │ │ ├── sku_details_wrapper.dart │ │ │ │ │ └── sku_details_wrapper.g.dart │ │ │ │ ├── channel.dart │ │ │ │ ├── in_app_purchase_android_platform.dart │ │ │ │ ├── in_app_purchase_android_platform_addition.dart │ │ │ │ └── types/ │ │ │ │ ├── change_subscription_param.dart │ │ │ │ ├── google_play_product_details.dart │ │ │ │ ├── google_play_purchase_details.dart │ │ │ │ ├── google_play_purchase_param.dart │ │ │ │ ├── query_purchase_details_response.dart │ │ │ │ └── types.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── billing_client_wrappers/ │ │ │ │ ├── billing_client_wrapper_test.dart │ │ │ │ ├── purchase_wrapper_test.dart │ │ │ │ ├── sku_details_wrapper_deprecated_test.dart │ │ │ │ └── sku_details_wrapper_test.dart │ │ │ ├── in_app_purchase_android_platform_addition_test.dart │ │ │ ├── in_app_purchase_android_platform_test.dart │ │ │ └── stub_in_app_purchase_platform.dart │ │ ├── in_app_purchase_platform_interface/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── lib/ │ │ │ │ ├── in_app_purchase_platform_interface.dart │ │ │ │ └── src/ │ │ │ │ ├── errors/ │ │ │ │ │ ├── errors.dart │ │ │ │ │ ├── in_app_purchase_error.dart │ │ │ │ │ └── in_app_purchase_exception.dart │ │ │ │ ├── in_app_purchase_platform.dart │ │ │ │ ├── in_app_purchase_platform_addition.dart │ │ │ │ ├── in_app_purchase_platform_addition_provider.dart │ │ │ │ └── types/ │ │ │ │ ├── product_details.dart │ │ │ │ ├── product_details_response.dart │ │ │ │ ├── purchase_details.dart │ │ │ │ ├── purchase_param.dart │ │ │ │ ├── purchase_status.dart │ │ │ │ ├── purchase_verification_data.dart │ │ │ │ └── types.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── in_app_purchase_platform_test.dart │ │ │ └── src/ │ │ │ ├── errors/ │ │ │ │ ├── in_app_purchase_error_test.dart │ │ │ │ └── in_app_purchase_exception_test.dart │ │ │ └── types/ │ │ │ └── product_details_test.dart │ │ └── in_app_purchase_storekit/ │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── build.yaml │ │ ├── darwin/ │ │ │ ├── Classes/ │ │ │ │ ├── FIAObjectTranslator.h │ │ │ │ ├── FIAObjectTranslator.m │ │ │ │ ├── FIAPPaymentQueueDelegate.h │ │ │ │ ├── FIAPPaymentQueueDelegate.m │ │ │ │ ├── FIAPReceiptManager.h │ │ │ │ ├── FIAPReceiptManager.m │ │ │ │ ├── FIAPRequestHandler.h │ │ │ │ ├── FIAPRequestHandler.m │ │ │ │ ├── FIAPaymentQueueHandler.h │ │ │ │ ├── FIAPaymentQueueHandler.m │ │ │ │ ├── FIATransactionCache.h │ │ │ │ ├── FIATransactionCache.m │ │ │ │ ├── InAppPurchasePlugin.h │ │ │ │ └── InAppPurchasePlugin.m │ │ │ └── in_app_purchase_storekit.podspec │ │ ├── example/ │ │ │ ├── README.md │ │ │ ├── integration_test/ │ │ │ │ └── in_app_purchase_test.dart │ │ │ ├── ios/ │ │ │ │ ├── Flutter/ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ └── Release.xcconfig │ │ │ │ ├── Podfile │ │ │ │ ├── Runner/ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ └── README.md │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ ├── Configuration.storekit │ │ │ │ │ ├── Info.plist │ │ │ │ │ └── main.m │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata/ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ ├── lib/ │ │ │ │ ├── consumable_store.dart │ │ │ │ ├── example_payment_queue_delegate.dart │ │ │ │ └── main.dart │ │ │ ├── macos/ │ │ │ │ ├── Flutter/ │ │ │ │ │ ├── Flutter-Debug.xcconfig │ │ │ │ │ └── Flutter-Release.xcconfig │ │ │ │ ├── Podfile │ │ │ │ ├── Runner/ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ └── MainMenu.xib │ │ │ │ │ ├── Configs/ │ │ │ │ │ │ ├── AppInfo.xcconfig │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── DebugProfile.entitlements │ │ │ │ │ ├── Info.plist │ │ │ │ │ ├── MainFlutterWindow.swift │ │ │ │ │ └── Release.entitlements │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata/ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ ├── pubspec.yaml │ │ │ ├── shared/ │ │ │ │ └── RunnerTests/ │ │ │ │ ├── FIAPPaymentQueueDeleteTests.m │ │ │ │ ├── FIATransactionCacheTests.m │ │ │ │ ├── InAppPurchasePluginTests.m │ │ │ │ ├── Info.plist │ │ │ │ ├── PaymentQueueTests.m │ │ │ │ ├── ProductRequestHandlerTests.m │ │ │ │ ├── Stubs.h │ │ │ │ ├── Stubs.m │ │ │ │ └── TranslatorTests.m │ │ │ └── test_driver/ │ │ │ └── integration_test.dart │ │ ├── lib/ │ │ │ ├── in_app_purchase_storekit.dart │ │ │ ├── src/ │ │ │ │ ├── channel.dart │ │ │ │ ├── in_app_purchase_storekit_platform.dart │ │ │ │ ├── in_app_purchase_storekit_platform_addition.dart │ │ │ │ ├── store_kit_wrappers/ │ │ │ │ │ ├── README.md │ │ │ │ │ ├── enum_converters.dart │ │ │ │ │ ├── enum_converters.g.dart │ │ │ │ │ ├── sk_payment_queue_delegate_wrapper.dart │ │ │ │ │ ├── sk_payment_queue_wrapper.dart │ │ │ │ │ ├── sk_payment_queue_wrapper.g.dart │ │ │ │ │ ├── sk_payment_transaction_wrappers.dart │ │ │ │ │ ├── sk_payment_transaction_wrappers.g.dart │ │ │ │ │ ├── sk_product_wrapper.dart │ │ │ │ │ ├── sk_product_wrapper.g.dart │ │ │ │ │ ├── sk_receipt_manager.dart │ │ │ │ │ ├── sk_request_maker.dart │ │ │ │ │ ├── sk_storefront_wrapper.dart │ │ │ │ │ └── sk_storefront_wrapper.g.dart │ │ │ │ └── types/ │ │ │ │ ├── app_store_product_details.dart │ │ │ │ ├── app_store_purchase_details.dart │ │ │ │ ├── app_store_purchase_param.dart │ │ │ │ └── types.dart │ │ │ └── store_kit_wrappers.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ ├── fakes/ │ │ │ └── fake_storekit_platform.dart │ │ ├── in_app_purchase_storekit_platform_addtion_test.dart │ │ ├── in_app_purchase_storekit_platform_test.dart │ │ └── store_kit_wrappers/ │ │ ├── sk_methodchannel_apis_test.dart │ │ ├── sk_payment_queue_delegate_api_test.dart │ │ ├── sk_product_test.dart │ │ └── sk_test_stub_objects.dart │ ├── integration_test/ │ │ ├── .gitignore │ │ ├── .metadata │ │ └── README.md │ ├── ios_platform_images/ │ │ ├── .gitignore │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── example/ │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── ios/ │ │ │ │ ├── .gitignore │ │ │ │ ├── Flutter/ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ └── Release.xcconfig │ │ │ │ ├── Podfile │ │ │ │ ├── Runner/ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ ├── LaunchImage.imageset/ │ │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ │ └── README.md │ │ │ │ │ │ └── flutter.imageset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ ├── Info.plist │ │ │ │ │ ├── Runner-Bridging-Header.h │ │ │ │ │ └── textfile │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ └── RunnerTests/ │ │ │ │ ├── Info.plist │ │ │ │ └── IosPlatformImagesTests.m │ │ │ ├── lib/ │ │ │ │ └── main.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── widget_test.dart │ │ ├── ios/ │ │ │ ├── .gitignore │ │ │ ├── Assets/ │ │ │ │ └── .gitkeep │ │ │ ├── Classes/ │ │ │ │ ├── IosPlatformImagesPlugin.h │ │ │ │ ├── IosPlatformImagesPlugin.m │ │ │ │ ├── UIImage+ios_platform_images.h │ │ │ │ └── UIImage+ios_platform_images.m │ │ │ └── ios_platform_images.podspec │ │ ├── lib/ │ │ │ └── ios_platform_images.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ └── ios_platform_images_test.dart │ ├── local_auth/ │ │ ├── local_auth/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── localauth/ │ │ │ │ │ │ │ └── FlutterFragmentActivityTest.java │ │ │ │ │ │ └── main/ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ ├── settings.gradle │ │ │ │ │ └── settings_aar.gradle │ │ │ │ ├── build.excerpt.yaml │ │ │ │ ├── integration_test/ │ │ │ │ │ └── local_auth_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ ├── lib/ │ │ │ │ │ ├── main.dart │ │ │ │ │ └── readme_excerpts.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ ├── test_driver/ │ │ │ │ │ └── integration_test.dart │ │ │ │ └── windows/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── flutter/ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ └── generated_plugins.cmake │ │ │ │ └── runner/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── Runner.rc │ │ │ │ ├── flutter_window.cpp │ │ │ │ ├── flutter_window.h │ │ │ │ ├── main.cpp │ │ │ │ ├── resource.h │ │ │ │ ├── runner.exe.manifest │ │ │ │ ├── utils.cpp │ │ │ │ ├── utils.h │ │ │ │ ├── win32_window.cpp │ │ │ │ └── win32_window.h │ │ │ ├── lib/ │ │ │ │ ├── error_codes.dart │ │ │ │ ├── local_auth.dart │ │ │ │ └── src/ │ │ │ │ └── local_auth.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── local_auth_test.dart │ │ ├── local_auth_android/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── android/ │ │ │ │ ├── build.gradle │ │ │ │ ├── lint-baseline.xml │ │ │ │ ├── settings.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ ├── java/ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ └── localauth/ │ │ │ │ │ │ ├── AuthenticationHelper.java │ │ │ │ │ │ └── LocalAuthPlugin.java │ │ │ │ │ └── res/ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ ├── fingerprint_initial_icon.xml │ │ │ │ │ │ ├── fingerprint_success_icon.xml │ │ │ │ │ │ ├── fingerprint_warning_icon.xml │ │ │ │ │ │ ├── ic_done_white_24dp.xml │ │ │ │ │ │ ├── ic_fingerprint_white_24dp.xml │ │ │ │ │ │ └── ic_priority_high_white_24dp.xml │ │ │ │ │ ├── layout/ │ │ │ │ │ │ ├── go_to_setting.xml │ │ │ │ │ │ └── scan_fp.xml │ │ │ │ │ └── values/ │ │ │ │ │ ├── colors.xml │ │ │ │ │ ├── dimens.xml │ │ │ │ │ └── styles.xml │ │ │ │ └── test/ │ │ │ │ └── java/ │ │ │ │ └── io/ │ │ │ │ └── flutter/ │ │ │ │ └── plugins/ │ │ │ │ └── localauth/ │ │ │ │ └── LocalAuthTest.java │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── localauth/ │ │ │ │ │ │ │ └── FlutterFragmentActivityTest.java │ │ │ │ │ │ └── main/ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ ├── settings.gradle │ │ │ │ │ └── settings_aar.gradle │ │ │ │ ├── integration_test/ │ │ │ │ │ └── local_auth_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── lib/ │ │ │ │ ├── local_auth_android.dart │ │ │ │ └── types/ │ │ │ │ └── auth_messages_android.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── local_auth_test.dart │ │ ├── local_auth_ios/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── integration_test/ │ │ │ │ │ └── local_auth_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ └── RunnerTests/ │ │ │ │ │ ├── FLTLocalAuthPluginTests.m │ │ │ │ │ └── Info.plist │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── ios/ │ │ │ │ ├── Assets/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── Classes/ │ │ │ │ │ ├── FLTLocalAuthPlugin.h │ │ │ │ │ └── FLTLocalAuthPlugin.m │ │ │ │ └── local_auth_ios.podspec │ │ │ ├── lib/ │ │ │ │ ├── local_auth_ios.dart │ │ │ │ └── types/ │ │ │ │ └── auth_messages_ios.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── local_auth_test.dart │ │ ├── local_auth_platform_interface/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── lib/ │ │ │ │ ├── default_method_channel_platform.dart │ │ │ │ ├── local_auth_platform_interface.dart │ │ │ │ └── types/ │ │ │ │ ├── auth_messages.dart │ │ │ │ ├── auth_options.dart │ │ │ │ ├── biometric_type.dart │ │ │ │ └── types.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── default_method_channel_platform_test.dart │ │ └── local_auth_windows/ │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── example/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── README.md │ │ │ ├── integration_test/ │ │ │ │ └── local_auth_test.dart │ │ │ ├── lib/ │ │ │ │ └── main.dart │ │ │ ├── pubspec.yaml │ │ │ ├── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ └── windows/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ ├── flutter/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ └── generated_plugins.cmake │ │ │ └── runner/ │ │ │ ├── CMakeLists.txt │ │ │ ├── Runner.rc │ │ │ ├── flutter_window.cpp │ │ │ ├── flutter_window.h │ │ │ ├── main.cpp │ │ │ ├── resource.h │ │ │ ├── runner.exe.manifest │ │ │ ├── utils.cpp │ │ │ ├── utils.h │ │ │ ├── win32_window.cpp │ │ │ └── win32_window.h │ │ ├── lib/ │ │ │ ├── local_auth_windows.dart │ │ │ ├── src/ │ │ │ │ └── messages.g.dart │ │ │ └── types/ │ │ │ └── auth_messages_windows.dart │ │ ├── pigeons/ │ │ │ ├── copyright.txt │ │ │ └── messages.dart │ │ ├── pubspec.yaml │ │ ├── test/ │ │ │ └── local_auth_test.dart │ │ └── windows/ │ │ ├── CMakeLists.txt │ │ ├── include/ │ │ │ └── local_auth_windows/ │ │ │ └── local_auth_plugin.h │ │ ├── local_auth.h │ │ ├── local_auth_plugin.cpp │ │ ├── local_auth_windows.cpp │ │ ├── messages.g.cpp │ │ ├── messages.g.h │ │ └── test/ │ │ ├── local_auth_plugin_test.cpp │ │ └── mocks.h │ ├── path_provider/ │ │ ├── path_provider/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── pathprovider/ │ │ │ │ │ │ │ └── MainActivityTest.java │ │ │ │ │ │ └── main/ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── integration_test/ │ │ │ │ │ └── path_provider_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ └── RunnerTests/ │ │ │ │ │ ├── Info.plist │ │ │ │ │ └── PathProviderTests.m │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── linux/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ ├── flutter/ │ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ │ └── generated_plugins.cmake │ │ │ │ │ ├── main.cc │ │ │ │ │ ├── my_application.cc │ │ │ │ │ └── my_application.h │ │ │ │ ├── macos/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── Flutter-Debug.xcconfig │ │ │ │ │ │ └── Flutter-Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ └── MainMenu.xib │ │ │ │ │ │ ├── Configs/ │ │ │ │ │ │ │ ├── AppInfo.xcconfig │ │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ │ ├── Release.xcconfig │ │ │ │ │ │ │ └── Warnings.xcconfig │ │ │ │ │ │ ├── DebugProfile.entitlements │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ ├── MainFlutterWindow.swift │ │ │ │ │ │ └── Release.entitlements │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ ├── pubspec.yaml │ │ │ │ ├── test_driver/ │ │ │ │ │ └── integration_test.dart │ │ │ │ └── windows/ │ │ │ │ ├── .gitignore │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── flutter/ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ └── generated_plugins.cmake │ │ │ │ └── runner/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── Runner.rc │ │ │ │ ├── flutter_window.cpp │ │ │ │ ├── flutter_window.h │ │ │ │ ├── main.cpp │ │ │ │ ├── resource.h │ │ │ │ ├── run_loop.cpp │ │ │ │ ├── run_loop.h │ │ │ │ ├── runner.exe.manifest │ │ │ │ ├── utils.cpp │ │ │ │ ├── utils.h │ │ │ │ ├── win32_window.cpp │ │ │ │ └── win32_window.h │ │ │ ├── lib/ │ │ │ │ └── path_provider.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── path_provider_test.dart │ │ ├── path_provider_android/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── android/ │ │ │ │ ├── build.gradle │ │ │ │ ├── settings.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ └── java/ │ │ │ │ │ └── io/ │ │ │ │ │ └── flutter/ │ │ │ │ │ └── plugins/ │ │ │ │ │ └── pathprovider/ │ │ │ │ │ ├── Messages.java │ │ │ │ │ ├── PathProviderPlugin.java │ │ │ │ │ └── StorageDirectoryMapper.java │ │ │ │ └── test/ │ │ │ │ └── java/ │ │ │ │ └── io/ │ │ │ │ └── flutter/ │ │ │ │ └── plugins/ │ │ │ │ └── pathprovider/ │ │ │ │ └── StorageDirectoryMapperTest.java │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── pathprovider/ │ │ │ │ │ │ │ └── MainActivityTest.java │ │ │ │ │ │ └── main/ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── integration_test/ │ │ │ │ │ └── path_provider_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── lib/ │ │ │ │ ├── messages.g.dart │ │ │ │ └── path_provider_android.dart │ │ │ ├── pigeons/ │ │ │ │ ├── copyright.txt │ │ │ │ └── messages.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── messages_test.g.dart │ │ │ └── path_provider_android_test.dart │ │ ├── path_provider_foundation/ │ │ │ ├── .gitignore │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── darwin/ │ │ │ │ ├── Classes/ │ │ │ │ │ ├── PathProviderPlugin.swift │ │ │ │ │ └── messages.g.swift │ │ │ │ ├── RunnerTests/ │ │ │ │ │ └── RunnerTests.swift │ │ │ │ └── path_provider_foundation.podspec │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── integration_test/ │ │ │ │ │ └── path_provider_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ │ └── README.md │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── Runner-Bridging-Header.h │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── macos/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── Flutter-Debug.xcconfig │ │ │ │ │ │ └── Flutter-Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ └── MainMenu.xib │ │ │ │ │ │ ├── Configs/ │ │ │ │ │ │ │ ├── AppInfo.xcconfig │ │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ │ ├── Release.xcconfig │ │ │ │ │ │ │ └── Warnings.xcconfig │ │ │ │ │ │ ├── DebugProfile.entitlements │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ ├── MainFlutterWindow.swift │ │ │ │ │ │ └── Release.entitlements │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ └── RunnerTests/ │ │ │ │ │ └── Info.plist │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── ios/ │ │ │ │ └── README.md │ │ │ ├── lib/ │ │ │ │ ├── messages.g.dart │ │ │ │ └── path_provider_foundation.dart │ │ │ ├── macos/ │ │ │ │ └── README.md │ │ │ ├── pigeons/ │ │ │ │ ├── copyright.txt │ │ │ │ └── messages.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── messages_test.g.dart │ │ │ ├── path_provider_foundation_test.dart │ │ │ └── path_provider_foundation_test.mocks.dart │ │ ├── path_provider_linux/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── .gitignore │ │ │ │ ├── .metadata │ │ │ │ ├── README.md │ │ │ │ ├── integration_test/ │ │ │ │ │ └── path_provider_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── linux/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ ├── flutter/ │ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ │ └── generated_plugins.cmake │ │ │ │ │ ├── main.cc │ │ │ │ │ ├── my_application.cc │ │ │ │ │ └── my_application.h │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── lib/ │ │ │ │ ├── path_provider_linux.dart │ │ │ │ └── src/ │ │ │ │ ├── get_application_id.dart │ │ │ │ ├── get_application_id_real.dart │ │ │ │ ├── get_application_id_stub.dart │ │ │ │ └── path_provider_linux.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── get_application_id_test.dart │ │ │ └── path_provider_linux_test.dart │ │ ├── path_provider_platform_interface/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── lib/ │ │ │ │ ├── path_provider_platform_interface.dart │ │ │ │ └── src/ │ │ │ │ ├── enums.dart │ │ │ │ └── method_channel_path_provider.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── method_channel_path_provider_test.dart │ │ └── path_provider_windows/ │ │ ├── .gitignore │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── example/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── README.md │ │ │ ├── integration_test/ │ │ │ │ └── path_provider_test.dart │ │ │ ├── lib/ │ │ │ │ └── main.dart │ │ │ ├── pubspec.yaml │ │ │ ├── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ └── windows/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ ├── flutter/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ └── generated_plugins.cmake │ │ │ └── runner/ │ │ │ ├── CMakeLists.txt │ │ │ ├── Runner.rc │ │ │ ├── flutter_window.cpp │ │ │ ├── flutter_window.h │ │ │ ├── main.cpp │ │ │ ├── resource.h │ │ │ ├── run_loop.cpp │ │ │ ├── run_loop.h │ │ │ ├── runner.exe.manifest │ │ │ ├── utils.cpp │ │ │ ├── utils.h │ │ │ ├── win32_window.cpp │ │ │ └── win32_window.h │ │ ├── lib/ │ │ │ ├── path_provider_windows.dart │ │ │ └── src/ │ │ │ ├── folders.dart │ │ │ ├── folders_stub.dart │ │ │ ├── path_provider_windows_real.dart │ │ │ └── path_provider_windows_stub.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ └── path_provider_windows_test.dart │ ├── plugin_platform_interface/ │ │ ├── .gitignore │ │ ├── .metadata │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── lib/ │ │ │ └── plugin_platform_interface.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ └── plugin_platform_interface_test.dart │ ├── quick_actions/ │ │ ├── quick_actions/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── quickactionsexample/ │ │ │ │ │ │ │ └── FlutterActivityTest.java │ │ │ │ │ │ ├── debug/ │ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ │ └── main/ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ └── quickactionsexample/ │ │ │ │ │ │ │ └── QuickActionsTestActivity.java │ │ │ │ │ │ └── res/ │ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ │ └── ic_launcher_background.xml │ │ │ │ │ │ └── values/ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── integration_test/ │ │ │ │ │ └── quick_actions_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ ├── Runner.xcscheme │ │ │ │ │ │ └── RunnerUITests.xcscheme │ │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ ├── RunnerTests/ │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── RunnerTests.m │ │ │ │ │ └── RunnerUITests/ │ │ │ │ │ ├── Info.plist │ │ │ │ │ └── RunnerUITests.m │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── lib/ │ │ │ │ └── quick_actions.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── quick_actions_test.dart │ │ ├── quick_actions_android/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── android/ │ │ │ │ ├── build.gradle │ │ │ │ ├── settings.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ └── java/ │ │ │ │ │ └── io/ │ │ │ │ │ └── flutter/ │ │ │ │ │ └── plugins/ │ │ │ │ │ └── quickactions/ │ │ │ │ │ ├── MethodCallHandlerImpl.java │ │ │ │ │ └── QuickActionsPlugin.java │ │ │ │ └── test/ │ │ │ │ └── java/ │ │ │ │ └── io/ │ │ │ │ └── flutter/ │ │ │ │ └── plugins/ │ │ │ │ └── quickactions/ │ │ │ │ └── QuickActionsTest.java │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── quickactionsexample/ │ │ │ │ │ │ │ ├── FlutterActivityTest.java │ │ │ │ │ │ │ └── QuickActionsTest.java │ │ │ │ │ │ ├── debug/ │ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ │ └── main/ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ ├── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ └── quickactionsexample/ │ │ │ │ │ │ │ └── QuickActionsTestActivity.java │ │ │ │ │ │ └── res/ │ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ │ └── ic_launcher_background.xml │ │ │ │ │ │ └── values/ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── integration_test/ │ │ │ │ │ └── quick_actions_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── lib/ │ │ │ │ └── quick_actions_android.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── quick_actions_android_test.dart │ │ ├── quick_actions_ios/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── integration_test/ │ │ │ │ │ └── quick_actions_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ ├── Runner.xcscheme │ │ │ │ │ │ └── RunnerUITests.xcscheme │ │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ ├── RunnerTests/ │ │ │ │ │ │ ├── DefaultShortcutItemParserTests.swift │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ ├── Mocks/ │ │ │ │ │ │ │ ├── MockMethodChannel.swift │ │ │ │ │ │ │ ├── MockShortcutItemParser.swift │ │ │ │ │ │ │ └── MockShortcutItemProvider.swift │ │ │ │ │ │ └── QuickActionsPluginTests.swift │ │ │ │ │ └── RunnerUITests/ │ │ │ │ │ ├── Info.plist │ │ │ │ │ └── RunnerUITests.swift │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── ios/ │ │ │ │ ├── Assets/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── Classes/ │ │ │ │ │ ├── MethodChannel.swift │ │ │ │ │ ├── QuickActionsPlugin.swift │ │ │ │ │ ├── ShortcutItemParser.swift │ │ │ │ │ └── ShortcutItemProviding.swift │ │ │ │ └── quick_actions_ios.podspec │ │ │ ├── lib/ │ │ │ │ └── quick_actions_ios.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── quick_actions_ios_test.dart │ │ └── quick_actions_platform_interface/ │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── lib/ │ │ │ ├── method_channel/ │ │ │ │ └── method_channel_quick_actions.dart │ │ │ ├── platform_interface/ │ │ │ │ └── quick_actions_platform.dart │ │ │ ├── quick_actions_platform_interface.dart │ │ │ └── types/ │ │ │ ├── quick_action_handler.dart │ │ │ ├── shortcut_item.dart │ │ │ └── types.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ ├── method_channel_quick_actions_test.dart │ │ └── quick_actions_platform_interface_test.dart │ ├── shared_preferences/ │ │ ├── shared_preferences/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── .gitignore │ │ │ │ ├── .metadata │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── sharedpreferencesexample/ │ │ │ │ │ │ │ ├── FlutterActivityTest.java │ │ │ │ │ │ │ └── MainActivityTest.java │ │ │ │ │ │ ├── main/ │ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ │ └── res/ │ │ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ │ ├── drawable-v21/ │ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ │ ├── values/ │ │ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ │ │ └── values-night/ │ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ │ └── profile/ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── integration_test/ │ │ │ │ │ └── shared_preferences_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ │ └── README.md │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ ├── Runner-Bridging-Header.h │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── linux/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ ├── flutter/ │ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ │ └── generated_plugins.cmake │ │ │ │ │ ├── main.cc │ │ │ │ │ ├── my_application.cc │ │ │ │ │ └── my_application.h │ │ │ │ ├── macos/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── Flutter-Debug.xcconfig │ │ │ │ │ │ └── Flutter-Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ └── MainMenu.xib │ │ │ │ │ │ ├── Configs/ │ │ │ │ │ │ │ ├── AppInfo.xcconfig │ │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ │ ├── Release.xcconfig │ │ │ │ │ │ │ └── Warnings.xcconfig │ │ │ │ │ │ ├── DebugProfile.entitlements │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ ├── MainFlutterWindow.swift │ │ │ │ │ │ └── Release.entitlements │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ ├── pubspec.yaml │ │ │ │ ├── test_driver/ │ │ │ │ │ └── integration_test.dart │ │ │ │ ├── web/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── manifest.json │ │ │ │ └── windows/ │ │ │ │ ├── .gitignore │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── flutter/ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ └── generated_plugins.cmake │ │ │ │ └── runner/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── Runner.rc │ │ │ │ ├── flutter_window.cpp │ │ │ │ ├── flutter_window.h │ │ │ │ ├── main.cpp │ │ │ │ ├── resource.h │ │ │ │ ├── run_loop.cpp │ │ │ │ ├── run_loop.h │ │ │ │ ├── runner.exe.manifest │ │ │ │ ├── utils.cpp │ │ │ │ ├── utils.h │ │ │ │ ├── win32_window.cpp │ │ │ │ └── win32_window.h │ │ │ ├── lib/ │ │ │ │ └── shared_preferences.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── shared_preferences_test.dart │ │ ├── shared_preferences_android/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── android/ │ │ │ │ ├── build.gradle │ │ │ │ ├── lint-baseline.xml │ │ │ │ ├── settings.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ └── java/ │ │ │ │ │ └── io/ │ │ │ │ │ └── flutter/ │ │ │ │ │ └── plugins/ │ │ │ │ │ └── sharedpreferences/ │ │ │ │ │ ├── MethodCallHandlerImpl.java │ │ │ │ │ └── SharedPreferencesPlugin.java │ │ │ │ └── test/ │ │ │ │ └── java/ │ │ │ │ └── io/ │ │ │ │ └── flutter/ │ │ │ │ └── plugins/ │ │ │ │ └── sharedpreferences/ │ │ │ │ └── SharedPreferencesTest.java │ │ │ ├── example/ │ │ │ │ ├── .gitignore │ │ │ │ ├── .metadata │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── sharedpreferencesexample/ │ │ │ │ │ │ │ └── MainActivityTest.java │ │ │ │ │ │ ├── debug/ │ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ │ ├── main/ │ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ │ └── res/ │ │ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ │ ├── drawable-v21/ │ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ │ ├── values/ │ │ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ │ │ └── values-night/ │ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ │ └── profile/ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── integration_test/ │ │ │ │ │ └── shared_preferences_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── lib/ │ │ │ │ └── shared_preferences_android.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── shared_preferences_android_test.dart │ │ ├── shared_preferences_foundation/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── darwin/ │ │ │ │ ├── Classes/ │ │ │ │ │ ├── SharedPreferencesPlugin.swift │ │ │ │ │ └── messages.g.swift │ │ │ │ ├── Tests/ │ │ │ │ │ └── RunnerTests.swift │ │ │ │ └── shared_preferences_foundation.podspec │ │ │ ├── example/ │ │ │ │ ├── .gitignore │ │ │ │ ├── README.md │ │ │ │ ├── integration_test/ │ │ │ │ │ └── shared_preferences_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ │ └── README.md │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── Runner-Bridging-Header.h │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── macos/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── Flutter-Debug.xcconfig │ │ │ │ │ │ └── Flutter-Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ └── MainMenu.xib │ │ │ │ │ │ ├── Configs/ │ │ │ │ │ │ │ ├── AppInfo.xcconfig │ │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ │ ├── Release.xcconfig │ │ │ │ │ │ │ └── Warnings.xcconfig │ │ │ │ │ │ ├── DebugProfile.entitlements │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ ├── MainFlutterWindow.swift │ │ │ │ │ │ └── Release.entitlements │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ └── RunnerTests/ │ │ │ │ │ └── Info.plist │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── ios/ │ │ │ │ └── README.md │ │ │ ├── lib/ │ │ │ │ ├── messages.g.dart │ │ │ │ └── shared_preferences_foundation.dart │ │ │ ├── macos/ │ │ │ │ └── README.md │ │ │ ├── pigeons/ │ │ │ │ ├── copyright_header.txt │ │ │ │ └── messages.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── shared_preferences_foundation_test.dart │ │ │ └── test_api.g.dart │ │ ├── shared_preferences_linux/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── .gitignore │ │ │ │ ├── .metadata │ │ │ │ ├── README.md │ │ │ │ ├── integration_test/ │ │ │ │ │ └── shared_preferences_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── linux/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ ├── flutter/ │ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ │ └── generated_plugins.cmake │ │ │ │ │ ├── main.cc │ │ │ │ │ ├── my_application.cc │ │ │ │ │ └── my_application.h │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── lib/ │ │ │ │ └── shared_preferences_linux.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── shared_preferences_linux_test.dart │ │ ├── shared_preferences_platform_interface/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── lib/ │ │ │ │ ├── method_channel_shared_preferences.dart │ │ │ │ └── shared_preferences_platform_interface.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── method_channel_shared_preferences_test.dart │ │ │ └── shared_preferences_platform_interface_test.dart │ │ ├── shared_preferences_web/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── integration_test/ │ │ │ │ │ └── shared_preferences_web_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ ├── run_test.sh │ │ │ │ ├── test_driver/ │ │ │ │ │ └── integration_test.dart │ │ │ │ └── web/ │ │ │ │ └── index.html │ │ │ ├── lib/ │ │ │ │ └── shared_preferences_web.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── README.md │ │ │ └── tests_exist_elsewhere_test.dart │ │ └── shared_preferences_windows/ │ │ ├── .metadata │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── example/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── AUTHORS │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── integration_test/ │ │ │ │ └── shared_preferences_test.dart │ │ │ ├── lib/ │ │ │ │ └── main.dart │ │ │ ├── pubspec.yaml │ │ │ ├── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ └── windows/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ ├── flutter/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ └── generated_plugins.cmake │ │ │ └── runner/ │ │ │ ├── CMakeLists.txt │ │ │ ├── Runner.rc │ │ │ ├── flutter_window.cpp │ │ │ ├── flutter_window.h │ │ │ ├── main.cpp │ │ │ ├── resource.h │ │ │ ├── run_loop.cpp │ │ │ ├── run_loop.h │ │ │ ├── runner.exe.manifest │ │ │ ├── utils.cpp │ │ │ ├── utils.h │ │ │ ├── win32_window.cpp │ │ │ └── win32_window.h │ │ ├── lib/ │ │ │ └── shared_preferences_windows.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ └── shared_preferences_windows_test.dart │ ├── url_launcher/ │ │ ├── url_launcher/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── urllauncherexample/ │ │ │ │ │ │ │ └── FlutterActivityTest.java │ │ │ │ │ │ └── main/ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── build.excerpt.yaml │ │ │ │ ├── integration_test/ │ │ │ │ │ └── url_launcher_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ ├── lib/ │ │ │ │ │ ├── basic.dart │ │ │ │ │ ├── encoding.dart │ │ │ │ │ ├── files.dart │ │ │ │ │ └── main.dart │ │ │ │ ├── linux/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ ├── flutter/ │ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ │ └── generated_plugins.cmake │ │ │ │ │ ├── main.cc │ │ │ │ │ ├── my_application.cc │ │ │ │ │ └── my_application.h │ │ │ │ ├── macos/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── Flutter-Debug.xcconfig │ │ │ │ │ │ └── Flutter-Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ └── MainMenu.xib │ │ │ │ │ │ ├── Configs/ │ │ │ │ │ │ │ ├── AppInfo.xcconfig │ │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ │ ├── Release.xcconfig │ │ │ │ │ │ │ └── Warnings.xcconfig │ │ │ │ │ │ ├── DebugProfile.entitlements │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ ├── MainFlutterWindow.swift │ │ │ │ │ │ └── Release.entitlements │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ ├── pubspec.yaml │ │ │ │ ├── test_driver/ │ │ │ │ │ └── integration_test.dart │ │ │ │ ├── web/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── manifest.json │ │ │ │ └── windows/ │ │ │ │ ├── .gitignore │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── flutter/ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ └── generated_plugins.cmake │ │ │ │ └── runner/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── Runner.rc │ │ │ │ ├── flutter_window.cpp │ │ │ │ ├── flutter_window.h │ │ │ │ ├── main.cpp │ │ │ │ ├── resource.h │ │ │ │ ├── run_loop.cpp │ │ │ │ ├── run_loop.h │ │ │ │ ├── runner.exe.manifest │ │ │ │ ├── utils.cpp │ │ │ │ ├── utils.h │ │ │ │ ├── win32_window.cpp │ │ │ │ └── win32_window.h │ │ │ ├── lib/ │ │ │ │ ├── link.dart │ │ │ │ ├── src/ │ │ │ │ │ ├── legacy_api.dart │ │ │ │ │ ├── link.dart │ │ │ │ │ ├── type_conversion.dart │ │ │ │ │ ├── types.dart │ │ │ │ │ ├── url_launcher_string.dart │ │ │ │ │ └── url_launcher_uri.dart │ │ │ │ ├── url_launcher.dart │ │ │ │ └── url_launcher_string.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── link_test.dart │ │ │ ├── mocks/ │ │ │ │ └── mock_url_launcher_platform.dart │ │ │ └── src/ │ │ │ ├── legacy_api_test.dart │ │ │ ├── url_launcher_string_test.dart │ │ │ └── url_launcher_uri_test.dart │ │ ├── url_launcher_android/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── android/ │ │ │ │ ├── build.gradle │ │ │ │ ├── settings.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ └── java/ │ │ │ │ │ └── io/ │ │ │ │ │ └── flutter/ │ │ │ │ │ └── plugins/ │ │ │ │ │ └── urllauncher/ │ │ │ │ │ ├── MethodCallHandlerImpl.java │ │ │ │ │ ├── UrlLauncher.java │ │ │ │ │ ├── UrlLauncherPlugin.java │ │ │ │ │ └── WebViewActivity.java │ │ │ │ └── test/ │ │ │ │ └── java/ │ │ │ │ └── io/ │ │ │ │ └── flutter/ │ │ │ │ └── plugins/ │ │ │ │ └── urllauncher/ │ │ │ │ ├── MethodCallHandlerImplTest.java │ │ │ │ └── WebViewActivityTest.java │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── urllauncherexample/ │ │ │ │ │ │ │ └── FlutterActivityTest.java │ │ │ │ │ │ └── main/ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── integration_test/ │ │ │ │ │ └── url_launcher_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── lib/ │ │ │ │ └── url_launcher_android.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── url_launcher_android_test.dart │ │ ├── url_launcher_ios/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── integration_test/ │ │ │ │ │ └── url_launcher_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ ├── RunnerTests/ │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── URLLauncherTests.m │ │ │ │ │ └── RunnerUITests/ │ │ │ │ │ ├── Info.plist │ │ │ │ │ └── URLLauncherUITests.m │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── ios/ │ │ │ │ ├── Assets/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── Classes/ │ │ │ │ │ ├── FLTURLLauncherPlugin.h │ │ │ │ │ └── FLTURLLauncherPlugin.m │ │ │ │ └── url_launcher_ios.podspec │ │ │ ├── lib/ │ │ │ │ └── url_launcher_ios.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── url_launcher_ios_test.dart │ │ ├── url_launcher_linux/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── .gitignore │ │ │ │ ├── .metadata │ │ │ │ ├── README.md │ │ │ │ ├── integration_test/ │ │ │ │ │ └── url_launcher_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── linux/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ ├── flutter/ │ │ │ │ │ │ ├── CMakeLists.txt │ │ │ │ │ │ └── generated_plugins.cmake │ │ │ │ │ ├── main.cc │ │ │ │ │ ├── my_application.cc │ │ │ │ │ └── my_application.h │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── lib/ │ │ │ │ └── url_launcher_linux.dart │ │ │ ├── linux/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── include/ │ │ │ │ │ └── url_launcher_linux/ │ │ │ │ │ └── url_launcher_plugin.h │ │ │ │ ├── test/ │ │ │ │ │ └── url_launcher_linux_test.cc │ │ │ │ ├── url_launcher_plugin.cc │ │ │ │ └── url_launcher_plugin_private.h │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── url_launcher_linux_test.dart │ │ ├── url_launcher_macos/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── integration_test/ │ │ │ │ │ └── url_launcher_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── macos/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── Flutter-Debug.xcconfig │ │ │ │ │ │ └── Flutter-Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.swift │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ └── MainMenu.xib │ │ │ │ │ │ ├── Configs/ │ │ │ │ │ │ │ ├── AppInfo.xcconfig │ │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ │ ├── Release.xcconfig │ │ │ │ │ │ │ └── Warnings.xcconfig │ │ │ │ │ │ ├── DebugProfile.entitlements │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ ├── MainFlutterWindow.swift │ │ │ │ │ │ └── Release.entitlements │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ └── RunnerTests/ │ │ │ │ │ ├── Info.plist │ │ │ │ │ └── RunnerTests.swift │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ ├── lib/ │ │ │ │ └── url_launcher_macos.dart │ │ │ ├── macos/ │ │ │ │ ├── Classes/ │ │ │ │ │ └── UrlLauncherPlugin.swift │ │ │ │ └── url_launcher_macos.podspec │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ └── url_launcher_macos_test.dart │ │ ├── url_launcher_platform_interface/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── lib/ │ │ │ │ ├── link.dart │ │ │ │ ├── method_channel_url_launcher.dart │ │ │ │ ├── src/ │ │ │ │ │ ├── types.dart │ │ │ │ │ └── url_launcher_platform.dart │ │ │ │ └── url_launcher_platform_interface.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── link_test.dart │ │ │ ├── method_channel_url_launcher_test.dart │ │ │ └── url_launcher_platform_test.dart │ │ ├── url_launcher_web/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── README.md │ │ │ │ ├── build.yaml │ │ │ │ ├── integration_test/ │ │ │ │ │ ├── link_widget_test.dart │ │ │ │ │ ├── url_launcher_web_test.dart │ │ │ │ │ └── url_launcher_web_test.mocks.dart │ │ │ │ ├── lib/ │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ ├── run_test.sh │ │ │ │ ├── test_driver/ │ │ │ │ │ └── integration_test.dart │ │ │ │ └── web/ │ │ │ │ └── index.html │ │ │ ├── lib/ │ │ │ │ ├── src/ │ │ │ │ │ ├── link.dart │ │ │ │ │ ├── shims/ │ │ │ │ │ │ ├── dart_ui.dart │ │ │ │ │ │ ├── dart_ui_fake.dart │ │ │ │ │ │ └── dart_ui_real.dart │ │ │ │ │ └── third_party/ │ │ │ │ │ └── platform_detect/ │ │ │ │ │ ├── AUTHORS │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── README.md │ │ │ │ │ └── browser.dart │ │ │ │ └── url_launcher_web.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── README.md │ │ │ └── tests_exist_elsewhere_test.dart │ │ └── url_launcher_windows/ │ │ ├── .gitignore │ │ ├── .metadata │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── example/ │ │ │ ├── .gitignore │ │ │ ├── .metadata │ │ │ ├── README.md │ │ │ ├── integration_test/ │ │ │ │ └── url_launcher_test.dart │ │ │ ├── lib/ │ │ │ │ └── main.dart │ │ │ ├── pubspec.yaml │ │ │ ├── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ └── windows/ │ │ │ ├── .gitignore │ │ │ ├── CMakeLists.txt │ │ │ ├── flutter/ │ │ │ │ ├── CMakeLists.txt │ │ │ │ └── generated_plugins.cmake │ │ │ └── runner/ │ │ │ ├── CMakeLists.txt │ │ │ ├── Runner.rc │ │ │ ├── flutter_window.cpp │ │ │ ├── flutter_window.h │ │ │ ├── main.cpp │ │ │ ├── resource.h │ │ │ ├── run_loop.cpp │ │ │ ├── run_loop.h │ │ │ ├── runner.exe.manifest │ │ │ ├── utils.cpp │ │ │ ├── utils.h │ │ │ ├── win32_window.cpp │ │ │ └── win32_window.h │ │ ├── lib/ │ │ │ ├── src/ │ │ │ │ └── messages.g.dart │ │ │ └── url_launcher_windows.dart │ │ ├── pigeons/ │ │ │ ├── copyright.txt │ │ │ └── messages.dart │ │ ├── pubspec.yaml │ │ ├── test/ │ │ │ └── url_launcher_windows_test.dart │ │ └── windows/ │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── include/ │ │ │ └── url_launcher_windows/ │ │ │ └── url_launcher_windows.h │ │ ├── messages.g.cpp │ │ ├── messages.g.h │ │ ├── system_apis.cpp │ │ ├── system_apis.h │ │ ├── test/ │ │ │ └── url_launcher_windows_test.cpp │ │ ├── url_launcher_plugin.cpp │ │ ├── url_launcher_plugin.h │ │ └── url_launcher_windows.cpp │ ├── video_player/ │ │ ├── video_player/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── .gitignore │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── videoplayerexample/ │ │ │ │ │ │ │ └── FlutterActivityTest.java │ │ │ │ │ │ ├── main/ │ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ │ └── res/ │ │ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ │ ├── values/ │ │ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ │ │ └── xml/ │ │ │ │ │ │ │ └── network_security_config.xml │ │ │ │ │ │ └── test/ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ └── videoplayerexample/ │ │ │ │ │ │ └── FlutterActivityTest.java │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── assets/ │ │ │ │ │ ├── Butterfly-209.webm │ │ │ │ │ ├── bumble_bee_captions.srt │ │ │ │ │ └── bumble_bee_captions.vtt │ │ │ │ ├── build.excerpt.yaml │ │ │ │ ├── integration_test/ │ │ │ │ │ ├── controller_swap_test.dart │ │ │ │ │ └── video_player_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ │ └── README.md │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ └── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ ├── lib/ │ │ │ │ │ ├── basic.dart │ │ │ │ │ └── main.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ ├── test_driver/ │ │ │ │ │ ├── integration_test.dart │ │ │ │ │ ├── video_player.dart │ │ │ │ │ └── video_player_test.dart │ │ │ │ └── web/ │ │ │ │ └── index.html │ │ │ ├── lib/ │ │ │ │ ├── src/ │ │ │ │ │ ├── closed_caption_file.dart │ │ │ │ │ ├── sub_rip.dart │ │ │ │ │ └── web_vtt.dart │ │ │ │ └── video_player.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── closed_caption_file_test.dart │ │ │ ├── sub_rip_file_test.dart │ │ │ ├── video_player_initialization_test.dart │ │ │ ├── video_player_test.dart │ │ │ └── web_vtt_test.dart │ │ ├── video_player_android/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── android/ │ │ │ │ ├── build.gradle │ │ │ │ ├── settings.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ └── java/ │ │ │ │ │ └── io/ │ │ │ │ │ └── flutter/ │ │ │ │ │ └── plugins/ │ │ │ │ │ └── videoplayer/ │ │ │ │ │ ├── CustomSSLSocketFactory.java │ │ │ │ │ ├── Messages.java │ │ │ │ │ ├── QueuingEventSink.java │ │ │ │ │ ├── VideoPlayer.java │ │ │ │ │ ├── VideoPlayerOptions.java │ │ │ │ │ └── VideoPlayerPlugin.java │ │ │ │ └── test/ │ │ │ │ └── java/ │ │ │ │ └── io/ │ │ │ │ └── flutter/ │ │ │ │ └── plugins/ │ │ │ │ └── videoplayer/ │ │ │ │ ├── VideoPlayerPluginTest.java │ │ │ │ └── VideoPlayerTest.java │ │ │ ├── example/ │ │ │ │ ├── .gitignore │ │ │ │ ├── README.md │ │ │ │ ├── android/ │ │ │ │ │ ├── app/ │ │ │ │ │ │ ├── build.gradle │ │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ │ └── src/ │ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ │ └── videoplayerexample/ │ │ │ │ │ │ │ └── FlutterActivityTest.java │ │ │ │ │ │ ├── main/ │ │ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ │ │ └── res/ │ │ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ │ │ ├── values/ │ │ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ │ │ └── xml/ │ │ │ │ │ │ │ └── network_security_config.xml │ │ │ │ │ │ └── test/ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ └── videoplayerexample/ │ │ │ │ │ │ └── FlutterActivityTest.java │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ ├── gradle.properties │ │ │ │ │ └── settings.gradle │ │ │ │ ├── integration_test/ │ │ │ │ │ └── video_player_test.dart │ │ │ │ ├── lib/ │ │ │ │ │ ├── main.dart │ │ │ │ │ └── mini_controller.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ ├── integration_test.dart │ │ │ │ └── video_player.dart │ │ │ ├── lib/ │ │ │ │ ├── src/ │ │ │ │ │ ├── android_video_player.dart │ │ │ │ │ └── messages.g.dart │ │ │ │ └── video_player_android.dart │ │ │ ├── pigeons/ │ │ │ │ ├── copyright.txt │ │ │ │ └── messages.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── android_video_player_test.dart │ │ │ └── test_api.g.dart │ │ ├── video_player_avfoundation/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── example/ │ │ │ │ ├── .gitignore │ │ │ │ ├── README.md │ │ │ │ ├── integration_test/ │ │ │ │ │ └── video_player_test.dart │ │ │ │ ├── ios/ │ │ │ │ │ ├── Flutter/ │ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ │ └── Release.xcconfig │ │ │ │ │ ├── Podfile │ │ │ │ │ ├── Runner/ │ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ │ └── README.md │ │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── main.m │ │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ │ ├── RunnerTests/ │ │ │ │ │ │ ├── Info.plist │ │ │ │ │ │ └── VideoPlayerTests.m │ │ │ │ │ └── RunnerUITests/ │ │ │ │ │ ├── Info.plist │ │ │ │ │ └── VideoPlayerUITests.m │ │ │ │ ├── lib/ │ │ │ │ │ ├── main.dart │ │ │ │ │ └── mini_controller.dart │ │ │ │ ├── pubspec.yaml │ │ │ │ └── test_driver/ │ │ │ │ ├── integration_test.dart │ │ │ │ └── video_player.dart │ │ │ ├── ios/ │ │ │ │ ├── Assets/ │ │ │ │ │ └── .gitkeep │ │ │ │ ├── Classes/ │ │ │ │ │ ├── AVAssetTrackUtils.h │ │ │ │ │ ├── AVAssetTrackUtils.m │ │ │ │ │ ├── FLTVideoPlayerPlugin.h │ │ │ │ │ ├── FLTVideoPlayerPlugin.m │ │ │ │ │ ├── messages.g.h │ │ │ │ │ └── messages.g.m │ │ │ │ └── video_player_avfoundation.podspec │ │ │ ├── lib/ │ │ │ │ ├── src/ │ │ │ │ │ ├── avfoundation_video_player.dart │ │ │ │ │ └── messages.g.dart │ │ │ │ └── video_player_avfoundation.dart │ │ │ ├── pigeons/ │ │ │ │ ├── copyright.txt │ │ │ │ └── messages.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── avfoundation_video_player_test.dart │ │ │ └── test_api.g.dart │ │ ├── video_player_platform_interface/ │ │ │ ├── AUTHORS │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── lib/ │ │ │ │ └── video_player_platform_interface.dart │ │ │ ├── pubspec.yaml │ │ │ └── test/ │ │ │ ├── video_player_options_test.dart │ │ │ └── video_player_platform_interface_test.dart │ │ └── video_player_web/ │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── example/ │ │ │ ├── README.md │ │ │ ├── integration_test/ │ │ │ │ ├── duration_utils_test.dart │ │ │ │ ├── utils.dart │ │ │ │ ├── video_player_test.dart │ │ │ │ └── video_player_web_test.dart │ │ │ ├── lib/ │ │ │ │ └── main.dart │ │ │ ├── pubspec.yaml │ │ │ ├── run_test.sh │ │ │ ├── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ └── web/ │ │ │ └── index.html │ │ ├── lib/ │ │ │ ├── src/ │ │ │ │ ├── duration_utils.dart │ │ │ │ ├── shims/ │ │ │ │ │ ├── dart_ui.dart │ │ │ │ │ ├── dart_ui_fake.dart │ │ │ │ │ └── dart_ui_real.dart │ │ │ │ └── video_player.dart │ │ │ └── video_player_web.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ ├── README.md │ │ └── tests_exist_elsewhere_test.dart │ └── webview_flutter/ │ ├── webview_flutter/ │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── example/ │ │ │ ├── .metadata │ │ │ ├── README.md │ │ │ ├── android/ │ │ │ │ ├── app/ │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ └── src/ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ └── webviewflutterexample/ │ │ │ │ │ │ └── MainActivityTest.java │ │ │ │ │ ├── debug/ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ └── main/ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ ├── java/ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ └── webviewflutterexample/ │ │ │ │ │ │ └── WebViewTestActivity.java │ │ │ │ │ └── res/ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ └── values/ │ │ │ │ │ └── styles.xml │ │ │ │ ├── build.gradle │ │ │ │ ├── gradle/ │ │ │ │ │ └── wrapper/ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ ├── gradle.properties │ │ │ │ └── settings.gradle │ │ │ ├── assets/ │ │ │ │ ├── sample_audio.ogg │ │ │ │ └── www/ │ │ │ │ ├── index.html │ │ │ │ └── styles/ │ │ │ │ └── style.css │ │ │ ├── build.excerpt.yaml │ │ │ ├── integration_test/ │ │ │ │ ├── legacy/ │ │ │ │ │ └── webview_flutter_test.dart │ │ │ │ └── webview_flutter_test.dart │ │ │ ├── ios/ │ │ │ │ ├── Flutter/ │ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ │ ├── Debug.xcconfig │ │ │ │ │ └── Release.xcconfig │ │ │ │ ├── Podfile │ │ │ │ ├── Runner/ │ │ │ │ │ ├── AppDelegate.h │ │ │ │ │ ├── AppDelegate.m │ │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ └── README.md │ │ │ │ │ ├── Base.lproj/ │ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ │ └── Main.storyboard │ │ │ │ │ ├── Info.plist │ │ │ │ │ └── main.m │ │ │ │ ├── Runner.xcodeproj/ │ │ │ │ │ ├── project.pbxproj │ │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── xcschemes/ │ │ │ │ │ └── Runner.xcscheme │ │ │ │ ├── Runner.xcworkspace/ │ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ │ └── xcshareddata/ │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ ├── RunnerTests/ │ │ │ │ │ ├── FLTWKNavigationDelegateTests.m │ │ │ │ │ ├── FLTWebViewTests.m │ │ │ │ │ └── Info.plist │ │ │ │ └── RunnerUITests/ │ │ │ │ ├── FLTWebViewUITests.m │ │ │ │ └── Info.plist │ │ │ ├── lib/ │ │ │ │ ├── main.dart │ │ │ │ └── simple_example.dart │ │ │ ├── pubspec.yaml │ │ │ ├── test/ │ │ │ │ └── main_test.dart │ │ │ └── test_driver/ │ │ │ └── integration_test.dart │ │ ├── lib/ │ │ │ ├── src/ │ │ │ │ ├── legacy/ │ │ │ │ │ ├── platform_interface.dart │ │ │ │ │ └── webview.dart │ │ │ │ ├── navigation_delegate.dart │ │ │ │ ├── webview_controller.dart │ │ │ │ ├── webview_cookie_manager.dart │ │ │ │ ├── webview_flutter_legacy.dart │ │ │ │ └── webview_widget.dart │ │ │ └── webview_flutter.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ ├── legacy/ │ │ │ ├── webview_flutter_test.dart │ │ │ └── webview_flutter_test.mocks.dart │ │ ├── navigation_delegate_test.dart │ │ ├── navigation_delegate_test.mocks.dart │ │ ├── webview_controller_test.dart │ │ ├── webview_controller_test.mocks.dart │ │ ├── webview_cookie_manager_test.dart │ │ ├── webview_cookie_manager_test.mocks.dart │ │ ├── webview_flutter_test.dart │ │ ├── webview_widget_test.dart │ │ └── webview_widget_test.mocks.dart │ ├── webview_flutter_android/ │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── android/ │ │ │ ├── build.gradle │ │ │ ├── settings.gradle │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java/ │ │ │ │ └── io/ │ │ │ │ └── flutter/ │ │ │ │ └── plugins/ │ │ │ │ └── webviewflutter/ │ │ │ │ ├── CookieManagerHostApiImpl.java │ │ │ │ ├── DisplayListenerProxy.java │ │ │ │ ├── DownloadListenerFlutterApiImpl.java │ │ │ │ ├── DownloadListenerHostApiImpl.java │ │ │ │ ├── FileChooserParamsFlutterApiImpl.java │ │ │ │ ├── FlutterAssetManager.java │ │ │ │ ├── FlutterAssetManagerHostApiImpl.java │ │ │ │ ├── FlutterWebViewFactory.java │ │ │ │ ├── GeneratedAndroidWebView.java │ │ │ │ ├── InputAwareWebView.java │ │ │ │ ├── InstanceManager.java │ │ │ │ ├── JavaObjectHostApiImpl.java │ │ │ │ ├── JavaScriptChannel.java │ │ │ │ ├── JavaScriptChannelFlutterApiImpl.java │ │ │ │ ├── JavaScriptChannelHostApiImpl.java │ │ │ │ ├── ThreadedInputConnectionProxyAdapterView.java │ │ │ │ ├── WebChromeClientFlutterApiImpl.java │ │ │ │ ├── WebChromeClientHostApiImpl.java │ │ │ │ ├── WebSettingsHostApiImpl.java │ │ │ │ ├── WebStorageHostApiImpl.java │ │ │ │ ├── WebViewClientFlutterApiImpl.java │ │ │ │ ├── WebViewClientHostApiImpl.java │ │ │ │ ├── WebViewFlutterAndroidExternalApi.java │ │ │ │ ├── WebViewFlutterPlugin.java │ │ │ │ └── WebViewHostApiImpl.java │ │ │ └── test/ │ │ │ └── java/ │ │ │ ├── android/ │ │ │ │ └── util/ │ │ │ │ └── LongSparseArray.java │ │ │ └── io/ │ │ │ └── flutter/ │ │ │ └── plugins/ │ │ │ └── webviewflutter/ │ │ │ ├── CookieManagerHostApiImplTest.java │ │ │ ├── DownloadListenerTest.java │ │ │ ├── FileChooserParamsTest.java │ │ │ ├── FlutterAssetManagerHostApiImplTest.java │ │ │ ├── InputAwareWebViewTest.java │ │ │ ├── InstanceManagerTest.java │ │ │ ├── JavaObjectHostApiTest.java │ │ │ ├── JavaScriptChannelTest.java │ │ │ ├── PluginBindingFlutterAssetManagerTest.java │ │ │ ├── RegistrarFlutterAssetManagerTest.java │ │ │ ├── WebChromeClientTest.java │ │ │ ├── WebSettingsTest.java │ │ │ ├── WebStorageHostApiImplTest.java │ │ │ ├── WebViewClientTest.java │ │ │ ├── WebViewFlutterAndroidExternalApiTest.java │ │ │ ├── WebViewTest.java │ │ │ └── utils/ │ │ │ └── TestUtils.java │ │ ├── example/ │ │ │ ├── .metadata │ │ │ ├── README.md │ │ │ ├── android/ │ │ │ │ ├── app/ │ │ │ │ │ ├── build.gradle │ │ │ │ │ ├── gradle/ │ │ │ │ │ │ └── wrapper/ │ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ │ └── src/ │ │ │ │ │ ├── androidTest/ │ │ │ │ │ │ └── java/ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ ├── DartIntegrationTest.java │ │ │ │ │ │ └── webviewflutterexample/ │ │ │ │ │ │ ├── BackgroundColorTest.java │ │ │ │ │ │ ├── MainActivityTest.java │ │ │ │ │ │ └── WebViewTest.java │ │ │ │ │ ├── debug/ │ │ │ │ │ │ └── AndroidManifest.xml │ │ │ │ │ └── main/ │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ ├── java/ │ │ │ │ │ │ └── io/ │ │ │ │ │ │ └── flutter/ │ │ │ │ │ │ └── plugins/ │ │ │ │ │ │ └── webviewflutterexample/ │ │ │ │ │ │ ├── DriverExtensionActivity.java │ │ │ │ │ │ └── WebViewTestActivity.java │ │ │ │ │ └── res/ │ │ │ │ │ ├── drawable/ │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ └── values/ │ │ │ │ │ └── styles.xml │ │ │ │ ├── build.gradle │ │ │ │ ├── gradle/ │ │ │ │ │ └── wrapper/ │ │ │ │ │ └── gradle-wrapper.properties │ │ │ │ ├── gradle.properties │ │ │ │ └── settings.gradle │ │ │ ├── assets/ │ │ │ │ ├── sample_audio.ogg │ │ │ │ └── www/ │ │ │ │ ├── index.html │ │ │ │ └── styles/ │ │ │ │ └── style.css │ │ │ ├── integration_test/ │ │ │ │ ├── legacy/ │ │ │ │ │ └── webview_flutter_test.dart │ │ │ │ └── webview_flutter_test.dart │ │ │ ├── lib/ │ │ │ │ ├── legacy/ │ │ │ │ │ ├── navigation_decision.dart │ │ │ │ │ ├── navigation_request.dart │ │ │ │ │ └── web_view.dart │ │ │ │ └── main.dart │ │ │ ├── pubspec.yaml │ │ │ └── test_driver/ │ │ │ └── integration_test.dart │ │ ├── lib/ │ │ │ ├── src/ │ │ │ │ ├── android_proxy.dart │ │ │ │ ├── android_webview.dart │ │ │ │ ├── android_webview.g.dart │ │ │ │ ├── android_webview_api_impls.dart │ │ │ │ ├── android_webview_controller.dart │ │ │ │ ├── android_webview_cookie_manager.dart │ │ │ │ ├── android_webview_platform.dart │ │ │ │ ├── instance_manager.dart │ │ │ │ ├── legacy/ │ │ │ │ │ ├── webview_android.dart │ │ │ │ │ ├── webview_android_cookie_manager.dart │ │ │ │ │ ├── webview_android_widget.dart │ │ │ │ │ └── webview_surface_android.dart │ │ │ │ ├── platform_views_service_proxy.dart │ │ │ │ ├── weak_reference_utils.dart │ │ │ │ └── webview_flutter_android_legacy.dart │ │ │ └── webview_flutter_android.dart │ │ ├── pigeons/ │ │ │ └── android_webview.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ ├── android_navigation_delegate_test.dart │ │ ├── android_webview_controller_test.dart │ │ ├── android_webview_controller_test.mocks.dart │ │ ├── android_webview_cookie_manager_test.dart │ │ ├── android_webview_cookie_manager_test.mocks.dart │ │ ├── android_webview_test.dart │ │ ├── android_webview_test.mocks.dart │ │ ├── instance_manager_test.dart │ │ ├── legacy/ │ │ │ ├── surface_android_test.dart │ │ │ ├── webview_android_cookie_manager_test.dart │ │ │ ├── webview_android_cookie_manager_test.mocks.dart │ │ │ ├── webview_android_widget_test.dart │ │ │ └── webview_android_widget_test.mocks.dart │ │ └── test_android_webview.g.dart │ ├── webview_flutter_platform_interface/ │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── lib/ │ │ │ ├── src/ │ │ │ │ ├── legacy/ │ │ │ │ │ ├── platform_interface/ │ │ │ │ │ │ ├── javascript_channel_registry.dart │ │ │ │ │ │ ├── platform_interface.dart │ │ │ │ │ │ ├── webview_cookie_manager.dart │ │ │ │ │ │ ├── webview_platform.dart │ │ │ │ │ │ ├── webview_platform_callbacks_handler.dart │ │ │ │ │ │ └── webview_platform_controller.dart │ │ │ │ │ └── types/ │ │ │ │ │ ├── auto_media_playback_policy.dart │ │ │ │ │ ├── creation_params.dart │ │ │ │ │ ├── javascript_channel.dart │ │ │ │ │ ├── javascript_message.dart │ │ │ │ │ ├── javascript_mode.dart │ │ │ │ │ ├── types.dart │ │ │ │ │ ├── web_resource_error.dart │ │ │ │ │ ├── web_resource_error_type.dart │ │ │ │ │ ├── web_settings.dart │ │ │ │ │ ├── webview_cookie.dart │ │ │ │ │ └── webview_request.dart │ │ │ │ ├── platform_navigation_delegate.dart │ │ │ │ ├── platform_webview_controller.dart │ │ │ │ ├── platform_webview_cookie_manager.dart │ │ │ │ ├── platform_webview_widget.dart │ │ │ │ ├── types/ │ │ │ │ │ ├── javascript_message.dart │ │ │ │ │ ├── javascript_mode.dart │ │ │ │ │ ├── load_request_params.dart │ │ │ │ │ ├── navigation_decision.dart │ │ │ │ │ ├── navigation_request.dart │ │ │ │ │ ├── platform_navigation_delegate_creation_params.dart │ │ │ │ │ ├── platform_webview_controller_creation_params.dart │ │ │ │ │ ├── platform_webview_cookie_manager_creation_params.dart │ │ │ │ │ ├── platform_webview_widget_creation_params.dart │ │ │ │ │ ├── types.dart │ │ │ │ │ ├── web_resource_error.dart │ │ │ │ │ └── webview_cookie.dart │ │ │ │ ├── webview_flutter_platform_interface_legacy.dart │ │ │ │ └── webview_platform.dart │ │ │ └── webview_flutter_platform_interface.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ ├── legacy/ │ │ │ ├── platform_interface/ │ │ │ │ ├── javascript_channel_registry_test.dart │ │ │ │ └── webview_cookie_manager_test.dart │ │ │ └── types/ │ │ │ ├── javascript_channel_test.dart │ │ │ ├── webview_cookie_test.dart │ │ │ └── webview_request_test.dart │ │ ├── platform_navigation_delegate_test.dart │ │ ├── platform_webview_controller_test.dart │ │ ├── platform_webview_controller_test.mocks.dart │ │ ├── platform_webview_widget_test.dart │ │ ├── webview_platform_test.dart │ │ └── webview_platform_test.mocks.dart │ ├── webview_flutter_web/ │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── example/ │ │ │ ├── .metadata │ │ │ ├── README.md │ │ │ ├── integration_test/ │ │ │ │ ├── legacy/ │ │ │ │ │ └── webview_flutter_test.dart │ │ │ │ └── webview_flutter_test.dart │ │ │ ├── lib/ │ │ │ │ ├── legacy/ │ │ │ │ │ └── web_view.dart │ │ │ │ └── main.dart │ │ │ ├── pubspec.yaml │ │ │ ├── run_test.sh │ │ │ ├── test_driver/ │ │ │ │ └── integration_test.dart │ │ │ └── web/ │ │ │ ├── index.html │ │ │ └── manifest.json │ │ ├── lib/ │ │ │ ├── src/ │ │ │ │ ├── content_type.dart │ │ │ │ ├── http_request_factory.dart │ │ │ │ ├── shims/ │ │ │ │ │ ├── dart_ui.dart │ │ │ │ │ ├── dart_ui_fake.dart │ │ │ │ │ └── dart_ui_real.dart │ │ │ │ ├── web_webview_controller.dart │ │ │ │ ├── web_webview_platform.dart │ │ │ │ └── webview_flutter_web_legacy.dart │ │ │ └── webview_flutter_web.dart │ │ ├── pubspec.yaml │ │ └── test/ │ │ ├── content_type_test.dart │ │ ├── legacy/ │ │ │ ├── webview_flutter_web_test.dart │ │ │ └── webview_flutter_web_test.mocks.dart │ │ ├── web_webview_controller_test.dart │ │ ├── web_webview_controller_test.mocks.dart │ │ ├── web_webview_widget_test.dart │ │ └── webview_flutter_web_test.dart │ └── webview_flutter_wkwebview/ │ ├── AUTHORS │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── example/ │ │ ├── .metadata │ │ ├── README.md │ │ ├── assets/ │ │ │ ├── sample_audio.ogg │ │ │ └── www/ │ │ │ ├── index.html │ │ │ └── styles/ │ │ │ └── style.css │ │ ├── integration_test/ │ │ │ ├── legacy/ │ │ │ │ └── webview_flutter_test.dart │ │ │ └── webview_flutter_test.dart │ │ ├── ios/ │ │ │ ├── Flutter/ │ │ │ │ ├── AppFrameworkInfo.plist │ │ │ │ ├── Debug.xcconfig │ │ │ │ └── Release.xcconfig │ │ │ ├── Podfile │ │ │ ├── Runner/ │ │ │ │ ├── AppDelegate.h │ │ │ │ ├── AppDelegate.m │ │ │ │ ├── Assets.xcassets/ │ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── README.md │ │ │ │ ├── Base.lproj/ │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ └── Main.storyboard │ │ │ │ ├── Info.plist │ │ │ │ └── main.m │ │ │ ├── Runner.xcodeproj/ │ │ │ │ ├── project.pbxproj │ │ │ │ ├── project.xcworkspace/ │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ └── xcshareddata/ │ │ │ │ └── xcschemes/ │ │ │ │ └── Runner.xcscheme │ │ │ ├── Runner.xcworkspace/ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata/ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ ├── RunnerTests/ │ │ │ │ ├── FWFDataConvertersTests.m │ │ │ │ ├── FWFHTTPCookieStoreHostApiTests.m │ │ │ │ ├── FWFInstanceManagerTests.m │ │ │ │ ├── FWFNavigationDelegateHostApiTests.m │ │ │ │ ├── FWFObjectHostApiTests.m │ │ │ │ ├── FWFPreferencesHostApiTests.m │ │ │ │ ├── FWFScriptMessageHandlerHostApiTests.m │ │ │ │ ├── FWFScrollViewHostApiTests.m │ │ │ │ ├── FWFUIDelegateHostApiTests.m │ │ │ │ ├── FWFUIViewHostApiTests.m │ │ │ │ ├── FWFUserContentControllerHostApiTests.m │ │ │ │ ├── FWFWebViewConfigurationHostApiTests.m │ │ │ │ ├── FWFWebViewFlutterWKWebViewExternalAPITests.m │ │ │ │ ├── FWFWebViewHostApiTests.m │ │ │ │ ├── FWFWebsiteDataStoreHostApiTests.m │ │ │ │ └── Info.plist │ │ │ └── RunnerUITests/ │ │ │ ├── FLTWebViewUITests.m │ │ │ └── Info.plist │ │ ├── lib/ │ │ │ ├── legacy/ │ │ │ │ ├── navigation_decision.dart │ │ │ │ ├── navigation_request.dart │ │ │ │ └── web_view.dart │ │ │ └── main.dart │ │ ├── pubspec.yaml │ │ └── test_driver/ │ │ └── integration_test.dart │ ├── ios/ │ │ ├── Assets/ │ │ │ └── .gitkeep │ │ ├── Classes/ │ │ │ ├── FLTWebViewFlutterPlugin.h │ │ │ ├── FLTWebViewFlutterPlugin.m │ │ │ ├── FWFDataConverters.h │ │ │ ├── FWFDataConverters.m │ │ │ ├── FWFGeneratedWebKitApis.h │ │ │ ├── FWFGeneratedWebKitApis.m │ │ │ ├── FWFHTTPCookieStoreHostApi.h │ │ │ ├── FWFHTTPCookieStoreHostApi.m │ │ │ ├── FWFInstanceManager.h │ │ │ ├── FWFInstanceManager.m │ │ │ ├── FWFInstanceManager_Test.h │ │ │ ├── FWFNavigationDelegateHostApi.h │ │ │ ├── FWFNavigationDelegateHostApi.m │ │ │ ├── FWFObjectHostApi.h │ │ │ ├── FWFObjectHostApi.m │ │ │ ├── FWFPreferencesHostApi.h │ │ │ ├── FWFPreferencesHostApi.m │ │ │ ├── FWFScriptMessageHandlerHostApi.h │ │ │ ├── FWFScriptMessageHandlerHostApi.m │ │ │ ├── FWFScrollViewHostApi.h │ │ │ ├── FWFScrollViewHostApi.m │ │ │ ├── FWFUIDelegateHostApi.h │ │ │ ├── FWFUIDelegateHostApi.m │ │ │ ├── FWFUIViewHostApi.h │ │ │ ├── FWFUIViewHostApi.m │ │ │ ├── FWFUserContentControllerHostApi.h │ │ │ ├── FWFUserContentControllerHostApi.m │ │ │ ├── FWFWebViewConfigurationHostApi.h │ │ │ ├── FWFWebViewConfigurationHostApi.m │ │ │ ├── FWFWebViewFlutterWKWebViewExternalAPI.h │ │ │ ├── FWFWebViewFlutterWKWebViewExternalAPI.m │ │ │ ├── FWFWebViewHostApi.h │ │ │ ├── FWFWebViewHostApi.m │ │ │ ├── FWFWebsiteDataStoreHostApi.h │ │ │ ├── FWFWebsiteDataStoreHostApi.m │ │ │ ├── FlutterWebView.modulemap │ │ │ └── webview-umbrella.h │ │ └── webview_flutter_wkwebview.podspec │ ├── lib/ │ │ ├── src/ │ │ │ ├── common/ │ │ │ │ ├── instance_manager.dart │ │ │ │ ├── weak_reference_utils.dart │ │ │ │ └── web_kit.g.dart │ │ │ ├── foundation/ │ │ │ │ ├── foundation.dart │ │ │ │ └── foundation_api_impls.dart │ │ │ ├── legacy/ │ │ │ │ ├── web_kit_webview_widget.dart │ │ │ │ ├── webview_cupertino.dart │ │ │ │ └── wkwebview_cookie_manager.dart │ │ │ ├── ui_kit/ │ │ │ │ ├── ui_kit.dart │ │ │ │ └── ui_kit_api_impls.dart │ │ │ ├── web_kit/ │ │ │ │ ├── web_kit.dart │ │ │ │ └── web_kit_api_impls.dart │ │ │ ├── webkit_proxy.dart │ │ │ ├── webkit_webview_controller.dart │ │ │ ├── webkit_webview_cookie_manager.dart │ │ │ ├── webkit_webview_platform.dart │ │ │ └── webview_flutter_wkwebview_legacy.dart │ │ └── webview_flutter_wkwebview.dart │ ├── pigeons/ │ │ └── web_kit.dart │ ├── pubspec.yaml │ └── test/ │ ├── legacy/ │ │ ├── web_kit_cookie_manager_test.dart │ │ ├── web_kit_cookie_manager_test.mocks.dart │ │ ├── web_kit_webview_widget_test.dart │ │ └── web_kit_webview_widget_test.mocks.dart │ ├── src/ │ │ ├── common/ │ │ │ ├── instance_manager_test.dart │ │ │ └── test_web_kit.g.dart │ │ ├── foundation/ │ │ │ ├── foundation_test.dart │ │ │ └── foundation_test.mocks.dart │ │ ├── ui_kit/ │ │ │ ├── ui_kit_test.dart │ │ │ └── ui_kit_test.mocks.dart │ │ └── web_kit/ │ │ ├── web_kit_test.dart │ │ └── web_kit_test.mocks.dart │ ├── webkit_navigation_delegate_test.dart │ ├── webkit_navigation_delegate_test.mocks.dart │ ├── webkit_webview_controller_test.dart │ ├── webkit_webview_controller_test.mocks.dart │ ├── webkit_webview_cookie_manager_test.dart │ ├── webkit_webview_cookie_manager_test.mocks.dart │ ├── webkit_webview_widget_test.dart │ └── webkit_webview_widget_test.mocks.dart └── script/ ├── configs/ │ ├── README.md │ ├── custom_analysis.yaml │ ├── exclude_all_packages_app.yaml │ ├── exclude_integration_android.yaml │ ├── exclude_integration_ios.yaml │ ├── exclude_integration_linux.yaml │ ├── exclude_integration_macos.yaml │ ├── exclude_integration_web.yaml │ ├── exclude_integration_win32.yaml │ ├── exclude_native_unit_android.yaml │ └── temp_exclude_excerpt.yaml ├── install_chromium.sh ├── tool/ │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── bin/ │ │ └── flutter_plugin_tools.dart │ ├── lib/ │ │ └── src/ │ │ ├── analyze_command.dart │ │ ├── common/ │ │ │ ├── core.dart │ │ │ ├── git_version_finder.dart │ │ │ ├── package_command.dart │ │ │ ├── package_looping_command.dart │ │ │ ├── process_runner.dart │ │ │ └── repository_package.dart │ │ └── main.dart │ └── pubspec.yaml └── tool_runner.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .ci/Dockerfile ================================================ # The Flutter version is not important here, since the CI scripts update Flutter # before running. What matters is that the base image is pinned to minimize # unintended changes when modifying this file. # This is the hash for the 3.0.0 image. FROM cirrusci/flutter@sha256:0224587bba33241cf908184283ec2b544f1b672d87043ead1c00521c368cf844 RUN apt-get update -y # Set up Firebase Test Lab requirements. RUN apt-get install -y --no-install-recommends gnupg RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | \ sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | \ sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - RUN apt-get update && apt-get install -y google-cloud-sdk && \ gcloud config set core/disable_usage_reporting true && \ gcloud config set component_manager/disable_update_check true # Install formatter for C-based languages. RUN apt-get install -y clang-format # Install Linux desktop requirements: # - build tools. RUN apt-get install -y clang cmake ninja-build file pkg-config # - libraries. RUN apt-get install -y libgtk-3-dev libblkid-dev liblzma-dev libgcrypt20-dev # - xvfb to allow running headless. RUN apt-get install -y xvfb libegl1-mesa # Install Chrome and make it the default browser, for url_launcher tests. # IMPORTANT: Web tests should use a pinned version of Chromium, not this, since # this isn't pinned, so any time the docker image is re-created the version of # Chrome may change. RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - RUN echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | sudo tee /etc/apt/sources.list.d/google-chrome.list RUN apt-get update && apt-get install -y --no-install-recommends google-chrome-stable # Make Chrome the default for http:, https: and file:. RUN apt-get install -y xdg-utils RUN xdg-settings set default-web-browser google-chrome.desktop RUN xdg-mime default google-chrome.desktop inode/directory ================================================ FILE: .ci/flutter_master.version ================================================ 33e4d21f7c13e02a7c92c7272309afbff792a864 ================================================ FILE: .ci/flutter_stable.version ================================================ 7048ed95a5ad3e43d697e0c397464193991fc230 ================================================ FILE: .ci/scripts/build_all_plugins.sh ================================================ #!/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. platform="$1" build_mode="$2" shift 2 cd all_packages flutter build "$platform" --"$build_mode" "$@" ================================================ FILE: .ci/scripts/build_examples_win32.sh ================================================ #!/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. dart pub global run flutter_plugin_tools build-examples --windows \ --packages-for-branch --log-timing ================================================ FILE: .ci/scripts/create_all_plugins_app.sh ================================================ #!/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. dart pub global run flutter_plugin_tools create-all-packages-app \ --output-dir=. --exclude script/configs/exclude_all_packages_app.yaml ================================================ FILE: .ci/scripts/create_simulator.sh ================================================ #!/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. device=com.apple.CoreSimulator.SimDeviceType.iPhone-13 os=com.apple.CoreSimulator.SimRuntime.iOS-16-0 xcrun simctl list xcrun simctl create Flutter-iPhone "$device" "$os" | xargs xcrun simctl boot ================================================ FILE: .ci/scripts/drive_examples_win32.sh ================================================ #!/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. dart pub global run flutter_plugin_tools drive-examples --windows \ --exclude=script/configs/exclude_integration_win32.yaml --packages-for-branch --log-timing ================================================ FILE: .ci/scripts/native_test_win32.sh ================================================ #!/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. dart pub global run flutter_plugin_tools native-test --windows \ --no-integration --packages-for-branch --log-timing ================================================ FILE: .ci/scripts/prepare_tool.sh ================================================ #!/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # To set FETCH_HEAD for "git merge-base" to work git fetch origin main # Pinned version of the plugin tools, to avoid breakage in this repository # when pushing updates from flutter/packages. dart pub global activate flutter_plugin_tools 0.13.4+3 ================================================ FILE: .ci/targets/ios_build_all_plugins.yaml ================================================ 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"] ================================================ FILE: .ci/targets/ios_platform_tests.yaml ================================================ tasks: - name: prepare tool script: .ci/scripts/prepare_tool.sh - name: create simulator script: .ci/scripts/create_simulator.sh - name: build examples script: script/tool_runner.sh args: ["build-examples", "--ios"] - name: xcode analyze script: script/tool_runner.sh args: ["xcode-analyze", "--ios"] - name: xcode analyze deprecation # Ensure we don't accidentally introduce deprecated code. script: script/tool_runner.sh args: ["xcode-analyze", "--ios", "--ios-min-version=13.0"] - name: native test script: script/tool_runner.sh args: ["native-test", "--ios", "--ios-destination", "platform=iOS Simulator,name=iPhone 13,OS=latest"] - name: drive examples # `drive-examples` contains integration tests, which changes the UI of the application. # This UI change sometimes affects `xctest`. # So we run `drive-examples` after `native-test`; changing the order will result ci failure. script: script/tool_runner.sh args: ["drive-examples", "--ios", "--exclude=script/configs/exclude_integration_ios.yaml"] ================================================ FILE: .ci/targets/macos_build_all_plugins.yaml ================================================ 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 macOS debug script: .ci/scripts/build_all_plugins.sh args: ["macos", "debug"] - name: build all_plugins for macOS release script: .ci/scripts/build_all_plugins.sh args: ["macos", "release"] ================================================ FILE: .ci/targets/macos_lint_podspecs.yaml ================================================ tasks: - name: prepare tool script: .ci/scripts/prepare_tool.sh - name: validate iOS and macOS podspecs script: script/tool_runner.sh args: ["podspec-check"] ================================================ FILE: .ci/targets/macos_platform_tests.yaml ================================================ 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"] ================================================ FILE: .ci/targets/windows_build_all_plugins.yaml ================================================ 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 Windows debug script: .ci/scripts/build_all_plugins.sh args: ["windows", "debug"] - name: build all_plugins for Windows release script: .ci/scripts/build_all_plugins.sh args: ["windows", "release"] ================================================ FILE: .ci/targets/windows_build_and_platform_tests.yaml ================================================ tasks: - name: prepare tool script: .ci/scripts/prepare_tool.sh - name: build examples (Win32) script: .ci/scripts/build_examples_win32.sh - name: native unit tests (Win32) script: .ci/scripts/native_test_win32.sh - name: drive examples (Win32) script: .ci/scripts/drive_examples_win32.sh ================================================ FILE: .ci.yaml ================================================ # Describes the targets run in continuous integration environment. # # Flutter infra uses this file to generate a checklist of tasks to be performed # for every commit. # # More information at: # * https://github.com/flutter/cocoon/blob/main/CI_YAML.md enabled_branches: - main platform_properties: linux: properties: dependencies: > [ {"dependency": "curl", "version": "version:7.64.0"} ] device_type: none os: Linux windows: properties: dependencies: > [ {"dependency": "certs", "version": "version:9563bb"} ] 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: >- [ {"dependency": "xcode", "version": "14a5294e"}, {"dependency": "gems", "version": "v3.3.14"} ] os: Mac-12 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: macos_lint_podspecs.yaml ### macOS desktop tasks ### # 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 timeout: 30 properties: add_recipes_cq: "true" version_file: flutter_master.version target_file: macos_build_all_plugins.yaml channel: master - name: Mac_x64 build_all_plugins stable recipe: plugins/plugins timeout: 30 properties: add_recipes_cq: "true" version_file: flutter_stable.version 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 ### # ios_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 ios_build_all_plugins master 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 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): Change all of the ios_platform_tests_* task timeouts # to 60 minutes once https://github.com/flutter/flutter/issues/119750 is # fixed. - name: Mac_arm64 ios_platform_tests_shard_1 master - plugins recipe: plugins/plugins timeout: 120 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 recipe: plugins/plugins timeout: 120 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 recipe: plugins/plugins timeout: 120 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 recipe: plugins/plugins timeout: 120 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 recipe: plugins/plugins timeout: 120 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_arm64 ios_platform_tests_shard_1 stable - plugins recipe: plugins/plugins presubmit: false timeout: 120 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 recipe: plugins/plugins presubmit: false timeout: 120 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 recipe: plugins/plugins presubmit: false timeout: 120 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 recipe: plugins/plugins presubmit: false timeout: 120 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 recipe: plugins/plugins presubmit: false timeout: 120 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: 60 properties: add_recipes_cq: "true" target_file: windows_build_and_platform_tests.yaml channel: master version_file: flutter_master.version dependencies: > [ {"dependency": "vs_build", "version": "version:vs2019"} ] - name: Windows win32-platform_tests stable recipe: plugins/plugins presubmit: false timeout: 60 properties: add_recipes_cq: "true" target_file: windows_build_and_platform_tests.yaml channel: stable version_file: flutter_stable.version dependencies: > [ {"dependency": "vs_build", "version": "version:vs2019"} ] - name: Windows windows-build_all_plugins master recipe: plugins/plugins timeout: 30 properties: add_recipes_cq: "true" target_file: windows_build_all_plugins.yaml channel: master version_file: flutter_master.version dependencies: > [ {"dependency": "vs_build", "version": "version:vs2019"} ] - name: Windows windows-build_all_plugins stable recipe: plugins/plugins timeout: 30 properties: add_recipes_cq: "true" target_file: windows_build_all_plugins.yaml channel: stable version_file: flutter_stable.version dependencies: > [ {"dependency": "vs_build", "version": "version:vs2019"} ] - name: Linux ci_yaml plugins roller recipe: infra/ci_yaml timeout: 30 runIf: - .ci.yaml ================================================ FILE: .cirrus.yml ================================================ gcp_credentials: ENCRYPTED[!3a93d98d7c95a41f5033834ef30e50928fc5d81239dc632b153c2628200a8187f3811cb01ce338b1ab3b6505a7a65c37!] # Run on PRs and main branch post submit only. Don't run tests when tagging. only_if: $CIRRUS_TAG == '' && ($CIRRUS_PR != '' || $CIRRUS_BRANCH == 'main') env: CHANNEL: "master" # Default to master when not explicitly set by a task. PLUGIN_TOOL_COMMAND: "dart pub global run flutter_plugin_tools" install_chrome_linux_template: &INSTALL_CHROME_LINUX env: CHROME_NO_SANDBOX: true CHROME_DOWNLOAD_DIR: /tmp/chromium CHROME_EXECUTABLE: $CHROME_DOWNLOAD_DIR/chrome-linux/chrome CHROMEDRIVER_EXECUTABLE: $CHROME_DOWNLOAD_DIR/chromedriver/chromedriver PATH: $PATH:$CHROME_DOWNLOAD_DIR/chrome-linux install_chromium_script: # Install a pinned version of Chromium and its corresponding ChromeDriver. # Setting CHROME_EXECUTABLE above causes this version to be used for tests. - ./script/install_chromium.sh tool_setup_template: &TOOL_SETUP_TEMPLATE tool_setup_script: - .ci/scripts/prepare_tool.sh flutter_upgrade_template: &FLUTTER_UPGRADE_TEMPLATE upgrade_flutter_script: # Channels that are part of our normal test matrix use a pinned, # auto-rolled version to prevent out-of-band CI failures due to changes in # Flutter. - TARGET_TREEISH=$CHANNEL - if [[ "$CHANNEL" == "master" || "$CHANNEL" == "stable" ]]; then - TARGET_TREEISH=$(< .ci/flutter_$CHANNEL.version) - fi # Ensure that the repository has all the branches. - cd $FLUTTER_HOME - git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" - git fetch origin # Switch to the requested channel. - git checkout $TARGET_TREEISH # When using a branch rather than a hash or version tag, reset to the # upstream branch rather than using pull, since the base image can sometimes # be in a state where it has diverged from upstream (!). - if [[ "$TARGET_TREEISH" == "$CHANNEL" ]] && [[ "$CHANNEL" != *"."* ]]; then - git reset --hard @{u} - fi # Run doctor to allow auditing of what version of Flutter the run is using. - flutter doctor -v << : *TOOL_SETUP_TEMPLATE # 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_packages_release_script: - cd all_packages - flutter build $BUILD_ALL_ARGS --release # Light-workload Linux tasks. # These use default machines, with fewer CPUs, to reduce pressure on the # concurrency limits. task: << : *FLUTTER_UPGRADE_TEMPLATE gke_container: dockerfile: .ci/Dockerfile builder_image_name: docker-builder-linux # gce vm image builder_image_project: flutter-cirrus cluster_name: test-cluster zone: us-central1-a namespace: default matrix: ### Platform-agnostic tasks ### # Repository rules and best-practice enforcement. # Only channel-agnostic tests should go here since it is only run once # (on Flutter master). - name: repo_checks always: format_script: ./script/tool_runner.sh format --fail-on-change license_script: $PLUGIN_TOOL_COMMAND license-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 # to be converted. Once https://github.com/flutter/flutter/issues/102679 # has been fixed, this can be removed and there can just be a single # run with --require-excerpts and no exclusions. - ./script/tool_runner.sh readme-check --require-excerpts --exclude=script/configs/temp_exclude_excerpt.yaml dependabot_script: $PLUGIN_TOOL_COMMAND dependabot-check version_script: # For pre-submit, pass the PR labels to the script to allow for # check overrides. # For post-submit, ignore platform version breaking version changes # and missing version/CHANGELOG detection since the labels aren't # available outside of the context of the PR. - if [[ $CIRRUS_PR == "" ]]; then - ./script/tool_runner.sh version-check --ignore-platform-interface-breaks - else - ./script/tool_runner.sh version-check --check-for-missing-changes --pr-labels="$CIRRUS_PR_LABELS" - fi publishable_script: ./script/tool_runner.sh publish-check --allow-pre-release federated_safety_script: # This check is only meaningful for PRs, as it validates changes # rather than state. - if [[ $CIRRUS_PR == "" ]]; then - ./script/tool_runner.sh federation-safety-check - else - echo "Only run in presubmit" - fi - name: analyze env: matrix: CHANNEL: "master" CHANNEL: "stable" analyze_script: # DO NOT change the custom-analysis argument here without changing the Dart repo. # See the comment in script/configs/custom_analysis.yaml for details. - ./script/tool_runner.sh analyze --custom-analysis=script/configs/custom_analysis.yaml pathified_analyze_script: # Re-run analysis with path-based dependencies to ensure that publishing # the changes won't break analysis of other packages in the respository # that depend on it. - ./script/tool_runner.sh make-deps-path-based --target-dependencies-with-non-breaking-updates # This uses --run-on-dirty-packages rather than --packages-for-branch # since only the packages changed by 'make-deps-path-based' need to be # checked. - $PLUGIN_TOOL_COMMAND analyze --run-on-dirty-packages --log-timing --custom-analysis=script/configs/custom_analysis.yaml # Restore the tree to a clean state, to avoid accidental issues if # other script steps are added to this task. - git checkout . # Does a sanity check that packages at least pass analysis on the N-1 and N-2 # versions of Flutter stable if the package claims to support that version. # This is to minimize accidentally making changes that break old versions # (which we don't commit to supporting, but don't want to actively break) # without updating the constraints. # Note: The versions below should be manually updated after a new stable # version comes out. - 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: "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. - ./script/tool_runner.sh remove-dev-dependencies analyze_script: # Only analyze lib/; non-client code doesn't need to work on # all supported legacy version. - ./script/tool_runner.sh analyze --lib-only --skip-if-not-supporting-flutter-version="$CHANNEL" --skip-if-not-supporting-dart-version="$DART_VERSION" --custom-analysis=script/configs/custom_analysis.yaml # Does a sanity check that packages pass analysis with the lowest possible # versions of all dependencies. This is to catch cases where we add use of # new APIs but forget to update minimum versions of dependencies to where # those APIs are introduced. - name: downgraded_analyze depends_on: analyze analyze_script: - ./script/tool_runner.sh analyze --downgrade --custom-analysis=script/configs/custom_analysis.yaml - name: readme_excerpts env: CIRRUS_CLONE_SUBMODULES: true script: ./script/tool_runner.sh update-excerpts --fail-on-change ### Web tasks ### - name: web-build_all_packages env: BUILD_ALL_ARGS: "web" matrix: CHANNEL: "master" CHANNEL: "stable" << : *BUILD_ALL_PACKAGES_APP_TEMPLATE ### Linux desktop tasks ### - name: linux-build_all_packages env: BUILD_ALL_ARGS: "linux" matrix: CHANNEL: "master" CHANNEL: "stable" << : *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' env: matrix: CHANNEL: "master" CHANNEL: "stable" build_script: - ./script/tool_runner.sh build-examples --linux native_test_script: - xvfb-run ./script/tool_runner.sh native-test --linux --no-integration drive_script: - xvfb-run ./script/tool_runner.sh drive-examples --linux --exclude=script/configs/exclude_integration_linux.yaml # Heavy-workload Linux tasks. # These use machines with more CPUs and memory, so will reduce parallelization # for non-credit runs. task: << : *FLUTTER_UPGRADE_TEMPLATE gke_container: dockerfile: .ci/Dockerfile builder_image_name: docker-builder-linux # gce vm image builder_image_project: flutter-cirrus cluster_name: test-cluster zone: us-central1-a namespace: default cpu: 4 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. skip: $CIRRUS_PR != '' && $CHANNEL == 'stable' env: matrix: PACKAGE_SHARDING: "--shardIndex 0 --shardCount 5" PACKAGE_SHARDING: "--shardIndex 1 --shardCount 5" PACKAGE_SHARDING: "--shardIndex 2 --shardCount 5" PACKAGE_SHARDING: "--shardIndex 3 --shardCount 5" PACKAGE_SHARDING: "--shardIndex 4 --shardCount 5" matrix: CHANNEL: "master" CHANNEL: "stable" MAPS_API_KEY: ENCRYPTED[596a9f6bca436694625ac50851dc5da6b4d34cba8025f7db5bc9465142e8cd44e15f69e3507787753accebfc4910d550] GCLOUD_FIREBASE_TESTLAB_KEY: ENCRYPTED[df5cf97036c09184e386edbf4ab1e741189e0ac5ca7e4c73673c4bf02d8709c9ac733597e8f5b6511b51eafb52e4027f] build_script: - ./script/tool_runner.sh build-examples --apk 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 # 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 firebase_test_lab_script: - if [[ -n "$GCLOUD_FIREBASE_TESTLAB_KEY" ]]; then - echo $GCLOUD_FIREBASE_TESTLAB_KEY > ${HOME}/gcloud-service-key.json - ./script/tool_runner.sh firebase-test-lab --device model=redfin,version=30 --device model=starqlteue,version=26 --exclude=script/configs/exclude_integration_android.yaml - else - echo "This user does not have permission to run Firebase Test Lab tests." - fi # Upload the full lint results to Cirrus to display in the results UI. always: android-lint_artifacts: path: "**/reports/lint-results-debug.xml" type: text/xml format: android-lint - name: android-build_all_packages env: BUILD_ALL_ARGS: "apk" matrix: CHANNEL: "master" CHANNEL: "stable" << : *BUILD_ALL_PACKAGES_APP_TEMPLATE ### Web tasks ### - name: web-platform_tests env: matrix: PACKAGE_SHARDING: "--shardIndex 0 --shardCount 2" PACKAGE_SHARDING: "--shardIndex 1 --shardCount 2" matrix: CHANNEL: "master" CHANNEL: "stable" << : *INSTALL_CHROME_LINUX chromedriver_background_script: - $CHROMEDRIVER_EXECUTABLE --port=4444 build_script: - ./script/tool_runner.sh build-examples --web drive_script: - ./script/tool_runner.sh drive-examples --web --exclude=script/configs/exclude_integration_web.yaml ================================================ FILE: .clang-format ================================================ BasedOnStyle: Google --- Language: Cpp DerivePointerAlignment: false PointerAlignment: Left --- Language: ObjC DerivePointerAlignment: false PointerAlignment: Right ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto # Always perform LF normalization on these files *.dart text *.gradle text *.html text *.java text *.json text *.md text *.py text *.sh text *.txt text *.xml text *.yaml text # Make sure that these Windows files always have CRLF line endings in checkout *.bat text eol=crlf *.ps1 text eol=crlf # Never perform LF normalization on these files *.ico binary *.jar binary *.png binary *.zip binary ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ *Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.* *List which issues are fixed by this PR. You must list at least one issue.* *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [relevant style guides] and ran [the auto-formatter]. (Unlike the flutter/flutter repo, the flutter/plugins repo does use `dart format`.) - [ ] I signed the [CLA]. - [ ] The title of the PR starts with the name of the plugin surrounded by square brackets, e.g. `[shared_preferences]` - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated `pubspec.yaml` with an appropriate new version according to the [pub versioning philosophy], or this PR is [exempt from version changes]. - [ ] I updated `CHANGELOG.md` to add a description of the change, [following repository CHANGELOG style]. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. [Contributor Guide]: https://github.com/flutter/plugins/blob/main/CONTRIBUTING.md [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [relevant style guides]: https://github.com/flutter/plugins/blob/main/CONTRIBUTING.md#style [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat [pub versioning philosophy]: https://dart.dev/tools/pub/versioning [exempt from version changes]: https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#version-and-changelog-updates [following repository CHANGELOG style]: https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changelog-style [the auto-formatter]: https://github.com/flutter/plugins/blob/main/script/tool/README.md#format-code [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "gradle" directory: "/packages/camera/camera_android/android" commit-message: prefix: "[camera]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "com.android.tools.build:gradle" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "junit:junit" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.mockito:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "androidx.test:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.robolectric:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/camera/camera_android/example/android/app" commit-message: prefix: "[camera]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/camera/camera_android_camerax/android" commit-message: prefix: "[camera]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "com.android.tools.build:gradle" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "junit:junit" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.mockito:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "androidx.test:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.robolectric:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/camera/camera_android_camerax/example/android/app" commit-message: prefix: "[camera]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/camera/camera/example/android/app" commit-message: prefix: "[camera]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/espresso/android" commit-message: prefix: "[espresso]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "com.android.tools.build:gradle" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "junit:junit" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.mockito:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "androidx.test:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.robolectric:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/espresso/example/android/app" commit-message: prefix: "[espresso]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/flutter_plugin_android_lifecycle/android" commit-message: prefix: "[lifecycle]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "com.android.tools.build:gradle" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "junit:junit" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.mockito:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "androidx.test:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.robolectric:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/flutter_plugin_android_lifecycle/example/android/app" commit-message: prefix: "[lifecycle]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/google_maps_flutter/google_maps_flutter/example/android/app" commit-message: prefix: "[google_maps]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/google_maps_flutter/google_maps_flutter_android/android" commit-message: prefix: "[google_maps]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "com.android.tools.build:gradle" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "junit:junit" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.mockito:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "androidx.test:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.robolectric:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/google_maps_flutter/google_maps_flutter_android/example/android/app" commit-message: prefix: "[google_maps]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/google_sign_in/google_sign_in/example/android/app" commit-message: prefix: "[sign_in]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/google_sign_in/google_sign_in_android/android" commit-message: prefix: "[sign_in]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "com.android.tools.build:gradle" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "junit:junit" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.mockito:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "androidx.test:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.robolectric:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/google_sign_in/google_sign_in_android/example/android/app" commit-message: prefix: "[sign_in]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/in_app_purchase/in_app_purchase_android/android" commit-message: prefix: "[in_app_pur]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "com.android.tools.build:gradle" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "junit:junit" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.mockito:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "androidx.test:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.robolectric:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/in_app_purchase/in_app_purchase_android/example/android/app" commit-message: prefix: "[in_app_pur]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/in_app_purchase/in_app_purchase/example/android/app" commit-message: prefix: "[in_app_pur]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/image_picker/image_picker/example/android/app" commit-message: prefix: "[image_picker]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/image_picker/image_picker_android/android" commit-message: prefix: "[image_picker]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "com.android.tools.build:gradle" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "junit:junit" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.mockito:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "androidx.test:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.robolectric:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/image_picker/image_picker_android/example/android/app" commit-message: prefix: "[image_picker]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/local_auth/local_auth_android/android" commit-message: prefix: "[local_auth]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "com.android.tools.build:gradle" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "junit:junit" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.mockito:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "androidx.test:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.robolectric:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/local_auth/local_auth_android/example/android/app" commit-message: prefix: "[local_auth]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/local_auth/local_auth/example/android/app" commit-message: prefix: "[local_auth]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/path_provider/path_provider/example/android/app" commit-message: prefix: "[path_provider]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/path_provider/path_provider_android/android" commit-message: prefix: "[path_provider]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "com.android.tools.build:gradle" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "junit:junit" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.mockito:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "androidx.test:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.robolectric:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/path_provider/path_provider_android/example/android/app" commit-message: prefix: "[path_provider]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/quick_actions/quick_actions_android/android" commit-message: prefix: "[quick_actions]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "com.android.tools.build:gradle" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "junit:junit" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.mockito:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "androidx.test:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.robolectric:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/quick_actions/quick_actions_android/example/android/app" commit-message: prefix: "[quick_actions]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/quick_actions/quick_actions/example/android/app" commit-message: prefix: "[quick_actions]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/shared_preferences/shared_preferences/example/android/app" commit-message: prefix: "[shared_pref]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/shared_preferences/shared_preferences_android/android" commit-message: prefix: "[shared_pref]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "com.android.tools.build:gradle" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "junit:junit" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.mockito:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "androidx.test:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.robolectric:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/shared_preferences/shared_preferences_android/example/android/app" commit-message: prefix: "[shared_pref]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/url_launcher/url_launcher_android/android" commit-message: prefix: "[url_launcher]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "com.android.tools.build:gradle" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "junit:junit" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.mockito:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "androidx.test:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.robolectric:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/url_launcher/url_launcher_android/example/android/app" commit-message: prefix: "[url_launcher]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/url_launcher/url_launcher/example/android/app" commit-message: prefix: "[url_launcher]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/video_player/video_player/example/android/app" commit-message: prefix: "[video_player]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/video_player/video_player_android/android" commit-message: prefix: "[video_player]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "com.android.tools.build:gradle" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "junit:junit" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.mockito:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "androidx.test:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.robolectric:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/video_player/video_player_android/example/android/app" commit-message: prefix: "[video_player]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/webview_flutter/webview_flutter/example/android/app" commit-message: prefix: "[webview]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/webview_flutter/webview_flutter_android/android" commit-message: prefix: "[webview]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "com.android.tools.build:gradle" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "junit:junit" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.mockito:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "androidx.test:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "org.robolectric:*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "gradle" directory: "/packages/webview_flutter/webview_flutter_android/example/android/app" commit-message: prefix: "[webview]" schedule: interval: "weekly" open-pull-requests-limit: 10 ignore: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - package-ecosystem: "github-actions" directory: "/" commit-message: prefix: "[gh_actions]" schedule: interval: "weekly" open-pull-requests-limit: 10 ================================================ FILE: .github/labeler.yml ================================================ 'p: camera': - packages/camera/**/* 'p: espresso': - packages/espresso/**/* 'p: file_selector': - packages/file_selector/**/* 'p: flutter_plugin_android_lifecycle': - packages/flutter_plugin_android_lifecycle/**/* 'p: google_maps_flutter': - packages/google_maps_flutter/**/* 'p: google_sign_in': - packages/google_sign_in/**/* 'p: image_picker': - packages/image_picker/**/* 'p: in_app_purchase': - packages/in_app_purchase/**/* 'p: ios_platform_images': - packages/ios_platform_images/**/* 'p: local_auth': - packages/local_auth/**/* 'p: path_provider': - packages/path_provider/**/* 'p: plugin_platform_interface': - packages/plugin_platform_interface/**/* 'p: quick_actions': - packages/quick_actions/**/* 'p: shared_preferences': - packages/shared_preferences/**/* 'p: url_launcher': - packages/url_launcher/**/* 'p: video_player': - packages/video_player/**/* 'p: webview_flutter': - packages/webview_flutter/**/* 'platform-android': - packages/*/*_android/**/* - packages/**/android/**/* 'platform-ios': - packages/*/*_ios/**/* - packages/*/*_storekit/**/* - packages/*/*_wkwebview/**/* - packages/**/ios/**/* 'platform-linux': - packages/*/*_linux/**/* - packages/**/linux/**/* 'platform-macos': - packages/*/*_macos/**/* - packages/**/macos/**/* 'platform-web': - packages/*/*_web/**/* - packages/**/web/**/* 'platform-windows': - packages/*/*_windows/**/* - packages/**/windows/**/* ================================================ FILE: .github/post_merge_labeler.yml ================================================ 'needs-publishing': - packages/**/pubspec.yaml ================================================ FILE: .github/workflows/pull_request_label.yml ================================================ # This workflow applies labels to pull requests based on the # paths that are modified in the pull request. # # Edit `.github/labeler.yml` and `.github/post_merge_labeler.yml` # to configure labels. # # For more information, see: https://github.com/actions/labeler name: Pull Request Labeler on: pull_request_target: types: [opened, synchronize, reopened, closed] # Declare default permissions as read only. permissions: read-all jobs: label: permissions: pull-requests: write runs-on: ubuntu-latest steps: - uses: actions/labeler@5c7539237e04b714afd8ad9b4aed733815b9fab4 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" sync-labels: true ================================================ FILE: .github/workflows/release.yml ================================================ name: release on: push: branches: - main # Declare default permissions as read only. permissions: read-all jobs: release: if: github.repository_owner == 'flutter' name: release permissions: # Release needs to push a tag back to the repo. contents: write runs-on: ubuntu-latest steps: - name: "Install Flutter" # Github Actions don't support templates so it is hard to share this snippet with another action # If we eventually need to use this in more workflow, we could create a shell script that contains this # snippet. run: | cd $HOME git clone https://github.com/flutter/flutter.git --depth 1 -b stable _flutter echo "$HOME/_flutter/bin" >> $GITHUB_PATH cd $GITHUB_WORKSPACE # Checks out a copy of the repo. - name: Check out code 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 run: dart pub global activate flutter_plugin_tools 0.13.4+3 # This workflow should be the last to run. So wait for all the other tests to succeed. - name: Wait on all tests uses: lewagon/wait-on-check-action@3a563271c3f8d1611ed7352809303617ee7e54ac with: ref: ${{ github.sha }} running-workflow-name: 'release' repo-token: ${{ secrets.GITHUB_TOKEN }} wait-interval: 180 # seconds allowed-conclusions: success,neutral # verbose:true will produce too many logs that hang github actions web UI. verbose: false - name: run release run: | git config --global user.name ${{ secrets.USER_NAME }} git config --global user.email ${{ secrets.USER_EMAIL }} dart pub global run flutter_plugin_tools publish --all-changed --base-sha=HEAD~ --skip-confirmation --remote=origin env: {PUB_CREDENTIALS: "${{ secrets.PUB_CREDENTIALS }}"} ================================================ FILE: .github/workflows/scorecards-analysis.yml ================================================ name: Scorecards supply-chain security on: # Only the default branch is supported. branch_protection_rule: push: branches: [ main ] # Declare default permissions as read only. permissions: read-all jobs: analysis: name: Scorecards analysis runs-on: ubuntu-latest permissions: # Needed to upload the results to code-scanning dashboard. security-events: write actions: read contents: read # Needed to access OIDC token. id-token: write steps: - name: "Checkout code" uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 with: results_file: results.sarif results_format: sarif # Read-only PAT token. To create it, # follow the steps in https://github.com/ossf/scorecard-action#pat-token-creation. repo_token: ${{ secrets.SCORECARD_READ_TOKEN }} # Publish the results to enable scorecard badges. For more details, see # https://github.com/ossf/scorecard-action#publishing-results. # For private repositories, `publish_results` will automatically be set to `false`, # regardless of the value entered here. publish_results: true # Upload the results as artifacts (optional). - name: "Upload artifact" uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce with: name: SARIF file path: results.sarif retention-days: 5 # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@3ebbd71c74ef574dbc558c82f70e52732c8b44fe with: sarif_file: results.sarif ================================================ FILE: .gitignore ================================================ .DS_Store .atom/ .idea/ .vscode/ .packages .pub/ .dart_tool/ pubspec.lock flutter_export_environment.sh examples/all_plugins/pubspec.yaml Podfile.lock Pods/ .symlinks/ **/Flutter/App.framework/ **/Flutter/ephemeral/ **/Flutter/Flutter.podspec **/Flutter/Flutter.framework/ **/Flutter/Generated.xcconfig **/Flutter/flutter_assets/ ServiceDefinitions.json xcuserdata/ **/DerivedData/ local.properties keystore.properties .gradle/ gradlew gradlew.bat gradle-wrapper.jar .flutter-plugins-dependencies *.iml generated_plugin_registrant.cc generated_plugin_registrant.h generated_plugin_registrant.dart GeneratedPluginRegistrant.java GeneratedPluginRegistrant.h GeneratedPluginRegistrant.m GeneratedPluginRegistrant.swift build/ .flutter-plugins .project .classpath .settings # Downloaded by the plugin tools. google-java-format-1.3-all-deps.jar ================================================ FILE: .gitmodules ================================================ [submodule "site-shared"] path = site-shared url = https://github.com/dart-lang/site-shared ================================================ FILE: .opensource/project.json ================================================ { "name": "FlutterFire - MOVED", "platforms": [ "Android", "iOS" ], "content": "FlutterFire.md", "related": [ "FirebaseExtended/flutterfire" ] } ================================================ FILE: AUTHORS ================================================ # 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> Daniel Roek TheOneWithTheBraid Rulong Chen(陈汝龙) Hwanseok Kang Twin Sun, LLC ================================================ FILE: CODEOWNERS ================================================ # Below is a list of Flutter team members who are suggested reviewers # for contributions to plugins in this repository. # # These names are just suggestions. It is fine to have your changes # reviewed by someone else. # Plugin-level rules. packages/camera/** @bparrishMines packages/file_selector/** @stuartmorgan packages/google_maps_flutter/** @stuartmorgan packages/google_sign_in/** @stuartmorgan packages/image_picker/** @tarrinneal packages/in_app_purchase/** @bparrishMines packages/local_auth/** @stuartmorgan packages/path_provider/** @stuartmorgan packages/plugin_platform_interface/** @stuartmorgan packages/quick_actions/** @bparrishMines packages/shared_preferences/** @tarrinneal packages/url_launcher/** @stuartmorgan packages/video_player/** @tarrinneal packages/webview_flutter/** @bparrishMines # Sub-package-level rules. These should stay last, since the last matching # entry takes precedence. # - Web packages/**/*_web/** @ditman # - Android packages/camera/camera_android/** @camsim99 packages/camera/camera_android_camerax/** @camsim99 packages/espresso/** @reidbaker packages/flutter_plugin_android_lifecycle/** @reidbaker packages/google_maps_flutter/google_maps_flutter_android/** @reidbaker packages/google_sign_in/google_sign_in_android/** @camsim99 packages/image_picker/image_picker_android/** @gmackall packages/in_app_purchase/in_app_purchase_android/** @gmackall packages/local_auth/local_auth_android/** @camsim99 packages/path_provider/path_provider_android/** @camsim99 packages/quick_actions/quick_actions_android/** @camsim99 packages/shared_preferences/shared_preferences_android/** @reidbaker packages/url_launcher/url_launcher_android/** @gmackall packages/video_player/video_player_android/** @camsim99 # - iOS 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/** @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/** @louisehsu packages/path_provider/path_provider_foundation/** @jmagman packages/quick_actions/quick_actions_ios/** @hellohuanlin 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 # - Linux packages/file_selector/file_selector_linux/** @cbracken packages/path_provider/path_provider_linux/** @cbracken packages/shared_preferences/shared_preferences_linux/** @cbracken packages/url_launcher/url_launcher_linux/** @cbracken # - macOS packages/file_selector/file_selector_macos/** @cbracken packages/url_launcher/url_launcher_macos/** @cbracken # - Windows packages/camera/camera_windows/** @cbracken packages/file_selector/file_selector_windows/** @cbracken packages/image_picker/image_picker_windows/** @cbracken packages/local_auth/local_auth_windows/** @cbracken packages/path_provider/path_provider_windows/** @cbracken packages/shared_preferences/shared_preferences_windows/** @cbracken packages/url_launcher/url_launcher_windows/** @cbracken ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Flutter Plugins | **ARCHIVED** | |--------------| | This repository is no longer in use; all source has moved to [flutter/packages](https://github.com/flutter/packages) and future development work will be done there. | ## ARCHIVED CONTENT _See also: [Flutter's code of conduct](https://github.com/flutter/flutter/blob/master/CODE_OF_CONDUCT.md)_ ## Welcome For an introduction to contributing to Flutter, see [our contributor guide](https://github.com/flutter/flutter/blob/master/CONTRIBUTING.md). Additional resources specific to the plugins repository: - [Setting up the Plugins development environment](https://github.com/flutter/flutter/wiki/Setting-up-the-Plugins-development-environment), which covers the setup process for this repository. - [Plugins repository structure](https://github.com/flutter/flutter/wiki/Plugins-and-Packages-repository-structure), to get an overview of how this repository is laid out. - [Plugin tests](https://github.com/flutter/flutter/wiki/Plugin-Tests), which explains the different kinds of tests used for plugins, where to find them, and how to run them. As explained in the Flutter guide, [**PRs need tests**](https://github.com/flutter/flutter/wiki/Tree-hygiene#tests), so this is critical to read before submitting a PR. - [Contributing to Plugins and Packages](https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages), for more information about how to make PRs for this repository, especially when changing federated plugins. ## Other notes ### Style Flutter plugins follow Google style—or Flutter style for Dart—for the languages they use, and use auto-formatters: - [Dart](https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo) formatted with `dart format` - [C++](https://google.github.io/styleguide/cppguide.html) formatted with `clang-format` - **Note**: The Linux plugins generally follow idiomatic GObject-based C style. See [the engine style notes](https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style) for more details, and exceptions. - [Java](https://google.github.io/styleguide/javaguide.html) formatted with `google-java-format` - [Objective-C](https://google.github.io/styleguide/objcguide.html) formatted with `clang-format` ### Releasing If you are a team member landing a PR, or just want to know what the release process is for plugin changes, see [the release documentation](https://github.com/flutter/flutter/wiki/Releasing-a-Plugin-or-Package). ================================================ FILE: FlutterFire.md ================================================ # FlutterFire - MOVED The FlutterFire family of plugins has moved to the Firebase organization on GitHub. This makes it easier for us to collaborate with the Firebase team. We want to build the best integration we can! Visit FlutterFire at its new home: https://github.com/firebase/flutterfire ================================================ FILE: LICENSE ================================================ 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. ================================================ FILE: README.md ================================================ # Flutter plugins | **ARCHIVED** | |--------------| | This repository is no longer in use; all source has moved to [flutter/packages](https://github.com/flutter/packages) and future development work will be done there. | ## ARCHIVED CONTENT This repo is a companion repo to the main [flutter repo](https://github.com/flutter/flutter). It contains the source code for Flutter first-party plugins (i.e., plugins developed by the core Flutter team). Check the `packages` directory for all plugins. Flutter plugins enable access to platform-specific APIs. For more information about plugins, and how to use them, see [https://flutter.dev/platform-plugins/](https://flutter.dev/platform-plugins/). These plugins are also available on [pub](https://pub.dev/flutter/packages). ## Issues Please file any issues, bugs, or feature requests in the [main flutter repo](https://github.com/flutter/flutter/issues/new). Issues pertaining to this repository are [labeled "plugin"](https://github.com/flutter/flutter/issues?q=is%3Aopen+is%3Aissue+label%3Aplugin). ## Contributing If you wish to contribute a new plugin to the Flutter ecosystem, please see the documentation for [developing packages](https://flutter.dev/developing-packages/) and [platform channels](https://flutter.dev/platform-channels/). You can store your plugin source code in any GitHub repository (the present repo is only intended for plugins developed by the core Flutter team). Once your plugin is ready, you can [publish](https://flutter.dev/developing-packages/#publish) it to the [pub repository](https://pub.dev/). If you wish to contribute a change to any of the existing plugins in this repo, please review our [contribution guide](https://github.com/flutter/plugins/blob/main/CONTRIBUTING.md), and send a [pull request](https://github.com/flutter/plugins/pulls). ## Plugins These are the available plugins in this repository. | Plugin | Pub | Points | Popularity | Likes | Issues | Pull requests | |--------|-----|--------|------------|-------|--------|---------------| | [camera](./packages/camera/) | [![pub package](https://img.shields.io/pub/v/camera.svg)](https://pub.dev/packages/camera) | [![pub points](https://img.shields.io/pub/points/camera)](https://pub.dev/packages/camera/score) | [![popularity](https://img.shields.io/pub/popularity/camera)](https://pub.dev/packages/camera/score) | [![likes](https://img.shields.io/pub/likes/camera)](https://pub.dev/packages/camera/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p:%20camera?label=)](https://github.com/flutter/flutter/labels/p%3A%20camera) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/plugins/p:%20camera?label=)](https://github.com/flutter/plugins/labels/p%3A%20camera) | | [espresso](./packages/espresso/) | [![pub package](https://img.shields.io/pub/v/espresso.svg)](https://pub.dev/packages/espresso) | [![pub points](https://img.shields.io/pub/points/espresso)](https://pub.dev/packages/espresso/score) | [![popularity](https://img.shields.io/pub/popularity/espresso)](https://pub.dev/packages/espresso/score) | [![likes](https://img.shields.io/pub/likes/espresso)](https://pub.dev/packages/espresso/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p:%20espresso?label=)](https://github.com/flutter/flutter/labels/p%3A%20espresso) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/plugins/p:%20espresso?label=)](https://github.com/flutter/plugins/labels/p%3A%20espresso) | | [file_selector](./packages/file_selector/) | [![pub package](https://img.shields.io/pub/v/file_selector.svg)](https://pub.dev/packages/file_selector) | [![pub points](https://img.shields.io/pub/points/file_selector)](https://pub.dev/packages/file_selector/score) | [![popularity](https://img.shields.io/pub/popularity/file_selector)](https://pub.dev/packages/file_selector/score) | [![likes](https://img.shields.io/pub/likes/file_selector)](https://pub.dev/packages/file_selector/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p:%20file_selector?label=)](https://github.com/flutter/flutter/labels/p%3A%20file_selector) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/plugins/p:%20file_selector?label=)](https://github.com/flutter/plugins/labels/p%3A%20file_selector) | | [flutter_plugin_android_lifecycle](./packages/flutter_plugin_android_lifecycle/) | [![pub package](https://img.shields.io/pub/v/flutter_plugin_android_lifecycle.svg)](https://pub.dev/packages/flutter_plugin_android_lifecycle) | [![pub points](https://img.shields.io/pub/points/flutter_plugin_android_lifecycle)](https://pub.dev/packages/flutter_plugin_android_lifecycle/score) | [![popularity](https://img.shields.io/pub/popularity/flutter_plugin_android_lifecycle)](https://pub.dev/packages/flutter_plugin_android_lifecycle/score) | [![likes](https://img.shields.io/pub/likes/flutter_plugin_android_lifecycle)](https://pub.dev/packages/flutter_plugin_android_lifecycle/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p:%20flutter_plugin_android_lifecycle?label=)](https://github.com/flutter/flutter/labels/p%3A%20flutter_plugin_android_lifecycle) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/plugins/p:%20flutter_plugin_android_lifecycle?label=)](https://github.com/flutter/plugins/labels/p%3A%20flutter_plugin_android_lifecycle) | | [google_maps_flutter](./packages/google_maps_flutter) | [![pub package](https://img.shields.io/pub/v/google_maps_flutter.svg)](https://pub.dev/packages/google_maps_flutter) | [![pub points](https://img.shields.io/pub/points/google_maps_flutter)](https://pub.dev/packages/google_maps_flutter/score) | [![popularity](https://img.shields.io/pub/popularity/google_maps_flutter)](https://pub.dev/packages/google_maps_flutter/score) | [![likes](https://img.shields.io/pub/likes/google_maps_flutter)](https://pub.dev/packages/google_maps_flutter/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p:%20maps?label=)](https://github.com/flutter/flutter/labels/p%3A%20maps) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/plugins/p:%20google_maps_flutter?label=)](https://github.com/flutter/plugins/labels/p%3A%20google_maps_flutter) | | [google_sign_in](./packages/google_sign_in/) | [![pub package](https://img.shields.io/pub/v/google_sign_in.svg)](https://pub.dev/packages/google_sign_in) | [![pub points](https://img.shields.io/pub/points/google_sign_in)](https://pub.dev/packages/google_sign_in/score) | [![popularity](https://img.shields.io/pub/popularity/google_sign_in)](https://pub.dev/packages/google_sign_in/score) | [![likes](https://img.shields.io/pub/likes/google_sign_in)](https://pub.dev/packages/google_sign_in/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p:%20google_sign_in?label=)](https://github.com/flutter/flutter/labels/p%3A%20google_sign_in) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/plugins/p:%20google_sign_in?label=)](https://github.com/flutter/plugins/labels/p%3A%20google_sign_in) | | [image_picker](./packages/image_picker/) | [![pub package](https://img.shields.io/pub/v/image_picker.svg)](https://pub.dev/packages/image_picker) | [![pub points](https://img.shields.io/pub/points/image_picker)](https://pub.dev/packages/image_picker/score) | [![popularity](https://img.shields.io/pub/popularity/image_picker)](https://pub.dev/packages/image_picker/score) | [![likes](https://img.shields.io/pub/likes/image_picker)](https://pub.dev/packages/image_picker/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p:%20image_picker?label=)](https://github.com/flutter/flutter/labels/p%3A%20image_picker) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/plugins/p:%20image_picker?label=)](https://github.com/flutter/plugins/labels/p%3A%20image_picker) | | [in_app_purchase](./packages/in_app_purchase/) | [![pub package](https://img.shields.io/pub/v/in_app_purchase.svg)](https://pub.dev/packages/in_app_purchase) | [![pub points](https://img.shields.io/pub/points/in_app_purchase)](https://pub.dev/packages/in_app_purchase/score) | [![popularity](https://img.shields.io/pub/popularity/in_app_purchase)](https://pub.dev/packages/in_app_purchase/score) | [![likes](https://img.shields.io/pub/likes/in_app_purchase)](https://pub.dev/packages/in_app_purchase/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p:%20in_app_purchase?label=)](https://github.com/flutter/flutter/labels/p%3A%20in_app_purchase) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/plugins/p:%20in_app_purchase?label=)](https://github.com/flutter/plugins/labels/p%3A%20in_app_purchase) | | [ios_platform_images](./packages/ios_platform_images/) | [![pub package](https://img.shields.io/pub/v/ios_platform_images.svg)](https://pub.dev/packages/ios_platform_images) | [![pub points](https://img.shields.io/pub/points/ios_platform_images)](https://pub.dev/packages/ios_platform_images/score) | [![popularity](https://img.shields.io/pub/popularity/ios_platform_images)](https://pub.dev/packages/ios_platform_images/score) | [![likes](https://img.shields.io/pub/likes/ios_platform_images)](https://pub.dev/packages/ios_platform_images/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p:%20ios_platform_images?label=)](https://github.com/flutter/flutter/labels/p%3A%20ios_platform_images) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/plugins/p:%20ios_platform_images?label=)](https://github.com/flutter/plugins/labels/p%3A%20ios_platform_images) | | [local_auth](./packages/local_auth/) | [![pub package](https://img.shields.io/pub/v/local_auth.svg)](https://pub.dev/packages/local_auth) | [![pub points](https://img.shields.io/pub/points/local_auth)](https://pub.dev/packages/local_auth/score) | [![popularity](https://img.shields.io/pub/popularity/local_auth)](https://pub.dev/packages/local_auth/score) | [![likes](https://img.shields.io/pub/likes/local_auth)](https://pub.dev/packages/local_auth/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p:%20local_auth?label=)](https://github.com/flutter/flutter/labels/p%3A%20local_auth) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/plugins/p:%20local_auth?label=)](https://github.com/flutter/plugins/labels/p%3A%20local_auth) | | [path_provider](./packages/path_provider/) | [![pub package](https://img.shields.io/pub/v/path_provider.svg)](https://pub.dev/packages/path_provider) | [![pub points](https://img.shields.io/pub/points/path_provider)](https://pub.dev/packages/path_provider/score) | [![popularity](https://img.shields.io/pub/popularity/path_provider)](https://pub.dev/packages/path_provider/score) | [![likes](https://img.shields.io/pub/likes/path_provider)](https://pub.dev/packages/path_provider/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p:%20path_provider?label=)](https://github.com/flutter/flutter/labels/p%3A%20path_provider) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/plugins/p:%20path_provider?label=)](https://github.com/flutter/plugins/labels/p%3A%20path_provider) | | [plugin_platform_interface](./packages/plugin_platform_interface/) | [![pub package](https://img.shields.io/pub/v/plugin_platform_interface.svg)](https://pub.dev/packages/plugin_platform_interface) | [![pub points](https://img.shields.io/pub/points/plugin_platform_interface)](https://pub.dev/packages/plugin_platform_interface/score) | [![popularity](https://img.shields.io/pub/popularity/plugin_platform_interface)](https://pub.dev/packages/plugin_platform_interface/score) | [![likes](https://img.shields.io/pub/likes/plugin_platform_interface)](https://pub.dev/packages/plugin_platform_interface/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p:%20plugin_platform_interface?label=)](https://github.com/flutter/flutter/labels/p%3A%20plugin_platform_interface) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/plugins/p:%20plugin_platform_interface?label=)](https://github.com/flutter/plugins/labels/p%3A%20plugin_platform_interface) | | [quick_actions](./packages/quick_actions/) | [![pub package](https://img.shields.io/pub/v/quick_actions.svg)](https://pub.dev/packages/quick_actions) | [![pub points](https://img.shields.io/pub/points/quick_actions)](https://pub.dev/packages/quick_actions/score) | [![popularity](https://img.shields.io/pub/popularity/quick_actions)](https://pub.dev/packages/quick_actions/score) | [![likes](https://img.shields.io/pub/likes/quick_actions)](https://pub.dev/packages/quick_actions/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p:%20quick_actions?label=)](https://github.com/flutter/flutter/labels/p%3A%20quick_actions) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/plugins/p:%20quick_actions?label=)](https://github.com/flutter/plugins/labels/p%3A%20quick_actions) | | [shared_preferences](./packages/shared_preferences/) | [![pub package](https://img.shields.io/pub/v/shared_preferences.svg)](https://pub.dev/packages/shared_preferences) | [![pub points](https://img.shields.io/pub/points/shared_preferences)](https://pub.dev/packages/shared_preferences/score) | [![popularity](https://img.shields.io/pub/popularity/shared_preferences)](https://pub.dev/packages/shared_preferences/score) | [![likes](https://img.shields.io/pub/likes/shared_preferences)](https://pub.dev/packages/shared_preferences/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p:%20shared_preferences?label=)](https://github.com/flutter/flutter/labels/p%3A%20shared_preferences) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/plugins/p:%20shared_preferences?label=)](https://github.com/flutter/plugins/labels/p%3A%20shared_preferences) | | [url_launcher](./packages/url_launcher/) | [![pub package](https://img.shields.io/pub/v/url_launcher.svg)](https://pub.dev/packages/url_launcher) | [![pub points](https://img.shields.io/pub/points/url_launcher)](https://pub.dev/packages/url_launcher/score) | [![popularity](https://img.shields.io/pub/popularity/url_launcher)](https://pub.dev/packages/url_launcher/score) | [![likes](https://img.shields.io/pub/likes/url_launcher)](https://pub.dev/packages/url_launcher/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p:%20url_launcher?label=)](https://github.com/flutter/flutter/labels/p%3A%20url_launcher) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/plugins/p:%20url_launcher?label=)](https://github.com/flutter/plugins/labels/p%3A%20url_launcher) | | [video_player](./packages/video_player/) | [![pub package](https://img.shields.io/pub/v/video_player.svg)](https://pub.dev/packages/video_player) | [![pub points](https://img.shields.io/pub/points/video_player)](https://pub.dev/packages/video_player/score) | [![popularity](https://img.shields.io/pub/popularity/video_player)](https://pub.dev/packages/video_player/score) | [![likes](https://img.shields.io/pub/likes/video_player)](https://pub.dev/packages/video_player/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p:%20video_player?label=)](https://github.com/flutter/flutter/labels/p%3A%20video_player) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/plugins/p:%20video_player?label=)](https://github.com/flutter/plugins/labels/p%3A%20video_player) | | [webview_flutter](./packages/webview_flutter/) | [![pub package](https://img.shields.io/pub/v/webview_flutter.svg)](https://pub.dev/packages/webview_flutter) | [![pub points](https://img.shields.io/pub/points/webview_flutter)](https://pub.dev/packages/webview_flutter/score) | [![popularity](https://img.shields.io/pub/popularity/webview_flutter)](https://pub.dev/packages/webview_flutter/score) | [![likes](https://img.shields.io/pub/likes/webview_flutter)](https://pub.dev/packages/webview_flutter/score) | [![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/p:%20webview?label=)](https://github.com/flutter/flutter/labels/p%3A%20webview) | [![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/plugins/p:%20webview_flutter?label=)](https://github.com/flutter/plugins/labels/p%3A%20webview_flutter) | ================================================ FILE: analysis_options.yaml ================================================ # Specify analysis options. # # 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: # 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 # Turned off until null-safe rollout is complete. unnecessary_null_comparison: ignore exclude: # DIFFERENT FROM FLUTTER/FLUTTER # Ignore generated files - '**/*.g.dart' - '**/*.mocks.dart' # Mockito @GenerateMocks linter: rules: # This list is derived from the list of all available lints located at # https://github.com/dart-lang/linter/blob/master/example/all.yaml - always_declare_return_types - always_put_control_body_on_new_line # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 - always_require_non_null_named_parameters - always_specify_types # - always_use_package_imports # we do this commonly - annotate_overrides # - avoid_annotating_with_dynamic # conflicts with always_specify_types - avoid_bool_literals_in_conditional_expressions # - avoid_catches_without_on_clauses # blocked on https://github.com/dart-lang/linter/issues/3023 # - 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 - avoid_empty_else - avoid_equals_and_hash_code_on_mutable_classes - avoid_escaping_inner_quotes - avoid_field_initializers_in_const_classes # - avoid_final_parameters # incompatible with prefer_final_parameters - avoid_function_literals_in_foreach_calls - avoid_implementing_value_types - avoid_init_to_null - avoid_js_rounded_ints # - avoid_multiple_declarations_per_line # seems to be a stylistic choice we don't subscribe to - avoid_null_checks_in_equality_operators # - avoid_positional_boolean_parameters # would have been nice to enable this but by now there's too many places that break it - avoid_print # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) - avoid_redundant_argument_values - avoid_relative_lib_imports - avoid_renaming_method_parameters - avoid_return_types_on_setters - avoid_returning_null - avoid_returning_null_for_future - avoid_returning_null_for_void # - avoid_returning_this # there are enough valid reasons to return `this` that this lint ends up with too many false positives - avoid_setters_without_getters - avoid_shadowing_type_parameters - avoid_single_cascade_in_expression_statements - avoid_slow_async_io - avoid_type_to_string - avoid_types_as_parameter_names # - avoid_types_on_closure_parameters # conflicts with always_specify_types - avoid_unnecessary_containers - avoid_unused_constructor_parameters - avoid_void_async # - avoid_web_libraries_in_flutter # we use web libraries in web-specific code, and our tests prevent us from using them elsewhere - await_only_futures - camel_case_extensions - camel_case_types - cancel_subscriptions # - cascade_invocations # doesn't match the typical style of this repo - cast_nullable_to_non_nullable # - close_sinks # not reliable enough # - combinators_ordering # DIFFERENT FROM FLUTTER/FLUTTER: This isn't available on stable yet. # - comment_references # blocked on https://github.com/dart-lang/linter/issues/1142 - conditional_uri_does_not_exist # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 - control_flow_in_finally - curly_braces_in_flow_control_structures - depend_on_referenced_packages - deprecated_consistency # - diagnostic_describe_all_properties # enabled only at the framework level (packages/flutter/lib) - directives_ordering # - discarded_futures # not yet tested # - do_not_use_environment # there are appropriate times to use the environment, especially in our tests and build logic - empty_catches - empty_constructor_bodies - empty_statements - eol_at_end_of_file - exhaustive_cases - file_names - flutter_style_todos - hash_and_equals - implementation_imports - iterable_contains_unrelated_type # - join_return_with_assignment # not required by flutter style - leading_newlines_in_multiline_strings - library_names - library_prefixes - library_private_types_in_public_api # - lines_longer_than_80_chars # not required by flutter style - list_remove_unrelated_type # - 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 - no_duplicate_case_values - no_leading_underscores_for_library_prefixes - no_leading_underscores_for_local_identifiers - no_logic_in_create_state - 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 - overridden_fields - package_api_docs - package_names - package_prefixed_library_names # - parameter_assignments # we do this commonly - prefer_adjacent_string_concatenation - prefer_asserts_in_initializer_lists # - prefer_asserts_with_message # not required by flutter style - prefer_collection_literals - prefer_conditional_assignment - prefer_const_constructors - prefer_const_constructors_in_immutables - prefer_const_declarations - prefer_const_literals_to_create_immutables # - prefer_constructors_over_static_methods # far too many false positives - prefer_contains # - prefer_double_quotes # opposite of prefer_single_quotes - prefer_equal_for_default_values # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods - prefer_final_fields - prefer_final_in_for_each - prefer_final_locals # - prefer_final_parameters # we should enable this one day when it can be auto-fixed (https://github.com/dart-lang/linter/issues/3104), see also parameter_assignments - prefer_for_elements_to_map_fromIterable - prefer_foreach - prefer_function_declarations_over_variables - prefer_generic_function_type_aliases - prefer_if_elements_to_conditional_expressions - prefer_if_null_operators - prefer_initializing_formals - prefer_inlined_adds # - prefer_int_literals # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#use-double-literals-for-double-constants - prefer_interpolation_to_compose_strings - prefer_is_empty - prefer_is_not_empty - prefer_is_not_operator - prefer_iterable_whereType # - prefer_mixin # Has false positives, see https://github.com/dart-lang/linter/issues/3018 # - prefer_null_aware_method_calls # "call()" is confusing to people new to the language since it's not documented anywhere - prefer_null_aware_operators - prefer_relative_imports - prefer_single_quotes - prefer_spread_collections - prefer_typing_uninitialized_variables - prefer_void_to_null - provide_deprecation_message - 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 - sized_box_for_whitespace # - sized_box_shrink_expand # not yet tested - slash_for_doc_comments - sort_child_properties_last - sort_constructors_first - sort_pub_dependencies # DIFFERENT FROM FLUTTER/FLUTTER: Flutter's use case for not sorting does not apply to this repository. - sort_unnamed_constructors_first - test_types_in_equals - throw_in_finally - tighten_type_of_initializing_formals # - type_annotate_public_apis # subset of always_specify_types - type_init_formals # - unawaited_futures # too many false positives, especially with the way AnimationController works - unnecessary_await_in_return - unnecessary_brace_in_string_interps - unnecessary_const - unnecessary_constructor_name # - unnecessary_final # conflicts with prefer_final_locals - unnecessary_getters_setters # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 - unnecessary_late - unnecessary_new - unnecessary_null_aware_assignments - unnecessary_null_aware_operator_on_extension_on_nullable - unnecessary_null_checks - unnecessary_null_in_if_null_operators - unnecessary_nullable_for_final_variable_declarations - unnecessary_overrides - unnecessary_parenthesis # - unnecessary_raw_strings # what's "necessary" is a matter of opinion; consistency across strings can help readability more than this lint - unnecessary_statements - unnecessary_string_escapes - unnecessary_string_interpolations - unnecessary_this - unnecessary_to_list_in_spreads - unrelated_type_equality_checks - unsafe_html - use_build_context_synchronously # - use_colored_box # not yet tested # - use_decorated_box # not yet tested # - use_enums # not yet tested - use_full_hex_values_for_flutter_colors - use_function_type_syntax_for_parameters - use_if_null_to_convert_nulls_to_bools - use_is_even_rather_than_modulo - use_key_in_widget_constructors - use_late_for_private_fields_and_variables - use_named_constants - use_raw_strings - use_rethrow_when_possible - use_setters_to_change_properties # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 - use_super_parameters - use_test_throws_matchers # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review - valid_regexps - void_checks ================================================ FILE: packages/camera/camera/AUTHORS ================================================ # 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> ================================================ FILE: packages/camera/camera/CHANGELOG.md ================================================ ## 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. ## 0.10.0+5 * Updates code for stricter lint checks. ## 0.10.0+4 * Removes usage of `_ambiguate` method in example. * Updates minimum Flutter version to 3.0. ## 0.10.0+3 * Updates code for `no_leading_underscores_for_local_identifiers` lint. ## 0.10.0+2 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. ## 0.10.0+1 * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 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 `CameraAccessDenied` and `AudioAccessDenied`. * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/106316). ## 0.9.8+1 * Ignores deprecation warnings for upcoming styleFrom button API changes. ## 0.9.8 * Moves Android and iOS implementations to federated packages. * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/104231). ## 0.9.7+1 * Moves streaming implementation to the platform interface package. ## 0.9.7 * Returns all the available cameras on iOS. ## 0.9.6 * Adds audio access permission handling logic on iOS to fix an issue with `prepareForVideoRecording` not awaiting for the audio permission request result. ## 0.9.5+1 * Suppresses warnings for pre-iOS-11 codepaths. ## 0.9.5 * Adds camera access permission handling logic on iOS to fix a related crash when using the camera for the first time. ## 0.9.4+24 * Fixes preview orientation when pausing preview with locked orientation. ## 0.9.4+23 * Minor fixes for new analysis options. ## 0.9.4+22 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.9.4+21 * Fixes README code samples. ## 0.9.4+20 * Fixes an issue with the orientation of videos recorded in landscape on Android. ## 0.9.4+19 * Migrate deprecated Scaffold SnackBar methods to ScaffoldMessenger. ## 0.9.4+18 * Fixes a crash in iOS when streaming on low-performance devices. ## 0.9.4+17 * Removes obsolete information from README, and adds OS support table. ## 0.9.4+16 * Fixes a bug resulting in a `CameraAccessException` that prevents image capture on some Android devices. ## 0.9.4+15 * Uses dispatch queue for pixel buffer synchronization on iOS. * Minor iOS internal code cleanup related to queue helper functions. ## 0.9.4+14 * Restores compatibility with Flutter 2.5 and 2.8. ## 0.9.4+13 * Updates iOS camera's photo capture delegate reference on a background queue to prevent potential race conditions, and some related internal code cleanup. ## 0.9.4+12 * Skips unnecessary AppDelegate setup for unit tests on iOS. * Internal code cleanup for stricter analysis options. ## 0.9.4+11 * Manages iOS camera's orientation-related states on a background queue to prevent potential race conditions. ## 0.9.4+10 * iOS performance improvement by moving file writing from the main queue to a background IO queue. ## 0.9.4+9 * iOS performance improvement by moving sample buffer handling from the main queue to a background session queue. * Minor iOS internal code cleanup related to camera class and its delegate. * Minor iOS internal code cleanup related to resolution preset, video format, focus mode, exposure mode and device orientation. * Minor iOS internal code cleanup related to flash mode. ## 0.9.4+8 * Fixes a bug where ImageFormatGroup was ignored in `startImageStream` on iOS. ## 0.9.4+7 * Fixes a crash in iOS when passing null queue pointer into AVFoundation API due to race condition. * Minor iOS internal code cleanup related to dispatch queue. ## 0.9.4+6 * Fixes a crash in iOS when using image stream due to calling Flutter engine API on non-main thread. ## 0.9.4+5 * Fixes bug where calling a method after the camera was closed resulted in a Java `IllegalStateException` exception. * Fixes integration tests. ## 0.9.4+4 * Change Android compileSdkVersion to 31. * Remove usages of deprecated Android API `CamcorderProfile`. * Update gradle version to 7.0.2 on Android. ## 0.9.4+3 * Fix registerTexture and result being called on background thread on iOS. ## 0.9.4+2 * Updated package description; * Refactor unit test on iOS to make it compatible with new restrictions in Xcode 13 which only supports the use of the `XCUIDevice` in Xcode UI tests. ## 0.9.4+1 * Fixed Android implementation throwing IllegalStateException when switching to a different activity. ## 0.9.4 * Add web support by endorsing `package:camera_web`. ## 0.9.3+1 * Remove iOS 9 availability check around ultra high capture sessions. ## 0.9.3 * Update minimum Flutter SDK to 2.5 and iOS deployment target to 9.0. ## 0.9.2+2 * Ensure that setting the exposure offset returns the new offset value on Android. ## 0.9.2+1 * Fixed camera controller throwing an exception when being replaced in the preview widget. ## 0.9.2 * Added functions to pause and resume the camera preview. ## 0.9.1+1 * Replace `device_info` reference with `device_info_plus` in the [README.md](README.md) ## 0.9.1 * Added `lensAperture`, `sensorExposureTime` and `sensorSensitivity` properties to the `CameraImage` dto. ## 0.9.0 * Complete rewrite of Android plugin to fix many capture, focus, flash, orientation and exposure issues. * Fixed crash when opening front-facing cameras on some legacy android devices like Sony XZ. * Android Flash mode works with full precapture sequence. * Updated Android lint settings. ## 0.8.1+7 * Fix device orientation sometimes not affecting the camera preview orientation. ## 0.8.1+6 * Remove references to the Android V1 embedding. ## 0.8.1+5 * Make sure the `setFocusPoint` and `setExposurePoint` coordinates work correctly in all orientations on iOS (instead of only in portrait mode). ## 0.8.1+4 * Silenced warnings that may occur during build when using a very recent version of Flutter relating to null safety. ## 0.8.1+3 * Do not change camera orientation when iOS device is flat. ## 0.8.1+2 * Fix iOS crash when selecting an unsupported FocusMode. ## 0.8.1+1 * Migrate maven repository from jcenter to mavenCentral. ## 0.8.1 * Solved a rotation issue on iOS which caused the default preview to be displayed as landscape right instead of portrait. ## 0.8.0 * Stable null safety release. * Solved delay when using the zoom feature on iOS. * Added a timeout to the pre-capture sequence on Android to prevent crashes when the camera cannot get a focus. * Updates the example code listed in the [README.md](README.md), so it runs without errors when you simply copy/ paste it into a Flutter App. ## 0.7.0+4 * Fix crash when taking picture with orientation lock ## 0.7.0+3 * Clockwise rotation of focus point in android ## 0.7.0+2 * Fix example reference in README. * Revert compileSdkVersion back to 29 (from 30) as this is causing problems with add-to-app configurations. ## 0.7.0+1 * Ensure communication from JAVA to Dart is done on the main UI thread. ## 0.7.0 * BREAKING CHANGE: `CameraValue.aspectRatio` now returns `width / height` rather than `height / width`. [(commit)](https://github.com/flutter/plugins/commit/100c7470d4066b1d0f8f7e4ec6d7c943e736f970) * Added support for capture orientation locking on Android and iOS. * Fixed camera preview not rotating correctly on Android and iOS. * Fixed camera preview sometimes appearing stretched on Android and iOS. * Fixed videos & photos saving with the incorrect rotation on iOS. * New Features: * Adds auto focus support for Android and iOS implementations. [(commmit)](https://github.com/flutter/plugins/commit/71a831790220f898bf8120c8a23840ac6e742db5) * Adds ImageFormat selection for ImageStream and Video(iOS only). [(commit)](https://github.com/flutter/plugins/commit/da1b4638b750a5ff832d7be86a42831c42c6d6c0) * Bug Fixes: * Fixes crash when taking a picture on iOS devices without flash. [(commit)](https://github.com/flutter/plugins/commit/831344490984b1feec007afc9c8595d80b6c13f4) * Make sure the configured zoom scale is copied over to the final capture builder on Android. Fixes the issue where the preview is zoomed but the final picture is not. [(commit)](https://github.com/flutter/plugins/commit/5916f55664e1772a4c3f0c02c5c71fc11e491b76) * Fixes crash with using inner camera on some Android devices. [(commit)](https://github.com/flutter/plugins/commit/980b674cb4020c1927917426211a87e275346d5e) * Improved error feedback by differentiating between uninitialized and disposed camera controllers. [(commit)](https://github.com/flutter/plugins/commit/d0b7109f6b00a0eda03506fed2c74cc123ffc6f3) * Fixes picture captures causing a crash on some Huawei devices. [(commit)](https://github.com/flutter/plugins/commit/6d18db83f00f4861ffe485aba2d1f8aa08845ce6) ## 0.6.4+5 * Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets. ## 0.6.4+4 * Set camera auto focus enabled by default. ## 0.6.4+3 * Detect if selected camera supports auto focus and act accordingly on Android. This solves a problem where front facing cameras are not capturing the picture because auto focus is not supported. ## 0.6.4+2 * Set ImageStreamReader listener to null to prevent stale images when streaming images. ## 0.6.4+1 * Added closeCaptureSession() to stopVideoRecording in Camera.java to fix an Android 6 crash. ## 0.6.4 * Adds auto exposure support for Android and iOS implementations. ## 0.6.3+4 * Revert previous dependency update: Changed dependency on camera_platform_interface to >=1.04 <1.1.0. ## 0.6.3+3 * Updated dependency on camera_platform_interface to ^1.2.0. ## 0.6.3+2 * Fixes crash on Android which occurs after video recording has stopped just before taking a picture. ## 0.6.3+1 * Fixes flash & torch modes not working on some Android devices. ## 0.6.3 * Adds torch mode as a flash mode for Android and iOS implementations. ## 0.6.2+1 * Fix the API documentation for the `CameraController.takePicture` method. ## 0.6.2 * Add zoom support for Android and iOS implementations. ## 0.6.1+1 * Added implementation of the `didFinishProcessingPhoto` on iOS which allows saving image metadata (EXIF) on iOS 11 and up. ## 0.6.1 * Add flash support for Android and iOS implementations. ## 0.6.0+2 * Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) ## 0.6.0+1 Updated README to inform users that iOS 10.0+ is needed for use ## 0.6.0 As part of implementing federated architecture and making the interface compatible with the web this version contains the following **breaking changes**: Method changes in `CameraController`: - The `takePicture` method no longer accepts the `path` parameter, but instead returns the captured image as an instance of the `XFile` class; - The `startVideoRecording` method no longer accepts the `filePath`. Instead the recorded video is now returned as a `XFile` instance when the `stopVideoRecording` method completes; - The `stopVideoRecording` method now returns the captured video when it completes; - Added the `buildPreview` method which is now used to implement the CameraPreview widget. ## 0.5.8+19 * Update Flutter SDK constraint. ## 0.5.8+18 * Suppress unchecked warning in Android tests which prevented the tests to compile. ## 0.5.8+17 * Added Android 30 support. ## 0.5.8+16 * Moved package to camera/camera subdir, to allow for federated implementations. ## 0.5.8+15 * Added the `debugCheckIsDisposed` method which can be used in debug mode to validate if the `CameraController` class has been disposed. ## 0.5.8+14 * Changed the order of the setters for `mediaRecorder` in `MediaRecorderBuilder.java` to make it more readable. ## 0.5.8+13 * Added Dartdocs for all public APIs. ## 0.5.8+12 * Added information of video not working correctly on Android emulators to `README.md`. ## 0.5.8+11 * Fix rare nullptr exception on Android. * Updated README.md with information about handling App lifecycle changes. ## 0.5.8+10 * Suppress the `deprecated_member_use` warning in the example app for `ScaffoldMessenger.showSnackBar`. ## 0.5.8+9 * Update android compileSdkVersion to 29. ## 0.5.8+8 * Fixed garbled audio (in video) by setting audio encoding bitrate. ## 0.5.8+7 * Keep handling deprecated Android v1 classes for backward compatibility. ## 0.5.8+6 * Avoiding uses or overrides a deprecated API in CameraPlugin.java. ## 0.5.8+5 * Fix compilation/availability issues on iOS. ## 0.5.8+4 * Fixed bug caused by casting a `CameraAccessException` on Android. ## 0.5.8+3 * Fix bug in usage example in README.md ## 0.5.8+2 * Post-v2 embedding cleanups. ## 0.5.8+1 * Update lower bound of dart dependency to 2.1.0. ## 0.5.8 * Remove Android dependencies fallback. * Require Flutter SDK 1.12.13+hotfix.5 or greater. ## 0.5.7+5 * Replace deprecated `getFlutterEngine` call on Android. ## 0.5.7+4 * Add `pedantic` to dev_dependency. ## 0.5.7+3 * Fix an Android crash when permissions are requested multiple times. ## 0.5.7+2 * Remove the deprecated `author:` field from pubspec.yaml * Migrate the plugin to the pubspec platforms manifest. * Require Flutter SDK 1.10.0 or greater. ## 0.5.7+1 * Fix example null exception. ## 0.5.7 * Fix unawaited futures. ## 0.5.6+4 * Android: Use CameraDevice.TEMPLATE_RECORD to improve image streaming. ## 0.5.6+3 * Remove AndroidX warning. ## 0.5.6+2 * Include lifecycle dependency as a compileOnly one on Android to resolve potential version conflicts with other transitive libraries. ## 0.5.6+1 * Android: Use android.arch.lifecycle instead of androidx.lifecycle:lifecycle in `build.gradle` to support apps that has not been migrated to AndroidX. ## 0.5.6 * Add support for the v2 Android embedding. This shouldn't affect existing functionality. ## 0.5.5+1 * Fix event type check ## 0.5.5 * Define clang modules for iOS. ## 0.5.4+3 * Update and migrate iOS example project. ## 0.5.4+2 * Fix Android NullPointerException on devices with only front-facing camera. ## 0.5.4+1 * Fix Android pause and resume video crash when executing in APIs below 24. ## 0.5.4 * Add feature to pause and resume video recording. ## 0.5.3+1 * Fix too large request code for FragmentActivity users. ## 0.5.3 * Added new quality presets. * Now all quality presets can be used to control image capture quality. ## 0.5.2+2 * Fix memory leak related to not unregistering stream handler in FlutterEventChannel when disposing camera. ## 0.5.2+1 * Fix bug that prevented video recording with audio. ## 0.5.2 * Added capability to disable audio for the `CameraController`. (e.g. `CameraController(_, _, enableAudio: false);`) ## 0.5.1 * Can now be compiled with earlier Android sdks below 21 when `` has been added to the project `AndroidManifest.xml`. For sdks below 21, the plugin won't be registered and calls to it will throw a `MissingPluginException.` ## 0.5.0 * **Breaking Change** This plugin no longer handles closing and opening the camera on Android lifecycle changes. Please use `WidgetsBindingObserver` to control camera resources on lifecycle changes. See example project for example using `WidgetsBindingObserver`. ## 0.4.3+2 * Bump the minimum Flutter version to 1.2.0. * Add template type parameter to `invokeMethod` calls. ## 0.4.3+1 * Catch additional `Exception`s from Android and throw as `CameraException`s. ## 0.4.3 * Add capability to prepare the capture session for video recording on iOS. ## 0.4.2 * Add sensor orientation value to `CameraDescription`. ## 0.4.1 * Camera methods are ran in a background thread on iOS. ## 0.4.0+3 * Fixed a crash when the plugin is registered by a background FlutterView. ## 0.4.0+2 * Fix orientation of captured photos when camera is used for the first time on Android. ## 0.4.0+1 * Remove categories. ## 0.4.0 * **Breaking Change** Change iOS image stream format to `ImageFormatGroup.bgra8888` from `ImageFormatGroup.yuv420`. ## 0.3.0+4 * Fixed bug causing black screen on some Android devices. ## 0.3.0+3 * Log a more detailed warning at build time about the previous AndroidX migration. ## 0.3.0+2 * Fix issue with calculating iOS image orientation in certain edge cases. ## 0.3.0+1 * Remove initial method call invocation from static camera method. ## 0.3.0 * **Breaking change**. Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to [also migrate](https://developer.android.com/jetpack/androidx/migrate) if they're using the original support library. ## 0.2.9+1 * Fix a crash when failing to start preview. ## 0.2.9 * Save photo orientation data on iOS. ## 0.2.8 * Add access to the image stream from Dart. * Use `cameraController.startImageStream(listener)` to process the images. ## 0.2.7 * Fix issue with crash when the physical device's orientation is unknown. ## 0.2.6 * Update the camera to use the physical device's orientation instead of the UI orientation on Android. ## 0.2.5 * Fix preview and video size with satisfying conditions of multiple outputs. ## 0.2.4 * Unregister the activity lifecycle callbacks when disposing the camera. ## 0.2.3 * Added path_provider and video_player as dev dependencies because the example uses them. * Updated example path_provider version to get Dart 2 support. ## 0.2.2 * iOS image capture is done in high quality (full camera size) ## 0.2.1 * Updated Gradle tooling to match Android Studio 3.1.2. ## 0.2.0 * Added support for video recording. * Changed the example app to add video recording. A lot of **breaking changes** in this version: Getter changes: - Removed `isStarted` - Renamed `initialized` to `isInitialized` - Added `isRecordingVideo` Method changes: - Renamed `capture` to `takePicture` - Removed `start` (the preview starts automatically when `initialize` is called) - Added `startVideoRecording(String filePath)` - Removed `stop` (the preview stops automatically when `dispose` is called) - Added `stopVideoRecording` ## 0.1.2 * Fix Dart 2 runtime errors. ## 0.1.1 * Fix Dart 2 runtime error. ## 0.1.0 * **Breaking change**. Set SDK constraints to match the Flutter beta release. ## 0.0.4 * Revert regression of `CameraController.capture()` introduced in v. 0.0.3. ## 0.0.3 * Improved resource cleanup on Android. Avoids crash on Activity restart. * Made the Future returned by `CameraController.dispose()` and `CameraController.capture()` actually complete on Android. ## 0.0.2 * Simplified and upgraded Android project template to Android SDK 27. * Moved Android package to io.flutter.plugins. * Fixed warnings from the Dart 2.0 analyzer. ## 0.0.1 * Initial release ================================================ FILE: packages/camera/camera/LICENSE ================================================ 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. ================================================ FILE: packages/camera/camera/README.md ================================================ # Camera Plugin [![pub package](https://img.shields.io/pub/v/camera.svg)](https://pub.dev/packages/camera) A Flutter plugin for iOS, Android and Web allowing access to the device cameras. | | Android | iOS | Web | |----------------|---------|----------|------------------------| | **Support** | SDK 21+ | iOS 10+* | [See `camera_web `][1] | ## Features * Display live camera preview in a widget. * Snapshots can be captured and saved to a file. * Record video. * Add access to the image stream from Dart. ## Installation First, add `camera` as a [dependency in your pubspec.yaml file](https://flutter.dev/using-packages/). ### iOS \* The camera plugin compiles for any version of iOS, but its functionality requires iOS 10 or higher. If compiling for iOS 9, make sure to programmatically check the version of iOS running on the device before using any camera plugin features. The [device_info_plus](https://pub.dev/packages/device_info_plus) plugin, for example, can be used to check the iOS version. Add two rows to the `ios/Runner/Info.plist`: * one with the key `Privacy - Camera Usage Description` and a usage description. * and one with the key `Privacy - Microphone Usage Description` and a usage description. If editing `Info.plist` as text, add: ```xml NSCameraUsageDescription your usage description here NSMicrophoneUsageDescription your usage description here ``` ### Android Change the minimum Android sdk version to 21 (or higher) in your `android/app/build.gradle` file. ```groovy minSdkVersion 21 ``` It's important to note that the `MediaRecorder` class is not working properly on emulators, as stated in the documentation: https://developer.android.com/reference/android/media/MediaRecorder. Specifically, when recording a video with sound enabled and trying to play it back, the duration won't be correct and you will only see the first frame. ### Web integration For web integration details, see the [`camera_web` package](https://pub.dev/packages/camera_web). ### Handling Lifecycle states As of version [0.5.0](https://github.com/flutter/plugins/blob/main/packages/camera/CHANGELOG.md#050) of the camera plugin, lifecycle changes are no longer handled by the plugin. This means developers are now responsible to control camera resources when the lifecycle state is updated. Failure to do so might lead to unexpected behavior (for example as described in issue [#39109](https://github.com/flutter/flutter/issues/39109)). Handling lifecycle changes can be done by overriding the `didChangeAppLifecycleState` method like so: ```dart @override void didChangeAppLifecycleState(AppLifecycleState state) { final CameraController? cameraController = controller; // App state changed before we got the chance to initialize. if (cameraController == null || !cameraController.value.isInitialized) { return; } if (state == AppLifecycleState.inactive) { cameraController.dispose(); } else if (state == AppLifecycleState.resumed) { onNewCameraSelected(cameraController.description); } } ``` ### Handling camera access permissions Permission errors may be thrown when initializing the camera controller, and you are expected to handle them properly. Here is a list of all permission error codes that can be thrown: - `CameraAccessDenied`: Thrown when user denies the camera access permission. - `CameraAccessDeniedWithoutPrompt`: iOS only for now. Thrown when user has previously denied the permission. iOS does not allow prompting alert dialog a second time. Users will have to go to Settings > Privacy > Camera in order to enable camera access. - `CameraAccessRestricted`: iOS only for now. Thrown when camera access is restricted and users cannot grant permission (parental control). - `AudioAccessDenied`: Thrown when user denies the audio access permission. - `AudioAccessDeniedWithoutPrompt`: iOS only for now. Thrown when user has previously denied the permission. iOS does not allow prompting alert dialog a second time. Users will have to go to Settings > Privacy > Microphone in order to enable audio access. - `AudioAccessRestricted`: iOS only for now. Thrown when audio access is restricted and users cannot grant permission (parental control). ### Example Here is a small example flutter app displaying a full screen camera preview. ```dart import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; late List _cameras; Future main() async { WidgetsFlutterBinding.ensureInitialized(); _cameras = await availableCameras(); runApp(const CameraApp()); } /// CameraApp is the Main Application. class CameraApp extends StatefulWidget { /// Default Constructor const CameraApp({Key? key}) : super(key: key); @override State createState() => _CameraAppState(); } class _CameraAppState extends State { late CameraController controller; @override void initState() { super.initState(); controller = CameraController(_cameras[0], ResolutionPreset.max); controller.initialize().then((_) { if (!mounted) { return; } setState(() {}); }).catchError((Object e) { if (e is CameraException) { switch (e.code) { case 'CameraAccessDenied': // Handle access errors here. break; default: // Handle other errors here. break; } } }); } @override void dispose() { controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { if (!controller.value.isInitialized) { return Container(); } return MaterialApp( home: CameraPreview(controller), ); } } ``` For a more elaborate usage example see [here](https://github.com/flutter/plugins/tree/main/packages/camera/camera/example). [1]: https://pub.dev/packages/camera_web#limitations-on-the-web-platform ================================================ FILE: packages/camera/camera/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 lintOptions { disable 'InvalidPackage' } defaultConfig { applicationId "io.flutter.plugins.cameraexample" minSdkVersion 21 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } profile { matchingFallbacks = ['debug', 'release'] } } } flutter { source '../..' } dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' } ================================================ FILE: packages/camera/camera/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/camera/camera/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/camera/camera/example/android/app/src/androidTest/java/io/flutter/plugins/cameraexample/FlutterActivityTest.java ================================================ // Copyright 2013 The Flutter 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.cameraexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class FlutterActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/camera/camera/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/camera/camera/example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: packages/camera/camera/example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: packages/camera/camera/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/camera/camera/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/camera/camera/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=false android.enableR8=true ================================================ FILE: packages/camera/camera/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withInputStream { stream -> plugins.load(stream) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/camera/camera/example/build.excerpt.yaml ================================================ targets: $default: sources: include: - lib/** # Some default includes that aren't really used here but will prevent # false-negative warnings: - $package$ - lib/$lib$ exclude: - '**/.*/**' - '**/build/**' builders: code_excerpter|code_excerpter: enabled: true ================================================ FILE: packages/camera/camera/example/integration_test/camera_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; import 'dart:ui'; import 'package:camera/camera.dart'; import 'package:flutter/painting.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:path_provider/path_provider.dart'; import 'package:video_player/video_player.dart'; void main() { late Directory testDir; IntegrationTestWidgetsFlutterBinding.ensureInitialized(); setUpAll(() async { final Directory extDir = await getTemporaryDirectory(); testDir = await Directory('${extDir.path}/test').create(recursive: true); }); tearDownAll(() async { await testDir.delete(recursive: true); }); final Map presetExpectedSizes = { ResolutionPreset.low: Platform.isAndroid ? const Size(240, 320) : const Size(288, 352), ResolutionPreset.medium: Platform.isAndroid ? const Size(480, 720) : const Size(480, 640), ResolutionPreset.high: const Size(720, 1280), ResolutionPreset.veryHigh: const Size(1080, 1920), ResolutionPreset.ultraHigh: const Size(2160, 3840), // Don't bother checking for max here since it could be anything. }; /// Verify that [actual] has dimensions that are at least as large as /// [expectedSize]. Allows for a mismatch in portrait vs landscape. Returns /// whether the dimensions exactly match. bool assertExpectedDimensions(Size expectedSize, Size actual) { expect(actual.shortestSide, lessThanOrEqualTo(expectedSize.shortestSide)); expect(actual.longestSide, lessThanOrEqualTo(expectedSize.longestSide)); return actual.shortestSide == expectedSize.shortestSide && actual.longestSide == expectedSize.longestSide; } // This tests that the capture is no bigger than the preset, since we have // automatic code to fall back to smaller sizes when we need to. Returns // whether the image is exactly the desired resolution. Future testCaptureImageResolution( CameraController controller, ResolutionPreset preset) async { final Size expectedSize = presetExpectedSizes[preset]!; // Take Picture final XFile file = await controller.takePicture(); // Load picture final File fileImage = File(file.path); final Image image = await decodeImageFromList(fileImage.readAsBytesSync()); // Verify image dimensions are as expected expect(image, isNotNull); return assertExpectedDimensions( expectedSize, Size(image.height.toDouble(), image.width.toDouble())); } testWidgets( 'Capture specific image resolutions', (WidgetTester tester) async { final List cameras = await availableCameras(); if (cameras.isEmpty) { return; } for (final CameraDescription cameraDescription in cameras) { bool previousPresetExactlySupported = true; for (final MapEntry preset in presetExpectedSizes.entries) { final CameraController controller = CameraController(cameraDescription, preset.key); await controller.initialize(); final bool presetExactlySupported = await testCaptureImageResolution(controller, preset.key); assert(!(!previousPresetExactlySupported && presetExactlySupported), 'The camera took higher resolution pictures at a lower resolution.'); previousPresetExactlySupported = presetExactlySupported; await controller.dispose(); } } }, // TODO(egarciad): Fix https://github.com/flutter/flutter/issues/93686. skip: true, ); // This tests that the capture is no bigger than the preset, since we have // automatic code to fall back to smaller sizes when we need to. Returns // whether the image is exactly the desired resolution. Future testCaptureVideoResolution( CameraController controller, ResolutionPreset preset) async { final Size expectedSize = presetExpectedSizes[preset]!; // Take Video await controller.startVideoRecording(); sleep(const Duration(milliseconds: 300)); final XFile file = await controller.stopVideoRecording(); // Load video metadata final File videoFile = File(file.path); final VideoPlayerController videoController = VideoPlayerController.file(videoFile); await videoController.initialize(); final Size video = videoController.value.size; // Verify image dimensions are as expected expect(video, isNotNull); return assertExpectedDimensions( expectedSize, Size(video.height, video.width)); } testWidgets( 'Capture specific video resolutions', (WidgetTester tester) async { final List cameras = await availableCameras(); if (cameras.isEmpty) { return; } for (final CameraDescription cameraDescription in cameras) { bool previousPresetExactlySupported = true; for (final MapEntry preset in presetExpectedSizes.entries) { final CameraController controller = CameraController(cameraDescription, preset.key); await controller.initialize(); await controller.prepareForVideoRecording(); final bool presetExactlySupported = await testCaptureVideoResolution(controller, preset.key); assert(!(!previousPresetExactlySupported && presetExactlySupported), 'The camera took higher resolution pictures at a lower resolution.'); previousPresetExactlySupported = presetExactlySupported; await controller.dispose(); } } }, // TODO(egarciad): Fix https://github.com/flutter/flutter/issues/93686. skip: true, ); testWidgets('Pause and resume video recording', (WidgetTester tester) async { final List cameras = await availableCameras(); if (cameras.isEmpty) { return; } final CameraController controller = CameraController( cameras[0], ResolutionPreset.low, enableAudio: false, ); await controller.initialize(); await controller.prepareForVideoRecording(); int startPause; int timePaused = 0; await controller.startVideoRecording(); final int recordingStart = DateTime.now().millisecondsSinceEpoch; sleep(const Duration(milliseconds: 500)); await controller.pauseVideoRecording(); startPause = DateTime.now().millisecondsSinceEpoch; sleep(const Duration(milliseconds: 500)); await controller.resumeVideoRecording(); timePaused += DateTime.now().millisecondsSinceEpoch - startPause; sleep(const Duration(milliseconds: 500)); await controller.pauseVideoRecording(); startPause = DateTime.now().millisecondsSinceEpoch; sleep(const Duration(milliseconds: 500)); await controller.resumeVideoRecording(); timePaused += DateTime.now().millisecondsSinceEpoch - startPause; sleep(const Duration(milliseconds: 500)); final XFile file = await controller.stopVideoRecording(); final int recordingTime = DateTime.now().millisecondsSinceEpoch - recordingStart; final File videoFile = File(file.path); final VideoPlayerController videoController = VideoPlayerController.file( videoFile, ); await videoController.initialize(); final int duration = videoController.value.duration.inMilliseconds; await videoController.dispose(); expect(duration, lessThan(recordingTime - timePaused)); }, skip: !Platform.isAndroid); testWidgets( 'Android image streaming', (WidgetTester tester) async { final List cameras = await availableCameras(); if (cameras.isEmpty) { return; } final CameraController controller = CameraController( cameras[0], ResolutionPreset.low, enableAudio: false, ); await controller.initialize(); bool isDetecting = false; await controller.startImageStream((CameraImage image) { if (isDetecting) { return; } isDetecting = true; expectLater(image, isNotNull).whenComplete(() => isDetecting = false); }); expect(controller.value.isStreamingImages, true); sleep(const Duration(milliseconds: 500)); await controller.stopImageStream(); await controller.dispose(); }, skip: !Platform.isAndroid, ); /// Start streaming with specifying the ImageFormatGroup. Future startStreaming(List cameras, ImageFormatGroup? imageFormatGroup) async { final CameraController controller = CameraController( cameras.first, ResolutionPreset.low, enableAudio: false, imageFormatGroup: imageFormatGroup, ); await controller.initialize(); final Completer completer = Completer(); await controller.startImageStream((CameraImage image) { if (!completer.isCompleted) { Future(() async { await controller.stopImageStream(); await controller.dispose(); }).then((Object? value) { completer.complete(image); }); } }); return completer.future; } testWidgets( 'iOS image streaming with imageFormatGroup', (WidgetTester tester) async { final List cameras = await availableCameras(); if (cameras.isEmpty) { return; } CameraImage image = await startStreaming(cameras, null); expect(image, isNotNull); expect(image.format.group, ImageFormatGroup.bgra8888); expect(image.planes.length, 1); image = await startStreaming(cameras, ImageFormatGroup.yuv420); expect(image, isNotNull); expect(image.format.group, ImageFormatGroup.yuv420); expect(image.planes.length, 2); image = await startStreaming(cameras, ImageFormatGroup.bgra8888); expect(image, isNotNull); expect(image.format.group, ImageFormatGroup.bgra8888); expect(image.planes.length, 1); }, skip: !Platform.isIOS, ); } ================================================ FILE: packages/camera/camera/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 UIRequiredDeviceCapabilities arm64 MinimumOSVersion 9.0 ================================================ FILE: packages/camera/camera/example/ios/Flutter/Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/camera/camera/example/ios/Flutter/Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/camera/camera/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/camera/camera/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/camera/camera/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end ================================================ FILE: packages/camera/camera/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "idiom" : "ios-marketing", "size" : "1024x1024", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/camera/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "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" } } ================================================ FILE: packages/camera/camera/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: packages/camera/camera/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/camera/camera/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/camera/camera/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName camera_example CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSApplicationCategoryType LSRequiresIPhoneOS NSCameraUsageDescription Can I use the camera please? Only for demo purpose of the app NSMicrophoneUsageDescription Only for demo purpose of the app UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: packages/camera/camera/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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 { // The setup logic in `AppDelegate::didFinishLaunchingWithOptions:` eventually sends camera // operations on the background queue, which would run concurrently with the test cases during // unit tests, making the debugging process confusing. This setup is actually not necessary for // the unit tests, so it is better to skip the AppDelegate when running unit tests. BOOL isTesting = NSClassFromString(@"XCTestCase") != nil; return UIApplicationMain(argc, argv, nil, isTesting ? nil : NSStringFromClass([AppDelegate class])); } } ================================================ FILE: packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 236906D1621AE863A5B2E770 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 89D82918721FABF772705DB0 /* libPods-Runner.a */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 14AE82C910C2A12F2ECB2094 /* 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 = ""; }; 1944D8072499F3B5E7653D44 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 59848A7CA98C1FADF8840207 /* 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; 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 = ""; }; 89D82918721FABF772705DB0 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; 9C5CC6CAD53AD388B2694F3A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; A24F9E418BA48BCC7409B117 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 236906D1621AE863A5B2E770 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 3242FD2B467C15C62200632F /* Frameworks */ = { isa = PBXGroup; children = ( 89D82918721FABF772705DB0 /* libPods-Runner.a */, 1944D8072499F3B5E7653D44 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, FD386F00E98D73419C929072 /* Pods */, 3242FD2B467C15C62200632F /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; FD386F00E98D73419C929072 /* Pods */ = { isa = PBXGroup; children = ( 59848A7CA98C1FADF8840207 /* Pods-Runner.debug.xcconfig */, 14AE82C910C2A12F2ECB2094 /* Pods-Runner.release.xcconfig */, 9C5CC6CAD53AD388B2694F3A /* Pods-RunnerTests.debug.xcconfig */, A24F9E418BA48BCC7409B117 /* Pods-RunnerTests.release.xcconfig */, ); path = Pods; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 9872F2A25E8A171A111468CD /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; 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"; }; 9872F2A25E8A171A111468CD /* [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", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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 = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = ""; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.cameraExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = ""; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.cameraExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/camera/camera/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/camera/camera/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/camera/camera/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/camera/camera/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/camera/camera/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; import 'package:camera/camera.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:video_player/video_player.dart'; /// Camera example home widget. class CameraExampleHome extends StatefulWidget { /// Default Constructor const CameraExampleHome({Key? key}) : super(key: key); @override State createState() { return _CameraExampleHomeState(); } } /// Returns a suitable camera icon for [direction]. IconData getCameraLensIcon(CameraLensDirection direction) { switch (direction) { case CameraLensDirection.back: return Icons.camera_rear; case CameraLensDirection.front: return Icons.camera_front; case CameraLensDirection.external: return Icons.camera; } // 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) { // ignore: avoid_print print('Error: $code${message == null ? '' : '\nError Message: $message'}'); } class _CameraExampleHomeState extends State with WidgetsBindingObserver, TickerProviderStateMixin { CameraController? controller; XFile? imageFile; XFile? videoFile; VideoPlayerController? videoController; VoidCallback? videoPlayerListener; bool enableAudio = true; double _minAvailableExposureOffset = 0.0; double _maxAvailableExposureOffset = 0.0; double _currentExposureOffset = 0.0; late AnimationController _flashModeControlRowAnimationController; late Animation _flashModeControlRowAnimation; late AnimationController _exposureModeControlRowAnimationController; late Animation _exposureModeControlRowAnimation; late AnimationController _focusModeControlRowAnimationController; late Animation _focusModeControlRowAnimation; double _minAvailableZoom = 1.0; double _maxAvailableZoom = 1.0; double _currentScale = 1.0; double _baseScale = 1.0; // Counting pointers (number of user fingers on screen) int _pointers = 0; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _flashModeControlRowAnimationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _flashModeControlRowAnimation = CurvedAnimation( parent: _flashModeControlRowAnimationController, curve: Curves.easeInCubic, ); _exposureModeControlRowAnimationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _exposureModeControlRowAnimation = CurvedAnimation( parent: _exposureModeControlRowAnimationController, curve: Curves.easeInCubic, ); _focusModeControlRowAnimationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _focusModeControlRowAnimation = CurvedAnimation( parent: _focusModeControlRowAnimationController, curve: Curves.easeInCubic, ); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _flashModeControlRowAnimationController.dispose(); _exposureModeControlRowAnimationController.dispose(); super.dispose(); } // #docregion AppLifecycle @override void didChangeAppLifecycleState(AppLifecycleState state) { final CameraController? cameraController = controller; // App state changed before we got the chance to initialize. if (cameraController == null || !cameraController.value.isInitialized) { return; } if (state == AppLifecycleState.inactive) { cameraController.dispose(); } else if (state == AppLifecycleState.resumed) { onNewCameraSelected(cameraController.description); } } // #enddocregion AppLifecycle @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Camera example'), ), body: Column( children: [ Expanded( child: Container( decoration: BoxDecoration( color: Colors.black, border: Border.all( color: controller != null && controller!.value.isRecordingVideo ? Colors.redAccent : Colors.grey, width: 3.0, ), ), child: Padding( padding: const EdgeInsets.all(1.0), child: Center( child: _cameraPreviewWidget(), ), ), ), ), _captureControlRowWidget(), _modeControlRowWidget(), Padding( padding: const EdgeInsets.all(5.0), child: Row( children: [ _cameraTogglesRowWidget(), _thumbnailWidget(), ], ), ), ], ), ); } /// Display the preview from the camera (or a message if the preview is not available). Widget _cameraPreviewWidget() { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { return const Text( 'Tap a camera', style: TextStyle( color: Colors.white, fontSize: 24.0, fontWeight: FontWeight.w900, ), ); } else { return Listener( onPointerDown: (_) => _pointers++, onPointerUp: (_) => _pointers--, child: CameraPreview( controller!, child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return GestureDetector( behavior: HitTestBehavior.opaque, onScaleStart: _handleScaleStart, onScaleUpdate: _handleScaleUpdate, onTapDown: (TapDownDetails details) => onViewFinderTap(details, constraints), ); }), ), ); } } void _handleScaleStart(ScaleStartDetails details) { _baseScale = _currentScale; } Future _handleScaleUpdate(ScaleUpdateDetails details) async { // When there are not exactly two fingers on screen don't scale if (controller == null || _pointers != 2) { return; } _currentScale = (_baseScale * details.scale) .clamp(_minAvailableZoom, _maxAvailableZoom); await controller!.setZoomLevel(_currentScale); } /// Display the thumbnail of the captured image or video. Widget _thumbnailWidget() { final VideoPlayerController? localVideoController = videoController; return Expanded( child: Align( alignment: Alignment.centerRight, child: Row( mainAxisSize: MainAxisSize.min, children: [ if (localVideoController == null && imageFile == null) Container() else SizedBox( width: 64.0, height: 64.0, child: (localVideoController == null) ? ( // The captured image on the web contains a network-accessible URL // pointing to a location within the browser. It may be displayed // either with Image.network or Image.memory after loading the image // bytes to memory. kIsWeb ? Image.network(imageFile!.path) : Image.file(File(imageFile!.path))) : Container( decoration: BoxDecoration( border: Border.all(color: Colors.pink)), child: Center( child: AspectRatio( aspectRatio: localVideoController.value.size != null ? localVideoController.value.aspectRatio : 1.0, child: VideoPlayer(localVideoController)), ), ), ), ], ), ), ); } /// Display a bar with buttons to change the flash and exposure modes Widget _modeControlRowWidget() { return Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( icon: const Icon(Icons.flash_on), color: Colors.blue, onPressed: controller != null ? onFlashModeButtonPressed : null, ), // The exposure and focus mode are currently not supported on the web. ...!kIsWeb ? [ IconButton( icon: const Icon(Icons.exposure), color: Colors.blue, onPressed: controller != null ? onExposureModeButtonPressed : null, ), IconButton( icon: const Icon(Icons.filter_center_focus), color: Colors.blue, onPressed: controller != null ? onFocusModeButtonPressed : null, ) ] : [], IconButton( icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute), color: Colors.blue, onPressed: controller != null ? onAudioModeButtonPressed : null, ), IconButton( icon: Icon(controller?.value.isCaptureOrientationLocked ?? false ? Icons.screen_lock_rotation : Icons.screen_rotation), color: Colors.blue, onPressed: controller != null ? onCaptureOrientationLockButtonPressed : null, ), ], ), _flashModeControlRowWidget(), _exposureModeControlRowWidget(), _focusModeControlRowWidget(), ], ); } Widget _flashModeControlRowWidget() { return SizeTransition( sizeFactor: _flashModeControlRowAnimation, child: ClipRect( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( icon: const Icon(Icons.flash_off), color: controller?.value.flashMode == FlashMode.off ? Colors.orange : Colors.blue, onPressed: controller != null ? () => onSetFlashModeButtonPressed(FlashMode.off) : null, ), IconButton( icon: const Icon(Icons.flash_auto), color: controller?.value.flashMode == FlashMode.auto ? Colors.orange : Colors.blue, onPressed: controller != null ? () => onSetFlashModeButtonPressed(FlashMode.auto) : null, ), IconButton( icon: const Icon(Icons.flash_on), color: controller?.value.flashMode == FlashMode.always ? Colors.orange : Colors.blue, onPressed: controller != null ? () => onSetFlashModeButtonPressed(FlashMode.always) : null, ), IconButton( icon: const Icon(Icons.highlight), color: controller?.value.flashMode == FlashMode.torch ? Colors.orange : Colors.blue, onPressed: controller != null ? () => onSetFlashModeButtonPressed(FlashMode.torch) : null, ), ], ), ), ); } Widget _exposureModeControlRowWidget() { final ButtonStyle styleAuto = TextButton.styleFrom( // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: controller?.value.exposureMode == ExposureMode.auto ? Colors.orange : Colors.blue, ); final ButtonStyle styleLocked = TextButton.styleFrom( // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: controller?.value.exposureMode == ExposureMode.locked ? Colors.orange : Colors.blue, ); return SizeTransition( sizeFactor: _exposureModeControlRowAnimation, child: ClipRect( child: Container( color: Colors.grey.shade50, child: Column( children: [ const Center( child: Text('Exposure Mode'), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ TextButton( style: styleAuto, onPressed: controller != null ? () => onSetExposureModeButtonPressed(ExposureMode.auto) : null, onLongPress: () { if (controller != null) { controller!.setExposurePoint(null); showInSnackBar('Resetting exposure point'); } }, child: const Text('AUTO'), ), TextButton( style: styleLocked, onPressed: controller != null ? () => onSetExposureModeButtonPressed(ExposureMode.locked) : null, child: const Text('LOCKED'), ), TextButton( style: styleLocked, onPressed: controller != null ? () => controller!.setExposureOffset(0.0) : null, child: const Text('RESET OFFSET'), ), ], ), const Center( child: Text('Exposure Offset'), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text(_minAvailableExposureOffset.toString()), Slider( value: _currentExposureOffset, min: _minAvailableExposureOffset, max: _maxAvailableExposureOffset, label: _currentExposureOffset.toString(), onChanged: _minAvailableExposureOffset == _maxAvailableExposureOffset ? null : setExposureOffset, ), Text(_maxAvailableExposureOffset.toString()), ], ), ], ), ), ), ); } Widget _focusModeControlRowWidget() { final ButtonStyle styleAuto = TextButton.styleFrom( // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: controller?.value.focusMode == FocusMode.auto ? Colors.orange : Colors.blue, ); final ButtonStyle styleLocked = TextButton.styleFrom( // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: controller?.value.focusMode == FocusMode.locked ? Colors.orange : Colors.blue, ); return SizeTransition( sizeFactor: _focusModeControlRowAnimation, child: ClipRect( child: Container( color: Colors.grey.shade50, child: Column( children: [ const Center( child: Text('Focus Mode'), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ TextButton( style: styleAuto, onPressed: controller != null ? () => onSetFocusModeButtonPressed(FocusMode.auto) : null, onLongPress: () { if (controller != null) { controller!.setFocusPoint(null); } showInSnackBar('Resetting focus point'); }, child: const Text('AUTO'), ), TextButton( style: styleLocked, onPressed: controller != null ? () => onSetFocusModeButtonPressed(FocusMode.locked) : null, child: const Text('LOCKED'), ), ], ), ], ), ), ), ); } /// Display the control bar with buttons to take pictures and record videos. Widget _captureControlRowWidget() { final CameraController? cameraController = controller; return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( icon: const Icon(Icons.camera_alt), color: Colors.blue, onPressed: cameraController != null && cameraController.value.isInitialized && !cameraController.value.isRecordingVideo ? onTakePictureButtonPressed : null, ), IconButton( icon: const Icon(Icons.videocam), color: Colors.blue, onPressed: cameraController != null && cameraController.value.isInitialized && !cameraController.value.isRecordingVideo ? onVideoRecordButtonPressed : null, ), IconButton( icon: cameraController != null && cameraController.value.isRecordingPaused ? const Icon(Icons.play_arrow) : const Icon(Icons.pause), color: Colors.blue, onPressed: cameraController != null && cameraController.value.isInitialized && cameraController.value.isRecordingVideo ? (cameraController.value.isRecordingPaused) ? onResumeButtonPressed : onPauseButtonPressed : null, ), IconButton( icon: const Icon(Icons.stop), color: Colors.red, onPressed: cameraController != null && cameraController.value.isInitialized && cameraController.value.isRecordingVideo ? onStopButtonPressed : null, ), IconButton( icon: const Icon(Icons.pause_presentation), color: cameraController != null && cameraController.value.isPreviewPaused ? Colors.red : Colors.blue, onPressed: cameraController == null ? null : onPausePreviewButtonPressed, ), ], ); } /// Display a row of toggle to select the camera (or a message if no camera is available). Widget _cameraTogglesRowWidget() { final List toggles = []; void onChanged(CameraDescription? description) { if (description == null) { return; } onNewCameraSelected(description); } if (_cameras.isEmpty) { SchedulerBinding.instance.addPostFrameCallback((_) async { showInSnackBar('No camera found.'); }); return const Text('None'); } else { for (final CameraDescription cameraDescription in _cameras) { toggles.add( SizedBox( width: 90.0, child: RadioListTile( title: Icon(getCameraLensIcon(cameraDescription.lensDirection)), groupValue: controller?.description, value: cameraDescription, onChanged: controller != null && controller!.value.isRecordingVideo ? null : onChanged, ), ), ); } } return Row(children: toggles); } String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); void showInSnackBar(String message) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(message))); } void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { if (controller == null) { return; } final CameraController cameraController = controller!; final Offset offset = Offset( details.localPosition.dx / constraints.maxWidth, details.localPosition.dy / constraints.maxHeight, ); cameraController.setExposurePoint(offset); cameraController.setFocusPoint(offset); } Future onNewCameraSelected(CameraDescription cameraDescription) async { final CameraController? oldController = controller; if (oldController != null) { // `controller` needs to be set to null before getting disposed, // to avoid a race condition when we use the controller that is being // disposed. This happens when camera permission dialog shows up, // which triggers `didChangeAppLifecycleState`, which disposes and // re-creates the controller. controller = null; await oldController.dispose(); } final CameraController cameraController = CameraController( cameraDescription, kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, enableAudio: enableAudio, imageFormatGroup: ImageFormatGroup.jpeg, ); controller = cameraController; // If the controller is updated then update the UI. cameraController.addListener(() { if (mounted) { setState(() {}); } if (cameraController.value.hasError) { showInSnackBar( 'Camera error ${cameraController.value.errorDescription}'); } }); try { await cameraController.initialize(); await Future.wait(>[ // The exposure mode is currently not supported on the web. ...!kIsWeb ? >[ cameraController.getMinExposureOffset().then( (double value) => _minAvailableExposureOffset = value), cameraController .getMaxExposureOffset() .then((double value) => _maxAvailableExposureOffset = value) ] : >[], cameraController .getMaxZoomLevel() .then((double value) => _maxAvailableZoom = value), cameraController .getMinZoomLevel() .then((double value) => _minAvailableZoom = value), ]); } on CameraException catch (e) { switch (e.code) { case 'CameraAccessDenied': showInSnackBar('You have denied camera access.'); break; case 'CameraAccessDeniedWithoutPrompt': // iOS only showInSnackBar('Please go to Settings app to enable camera access.'); break; case 'CameraAccessRestricted': // iOS only showInSnackBar('Camera access is restricted.'); break; case 'AudioAccessDenied': showInSnackBar('You have denied audio access.'); break; case 'AudioAccessDeniedWithoutPrompt': // iOS only showInSnackBar('Please go to Settings app to enable audio access.'); break; case 'AudioAccessRestricted': // iOS only showInSnackBar('Audio access is restricted.'); break; default: _showCameraException(e); break; } } if (mounted) { setState(() {}); } } void onTakePictureButtonPressed() { takePicture().then((XFile? file) { if (mounted) { setState(() { imageFile = file; videoController?.dispose(); videoController = null; }); if (file != null) { showInSnackBar('Picture saved to ${file.path}'); } } }); } void onFlashModeButtonPressed() { if (_flashModeControlRowAnimationController.value == 1) { _flashModeControlRowAnimationController.reverse(); } else { _flashModeControlRowAnimationController.forward(); _exposureModeControlRowAnimationController.reverse(); _focusModeControlRowAnimationController.reverse(); } } void onExposureModeButtonPressed() { if (_exposureModeControlRowAnimationController.value == 1) { _exposureModeControlRowAnimationController.reverse(); } else { _exposureModeControlRowAnimationController.forward(); _flashModeControlRowAnimationController.reverse(); _focusModeControlRowAnimationController.reverse(); } } void onFocusModeButtonPressed() { if (_focusModeControlRowAnimationController.value == 1) { _focusModeControlRowAnimationController.reverse(); } else { _focusModeControlRowAnimationController.forward(); _flashModeControlRowAnimationController.reverse(); _exposureModeControlRowAnimationController.reverse(); } } void onAudioModeButtonPressed() { enableAudio = !enableAudio; if (controller != null) { onNewCameraSelected(controller!.description); } } Future onCaptureOrientationLockButtonPressed() async { try { if (controller != null) { final CameraController cameraController = controller!; if (cameraController.value.isCaptureOrientationLocked) { await cameraController.unlockCaptureOrientation(); showInSnackBar('Capture orientation unlocked'); } else { await cameraController.lockCaptureOrientation(); showInSnackBar( 'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}'); } } } on CameraException catch (e) { _showCameraException(e); } } void onSetFlashModeButtonPressed(FlashMode mode) { setFlashMode(mode).then((_) { if (mounted) { setState(() {}); } showInSnackBar('Flash mode set to ${mode.toString().split('.').last}'); }); } void onSetExposureModeButtonPressed(ExposureMode mode) { setExposureMode(mode).then((_) { if (mounted) { setState(() {}); } showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}'); }); } void onSetFocusModeButtonPressed(FocusMode mode) { setFocusMode(mode).then((_) { if (mounted) { setState(() {}); } showInSnackBar('Focus mode set to ${mode.toString().split('.').last}'); }); } void onVideoRecordButtonPressed() { startVideoRecording().then((_) { if (mounted) { setState(() {}); } }); } void onStopButtonPressed() { stopVideoRecording().then((XFile? file) { if (mounted) { setState(() {}); } if (file != null) { showInSnackBar('Video recorded to ${file.path}'); videoFile = file; _startVideoPlayer(); } }); } Future onPausePreviewButtonPressed() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return; } if (cameraController.value.isPreviewPaused) { await cameraController.resumePreview(); } else { await cameraController.pausePreview(); } if (mounted) { setState(() {}); } } void onPauseButtonPressed() { pauseVideoRecording().then((_) { if (mounted) { setState(() {}); } showInSnackBar('Video recording paused'); }); } void onResumeButtonPressed() { resumeVideoRecording().then((_) { if (mounted) { setState(() {}); } showInSnackBar('Video recording resumed'); }); } Future startVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return; } if (cameraController.value.isRecordingVideo) { // A recording is already started, do nothing. return; } try { await cameraController.startVideoRecording(); } on CameraException catch (e) { _showCameraException(e); return; } } Future stopVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { return null; } try { return cameraController.stopVideoRecording(); } on CameraException catch (e) { _showCameraException(e); return null; } } Future pauseVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { return; } try { await cameraController.pauseVideoRecording(); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future resumeVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { return; } try { await cameraController.resumeVideoRecording(); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future setFlashMode(FlashMode mode) async { if (controller == null) { return; } try { await controller!.setFlashMode(mode); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future setExposureMode(ExposureMode mode) async { if (controller == null) { return; } try { await controller!.setExposureMode(mode); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future setExposureOffset(double offset) async { if (controller == null) { return; } setState(() { _currentExposureOffset = offset; }); try { offset = await controller!.setExposureOffset(offset); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future setFocusMode(FocusMode mode) async { if (controller == null) { return; } try { await controller!.setFocusMode(mode); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future _startVideoPlayer() async { if (videoFile == null) { return; } final VideoPlayerController vController = kIsWeb ? VideoPlayerController.network(videoFile!.path) : VideoPlayerController.file(File(videoFile!.path)); videoPlayerListener = () { if (videoController != null && videoController!.value.size != null) { // Refreshing the state to update video player with the correct ratio. if (mounted) { setState(() {}); } videoController!.removeListener(videoPlayerListener!); } }; vController.addListener(videoPlayerListener!); await vController.setLooping(true); await vController.initialize(); await videoController?.dispose(); if (mounted) { setState(() { imageFile = null; videoController = vController; }); } await vController.play(); } Future takePicture() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return null; } if (cameraController.value.isTakingPicture) { // A capture is already pending, do nothing. return null; } try { final XFile file = await cameraController.takePicture(); return file; } on CameraException catch (e) { _showCameraException(e); return null; } } void _showCameraException(CameraException e) { _logError(e.code, e.description); showInSnackBar('Error: ${e.code}\n${e.description}'); } } /// CameraApp is the Main Application. class CameraApp extends StatelessWidget { /// Default Constructor const CameraApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const MaterialApp( home: CameraExampleHome(), ); } } List _cameras = []; Future main() async { // Fetch the available cameras before initializing the app. try { WidgetsFlutterBinding.ensureInitialized(); _cameras = await availableCameras(); } on CameraException catch (e) { _logError(e.code, e.description); } runApp(const CameraApp()); } ================================================ FILE: packages/camera/camera/example/lib/readme_full_example.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // #docregion FullAppExample import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; late List _cameras; Future main() async { WidgetsFlutterBinding.ensureInitialized(); _cameras = await availableCameras(); runApp(const CameraApp()); } /// CameraApp is the Main Application. class CameraApp extends StatefulWidget { /// Default Constructor const CameraApp({Key? key}) : super(key: key); @override State createState() => _CameraAppState(); } class _CameraAppState extends State { late CameraController controller; @override void initState() { super.initState(); controller = CameraController(_cameras[0], ResolutionPreset.max); controller.initialize().then((_) { if (!mounted) { return; } setState(() {}); }).catchError((Object e) { if (e is CameraException) { switch (e.code) { case 'CameraAccessDenied': // Handle access errors here. break; default: // Handle other errors here. break; } } }); } @override void dispose() { controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { if (!controller.value.isInitialized) { return Container(); } return MaterialApp( home: CameraPreview(controller), ); } } // #enddocregion FullAppExample ================================================ FILE: packages/camera/camera/example/pubspec.yaml ================================================ name: camera_example description: Demonstrates how to use the camera plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: camera: # When depending on this package from a real application you should use: # camera: ^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: ../ flutter: sdk: flutter path_provider: ^2.0.0 video_player: ^2.1.4 dev_dependencies: build_runner: ^2.1.10 flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/camera/camera/example/test/main_test.dart ================================================ // Copyright 2013 The Flutter 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_example/main.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('Test snackbar', (WidgetTester tester) async { WidgetsFlutterBinding.ensureInitialized(); await tester.pumpWidget(const CameraApp()); await tester.pumpAndSettle(); expect(find.byType(SnackBar), findsOneWidget); }); } ================================================ FILE: packages/camera/camera/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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: avoid_print import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter_driver/flutter_driver.dart'; const String _examplePackage = 'io.flutter.plugins.cameraexample'; Future main() async { if (!(Platform.isLinux || Platform.isMacOS)) { print('This test must be run on a POSIX host. Skipping...'); exit(0); } final bool adbExists = Process.runSync('which', ['adb']).exitCode == 0; if (!adbExists) { print(r'This test needs ADB to exist on the $PATH. Skipping...'); exit(0); } print('Granting camera permissions...'); Process.runSync('adb', [ 'shell', 'pm', 'grant', _examplePackage, 'android.permission.CAMERA' ]); Process.runSync('adb', [ 'shell', 'pm', 'grant', _examplePackage, 'android.permission.RECORD_AUDIO' ]); print('Starting test.'); final FlutterDriver driver = await FlutterDriver.connect(); final String data = await driver.requestData( null, timeout: const Duration(minutes: 1), ); await driver.close(); print('Test finished. Revoking camera permissions...'); Process.runSync('adb', [ 'shell', 'pm', 'revoke', _examplePackage, 'android.permission.CAMERA' ]); Process.runSync('adb', [ 'shell', 'pm', 'revoke', _examplePackage, 'android.permission.RECORD_AUDIO' ]); final Map result = jsonDecode(data) as Map; exit(result['result'] == 'true' ? 0 : 1); } ================================================ FILE: packages/camera/camera/example/web/index.html ================================================ Camera Web Example ================================================ FILE: packages/camera/camera/example/web/manifest.json ================================================ { "name": "camera example", "short_name": "camera", "start_url": ".", "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", "description": "An example of the camera on the web.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [ { "src": "icons/Icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" } ] } ================================================ FILE: packages/camera/camera/lib/camera.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'package:camera_platform_interface/camera_platform_interface.dart' show CameraDescription, CameraException, CameraLensDirection, FlashMode, ExposureMode, FocusMode, ResolutionPreset, XFile, ImageFormatGroup; export 'src/camera_controller.dart'; export 'src/camera_image.dart'; export 'src/camera_preview.dart'; ================================================ FILE: packages/camera/camera/lib/src/camera_controller.dart ================================================ // Copyright 2013 The Flutter 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 'dart:collection'; import 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../camera.dart'; /// Signature for a callback receiving the a camera image. /// /// This is used by [CameraController.startImageStream]. // TODO(stuartmorgan): Fix this naming the next time there's a breaking change // to this package. // ignore: camel_case_types typedef onLatestImageAvailable = Function(CameraImage image); /// Completes with a list of available cameras. /// /// May throw a [CameraException]. Future> availableCameras() async { return CameraPlatform.instance.availableCameras(); } // TODO(stuartmorgan): Remove this once the package requires 2.10, where the // dart:async `unawaited` accepts a nullable future. void _unawaited(Future? future) {} /// The state of a [CameraController]. class CameraValue { /// Creates a new camera controller state. const CameraValue({ required this.isInitialized, this.errorDescription, this.previewSize, required this.isRecordingVideo, required this.isTakingPicture, required this.isStreamingImages, required bool isRecordingPaused, required this.flashMode, required this.exposureMode, required this.focusMode, required this.exposurePointSupported, required this.focusPointSupported, required this.deviceOrientation, this.lockedCaptureOrientation, this.recordingOrientation, this.isPreviewPaused = false, this.previewPauseOrientation, }) : _isRecordingPaused = isRecordingPaused; /// Creates a new camera controller state for an uninitialized controller. const CameraValue.uninitialized() : this( isInitialized: false, isRecordingVideo: false, isTakingPicture: false, isStreamingImages: false, isRecordingPaused: false, flashMode: FlashMode.auto, exposureMode: ExposureMode.auto, exposurePointSupported: false, focusMode: FocusMode.auto, focusPointSupported: false, deviceOrientation: DeviceOrientation.portraitUp, isPreviewPaused: false, ); /// True after [CameraController.initialize] has completed successfully. final bool isInitialized; /// True when a picture capture request has been sent but as not yet returned. final bool isTakingPicture; /// True when the camera is recording (not the same as previewing). final bool isRecordingVideo; /// True when images from the camera are being streamed. final bool isStreamingImages; final bool _isRecordingPaused; /// True when the preview widget has been paused manually. final bool isPreviewPaused; /// Set to the orientation the preview was paused in, if it is currently paused. final DeviceOrientation? previewPauseOrientation; /// True when camera [isRecordingVideo] and recording is paused. bool get isRecordingPaused => isRecordingVideo && _isRecordingPaused; /// Description of an error state. /// /// This is null while the controller is not in an error state. /// When [hasError] is true this contains the error description. final String? errorDescription; /// The size of the preview in pixels. /// /// Is `null` until [isInitialized] is `true`. final Size? previewSize; /// Convenience getter for `previewSize.width / previewSize.height`. /// /// Can only be called when [initialize] is done. double get aspectRatio => previewSize!.width / previewSize!.height; /// Whether the controller is in an error state. /// /// When true [errorDescription] describes the error. bool get hasError => errorDescription != null; /// The flash mode the camera is currently set to. final FlashMode flashMode; /// The exposure mode the camera is currently set to. final ExposureMode exposureMode; /// The focus mode the camera is currently set to. final FocusMode focusMode; /// Whether setting the exposure point is supported. final bool exposurePointSupported; /// Whether setting the focus point is supported. final bool focusPointSupported; /// The current device UI orientation. final DeviceOrientation deviceOrientation; /// The currently locked capture orientation. final DeviceOrientation? lockedCaptureOrientation; /// Whether the capture orientation is currently locked. bool get isCaptureOrientationLocked => lockedCaptureOrientation != null; /// The orientation of the currently running video recording. final DeviceOrientation? recordingOrientation; /// Creates a modified copy of the object. /// /// Explicitly specified fields get the specified value, all other fields get /// the same value of the current object. CameraValue copyWith({ bool? isInitialized, bool? isRecordingVideo, bool? isTakingPicture, bool? isStreamingImages, String? errorDescription, Size? previewSize, bool? isRecordingPaused, FlashMode? flashMode, ExposureMode? exposureMode, FocusMode? focusMode, bool? exposurePointSupported, bool? focusPointSupported, DeviceOrientation? deviceOrientation, Optional? lockedCaptureOrientation, Optional? recordingOrientation, bool? isPreviewPaused, Optional? previewPauseOrientation, }) { return CameraValue( isInitialized: isInitialized ?? this.isInitialized, errorDescription: errorDescription, previewSize: previewSize ?? this.previewSize, isRecordingVideo: isRecordingVideo ?? this.isRecordingVideo, isTakingPicture: isTakingPicture ?? this.isTakingPicture, isStreamingImages: isStreamingImages ?? this.isStreamingImages, isRecordingPaused: isRecordingPaused ?? _isRecordingPaused, flashMode: flashMode ?? this.flashMode, exposureMode: exposureMode ?? this.exposureMode, focusMode: focusMode ?? this.focusMode, exposurePointSupported: exposurePointSupported ?? this.exposurePointSupported, focusPointSupported: focusPointSupported ?? this.focusPointSupported, deviceOrientation: deviceOrientation ?? this.deviceOrientation, lockedCaptureOrientation: lockedCaptureOrientation == null ? this.lockedCaptureOrientation : lockedCaptureOrientation.orNull, recordingOrientation: recordingOrientation == null ? this.recordingOrientation : recordingOrientation.orNull, isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, previewPauseOrientation: previewPauseOrientation == null ? this.previewPauseOrientation : previewPauseOrientation.orNull, ); } @override String toString() { return '${objectRuntimeType(this, 'CameraValue')}(' 'isRecordingVideo: $isRecordingVideo, ' 'isInitialized: $isInitialized, ' 'errorDescription: $errorDescription, ' 'previewSize: $previewSize, ' 'isStreamingImages: $isStreamingImages, ' 'flashMode: $flashMode, ' 'exposureMode: $exposureMode, ' 'focusMode: $focusMode, ' 'exposurePointSupported: $exposurePointSupported, ' 'focusPointSupported: $focusPointSupported, ' 'deviceOrientation: $deviceOrientation, ' 'lockedCaptureOrientation: $lockedCaptureOrientation, ' 'recordingOrientation: $recordingOrientation, ' 'isPreviewPaused: $isPreviewPaused, ' 'previewPausedOrientation: $previewPauseOrientation)'; } } /// Controls a device camera. /// /// Use [availableCameras] to get a list of available cameras. /// /// Before using a [CameraController] a call to [initialize] must complete. /// /// To show the camera preview on the screen use a [CameraPreview] widget. class CameraController extends ValueNotifier { /// Creates a new camera controller in an uninitialized state. CameraController( this.description, this.resolutionPreset, { this.enableAudio = true, this.imageFormatGroup, }) : super(const CameraValue.uninitialized()); /// The properties of the camera device controlled by this controller. final CameraDescription description; /// The resolution this controller is targeting. /// /// This resolution preset is not guaranteed to be available on the device, /// if unavailable a lower resolution will be used. /// /// See also: [ResolutionPreset]. final ResolutionPreset resolutionPreset; /// Whether to include audio when recording a video. final bool enableAudio; /// The [ImageFormatGroup] describes the output of the raw image format. /// /// When null the imageFormat will fallback to the platforms default. final ImageFormatGroup? imageFormatGroup; /// The id of a camera that hasn't been initialized. @visibleForTesting static const int kUninitializedCameraId = -1; int _cameraId = kUninitializedCameraId; bool _isDisposed = false; StreamSubscription? _imageStreamSubscription; FutureOr? _initCalled; StreamSubscription? _deviceOrientationSubscription; /// Checks whether [CameraController.dispose] has completed successfully. /// /// This is a no-op when asserts are disabled. void debugCheckIsDisposed() { assert(_isDisposed); } /// The camera identifier with which the controller is associated. int get cameraId => _cameraId; /// Initializes the camera on the device. /// /// Throws a [CameraException] if the initialization fails. Future initialize() async { if (_isDisposed) { throw CameraException( 'Disposed CameraController', 'initialize was called on a disposed CameraController', ); } try { final Completer initializeCompleter = Completer(); _deviceOrientationSubscription = CameraPlatform.instance .onDeviceOrientationChanged() .listen((DeviceOrientationChangedEvent event) { value = value.copyWith( deviceOrientation: event.orientation, ); }); _cameraId = await CameraPlatform.instance.createCamera( description, resolutionPreset, enableAudio: enableAudio, ); _unawaited(CameraPlatform.instance .onCameraInitialized(_cameraId) .first .then((CameraInitializedEvent event) { initializeCompleter.complete(event); })); await CameraPlatform.instance.initializeCamera( _cameraId, imageFormatGroup: imageFormatGroup ?? ImageFormatGroup.unknown, ); value = value.copyWith( isInitialized: true, previewSize: await initializeCompleter.future .then((CameraInitializedEvent event) => Size( event.previewWidth, event.previewHeight, )), exposureMode: await initializeCompleter.future .then((CameraInitializedEvent event) => event.exposureMode), focusMode: await initializeCompleter.future .then((CameraInitializedEvent event) => event.focusMode), exposurePointSupported: await initializeCompleter.future.then( (CameraInitializedEvent event) => event.exposurePointSupported), focusPointSupported: await initializeCompleter.future .then((CameraInitializedEvent event) => event.focusPointSupported), ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } _initCalled = true; } /// Prepare the capture session for video recording. /// /// Use of this method is optional, but it may be called for performance /// reasons on iOS. /// /// Preparing audio can cause a minor delay in the CameraPreview view on iOS. /// If video recording is intended, calling this early eliminates this delay /// that would otherwise be experienced when video recording is started. /// This operation is a no-op on Android and Web. /// /// Throws a [CameraException] if the prepare fails. Future prepareForVideoRecording() async { await CameraPlatform.instance.prepareForVideoRecording(); } /// Pauses the current camera preview Future pausePreview() async { if (value.isPreviewPaused) { return; } try { await CameraPlatform.instance.pausePreview(_cameraId); value = value.copyWith( isPreviewPaused: true, previewPauseOrientation: Optional.of( value.lockedCaptureOrientation ?? value.deviceOrientation)); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Resumes the current camera preview Future resumePreview() async { if (!value.isPreviewPaused) { return; } try { await CameraPlatform.instance.resumePreview(_cameraId); value = value.copyWith( isPreviewPaused: false, previewPauseOrientation: const Optional.absent()); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Captures an image and returns the file where it was saved. /// /// Throws a [CameraException] if the capture fails. Future takePicture() async { _throwIfNotInitialized('takePicture'); if (value.isTakingPicture) { throw CameraException( 'Previous capture has not returned yet.', 'takePicture was called before the previous capture returned.', ); } try { value = value.copyWith(isTakingPicture: true); final XFile file = await CameraPlatform.instance.takePicture(_cameraId); value = value.copyWith(isTakingPicture: false); return file; } on PlatformException catch (e) { value = value.copyWith(isTakingPicture: false); throw CameraException(e.code, e.message); } } /// Start streaming images from platform camera. /// /// Settings for capturing images on iOS and Android is set to always use the /// latest image available from the camera and will drop all other images. /// /// When running continuously with [CameraPreview] widget, this function runs /// best with [ResolutionPreset.low]. Running on [ResolutionPreset.high] can /// have significant frame rate drops for [CameraPreview] on lower end /// devices. /// /// Throws a [CameraException] if image streaming or video recording has /// already started. /// /// The `startImageStream` method is only available on Android and iOS (other /// platforms won't be supported in current setup). /// // TODO(bmparr): Add settings for resolution and fps. Future startImageStream(onLatestImageAvailable onAvailable) async { assert(defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS); _throwIfNotInitialized('startImageStream'); if (value.isRecordingVideo) { throw CameraException( 'A video recording is already started.', 'startImageStream was called while a video is being recorded.', ); } if (value.isStreamingImages) { throw CameraException( 'A camera has started streaming images.', 'startImageStream was called while a camera was streaming images.', ); } try { _imageStreamSubscription = CameraPlatform.instance .onStreamedFrameAvailable(_cameraId) .listen((CameraImageData imageData) { onAvailable(CameraImage.fromPlatformInterface(imageData)); }); value = value.copyWith(isStreamingImages: true); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Stop streaming images from platform camera. /// /// Throws a [CameraException] if image streaming was not started or video /// recording was started. /// /// The `stopImageStream` method is only available on Android and iOS (other /// platforms won't be supported in current setup). Future stopImageStream() async { assert(defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS); _throwIfNotInitialized('stopImageStream'); if (!value.isStreamingImages) { throw CameraException( 'No camera is streaming images', 'stopImageStream was called when no camera is streaming images.', ); } try { value = value.copyWith(isStreamingImages: false); await _imageStreamSubscription?.cancel(); _imageStreamSubscription = null; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// 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( {onLatestImageAvailable? onAvailable}) async { _throwIfNotInitialized('startVideoRecording'); if (value.isRecordingVideo) { throw CameraException( 'A video recording is already started.', 'startVideoRecording was called when a recording is already started.', ); } Function(CameraImageData image)? streamCallback; if (onAvailable != null) { streamCallback = (CameraImageData imageData) { onAvailable(CameraImage.fromPlatformInterface(imageData)); }; } try { await CameraPlatform.instance.startVideoCapturing( VideoCaptureOptions(_cameraId, streamCallback: streamCallback)); value = value.copyWith( isRecordingVideo: true, isRecordingPaused: false, recordingOrientation: Optional.of( value.lockedCaptureOrientation ?? value.deviceOrientation), isStreamingImages: onAvailable != null); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Stops the video recording and returns the file where it was saved. /// /// Throws a [CameraException] if the capture failed. Future stopVideoRecording() async { _throwIfNotInitialized('stopVideoRecording'); if (!value.isRecordingVideo) { throw CameraException( 'No video is recording', '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, recordingOrientation: const Optional.absent(), ); return file; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Pause video recording. /// /// This feature is only available on iOS and Android sdk 24+. Future pauseVideoRecording() async { _throwIfNotInitialized('pauseVideoRecording'); if (!value.isRecordingVideo) { throw CameraException( 'No video is recording', 'pauseVideoRecording was called when no video is recording.', ); } try { await CameraPlatform.instance.pauseVideoRecording(_cameraId); value = value.copyWith(isRecordingPaused: true); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Resume video recording after pausing. /// /// This feature is only available on iOS and Android sdk 24+. Future resumeVideoRecording() async { _throwIfNotInitialized('resumeVideoRecording'); if (!value.isRecordingVideo) { throw CameraException( 'No video is recording', 'resumeVideoRecording was called when no video is recording.', ); } try { await CameraPlatform.instance.resumeVideoRecording(_cameraId); value = value.copyWith(isRecordingPaused: false); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Returns a widget showing a live camera preview. Widget buildPreview() { _throwIfNotInitialized('buildPreview'); try { return CameraPlatform.instance.buildPreview(_cameraId); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Gets the maximum supported zoom level for the selected camera. Future getMaxZoomLevel() { _throwIfNotInitialized('getMaxZoomLevel'); try { return CameraPlatform.instance.getMaxZoomLevel(_cameraId); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Gets the minimum supported zoom level for the selected camera. Future getMinZoomLevel() { _throwIfNotInitialized('getMinZoomLevel'); try { return CameraPlatform.instance.getMinZoomLevel(_cameraId); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Set the zoom level for the selected camera. /// /// The supplied [zoom] value should be between 1.0 and the maximum supported /// zoom level returned by the `getMaxZoomLevel`. Throws an `CameraException` /// when an illegal zoom level is suplied. Future setZoomLevel(double zoom) { _throwIfNotInitialized('setZoomLevel'); try { return CameraPlatform.instance.setZoomLevel(_cameraId, zoom); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Sets the flash mode for taking pictures. Future setFlashMode(FlashMode mode) async { try { await CameraPlatform.instance.setFlashMode(_cameraId, mode); value = value.copyWith(flashMode: mode); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Sets the exposure mode for taking pictures. Future setExposureMode(ExposureMode mode) async { try { await CameraPlatform.instance.setExposureMode(_cameraId, mode); value = value.copyWith(exposureMode: mode); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Sets the exposure point for automatically determining the exposure value. /// /// Supplying a `null` value will reset the exposure point to it's default /// value. Future setExposurePoint(Offset? point) async { if (point != null && (point.dx < 0 || point.dx > 1 || point.dy < 0 || point.dy > 1)) { throw ArgumentError( 'The values of point should be anywhere between (0,0) and (1,1).'); } try { await CameraPlatform.instance.setExposurePoint( _cameraId, point == null ? null : Point( point.dx, point.dy, ), ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Gets the minimum supported exposure offset for the selected camera in EV units. Future getMinExposureOffset() async { _throwIfNotInitialized('getMinExposureOffset'); try { return CameraPlatform.instance.getMinExposureOffset(_cameraId); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Gets the maximum supported exposure offset for the selected camera in EV units. Future getMaxExposureOffset() async { _throwIfNotInitialized('getMaxExposureOffset'); try { return CameraPlatform.instance.getMaxExposureOffset(_cameraId); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Gets the supported step size for exposure offset for the selected camera in EV units. /// /// Returns 0 when the camera supports using a free value without stepping. Future getExposureOffsetStepSize() async { _throwIfNotInitialized('getExposureOffsetStepSize'); try { return CameraPlatform.instance.getExposureOffsetStepSize(_cameraId); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Sets the exposure offset for the selected camera. /// /// The supplied [offset] value should be in EV units. 1 EV unit represents a /// doubling in brightness. It should be between the minimum and maximum offsets /// obtained through `getMinExposureOffset` and `getMaxExposureOffset` respectively. /// Throws a `CameraException` when an illegal offset is supplied. /// /// When the supplied [offset] value does not align with the step size obtained /// through `getExposureStepSize`, it will automatically be rounded to the nearest step. /// /// Returns the (rounded) offset value that was set. Future setExposureOffset(double offset) async { _throwIfNotInitialized('setExposureOffset'); // Check if offset is in range final List range = await Future.wait( >[getMinExposureOffset(), getMaxExposureOffset()]); if (offset < range[0] || offset > range[1]) { throw CameraException( 'exposureOffsetOutOfBounds', 'The provided exposure offset was outside the supported range for this device.', ); } // Round to the closest step if needed final double stepSize = await getExposureOffsetStepSize(); if (stepSize > 0) { final double inv = 1.0 / stepSize; double roundedOffset = (offset * inv).roundToDouble() / inv; if (roundedOffset > range[1]) { roundedOffset = (offset * inv).floorToDouble() / inv; } else if (roundedOffset < range[0]) { roundedOffset = (offset * inv).ceilToDouble() / inv; } offset = roundedOffset; } try { return CameraPlatform.instance.setExposureOffset(_cameraId, offset); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Locks the capture orientation. /// /// If [orientation] is omitted, the current device orientation is used. Future lockCaptureOrientation([DeviceOrientation? orientation]) async { try { await CameraPlatform.instance.lockCaptureOrientation( _cameraId, orientation ?? value.deviceOrientation); value = value.copyWith( lockedCaptureOrientation: Optional.of( orientation ?? value.deviceOrientation)); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Sets the focus mode for taking pictures. Future setFocusMode(FocusMode mode) async { try { await CameraPlatform.instance.setFocusMode(_cameraId, mode); value = value.copyWith(focusMode: mode); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Unlocks the capture orientation. Future unlockCaptureOrientation() async { try { await CameraPlatform.instance.unlockCaptureOrientation(_cameraId); value = value.copyWith( lockedCaptureOrientation: const Optional.absent()); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Sets the focus point for automatically determining the focus value. /// /// Supplying a `null` value will reset the focus point to it's default /// value. Future setFocusPoint(Offset? point) async { if (point != null && (point.dx < 0 || point.dx > 1 || point.dy < 0 || point.dy > 1)) { throw ArgumentError( 'The values of point should be anywhere between (0,0) and (1,1).'); } try { await CameraPlatform.instance.setFocusPoint( _cameraId, point == null ? null : Point( point.dx, point.dy, ), ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Releases the resources of this camera. @override Future dispose() async { if (_isDisposed) { return; } _unawaited(_deviceOrientationSubscription?.cancel()); _isDisposed = true; super.dispose(); if (_initCalled != null) { await _initCalled; await CameraPlatform.instance.dispose(_cameraId); } } void _throwIfNotInitialized(String functionName) { if (!value.isInitialized) { throw CameraException( 'Uninitialized CameraController', '$functionName() was called on an uninitialized CameraController.', ); } if (_isDisposed) { throw CameraException( 'Disposed CameraController', '$functionName() was called on a disposed CameraController.', ); } } @override void removeListener(VoidCallback listener) { // Prevent ValueListenableBuilder in CameraPreview widget from causing an // exception to be thrown by attempting to remove its own listener after // the controller has already been disposed. if (!_isDisposed) { super.removeListener(listener); } } } /// 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 }'; } } ================================================ FILE: packages/camera/camera/lib/src/camera_image.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; // TODO(stuartmorgan): Remove all of these classes in a breaking change, and // vend the platform interface versions directly. See // https://github.com/flutter/flutter/issues/104188 /// A single color plane of image data. /// /// The number and meaning of the planes in an image are determined by the /// format of the Image. class Plane { Plane._fromPlatformInterface(CameraImagePlane plane) : bytes = plane.bytes, bytesPerPixel = plane.bytesPerPixel, bytesPerRow = plane.bytesPerRow, height = plane.height, width = plane.width; // Only used by the deprecated codepath that's kept to avoid breaking changes. // Never called by the plugin itself. Plane._fromPlatformData(Map data) : bytes = data['bytes'] as Uint8List, bytesPerPixel = data['bytesPerPixel'] as int?, bytesPerRow = data['bytesPerRow'] as int, height = data['height'] as int?, width = data['width'] as int?; /// Bytes representing this plane. final Uint8List bytes; /// The distance between adjacent pixel samples on Android, in bytes. /// /// Will be `null` on iOS. final int? bytesPerPixel; /// The row stride for this color plane, in bytes. final int bytesPerRow; /// Height of the pixel buffer on iOS. /// /// Will be `null` on Android final int? height; /// Width of the pixel buffer on iOS. /// /// Will be `null` on Android. final int? width; } /// Describes how pixels are represented in an image. class ImageFormat { ImageFormat._fromPlatformInterface(CameraImageFormat format) : group = format.group, raw = format.raw; // Only used by the deprecated codepath that's kept to avoid breaking changes. // Never called by the plugin itself. ImageFormat._fromPlatformData(this.raw) : group = _asImageFormatGroup(raw); /// Describes the format group the raw image format falls into. final ImageFormatGroup group; /// Raw version of the format from the Android or iOS platform. /// /// On Android, this is an `int` from class `android.graphics.ImageFormat`. See /// https://developer.android.com/reference/android/graphics/ImageFormat /// /// On iOS, this is a `FourCharCode` constant from Pixel Format Identifiers. /// See https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers?language=objc final dynamic raw; } // Only used by the deprecated codepath that's kept to avoid breaking changes. // Never called by the plugin itself. ImageFormatGroup _asImageFormatGroup(dynamic rawFormat) { if (defaultTargetPlatform == TargetPlatform.android) { switch (rawFormat) { // android.graphics.ImageFormat.YUV_420_888 case 35: return ImageFormatGroup.yuv420; // android.graphics.ImageFormat.JPEG case 256: return ImageFormatGroup.jpeg; } } if (defaultTargetPlatform == TargetPlatform.iOS) { switch (rawFormat) { // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange case 875704438: return ImageFormatGroup.yuv420; // kCVPixelFormatType_32BGRA case 1111970369: return ImageFormatGroup.bgra8888; } } return ImageFormatGroup.unknown; } /// A single complete image buffer from the platform camera. /// /// This class allows for direct application access to the pixel data of an /// Image through one or more [Uint8List]. Each buffer is encapsulated in a /// [Plane] that describes the layout of the pixel data in that plane. The /// [CameraImage] is not directly usable as a UI resource. /// /// Although not all image formats are planar on iOS, we treat 1-dimensional /// images as single planar images. class CameraImage { /// Creates a [CameraImage] from the platform interface version. CameraImage.fromPlatformInterface(CameraImageData data) : format = ImageFormat._fromPlatformInterface(data.format), height = data.height, width = data.width, planes = List.unmodifiable(data.planes.map( (CameraImagePlane plane) => Plane._fromPlatformInterface(plane))), lensAperture = data.lensAperture, sensorExposureTime = data.sensorExposureTime, sensorSensitivity = data.sensorSensitivity; /// Creates a [CameraImage] from method channel data. @Deprecated('Use fromPlatformInterface instead') CameraImage.fromPlatformData(Map data) : format = ImageFormat._fromPlatformData(data['format']), height = data['height'] as int, width = data['width'] as int, lensAperture = data['lensAperture'] as double?, sensorExposureTime = data['sensorExposureTime'] as int?, sensorSensitivity = data['sensorSensitivity'] as double?, planes = List.unmodifiable((data['planes'] as List) .map((dynamic planeData) => Plane._fromPlatformData(planeData as Map))); /// Format of the image provided. /// /// Determines the number of planes needed to represent the image, and /// the general layout of the pixel data in each [Uint8List]. final ImageFormat format; /// Height of the image in pixels. /// /// For formats where some color channels are subsampled, this is the height /// of the largest-resolution plane. final int height; /// Width of the image in pixels. /// /// For formats where some color channels are subsampled, this is the width /// of the largest-resolution plane. final int width; /// The pixels planes for this image. /// /// The number of planes is determined by the format of the image. final List planes; /// The aperture settings for this image. /// /// Represented as an f-stop value. final double? lensAperture; /// The sensor exposure time for this image in nanoseconds. final int? sensorExposureTime; /// The sensor sensitivity in standard ISO arithmetic units. final double? sensorSensitivity; } ================================================ FILE: packages/camera/camera/lib/src/camera_preview.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../camera.dart'; /// A widget showing a live camera preview. class CameraPreview extends StatelessWidget { /// Creates a preview widget for the given camera controller. const CameraPreview(this.controller, {Key? key, this.child}) : super(key: key); /// The controller for the camera that the preview is shown for. final CameraController controller; /// A widget to overlay on top of the camera preview final Widget? child; @override Widget build(BuildContext context) { return controller.value.isInitialized ? ValueListenableBuilder( valueListenable: controller, builder: (BuildContext context, Object? value, Widget? child) { return AspectRatio( aspectRatio: _isLandscape() ? controller.value.aspectRatio : (1 / controller.value.aspectRatio), child: Stack( fit: StackFit.expand, children: [ _wrapInRotatedBox(child: controller.buildPreview()), child ?? Container(), ], ), ); }, child: child, ) : Container(); } Widget _wrapInRotatedBox({required Widget child}) { if (kIsWeb || defaultTargetPlatform != TargetPlatform.android) { return child; } return RotatedBox( quarterTurns: _getQuarterTurns(), child: child, ); } bool _isLandscape() { return [ DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight ].contains(_getApplicableOrientation()); } int _getQuarterTurns() { final Map turns = { DeviceOrientation.portraitUp: 0, DeviceOrientation.landscapeRight: 1, DeviceOrientation.portraitDown: 2, DeviceOrientation.landscapeLeft: 3, }; return turns[_getApplicableOrientation()]!; } DeviceOrientation _getApplicableOrientation() { return controller.value.isRecordingVideo ? controller.value.recordingOrientation! : (controller.value.previewPauseOrientation ?? controller.value.lockedCaptureOrientation ?? controller.value.deviceOrientation); } } ================================================ FILE: packages/camera/camera/pubspec.yaml ================================================ name: camera description: A Flutter plugin for controlling the camera. Supports previewing the camera feed, capturing images and video, and streaming image buffers to 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.3 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: platforms: android: default_package: camera_android ios: default_package: camera_avfoundation web: default_package: camera_web dependencies: 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 quiver: ^3.0.0 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter mockito: ^5.0.0 plugin_platform_interface: ^2.0.0 video_player: ^2.0.0 ================================================ FILE: packages/camera/camera/test/camera_image_stream_test.dart ================================================ // Copyright 2013 The Flutter 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/camera.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter_test/flutter_test.dart'; import 'camera_test.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); late MockStreamingCameraPlatform mockPlatform; setUp(() { mockPlatform = MockStreamingCameraPlatform(); CameraPlatform.instance = mockPlatform; }); test('startImageStream() throws $CameraException when uninitialized', () { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); expect( () => cameraController.startImageStream((CameraImage image) => null), throwsA( isA() .having( (CameraException error) => error.code, 'code', 'Uninitialized CameraController', ) .having( (CameraException error) => error.description, 'description', 'startImageStream() was called on an uninitialized CameraController.', ), ), ); }); test('startImageStream() throws $CameraException when recording videos', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); cameraController.value = cameraController.value.copyWith(isRecordingVideo: true); expect( () => cameraController.startImageStream((CameraImage image) => null), throwsA(isA().having( (CameraException error) => error.description, 'A video recording is already started.', 'startImageStream was called while a video is being recorded.', ))); }); test( 'startImageStream() 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.startImageStream((CameraImage image) => null), throwsA(isA().having( (CameraException error) => error.description, 'A camera has started streaming images.', 'startImageStream was called while a camera was streaming images.', ))); }); test('startImageStream() calls CameraPlatform', () 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); expect(mockPlatform.streamCallLog, ['onStreamedFrameAvailable', 'listen']); }); test('stopImageStream() throws $CameraException when uninitialized', () { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); expect( cameraController.stopImageStream, throwsA( isA() .having( (CameraException error) => error.code, 'code', 'Uninitialized CameraController', ) .having( (CameraException error) => error.description, 'description', 'stopImageStream() was called on an uninitialized CameraController.', ), ), ); }); test('stopImageStream() throws $CameraException when not streaming images', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); expect( cameraController.stopImageStream, throwsA(isA().having( (CameraException error) => error.description, 'No camera is streaming images', 'stopImageStream was called when no camera is streaming images.', ))); }); test('stopImageStream() intended behaviour', () 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']); }); 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( mockPlatform.streamCallLog.contains('startVideoCapturing with stream'), isTrue); }); 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(); cameraController.startVideoRecording(); expect(mockPlatform.streamCallLog.contains('startVideoCapturing'), isTrue); }); } class MockStreamingCameraPlatform extends MockCameraPlatform { List streamCallLog = []; StreamController? _streamController; @override Stream onStreamedFrameAvailable(int cameraId, {CameraImageStreamOptions? options}) { streamCallLog.add('onStreamedFrameAvailable'); _streamController = StreamController( onListen: _onFrameStreamListen, onCancel: _onFrameStreamCancel, ); 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'); } FutureOr _onFrameStreamCancel() async { streamCallLog.add('cancel'); _streamController = null; } } ================================================ FILE: packages/camera/camera/test/camera_image_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:camera/camera.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('translates correctly from platform interface classes', () { final CameraImageData originalImage = CameraImageData( format: const CameraImageFormat(ImageFormatGroup.jpeg, raw: 1234), planes: [ CameraImagePlane( bytes: Uint8List.fromList([1, 2, 3, 4]), bytesPerRow: 20, bytesPerPixel: 3, width: 200, height: 100, ), CameraImagePlane( bytes: Uint8List.fromList([5, 6, 7, 8]), bytesPerRow: 18, bytesPerPixel: 4, width: 220, height: 110, ), ], width: 640, height: 480, lensAperture: 2.5, sensorExposureTime: 5, sensorSensitivity: 1.3, ); final CameraImage image = CameraImage.fromPlatformInterface(originalImage); // Simple values. expect(image.width, 640); expect(image.height, 480); expect(image.lensAperture, 2.5); expect(image.sensorExposureTime, 5); expect(image.sensorSensitivity, 1.3); // Format. expect(image.format.group, ImageFormatGroup.jpeg); expect(image.format.raw, 1234); // Planes. expect(image.planes.length, originalImage.planes.length); for (int i = 0; i < image.planes.length; i++) { expect( image.planes[i].bytes.length, originalImage.planes[i].bytes.length); for (int j = 0; j < image.planes[i].bytes.length; j++) { expect(image.planes[i].bytes[j], originalImage.planes[i].bytes[j]); } expect( image.planes[i].bytesPerPixel, originalImage.planes[i].bytesPerPixel); expect(image.planes[i].bytesPerRow, originalImage.planes[i].bytesPerRow); expect(image.planes[i].width, originalImage.planes[i].width); expect(image.planes[i].height, originalImage.planes[i].height); } }); group('legacy constructors', () { test('$CameraImage can be created', () { debugDefaultTargetPlatformOverride = TargetPlatform.android; final CameraImage cameraImage = CameraImage.fromPlatformData({ 'format': 35, 'height': 1, 'width': 4, 'lensAperture': 1.8, 'sensorExposureTime': 9991324, 'sensorSensitivity': 92.0, 'planes': [ { 'bytes': Uint8List.fromList([1, 2, 3, 4]), 'bytesPerPixel': 1, 'bytesPerRow': 4, 'height': 1, 'width': 4 } ] }); expect(cameraImage.height, 1); expect(cameraImage.width, 4); expect(cameraImage.format.group, ImageFormatGroup.yuv420); expect(cameraImage.planes.length, 1); }); test('$CameraImage has ImageFormatGroup.yuv420 for iOS', () { debugDefaultTargetPlatformOverride = TargetPlatform.iOS; final CameraImage cameraImage = CameraImage.fromPlatformData({ 'format': 875704438, 'height': 1, 'width': 4, 'lensAperture': 1.8, 'sensorExposureTime': 9991324, 'sensorSensitivity': 92.0, 'planes': [ { 'bytes': Uint8List.fromList([1, 2, 3, 4]), 'bytesPerPixel': 1, 'bytesPerRow': 4, 'height': 1, 'width': 4 } ] }); expect(cameraImage.format.group, ImageFormatGroup.yuv420); }); test('$CameraImage has ImageFormatGroup.yuv420 for Android', () { debugDefaultTargetPlatformOverride = TargetPlatform.android; final CameraImage cameraImage = CameraImage.fromPlatformData({ 'format': 35, 'height': 1, 'width': 4, 'lensAperture': 1.8, 'sensorExposureTime': 9991324, 'sensorSensitivity': 92.0, 'planes': [ { 'bytes': Uint8List.fromList([1, 2, 3, 4]), 'bytesPerPixel': 1, 'bytesPerRow': 4, 'height': 1, 'width': 4 } ] }); expect(cameraImage.format.group, ImageFormatGroup.yuv420); }); test('$CameraImage has ImageFormatGroup.bgra8888 for iOS', () { debugDefaultTargetPlatformOverride = TargetPlatform.iOS; final CameraImage cameraImage = CameraImage.fromPlatformData({ 'format': 1111970369, 'height': 1, 'width': 4, 'lensAperture': 1.8, 'sensorExposureTime': 9991324, 'sensorSensitivity': 92.0, 'planes': [ { 'bytes': Uint8List.fromList([1, 2, 3, 4]), 'bytesPerPixel': 1, 'bytesPerRow': 4, 'height': 1, 'width': 4 } ] }); expect(cameraImage.format.group, ImageFormatGroup.bgra8888); }); test('$CameraImage has ImageFormatGroup.unknown', () { final CameraImage cameraImage = CameraImage.fromPlatformData({ 'format': null, 'height': 1, 'width': 4, 'lensAperture': 1.8, 'sensorExposureTime': 9991324, 'sensorSensitivity': 92.0, 'planes': [ { 'bytes': Uint8List.fromList([1, 2, 3, 4]), 'bytesPerPixel': 1, 'bytesPerRow': 4, 'height': 1, 'width': 4 } ] }); expect(cameraImage.format.group, ImageFormatGroup.unknown); }); }); } ================================================ FILE: packages/camera/camera/test/camera_preview_test.dart ================================================ // Copyright 2013 The Flutter 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/camera.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; class FakeController extends ValueNotifier implements CameraController { FakeController() : super(const CameraValue.uninitialized()); @override Future dispose() async { super.dispose(); } @override Widget buildPreview() { return const Texture(textureId: CameraController.kUninitializedCameraId); } @override int get cameraId => CameraController.kUninitializedCameraId; @override void debugCheckIsDisposed() {} @override CameraDescription get description => const CameraDescription( name: '', lensDirection: CameraLensDirection.back, sensorOrientation: 0); @override bool get enableAudio => false; @override Future getExposureOffsetStepSize() async => 1.0; @override Future getMaxExposureOffset() async => 1.0; @override Future getMaxZoomLevel() async => 1.0; @override Future getMinExposureOffset() async => 1.0; @override Future getMinZoomLevel() async => 1.0; @override ImageFormatGroup? get imageFormatGroup => null; @override Future initialize() async {} @override Future lockCaptureOrientation([DeviceOrientation? orientation]) async {} @override Future pauseVideoRecording() async {} @override Future prepareForVideoRecording() async {} @override ResolutionPreset get resolutionPreset => ResolutionPreset.low; @override Future resumeVideoRecording() async {} @override Future setExposureMode(ExposureMode mode) async {} @override Future setExposureOffset(double offset) async => offset; @override Future setExposurePoint(Offset? point) async {} @override Future setFlashMode(FlashMode mode) async {} @override Future setFocusMode(FocusMode mode) async {} @override Future setFocusPoint(Offset? point) async {} @override Future setZoomLevel(double zoom) async {} @override Future startImageStream(onLatestImageAvailable onAvailable) async {} @override Future startVideoRecording( {onLatestImageAvailable? onAvailable}) async {} @override Future stopImageStream() async {} @override Future stopVideoRecording() async => XFile(''); @override Future takePicture() async => XFile(''); @override Future unlockCaptureOrientation() async {} @override Future pausePreview() async {} @override Future resumePreview() async {} } void main() { group('RotatedBox (Android only)', () { testWidgets( 'when recording rotatedBox should turn according to recording orientation', ( WidgetTester tester, ) async { debugDefaultTargetPlatformOverride = TargetPlatform.android; final FakeController controller = FakeController(); controller.value = controller.value.copyWith( isInitialized: true, isRecordingVideo: true, deviceOrientation: DeviceOrientation.portraitUp, lockedCaptureOrientation: const Optional.fromNullable( DeviceOrientation.landscapeRight), recordingOrientation: const Optional.fromNullable( DeviceOrientation.landscapeLeft), previewSize: const Size(480, 640), ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: CameraPreview(controller), ), ); expect(find.byType(RotatedBox), findsOneWidget); final RotatedBox rotatedBox = tester.widget(find.byType(RotatedBox)); expect(rotatedBox.quarterTurns, 3); debugDefaultTargetPlatformOverride = null; }); testWidgets( 'when orientation locked rotatedBox should turn according to locked orientation', ( WidgetTester tester, ) async { debugDefaultTargetPlatformOverride = TargetPlatform.android; final FakeController controller = FakeController(); controller.value = controller.value.copyWith( isInitialized: true, deviceOrientation: DeviceOrientation.portraitUp, lockedCaptureOrientation: const Optional.fromNullable( DeviceOrientation.landscapeRight), recordingOrientation: const Optional.fromNullable( DeviceOrientation.landscapeLeft), previewSize: const Size(480, 640), ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: CameraPreview(controller), ), ); expect(find.byType(RotatedBox), findsOneWidget); final RotatedBox rotatedBox = tester.widget(find.byType(RotatedBox)); expect(rotatedBox.quarterTurns, 1); debugDefaultTargetPlatformOverride = null; }); testWidgets( 'when not locked and not recording rotatedBox should turn according to device orientation', ( WidgetTester tester, ) async { debugDefaultTargetPlatformOverride = TargetPlatform.android; final FakeController controller = FakeController(); controller.value = controller.value.copyWith( isInitialized: true, deviceOrientation: DeviceOrientation.portraitUp, recordingOrientation: const Optional.fromNullable( DeviceOrientation.landscapeLeft), previewSize: const Size(480, 640), ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: CameraPreview(controller), ), ); expect(find.byType(RotatedBox), findsOneWidget); final RotatedBox rotatedBox = tester.widget(find.byType(RotatedBox)); expect(rotatedBox.quarterTurns, 0); debugDefaultTargetPlatformOverride = null; }); }, skip: kIsWeb); testWidgets('when not on Android there should not be a rotated box', (WidgetTester tester) async { debugDefaultTargetPlatformOverride = TargetPlatform.iOS; final FakeController controller = FakeController(); controller.value = controller.value.copyWith( isInitialized: true, previewSize: const Size(480, 640), ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: CameraPreview(controller), ), ); expect(find.byType(RotatedBox), findsNothing); expect(find.byType(Texture), findsOneWidget); debugDefaultTargetPlatformOverride = null; }); } ================================================ FILE: packages/camera/camera/test/camera_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:math'; import 'package:camera/camera.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; List get mockAvailableCameras => [ const CameraDescription( name: 'camBack', lensDirection: CameraLensDirection.back, sensorOrientation: 90), const CameraDescription( name: 'camFront', lensDirection: CameraLensDirection.front, sensorOrientation: 180), ]; int get mockInitializeCamera => 13; CameraInitializedEvent get mockOnCameraInitializedEvent => const CameraInitializedEvent( 13, 75, 75, ExposureMode.auto, true, FocusMode.auto, true, ); DeviceOrientationChangedEvent get mockOnDeviceOrientationChangedEvent => const DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); CameraClosingEvent get mockOnCameraClosingEvent => const CameraClosingEvent(13); CameraErrorEvent get mockOnCameraErrorEvent => const CameraErrorEvent(13, 'closing'); XFile mockTakePicture = XFile('foo/bar.png'); XFile mockVideoRecordingXFile = XFile('foo/bar.mpeg'); bool mockPlatformException = false; void main() { WidgetsFlutterBinding.ensureInitialized(); group('camera', () { test('debugCheckIsDisposed should not throw assertion error when disposed', () { const MockCameraDescription description = MockCameraDescription(); final CameraController controller = CameraController( description, ResolutionPreset.low, ); controller.dispose(); expect(controller.debugCheckIsDisposed, returnsNormally); }); test('debugCheckIsDisposed should throw assertion error when not disposed', () { const MockCameraDescription description = MockCameraDescription(); final CameraController controller = CameraController( description, ResolutionPreset.low, ); expect( () => controller.debugCheckIsDisposed(), throwsAssertionError, ); }); test('availableCameras() has camera', () async { CameraPlatform.instance = MockCameraPlatform(); final List camList = await availableCameras(); expect(camList, equals(mockAvailableCameras)); }); }); group('$CameraController', () { setUpAll(() { CameraPlatform.instance = MockCameraPlatform(); }); test('Can be initialized', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); expect(cameraController.value.aspectRatio, 1); expect(cameraController.value.previewSize, const Size(75, 75)); expect(cameraController.value.isInitialized, isTrue); }); test('can be disposed', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); expect(cameraController.value.aspectRatio, 1); expect(cameraController.value.previewSize, const Size(75, 75)); expect(cameraController.value.isInitialized, isTrue); await cameraController.dispose(); verify(CameraPlatform.instance.dispose(13)).called(1); }); test('initialize() throws CameraException when disposed', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); expect(cameraController.value.aspectRatio, 1); expect(cameraController.value.previewSize, const Size(75, 75)); expect(cameraController.value.isInitialized, isTrue); await cameraController.dispose(); verify(CameraPlatform.instance.dispose(13)).called(1); expect( cameraController.initialize, throwsA(isA().having( (CameraException error) => error.description, 'Error description', 'initialize was called on a disposed CameraController', ))); }); test('initialize() throws $CameraException on $PlatformException ', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); mockPlatformException = true; expect( cameraController.initialize, throwsA(isA().having( (CameraException error) => error.description, 'foo', 'bar', ))); mockPlatformException = false; }); test('initialize() sets imageFormat', () async { debugDefaultTargetPlatformOverride = TargetPlatform.android; final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max, imageFormatGroup: ImageFormatGroup.yuv420, ); await cameraController.initialize(); verify(CameraPlatform.instance .initializeCamera(13, imageFormatGroup: ImageFormatGroup.yuv420)) .called(1); }); test('prepareForVideoRecording() calls $CameraPlatform ', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); await cameraController.prepareForVideoRecording(); verify(CameraPlatform.instance.prepareForVideoRecording()).called(1); }); test('takePicture() throws $CameraException when uninitialized ', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); expect( cameraController.takePicture(), throwsA( isA() .having( (CameraException error) => error.code, 'code', 'Uninitialized CameraController', ) .having( (CameraException error) => error.description, 'description', 'takePicture() was called on an uninitialized CameraController.', ), ), ); }); test('takePicture() throws $CameraException when takePicture is true', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); cameraController.value = cameraController.value.copyWith(isTakingPicture: true); expect( cameraController.takePicture(), throwsA(isA().having( (CameraException error) => error.description, 'Previous capture has not returned yet.', 'takePicture was called before the previous capture returned.', ))); }); test('takePicture() returns $XFile', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); final XFile xFile = await cameraController.takePicture(); expect(xFile.path, mockTakePicture.path); }); test('takePicture() throws $CameraException on $PlatformException', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); mockPlatformException = true; expect( cameraController.takePicture(), throwsA(isA().having( (CameraException error) => error.description, 'foo', 'bar', ))); mockPlatformException = false; }); test('startVideoRecording() throws $CameraException when uninitialized', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); expect( cameraController.startVideoRecording(), throwsA( isA() .having( (CameraException error) => error.code, 'code', 'Uninitialized CameraController', ) .having( (CameraException error) => error.description, 'description', 'startVideoRecording() was called on an uninitialized CameraController.', ), ), ); }); test('startVideoRecording() throws $CameraException when recording videos', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); cameraController.value = cameraController.value.copyWith(isRecordingVideo: true); expect( cameraController.startVideoRecording(), throwsA(isA().having( (CameraException error) => error.description, 'A video recording is already started.', 'startVideoRecording was called when a recording is already started.', ))); }); test('getMaxZoomLevel() throws $CameraException when uninitialized', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); expect( cameraController.getMaxZoomLevel, throwsA( isA() .having( (CameraException error) => error.code, 'code', 'Uninitialized CameraController', ) .having( (CameraException error) => error.description, 'description', 'getMaxZoomLevel() was called on an uninitialized CameraController.', ), ), ); }); test('getMaxZoomLevel() throws $CameraException when disposed', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); await cameraController.dispose(); expect( cameraController.getMaxZoomLevel, throwsA( isA() .having( (CameraException error) => error.code, 'code', 'Disposed CameraController', ) .having( (CameraException error) => error.description, 'description', 'getMaxZoomLevel() was called on a disposed CameraController.', ), ), ); }); test( 'getMaxZoomLevel() throws $CameraException when a platform exception occured.', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance.getMaxZoomLevel(mockInitializeCamera)) .thenThrow(CameraException( 'TEST_ERROR', 'This is a test error messge', )); expect( cameraController.getMaxZoomLevel, throwsA(isA() .having( (CameraException error) => error.code, 'code', 'TEST_ERROR') .having( (CameraException error) => error.description, 'description', 'This is a test error messge', ))); }); test('getMaxZoomLevel() returns max zoom level.', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance.getMaxZoomLevel(mockInitializeCamera)) .thenAnswer((_) => Future.value(42.0)); final double maxZoomLevel = await cameraController.getMaxZoomLevel(); expect(maxZoomLevel, 42.0); }); test('getMinZoomLevel() throws $CameraException when uninitialized', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); expect( cameraController.getMinZoomLevel, throwsA( isA() .having( (CameraException error) => error.code, 'code', 'Uninitialized CameraController', ) .having( (CameraException error) => error.description, 'description', 'getMinZoomLevel() was called on an uninitialized CameraController.', ), ), ); }); test('getMinZoomLevel() throws $CameraException when disposed', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); await cameraController.dispose(); expect( cameraController.getMinZoomLevel, throwsA( isA() .having( (CameraException error) => error.code, 'code', 'Disposed CameraController', ) .having( (CameraException error) => error.description, 'description', 'getMinZoomLevel() was called on a disposed CameraController.', ), ), ); }); test( 'getMinZoomLevel() throws $CameraException when a platform exception occured.', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance.getMinZoomLevel(mockInitializeCamera)) .thenThrow(CameraException( 'TEST_ERROR', 'This is a test error messge', )); expect( cameraController.getMinZoomLevel, throwsA(isA() .having( (CameraException error) => error.code, 'code', 'TEST_ERROR') .having( (CameraException error) => error.description, 'description', 'This is a test error messge', ))); }); test('getMinZoomLevel() returns max zoom level.', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance.getMinZoomLevel(mockInitializeCamera)) .thenAnswer((_) => Future.value(42.0)); final double maxZoomLevel = await cameraController.getMinZoomLevel(); expect(maxZoomLevel, 42.0); }); test('setZoomLevel() throws $CameraException when uninitialized', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); expect( () => cameraController.setZoomLevel(42.0), throwsA( isA() .having( (CameraException error) => error.code, 'code', 'Uninitialized CameraController', ) .having( (CameraException error) => error.description, 'description', 'setZoomLevel() was called on an uninitialized CameraController.', ), ), ); }); test('setZoomLevel() throws $CameraException when disposed', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); await cameraController.dispose(); expect( () => cameraController.setZoomLevel(42.0), throwsA( isA() .having( (CameraException error) => error.code, 'code', 'Disposed CameraController', ) .having( (CameraException error) => error.description, 'description', 'setZoomLevel() was called on a disposed CameraController.', ), ), ); }); test( 'setZoomLevel() throws $CameraException when a platform exception occured.', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance.setZoomLevel(mockInitializeCamera, 42.0)) .thenThrow(CameraException( 'TEST_ERROR', 'This is a test error messge', )); expect( () => cameraController.setZoomLevel(42), throwsA(isA() .having( (CameraException error) => error.code, 'code', 'TEST_ERROR') .having( (CameraException error) => error.description, 'description', 'This is a test error messge', ))); reset(CameraPlatform.instance); }); test( 'setZoomLevel() completes and calls method channel with correct value.', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); await cameraController.setZoomLevel(42.0); verify(CameraPlatform.instance.setZoomLevel(mockInitializeCamera, 42.0)) .called(1); }); test('setFlashMode() calls $CameraPlatform', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); await cameraController.setFlashMode(FlashMode.always); verify(CameraPlatform.instance .setFlashMode(cameraController.cameraId, FlashMode.always)) .called(1); }); test('setFlashMode() throws $CameraException on $PlatformException', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance .setFlashMode(cameraController.cameraId, FlashMode.always)) .thenThrow( PlatformException( code: 'TEST_ERROR', message: 'This is a test error message', ), ); expect( cameraController.setFlashMode(FlashMode.always), throwsA(isA().having( (CameraException error) => error.description, 'TEST_ERROR', 'This is a test error message', ))); }); test('setExposureMode() calls $CameraPlatform', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); await cameraController.setExposureMode(ExposureMode.auto); verify(CameraPlatform.instance .setExposureMode(cameraController.cameraId, ExposureMode.auto)) .called(1); }); test('setExposureMode() throws $CameraException on $PlatformException', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance .setExposureMode(cameraController.cameraId, ExposureMode.auto)) .thenThrow( PlatformException( code: 'TEST_ERROR', message: 'This is a test error message', ), ); expect( cameraController.setExposureMode(ExposureMode.auto), throwsA(isA().having( (CameraException error) => error.description, 'TEST_ERROR', 'This is a test error message', ))); }); test('setExposurePoint() calls $CameraPlatform', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); await cameraController.setExposurePoint(const Offset(0.5, 0.5)); verify(CameraPlatform.instance.setExposurePoint( cameraController.cameraId, const Point(0.5, 0.5))) .called(1); }); test('setExposurePoint() throws $CameraException on $PlatformException', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance.setExposurePoint( cameraController.cameraId, const Point(0.5, 0.5))) .thenThrow( PlatformException( code: 'TEST_ERROR', message: 'This is a test error message', ), ); expect( cameraController.setExposurePoint(const Offset(0.5, 0.5)), throwsA(isA().having( (CameraException error) => error.description, 'TEST_ERROR', 'This is a test error message', ))); }); test('getMinExposureOffset() calls $CameraPlatform', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance .getMinExposureOffset(cameraController.cameraId)) .thenAnswer((_) => Future.value(0.0)); await cameraController.getMinExposureOffset(); verify(CameraPlatform.instance .getMinExposureOffset(cameraController.cameraId)) .called(1); }); test('getMinExposureOffset() throws $CameraException on $PlatformException', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance .getMinExposureOffset(cameraController.cameraId)) .thenThrow( CameraException( 'TEST_ERROR', 'This is a test error message', ), ); expect( cameraController.getMinExposureOffset(), throwsA(isA().having( (CameraException error) => error.description, 'TEST_ERROR', 'This is a test error message', ))); }); test('getMaxExposureOffset() calls $CameraPlatform', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance .getMaxExposureOffset(cameraController.cameraId)) .thenAnswer((_) => Future.value(1.0)); await cameraController.getMaxExposureOffset(); verify(CameraPlatform.instance .getMaxExposureOffset(cameraController.cameraId)) .called(1); }); test('getMaxExposureOffset() throws $CameraException on $PlatformException', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance .getMaxExposureOffset(cameraController.cameraId)) .thenThrow( CameraException( 'TEST_ERROR', 'This is a test error message', ), ); expect( cameraController.getMaxExposureOffset(), throwsA(isA().having( (CameraException error) => error.description, 'TEST_ERROR', 'This is a test error message', ))); }); test('getExposureOffsetStepSize() calls $CameraPlatform', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance .getExposureOffsetStepSize(cameraController.cameraId)) .thenAnswer((_) => Future.value(0.0)); await cameraController.getExposureOffsetStepSize(); verify(CameraPlatform.instance .getExposureOffsetStepSize(cameraController.cameraId)) .called(1); }); test( 'getExposureOffsetStepSize() throws $CameraException on $PlatformException', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance .getExposureOffsetStepSize(cameraController.cameraId)) .thenThrow( CameraException( 'TEST_ERROR', 'This is a test error message', ), ); expect( cameraController.getExposureOffsetStepSize(), throwsA(isA().having( (CameraException error) => error.description, 'TEST_ERROR', 'This is a test error message', ))); }); test('setExposureOffset() calls $CameraPlatform', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance .getMinExposureOffset(cameraController.cameraId)) .thenAnswer((_) async => -1.0); when(CameraPlatform.instance .getMaxExposureOffset(cameraController.cameraId)) .thenAnswer((_) async => 2.0); when(CameraPlatform.instance .getExposureOffsetStepSize(cameraController.cameraId)) .thenAnswer((_) async => 1.0); when(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 1.0)) .thenAnswer((_) async => 1.0); await cameraController.setExposureOffset(1.0); verify(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 1.0)) .called(1); }); test('setExposureOffset() throws $CameraException on $PlatformException', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance .getMinExposureOffset(cameraController.cameraId)) .thenAnswer((_) async => -1.0); when(CameraPlatform.instance .getMaxExposureOffset(cameraController.cameraId)) .thenAnswer((_) async => 2.0); when(CameraPlatform.instance .getExposureOffsetStepSize(cameraController.cameraId)) .thenAnswer((_) async => 1.0); when(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 1.0)) .thenThrow( CameraException( 'TEST_ERROR', 'This is a test error message', ), ); expect( cameraController.setExposureOffset(1.0), throwsA(isA().having( (CameraException error) => error.description, 'TEST_ERROR', 'This is a test error message', ))); }); test( 'setExposureOffset() throws $CameraException when offset is out of bounds', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance .getMinExposureOffset(cameraController.cameraId)) .thenAnswer((_) async => -1.0); when(CameraPlatform.instance .getMaxExposureOffset(cameraController.cameraId)) .thenAnswer((_) async => 2.0); when(CameraPlatform.instance .getExposureOffsetStepSize(cameraController.cameraId)) .thenAnswer((_) async => 1.0); when(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 0.0)) .thenAnswer((_) async => 0.0); when(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, -1.0)) .thenAnswer((_) async => 0.0); when(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 2.0)) .thenAnswer((_) async => 0.0); expect( cameraController.setExposureOffset(3.0), throwsA(isA().having( (CameraException error) => error.description, 'exposureOffsetOutOfBounds', 'The provided exposure offset was outside the supported range for this device.', ))); expect( cameraController.setExposureOffset(-2.0), throwsA(isA().having( (CameraException error) => error.description, 'exposureOffsetOutOfBounds', 'The provided exposure offset was outside the supported range for this device.', ))); await cameraController.setExposureOffset(0.0); await cameraController.setExposureOffset(-1.0); await cameraController.setExposureOffset(2.0); verify(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 0.0)) .called(1); verify(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, -1.0)) .called(1); verify(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 2.0)) .called(1); }); test('setExposureOffset() rounds offset to nearest step', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance .getMinExposureOffset(cameraController.cameraId)) .thenAnswer((_) async => -1.2); when(CameraPlatform.instance .getMaxExposureOffset(cameraController.cameraId)) .thenAnswer((_) async => 1.2); when(CameraPlatform.instance .getExposureOffsetStepSize(cameraController.cameraId)) .thenAnswer((_) async => 0.4); when(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, -1.2)) .thenAnswer((_) async => -1.2); when(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, -0.8)) .thenAnswer((_) async => -0.8); when(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, -0.4)) .thenAnswer((_) async => -0.4); when(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 0.0)) .thenAnswer((_) async => 0.0); when(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 0.4)) .thenAnswer((_) async => 0.4); when(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 0.8)) .thenAnswer((_) async => 0.8); when(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 1.2)) .thenAnswer((_) async => 1.2); await cameraController.setExposureOffset(1.2); await cameraController.setExposureOffset(-1.2); await cameraController.setExposureOffset(0.1); await cameraController.setExposureOffset(0.2); await cameraController.setExposureOffset(0.3); await cameraController.setExposureOffset(0.4); await cameraController.setExposureOffset(0.5); await cameraController.setExposureOffset(0.6); await cameraController.setExposureOffset(0.7); await cameraController.setExposureOffset(-0.1); await cameraController.setExposureOffset(-0.2); await cameraController.setExposureOffset(-0.3); await cameraController.setExposureOffset(-0.4); await cameraController.setExposureOffset(-0.5); await cameraController.setExposureOffset(-0.6); await cameraController.setExposureOffset(-0.7); verify(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 0.8)) .called(2); verify(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, -0.8)) .called(2); verify(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 0.0)) .called(2); verify(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, 0.4)) .called(4); verify(CameraPlatform.instance .setExposureOffset(cameraController.cameraId, -0.4)) .called(4); }); test('pausePreview() calls $CameraPlatform', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); cameraController.value = cameraController.value .copyWith(deviceOrientation: DeviceOrientation.portraitUp); await cameraController.pausePreview(); verify(CameraPlatform.instance.pausePreview(cameraController.cameraId)) .called(1); expect(cameraController.value.isPreviewPaused, equals(true)); expect(cameraController.value.previewPauseOrientation, DeviceOrientation.portraitUp); }); test('pausePreview() does not call $CameraPlatform when already paused', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); cameraController.value = cameraController.value.copyWith(isPreviewPaused: true); await cameraController.pausePreview(); verifyNever( CameraPlatform.instance.pausePreview(cameraController.cameraId)); expect(cameraController.value.isPreviewPaused, equals(true)); }); test( 'pausePreview() sets previewPauseOrientation according to locked orientation', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); cameraController.value = cameraController.value.copyWith( isPreviewPaused: false, deviceOrientation: DeviceOrientation.portraitUp, lockedCaptureOrientation: Optional.of(DeviceOrientation.landscapeRight)); await cameraController.pausePreview(); expect(cameraController.value.deviceOrientation, equals(DeviceOrientation.portraitUp)); expect(cameraController.value.previewPauseOrientation, equals(DeviceOrientation.landscapeRight)); }); test('pausePreview() throws $CameraException on $PlatformException', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance.pausePreview(cameraController.cameraId)) .thenThrow( PlatformException( code: 'TEST_ERROR', message: 'This is a test error message', ), ); expect( cameraController.pausePreview(), throwsA(isA().having( (CameraException error) => error.description, 'TEST_ERROR', 'This is a test error message', ))); }); test('resumePreview() calls $CameraPlatform', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); cameraController.value = cameraController.value.copyWith(isPreviewPaused: true); await cameraController.resumePreview(); verify(CameraPlatform.instance.resumePreview(cameraController.cameraId)) .called(1); expect(cameraController.value.isPreviewPaused, equals(false)); }); test('resumePreview() does not call $CameraPlatform when not paused', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); cameraController.value = cameraController.value.copyWith(isPreviewPaused: false); await cameraController.resumePreview(); verifyNever( CameraPlatform.instance.resumePreview(cameraController.cameraId)); expect(cameraController.value.isPreviewPaused, equals(false)); }); test('resumePreview() throws $CameraException on $PlatformException', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); cameraController.value = cameraController.value.copyWith(isPreviewPaused: true); when(CameraPlatform.instance.resumePreview(cameraController.cameraId)) .thenThrow( PlatformException( code: 'TEST_ERROR', message: 'This is a test error message', ), ); expect( cameraController.resumePreview(), throwsA(isA().having( (CameraException error) => error.description, 'TEST_ERROR', 'This is a test error message', ))); }); test('lockCaptureOrientation() calls $CameraPlatform', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); await cameraController.lockCaptureOrientation(); expect(cameraController.value.lockedCaptureOrientation, equals(DeviceOrientation.portraitUp)); await cameraController .lockCaptureOrientation(DeviceOrientation.landscapeRight); expect(cameraController.value.lockedCaptureOrientation, equals(DeviceOrientation.landscapeRight)); verify(CameraPlatform.instance.lockCaptureOrientation( cameraController.cameraId, DeviceOrientation.portraitUp)) .called(1); verify(CameraPlatform.instance.lockCaptureOrientation( cameraController.cameraId, DeviceOrientation.landscapeRight)) .called(1); }); test( 'lockCaptureOrientation() throws $CameraException on $PlatformException', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance.lockCaptureOrientation( cameraController.cameraId, DeviceOrientation.portraitUp)) .thenThrow( PlatformException( code: 'TEST_ERROR', message: 'This is a test error message', ), ); expect( cameraController.lockCaptureOrientation(DeviceOrientation.portraitUp), throwsA(isA().having( (CameraException error) => error.description, 'TEST_ERROR', 'This is a test error message', ))); }); test('unlockCaptureOrientation() calls $CameraPlatform', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); await cameraController.unlockCaptureOrientation(); expect(cameraController.value.lockedCaptureOrientation, equals(null)); verify(CameraPlatform.instance .unlockCaptureOrientation(cameraController.cameraId)) .called(1); }); test( 'unlockCaptureOrientation() throws $CameraException on $PlatformException', () async { final CameraController cameraController = CameraController( const CameraDescription( name: 'cam', lensDirection: CameraLensDirection.back, sensorOrientation: 90), ResolutionPreset.max); await cameraController.initialize(); when(CameraPlatform.instance .unlockCaptureOrientation(cameraController.cameraId)) .thenThrow( PlatformException( code: 'TEST_ERROR', message: 'This is a test error message', ), ); expect( cameraController.unlockCaptureOrientation(), throwsA(isA().having( (CameraException error) => error.description, 'TEST_ERROR', 'This is a test error message', ))); }); }); } class MockCameraPlatform extends Mock with MockPlatformInterfaceMixin implements CameraPlatform { @override Future initializeCamera( int? cameraId, { ImageFormatGroup? imageFormatGroup = ImageFormatGroup.unknown, }) async => super.noSuchMethod(Invocation.method( #initializeCamera, [cameraId], { #imageFormatGroup: imageFormatGroup, }, )); @override Future dispose(int? cameraId) async { return super.noSuchMethod(Invocation.method(#dispose, [cameraId])); } @override Future> availableCameras() => Future>.value(mockAvailableCameras); @override Future createCamera( CameraDescription description, ResolutionPreset? resolutionPreset, { bool enableAudio = false, }) => mockPlatformException ? throw PlatformException(code: 'foo', message: 'bar') : Future.value(mockInitializeCamera); @override Stream onCameraInitialized(int cameraId) => Stream.value(mockOnCameraInitializedEvent); @override Stream onCameraClosing(int cameraId) => Stream.value(mockOnCameraClosingEvent); @override Stream onCameraError(int cameraId) => Stream.value(mockOnCameraErrorEvent); @override Stream onDeviceOrientationChanged() => Stream.value( mockOnDeviceOrientationChangedEvent); @override Future takePicture(int cameraId) => mockPlatformException ? throw PlatformException(code: 'foo', message: 'bar') : Future.value(mockTakePicture); @override Future prepareForVideoRecording() async => super.noSuchMethod(Invocation.method(#prepareForVideoRecording, null)); @override Future startVideoRecording(int cameraId, {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 => super.noSuchMethod(Invocation.method( #lockCaptureOrientation, [cameraId, orientation])); @override Future unlockCaptureOrientation(int? cameraId) async => super.noSuchMethod( Invocation.method(#unlockCaptureOrientation, [cameraId])); @override Future pausePreview(int? cameraId) async => super.noSuchMethod(Invocation.method(#pausePreview, [cameraId])); @override Future resumePreview(int? cameraId) async => super .noSuchMethod(Invocation.method(#resumePreview, [cameraId])); @override Future getMaxZoomLevel(int? cameraId) async => super.noSuchMethod( Invocation.method(#getMaxZoomLevel, [cameraId]), returnValue: Future.value(1.0), ) as Future; @override Future getMinZoomLevel(int? cameraId) async => super.noSuchMethod( Invocation.method(#getMinZoomLevel, [cameraId]), returnValue: Future.value(0.0), ) as Future; @override Future setZoomLevel(int? cameraId, double? zoom) async => super.noSuchMethod( Invocation.method(#setZoomLevel, [cameraId, zoom])); @override Future setFlashMode(int? cameraId, FlashMode? mode) async => super.noSuchMethod( Invocation.method(#setFlashMode, [cameraId, mode])); @override Future setExposureMode(int? cameraId, ExposureMode? mode) async => super.noSuchMethod( Invocation.method(#setExposureMode, [cameraId, mode])); @override Future setExposurePoint(int? cameraId, Point? point) async => super.noSuchMethod( Invocation.method(#setExposurePoint, [cameraId, point])); @override Future getMinExposureOffset(int? cameraId) async => super.noSuchMethod( Invocation.method(#getMinExposureOffset, [cameraId]), returnValue: Future.value(0.0), ) as Future; @override Future getMaxExposureOffset(int? cameraId) async => super.noSuchMethod( Invocation.method(#getMaxExposureOffset, [cameraId]), returnValue: Future.value(1.0), ) as Future; @override Future getExposureOffsetStepSize(int? cameraId) async => super.noSuchMethod( Invocation.method(#getExposureOffsetStepSize, [cameraId]), returnValue: Future.value(1.0), ) as Future; @override Future setExposureOffset(int? cameraId, double? offset) async => super.noSuchMethod( Invocation.method(#setExposureOffset, [cameraId, offset]), returnValue: Future.value(1.0), ) as Future; } class MockCameraDescription extends CameraDescription { /// Creates a new camera description with the given properties. const MockCameraDescription() : super( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ); @override CameraLensDirection get lensDirection => CameraLensDirection.back; @override String get name => 'back'; } ================================================ FILE: packages/camera/camera/test/camera_value_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#106316) // ignore: unnecessary_import import 'dart:ui'; import 'package:camera/camera.dart'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#106316) // ignore: unnecessary_import import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('camera_value', () { test('Can be created', () { const CameraValue cameraValue = CameraValue( isInitialized: false, previewSize: Size(10, 10), isRecordingPaused: false, isRecordingVideo: false, isTakingPicture: false, isStreamingImages: false, flashMode: FlashMode.auto, exposureMode: ExposureMode.auto, exposurePointSupported: true, focusMode: FocusMode.auto, deviceOrientation: DeviceOrientation.portraitUp, lockedCaptureOrientation: DeviceOrientation.portraitUp, recordingOrientation: DeviceOrientation.portraitUp, focusPointSupported: true, previewPauseOrientation: DeviceOrientation.portraitUp, ); expect(cameraValue, isA()); expect(cameraValue.isInitialized, isFalse); expect(cameraValue.errorDescription, null); expect(cameraValue.previewSize, const Size(10, 10)); expect(cameraValue.isRecordingPaused, isFalse); expect(cameraValue.isRecordingVideo, isFalse); expect(cameraValue.isTakingPicture, isFalse); expect(cameraValue.isStreamingImages, isFalse); expect(cameraValue.flashMode, FlashMode.auto); expect(cameraValue.exposureMode, ExposureMode.auto); expect(cameraValue.exposurePointSupported, true); expect(cameraValue.deviceOrientation, DeviceOrientation.portraitUp); expect( cameraValue.lockedCaptureOrientation, DeviceOrientation.portraitUp); expect(cameraValue.recordingOrientation, DeviceOrientation.portraitUp); expect(cameraValue.isPreviewPaused, false); expect(cameraValue.previewPauseOrientation, DeviceOrientation.portraitUp); }); test('Can be created as uninitialized', () { const CameraValue cameraValue = CameraValue.uninitialized(); expect(cameraValue, isA()); expect(cameraValue.isInitialized, isFalse); expect(cameraValue.errorDescription, null); expect(cameraValue.previewSize, null); expect(cameraValue.isRecordingPaused, isFalse); expect(cameraValue.isRecordingVideo, isFalse); expect(cameraValue.isTakingPicture, isFalse); expect(cameraValue.isStreamingImages, isFalse); expect(cameraValue.flashMode, FlashMode.auto); expect(cameraValue.exposureMode, ExposureMode.auto); expect(cameraValue.exposurePointSupported, false); expect(cameraValue.focusMode, FocusMode.auto); expect(cameraValue.deviceOrientation, DeviceOrientation.portraitUp); expect(cameraValue.lockedCaptureOrientation, null); expect(cameraValue.recordingOrientation, null); expect(cameraValue.isPreviewPaused, isFalse); expect(cameraValue.previewPauseOrientation, null); }); test('Can be copied with isInitialized', () { const CameraValue cv = CameraValue.uninitialized(); final CameraValue cameraValue = cv.copyWith(isInitialized: true); expect(cameraValue, isA()); expect(cameraValue.isInitialized, isTrue); expect(cameraValue.errorDescription, null); expect(cameraValue.previewSize, null); expect(cameraValue.isRecordingPaused, isFalse); expect(cameraValue.isRecordingVideo, isFalse); expect(cameraValue.isTakingPicture, isFalse); expect(cameraValue.isStreamingImages, isFalse); expect(cameraValue.flashMode, FlashMode.auto); expect(cameraValue.focusMode, FocusMode.auto); expect(cameraValue.exposureMode, ExposureMode.auto); expect(cameraValue.exposurePointSupported, false); expect(cameraValue.deviceOrientation, DeviceOrientation.portraitUp); expect(cameraValue.lockedCaptureOrientation, null); expect(cameraValue.recordingOrientation, null); expect(cameraValue.isPreviewPaused, isFalse); expect(cameraValue.previewPauseOrientation, null); }); test('Has aspectRatio after setting size', () { const CameraValue cv = CameraValue.uninitialized(); final CameraValue cameraValue = cv.copyWith(isInitialized: true, previewSize: const Size(20, 10)); expect(cameraValue.aspectRatio, 2.0); }); test('hasError is true after setting errorDescription', () { const CameraValue cv = CameraValue.uninitialized(); final CameraValue cameraValue = cv.copyWith(errorDescription: 'error'); expect(cameraValue.hasError, isTrue); expect(cameraValue.errorDescription, 'error'); }); test('Recording paused is false when not recording', () { const CameraValue cv = CameraValue.uninitialized(); final CameraValue cameraValue = cv.copyWith( isInitialized: true, isRecordingVideo: false, isRecordingPaused: true); expect(cameraValue.isRecordingPaused, isFalse); }); test('toString() works as expected', () { const CameraValue cameraValue = CameraValue( isInitialized: false, previewSize: Size(10, 10), isRecordingPaused: false, isRecordingVideo: false, isTakingPicture: false, isStreamingImages: false, flashMode: FlashMode.auto, exposureMode: ExposureMode.auto, focusMode: FocusMode.auto, exposurePointSupported: true, focusPointSupported: true, deviceOrientation: DeviceOrientation.portraitUp, lockedCaptureOrientation: DeviceOrientation.portraitUp, recordingOrientation: DeviceOrientation.portraitUp, isPreviewPaused: true, previewPauseOrientation: DeviceOrientation.portraitUp); expect(cameraValue.toString(), 'CameraValue(isRecordingVideo: false, isInitialized: false, errorDescription: null, previewSize: Size(10.0, 10.0), isStreamingImages: false, flashMode: FlashMode.auto, exposureMode: ExposureMode.auto, focusMode: FocusMode.auto, exposurePointSupported: true, focusPointSupported: true, deviceOrientation: DeviceOrientation.portraitUp, lockedCaptureOrientation: DeviceOrientation.portraitUp, recordingOrientation: DeviceOrientation.portraitUp, isPreviewPaused: true, previewPausedOrientation: DeviceOrientation.portraitUp)'); }); }); } ================================================ FILE: packages/camera/camera_android/AUTHORS ================================================ # 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> ================================================ FILE: packages/camera/camera_android/CHANGELOG.md ================================================ ## 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. ## 0.10.1 * Implements an option to also stream when recording a video. ## 0.10.0+5 * Fixes `ArrayIndexOutOfBoundsException` when the permission request is interrupted. ## 0.10.0+4 * Upgrades `androidx.annotation` version to 1.5.0. ## 0.10.0+3 * Updates code for `no_leading_underscores_for_local_identifiers` lint. ## 0.10.0+2 * Removes call to `join` on the camera's background `HandlerThread`. * Updates minimum Flutter version to 2.10. ## 0.10.0+1 * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 0.10.0 * **Breaking Change** Updates Android camera access permission error codes to be consistent with other platforms. If your app still handles the legacy `cameraPermission` exception, please update it to handle the new permission exception codes that are noted in the README. * Ignores missing return warnings in preparation for [upcoming analysis changes](https://github.com/flutter/flutter/issues/105750). ## 0.9.8+3 * Skips duplicate calls to stop background thread and removes unnecessary closings of camera capture sessions on Android. ## 0.9.8+2 * Fixes exception in registerWith caused by the switch to an in-package method channel. ## 0.9.8+1 * Ignores deprecation warnings for upcoming styleFrom button API changes. ## 0.9.8 * Switches to internal method channel implementation. ## 0.9.7+1 * Splits from `camera` as a federated implementation. ================================================ FILE: packages/camera/camera_android/LICENSE ================================================ 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. ================================================ FILE: packages/camera/camera_android/README.md ================================================ # camera\_android The Android implementation of [`camera`][1]. ## Usage This package is [endorsed][2], which means you can simply use `camera` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/camera [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/camera/camera_android/android/build.gradle ================================================ group 'io.flutter.plugins.camera' version '1.0-SNAPSHOT' def args = ["-Xlint:deprecation","-Xlint:unchecked"] buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.2' } } rootProject.allprojects { repositories { google() mavenCentral() } } project.getTasks().withType(JavaCompile){ options.compilerArgs.addAll(args) } apply plugin: 'com.android.library' android { compileSdkVersion 31 defaultConfig { targetSdkVersion 31 minSdkVersion 21 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' baseline file("lint-baseline.xml") } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } testOptions { unitTests.includeAndroidResources = true unitTests.returnDefaultValues = true unitTests.all { testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" outputs.upToDateWhen {false} showStandardStreams = true } } } } dependencies { implementation 'androidx.annotation:annotation:1.5.0' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-inline:5.0.0' testImplementation 'androidx.test:core:1.4.0' testImplementation 'org.robolectric:robolectric:4.5' } ================================================ FILE: packages/camera/camera_android/android/lint-baseline.xml ================================================ ================================================ FILE: packages/camera/camera_android/android/settings.gradle ================================================ rootProject.name = 'camera_android' ================================================ FILE: packages/camera/camera_android/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/Camera.java ================================================ // Copyright 2013 The Flutter 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.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.graphics.ImageFormat; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.params.SessionConfiguration; import android.media.CamcorderProfile; import android.media.EncoderProfiles; import android.media.Image; import android.media.ImageReader; import android.media.MediaRecorder; import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.util.Log; import android.util.Size; import android.view.Display; import android.view.Surface; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.plugin.common.EventChannel; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugins.camera.features.CameraFeature; import io.flutter.plugins.camera.features.CameraFeatureFactory; import io.flutter.plugins.camera.features.CameraFeatures; import io.flutter.plugins.camera.features.Point; import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature; import io.flutter.plugins.camera.features.autofocus.FocusMode; import io.flutter.plugins.camera.features.exposurelock.ExposureLockFeature; import io.flutter.plugins.camera.features.exposurelock.ExposureMode; import io.flutter.plugins.camera.features.exposureoffset.ExposureOffsetFeature; import io.flutter.plugins.camera.features.exposurepoint.ExposurePointFeature; import io.flutter.plugins.camera.features.flash.FlashFeature; import io.flutter.plugins.camera.features.flash.FlashMode; import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature; import io.flutter.plugins.camera.features.resolution.ResolutionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionPreset; import io.flutter.plugins.camera.features.sensororientation.DeviceOrientationManager; import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature; import io.flutter.plugins.camera.features.zoomlevel.ZoomLevelFeature; import io.flutter.plugins.camera.media.MediaRecorderBuilder; import io.flutter.plugins.camera.types.CameraCaptureProperties; import io.flutter.plugins.camera.types.CaptureTimeoutsWrapper; import io.flutter.view.TextureRegistry.SurfaceTextureEntry; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.Executors; @FunctionalInterface interface ErrorCallback { void onError(String errorCode, String errorMessage); } /** A mockable wrapper for CameraDevice calls. */ interface CameraDeviceWrapper { @NonNull CaptureRequest.Builder createCaptureRequest(int templateType) throws CameraAccessException; @TargetApi(VERSION_CODES.P) void createCaptureSession(SessionConfiguration config) throws CameraAccessException; @TargetApi(VERSION_CODES.LOLLIPOP) void createCaptureSession( @NonNull List outputs, @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler) throws CameraAccessException; void close(); } class Camera implements CameraCaptureCallback.CameraCaptureStateListener, ImageReader.OnImageAvailableListener { private static final String TAG = "Camera"; private static final HashMap supportedImageFormats; // Current supported outputs. static { supportedImageFormats = new HashMap<>(); supportedImageFormats.put("yuv420", ImageFormat.YUV_420_888); supportedImageFormats.put("jpeg", ImageFormat.JPEG); } /** * Holds all of the camera features/settings and will be used to update the request builder when * one changes. */ private final CameraFeatures cameraFeatures; private final SurfaceTextureEntry flutterTexture; private final boolean enableAudio; private final Context applicationContext; private final DartMessenger dartMessenger; private final CameraProperties cameraProperties; private final CameraFeatureFactory cameraFeatureFactory; private final Activity activity; /** A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture. */ private final CameraCaptureCallback cameraCaptureCallback; /** A {@link Handler} for running tasks in the background. */ private Handler backgroundHandler; /** An additional thread for running tasks that shouldn't block the UI. */ private HandlerThread backgroundHandlerThread; private CameraDeviceWrapper cameraDevice; private CameraCaptureSession captureSession; private ImageReader pictureImageReader; private ImageReader imageStreamReader; /** {@link CaptureRequest.Builder} for the camera preview */ private CaptureRequest.Builder previewRequestBuilder; private MediaRecorder mediaRecorder; /** True when recording video. */ private boolean recordingVideo; /** True when the preview is paused. */ private boolean pausedPreview; private File captureFile; /** Holds the current capture timeouts */ private CaptureTimeoutsWrapper captureTimeouts; /** Holds the last known capture properties */ private CameraCaptureProperties captureProps; private MethodChannel.Result flutterResult; /** A CameraDeviceWrapper implementation that forwards calls to a CameraDevice. */ private class DefaultCameraDeviceWrapper implements CameraDeviceWrapper { private final CameraDevice cameraDevice; private DefaultCameraDeviceWrapper(CameraDevice cameraDevice) { this.cameraDevice = cameraDevice; } @NonNull @Override public CaptureRequest.Builder createCaptureRequest(int templateType) throws CameraAccessException { return cameraDevice.createCaptureRequest(templateType); } @TargetApi(VERSION_CODES.P) @Override public void createCaptureSession(SessionConfiguration config) throws CameraAccessException { cameraDevice.createCaptureSession(config); } @TargetApi(VERSION_CODES.LOLLIPOP) @SuppressWarnings("deprecation") @Override public void createCaptureSession( @NonNull List outputs, @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler) throws CameraAccessException { cameraDevice.createCaptureSession(outputs, callback, backgroundHandler); } @Override public void close() { cameraDevice.close(); } } public Camera( final Activity activity, final SurfaceTextureEntry flutterTexture, final CameraFeatureFactory cameraFeatureFactory, final DartMessenger dartMessenger, final CameraProperties cameraProperties, final ResolutionPreset resolutionPreset, final boolean enableAudio) { if (activity == null) { throw new IllegalStateException("No activity available!"); } this.activity = activity; this.enableAudio = enableAudio; this.flutterTexture = flutterTexture; this.dartMessenger = dartMessenger; this.applicationContext = activity.getApplicationContext(); this.cameraProperties = cameraProperties; this.cameraFeatureFactory = cameraFeatureFactory; this.cameraFeatures = CameraFeatures.init( cameraFeatureFactory, cameraProperties, activity, dartMessenger, resolutionPreset); // Create capture callback. captureTimeouts = new CaptureTimeoutsWrapper(3000, 3000); captureProps = new CameraCaptureProperties(); cameraCaptureCallback = CameraCaptureCallback.create(this, captureTimeouts, captureProps); startBackgroundThread(); } @Override public void onConverged() { takePictureAfterPrecapture(); } @Override public void onPrecapture() { runPrecaptureSequence(); } /** * Updates the builder settings with all of the available features. * * @param requestBuilder request builder to update. */ private void updateBuilderSettings(CaptureRequest.Builder requestBuilder) { for (CameraFeature feature : cameraFeatures.getAllFeatures()) { Log.d(TAG, "Updating builder with feature: " + feature.getDebugName()); feature.updateBuilder(requestBuilder); } } private void prepareMediaRecorder(String outputFilePath) throws IOException { Log.i(TAG, "prepareMediaRecorder"); if (mediaRecorder != null) { mediaRecorder.release(); } final PlatformChannel.DeviceOrientation lockedOrientation = ((SensorOrientationFeature) cameraFeatures.getSensorOrientation()) .getLockedCaptureOrientation(); MediaRecorderBuilder mediaRecorderBuilder; // 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); } mediaRecorder = mediaRecorderBuilder .setEnableAudio(enableAudio) .setMediaOrientation( lockedOrientation == null ? getDeviceOrientationManager().getVideoOrientation() : getDeviceOrientationManager().getVideoOrientation(lockedOrientation)) .build(); } @SuppressLint("MissingPermission") public void open(String imageFormatGroup) throws CameraAccessException { final ResolutionFeature resolutionFeature = cameraFeatures.getResolution(); if (!resolutionFeature.checkIsSupported()) { // Tell the user that the camera they are trying to open is not supported, // as its {@link android.media.CamcorderProfile} cannot be fetched due to the name // not being a valid parsable integer. dartMessenger.sendCameraErrorEvent( "Camera with name \"" + cameraProperties.getCameraName() + "\" is not supported by this plugin."); return; } // Always capture using JPEG format. pictureImageReader = ImageReader.newInstance( resolutionFeature.getCaptureSize().getWidth(), resolutionFeature.getCaptureSize().getHeight(), ImageFormat.JPEG, 1); // For image streaming, use the provided image format or fall back to YUV420. Integer imageFormat = supportedImageFormats.get(imageFormatGroup); if (imageFormat == null) { Log.w(TAG, "The selected imageFormatGroup is not supported by Android. Defaulting to yuv420"); imageFormat = ImageFormat.YUV_420_888; } imageStreamReader = ImageReader.newInstance( resolutionFeature.getPreviewSize().getWidth(), resolutionFeature.getPreviewSize().getHeight(), imageFormat, 1); // Open the camera. CameraManager cameraManager = CameraUtils.getCameraManager(activity); cameraManager.openCamera( cameraProperties.getCameraName(), new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice device) { cameraDevice = new DefaultCameraDeviceWrapper(device); try { startPreview(); dartMessenger.sendCameraInitializedEvent( resolutionFeature.getPreviewSize().getWidth(), resolutionFeature.getPreviewSize().getHeight(), cameraFeatures.getExposureLock().getValue(), cameraFeatures.getAutoFocus().getValue(), cameraFeatures.getExposurePoint().checkIsSupported(), cameraFeatures.getFocusPoint().checkIsSupported()); } catch (CameraAccessException e) { dartMessenger.sendCameraErrorEvent(e.getMessage()); close(); } } @Override public void onClosed(@NonNull CameraDevice camera) { Log.i(TAG, "open | onClosed"); // Prevents calls to methods that would otherwise result in IllegalStateException exceptions. cameraDevice = null; closeCaptureSession(); dartMessenger.sendCameraClosingEvent(); } @Override public void onDisconnected(@NonNull CameraDevice cameraDevice) { Log.i(TAG, "open | onDisconnected"); close(); dartMessenger.sendCameraErrorEvent("The camera was disconnected."); } @Override public void onError(@NonNull CameraDevice cameraDevice, int errorCode) { Log.i(TAG, "open | onError"); close(); String errorDescription; switch (errorCode) { case ERROR_CAMERA_IN_USE: errorDescription = "The camera device is in use already."; break; case ERROR_MAX_CAMERAS_IN_USE: errorDescription = "Max cameras in use"; break; case ERROR_CAMERA_DISABLED: errorDescription = "The camera device could not be opened due to a device policy."; break; case ERROR_CAMERA_DEVICE: errorDescription = "The camera device has encountered a fatal error"; break; case ERROR_CAMERA_SERVICE: errorDescription = "The camera service has encountered a fatal error."; break; default: errorDescription = "Unknown camera error"; } dartMessenger.sendCameraErrorEvent(errorDescription); } }, backgroundHandler); } @VisibleForTesting void createCaptureSession(int templateType, Surface... surfaces) throws CameraAccessException { createCaptureSession(templateType, null, surfaces); } private void createCaptureSession( int templateType, Runnable onSuccessCallback, Surface... surfaces) throws CameraAccessException { // Close any existing capture session. captureSession = null; // Create a new capture builder. previewRequestBuilder = cameraDevice.createCaptureRequest(templateType); // Build Flutter surface to render to. ResolutionFeature resolutionFeature = cameraFeatures.getResolution(); SurfaceTexture surfaceTexture = flutterTexture.surfaceTexture(); surfaceTexture.setDefaultBufferSize( resolutionFeature.getPreviewSize().getWidth(), resolutionFeature.getPreviewSize().getHeight()); Surface flutterSurface = new Surface(surfaceTexture); previewRequestBuilder.addTarget(flutterSurface); List remainingSurfaces = Arrays.asList(surfaces); if (templateType != CameraDevice.TEMPLATE_PREVIEW) { // If it is not preview mode, add all surfaces as targets. for (Surface surface : remainingSurfaces) { previewRequestBuilder.addTarget(surface); } } // Update camera regions. Size cameraBoundaries = CameraRegionUtils.getCameraBoundaries(cameraProperties, previewRequestBuilder); cameraFeatures.getExposurePoint().setCameraBoundaries(cameraBoundaries); cameraFeatures.getFocusPoint().setCameraBoundaries(cameraBoundaries); // Prepare the callback. CameraCaptureSession.StateCallback callback = new CameraCaptureSession.StateCallback() { boolean captureSessionClosed = false; @Override public void onConfigured(@NonNull CameraCaptureSession session) { Log.i(TAG, "CameraCaptureSession onConfigured"); // Camera was already closed. if (cameraDevice == null || captureSessionClosed) { dartMessenger.sendCameraErrorEvent("The camera was closed during configuration."); return; } captureSession = session; Log.i(TAG, "Updating builder settings"); updateBuilderSettings(previewRequestBuilder); refreshPreviewCaptureSession( onSuccessCallback, (code, message) -> dartMessenger.sendCameraErrorEvent(message)); } @Override public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { Log.i(TAG, "CameraCaptureSession onConfigureFailed"); dartMessenger.sendCameraErrorEvent("Failed to configure camera session."); } @Override public void onClosed(@NonNull CameraCaptureSession session) { Log.i(TAG, "CameraCaptureSession onClosed"); captureSessionClosed = true; } }; // Start the session. if (VERSION.SDK_INT >= VERSION_CODES.P) { // Collect all surfaces to render to. List configs = new ArrayList<>(); configs.add(new OutputConfiguration(flutterSurface)); for (Surface surface : remainingSurfaces) { configs.add(new OutputConfiguration(surface)); } createCaptureSessionWithSessionConfig(configs, callback); } else { // Collect all surfaces to render to. List surfaceList = new ArrayList<>(); surfaceList.add(flutterSurface); surfaceList.addAll(remainingSurfaces); createCaptureSession(surfaceList, callback); } } @TargetApi(VERSION_CODES.P) private void createCaptureSessionWithSessionConfig( List outputConfigs, CameraCaptureSession.StateCallback callback) throws CameraAccessException { cameraDevice.createCaptureSession( new SessionConfiguration( SessionConfiguration.SESSION_REGULAR, outputConfigs, Executors.newSingleThreadExecutor(), callback)); } @TargetApi(VERSION_CODES.LOLLIPOP) @SuppressWarnings("deprecation") private void createCaptureSession( List surfaces, CameraCaptureSession.StateCallback callback) throws CameraAccessException { cameraDevice.createCaptureSession(surfaces, callback, backgroundHandler); } // Send a repeating request to refresh capture session. private void refreshPreviewCaptureSession( @Nullable Runnable onSuccessCallback, @NonNull ErrorCallback onErrorCallback) { Log.i(TAG, "refreshPreviewCaptureSession"); if (captureSession == null) { Log.i( TAG, "refreshPreviewCaptureSession: captureSession not yet initialized, " + "skipping preview capture session refresh."); return; } try { if (!pausedPreview) { captureSession.setRepeatingRequest( previewRequestBuilder.build(), cameraCaptureCallback, backgroundHandler); } if (onSuccessCallback != null) { onSuccessCallback.run(); } } catch (IllegalStateException e) { onErrorCallback.onError("cameraAccess", "Camera is closed: " + e.getMessage()); } catch (CameraAccessException e) { onErrorCallback.onError("cameraAccess", e.getMessage()); } } private void startCapture(boolean record, boolean stream) throws CameraAccessException { List surfaces = new ArrayList<>(); Runnable successCallback = null; if (record) { surfaces.add(mediaRecorder.getSurface()); successCallback = () -> mediaRecorder.start(); } if (stream) { surfaces.add(imageStreamReader.getSurface()); } createCaptureSession( CameraDevice.TEMPLATE_RECORD, successCallback, surfaces.toArray(new Surface[0])); } public void takePicture(@NonNull final Result result) { // Only take one picture at a time. if (cameraCaptureCallback.getCameraState() != CameraState.STATE_PREVIEW) { result.error("captureAlreadyActive", "Picture is currently already being captured", null); return; } flutterResult = result; // Create temporary file. final File outputDir = applicationContext.getCacheDir(); try { captureFile = File.createTempFile("CAP", ".jpg", outputDir); captureTimeouts.reset(); } catch (IOException | SecurityException e) { dartMessenger.error(flutterResult, "cannotCreateFile", e.getMessage(), null); return; } // Listen for picture being taken. pictureImageReader.setOnImageAvailableListener(this, backgroundHandler); final AutoFocusFeature autoFocusFeature = cameraFeatures.getAutoFocus(); final boolean isAutoFocusSupported = autoFocusFeature.checkIsSupported(); if (isAutoFocusSupported && autoFocusFeature.getValue() == FocusMode.auto) { runPictureAutoFocus(); } else { runPrecaptureSequence(); } } /** * Run the precapture sequence for capturing a still image. This method should be called when a * response is received in {@link #cameraCaptureCallback} from lockFocus(). */ private void runPrecaptureSequence() { Log.i(TAG, "runPrecaptureSequence"); try { // First set precapture state to idle or else it can hang in STATE_WAITING_PRECAPTURE_START. previewRequestBuilder.set( CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE); captureSession.capture( previewRequestBuilder.build(), cameraCaptureCallback, backgroundHandler); // Repeating request to refresh preview session. refreshPreviewCaptureSession( null, (code, message) -> dartMessenger.error(flutterResult, "cameraAccess", message, null)); // Start precapture. cameraCaptureCallback.setCameraState(CameraState.STATE_WAITING_PRECAPTURE_START); previewRequestBuilder.set( CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); // Trigger one capture to start AE sequence. captureSession.capture( previewRequestBuilder.build(), cameraCaptureCallback, backgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * Capture a still picture. This method should be called when a response is received {@link * #cameraCaptureCallback} from both lockFocus(). */ private void takePictureAfterPrecapture() { Log.i(TAG, "captureStillPicture"); cameraCaptureCallback.setCameraState(CameraState.STATE_CAPTURING); if (cameraDevice == null) { return; } // This is the CaptureRequest.Builder that is used to take a picture. CaptureRequest.Builder stillBuilder; try { stillBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); } catch (CameraAccessException e) { dartMessenger.error(flutterResult, "cameraAccess", e.getMessage(), null); return; } stillBuilder.addTarget(pictureImageReader.getSurface()); // Zoom. stillBuilder.set( CaptureRequest.SCALER_CROP_REGION, previewRequestBuilder.get(CaptureRequest.SCALER_CROP_REGION)); // Have all features update the builder. updateBuilderSettings(stillBuilder); // Orientation. final PlatformChannel.DeviceOrientation lockedOrientation = ((SensorOrientationFeature) cameraFeatures.getSensorOrientation()) .getLockedCaptureOrientation(); stillBuilder.set( CaptureRequest.JPEG_ORIENTATION, lockedOrientation == null ? getDeviceOrientationManager().getPhotoOrientation() : getDeviceOrientationManager().getPhotoOrientation(lockedOrientation)); CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted( @NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { unlockAutoFocus(); } }; try { captureSession.stopRepeating(); Log.i(TAG, "sending capture request"); captureSession.capture(stillBuilder.build(), captureCallback, backgroundHandler); } catch (CameraAccessException e) { dartMessenger.error(flutterResult, "cameraAccess", e.getMessage(), null); } } @SuppressWarnings("deprecation") private Display getDefaultDisplay() { return activity.getWindowManager().getDefaultDisplay(); } /** Starts a background thread and its {@link Handler}. */ public void startBackgroundThread() { if (backgroundHandlerThread != null) { return; } backgroundHandlerThread = HandlerThreadFactory.create("CameraBackground"); try { backgroundHandlerThread.start(); } catch (IllegalThreadStateException e) { // Ignore exception in case the thread has already started. } backgroundHandler = HandlerFactory.create(backgroundHandlerThread.getLooper()); } /** Stops the background thread and its {@link Handler}. */ public void stopBackgroundThread() { if (backgroundHandlerThread != null) { backgroundHandlerThread.quitSafely(); } backgroundHandlerThread = null; backgroundHandler = null; } /** Start capturing a picture, doing autofocus first. */ private void runPictureAutoFocus() { Log.i(TAG, "runPictureAutoFocus"); cameraCaptureCallback.setCameraState(CameraState.STATE_WAITING_FOCUS); lockAutoFocus(); } private void lockAutoFocus() { Log.i(TAG, "lockAutoFocus"); if (captureSession == null) { Log.i(TAG, "[unlockAutoFocus] captureSession null, returning"); return; } // Trigger AF to start. previewRequestBuilder.set( CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); try { captureSession.capture(previewRequestBuilder.build(), null, backgroundHandler); } catch (CameraAccessException e) { dartMessenger.sendCameraErrorEvent(e.getMessage()); } } /** Cancel and reset auto focus state and refresh the preview session. */ private void unlockAutoFocus() { Log.i(TAG, "unlockAutoFocus"); if (captureSession == null) { Log.i(TAG, "[unlockAutoFocus] captureSession null, returning"); return; } try { // Cancel existing AF state. previewRequestBuilder.set( CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); captureSession.capture(previewRequestBuilder.build(), null, backgroundHandler); // Set AF state to idle again. previewRequestBuilder.set( CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE); captureSession.capture(previewRequestBuilder.build(), null, backgroundHandler); } catch (CameraAccessException e) { dartMessenger.sendCameraErrorEvent(e.getMessage()); return; } refreshPreviewCaptureSession( null, (errorCode, errorMessage) -> dartMessenger.error(flutterResult, errorCode, errorMessage, null)); } public void startVideoRecording( @NonNull Result result, @Nullable EventChannel imageStreamChannel) { prepareRecording(result); if (imageStreamChannel != null) { setStreamHandler(imageStreamChannel); } recordingVideo = true; try { startCapture(true, imageStreamChannel != null); result.success(null); } catch (CameraAccessException e) { recordingVideo = false; captureFile = null; result.error("videoRecordingFailed", e.getMessage(), null); } } public void stopVideoRecording(@NonNull final Result result) { if (!recordingVideo) { result.success(null); return; } // Re-create autofocus feature so it's using continuous capture focus mode now. cameraFeatures.setAutoFocus( cameraFeatureFactory.createAutoFocusFeature(cameraProperties, false)); recordingVideo = false; try { captureSession.abortCaptures(); mediaRecorder.stop(); } catch (CameraAccessException | IllegalStateException e) { // Ignore exceptions and try to continue (changes are camera session already aborted capture). } mediaRecorder.reset(); try { startPreview(); } catch (CameraAccessException | IllegalStateException e) { result.error("videoRecordingFailed", e.getMessage(), null); return; } result.success(captureFile.getAbsolutePath()); captureFile = null; } public void pauseVideoRecording(@NonNull final Result result) { if (!recordingVideo) { result.success(null); return; } try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { mediaRecorder.pause(); } else { result.error("videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null); return; } } catch (IllegalStateException e) { result.error("videoRecordingFailed", e.getMessage(), null); return; } result.success(null); } public void resumeVideoRecording(@NonNull final Result result) { if (!recordingVideo) { result.success(null); return; } try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { mediaRecorder.resume(); } else { result.error( "videoRecordingFailed", "resumeVideoRecording requires Android API +24.", null); return; } } catch (IllegalStateException e) { result.error("videoRecordingFailed", e.getMessage(), null); return; } result.success(null); } /** * Method handler for setting new flash modes. * * @param result Flutter result. * @param newMode new mode. */ public void setFlashMode(@NonNull final Result result, @NonNull FlashMode newMode) { // Save the new flash mode setting. final FlashFeature flashFeature = cameraFeatures.getFlash(); flashFeature.setValue(newMode); flashFeature.updateBuilder(previewRequestBuilder); refreshPreviewCaptureSession( () -> result.success(null), (code, message) -> result.error("setFlashModeFailed", "Could not set flash mode.", null)); } /** * Method handler for setting new exposure modes. * * @param result Flutter result. * @param newMode new mode. */ public void setExposureMode(@NonNull final Result result, @NonNull ExposureMode newMode) { final ExposureLockFeature exposureLockFeature = cameraFeatures.getExposureLock(); exposureLockFeature.setValue(newMode); exposureLockFeature.updateBuilder(previewRequestBuilder); refreshPreviewCaptureSession( () -> result.success(null), (code, message) -> result.error("setExposureModeFailed", "Could not set exposure mode.", null)); } /** * Sets new exposure point from dart. * * @param result Flutter result. * @param point The exposure point. */ public void setExposurePoint(@NonNull final Result result, @Nullable Point point) { final ExposurePointFeature exposurePointFeature = cameraFeatures.getExposurePoint(); exposurePointFeature.setValue(point); exposurePointFeature.updateBuilder(previewRequestBuilder); refreshPreviewCaptureSession( () -> result.success(null), (code, message) -> result.error("setExposurePointFailed", "Could not set exposure point.", null)); } /** Return the max exposure offset value supported by the camera to dart. */ public double getMaxExposureOffset() { return cameraFeatures.getExposureOffset().getMaxExposureOffset(); } /** Return the min exposure offset value supported by the camera to dart. */ public double getMinExposureOffset() { return cameraFeatures.getExposureOffset().getMinExposureOffset(); } /** Return the exposure offset step size to dart. */ public double getExposureOffsetStepSize() { return cameraFeatures.getExposureOffset().getExposureOffsetStepSize(); } /** * Sets new focus mode from dart. * * @param result Flutter result. * @param newMode New mode. */ public void setFocusMode(final Result result, @NonNull FocusMode newMode) { final AutoFocusFeature autoFocusFeature = cameraFeatures.getAutoFocus(); autoFocusFeature.setValue(newMode); autoFocusFeature.updateBuilder(previewRequestBuilder); /* * For focus mode an extra step of actually locking/unlocking the * focus has to be done, in order to ensure it goes into the correct state. */ if (!pausedPreview) { switch (newMode) { case locked: // Perform a single focus trigger. if (captureSession == null) { Log.i(TAG, "[unlockAutoFocus] captureSession null, returning"); return; } lockAutoFocus(); // Set AF state to idle again. previewRequestBuilder.set( CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE); try { captureSession.setRepeatingRequest( previewRequestBuilder.build(), null, backgroundHandler); } catch (CameraAccessException e) { if (result != null) { result.error( "setFocusModeFailed", "Error setting focus mode: " + e.getMessage(), null); } return; } break; case auto: // Cancel current AF trigger and set AF to idle again. unlockAutoFocus(); break; } } if (result != null) { result.success(null); } } /** * Sets new focus point from dart. * * @param result Flutter result. * @param point the new coordinates. */ public void setFocusPoint(@NonNull final Result result, @Nullable Point point) { final FocusPointFeature focusPointFeature = cameraFeatures.getFocusPoint(); focusPointFeature.setValue(point); focusPointFeature.updateBuilder(previewRequestBuilder); refreshPreviewCaptureSession( () -> result.success(null), (code, message) -> result.error("setFocusPointFailed", "Could not set focus point.", null)); this.setFocusMode(null, cameraFeatures.getAutoFocus().getValue()); } /** * Sets a new exposure offset from dart. From dart the offset comes as a double, like +1.3 or * -1.3. * * @param result flutter result. * @param offset new value. */ public void setExposureOffset(@NonNull final Result result, double offset) { final ExposureOffsetFeature exposureOffsetFeature = cameraFeatures.getExposureOffset(); exposureOffsetFeature.setValue(offset); exposureOffsetFeature.updateBuilder(previewRequestBuilder); refreshPreviewCaptureSession( () -> result.success(exposureOffsetFeature.getValue()), (code, message) -> result.error("setExposureOffsetFailed", "Could not set exposure offset.", null)); } public float getMaxZoomLevel() { return cameraFeatures.getZoomLevel().getMaximumZoomLevel(); } public float getMinZoomLevel() { return cameraFeatures.getZoomLevel().getMinimumZoomLevel(); } /** Shortcut to get current recording profile. Legacy method provides support for SDK < 31. */ CamcorderProfile getRecordingProfileLegacy() { return cameraFeatures.getResolution().getRecordingProfileLegacy(); } EncoderProfiles getRecordingProfile() { return cameraFeatures.getResolution().getRecordingProfile(); } /** Shortut to get deviceOrientationListener. */ DeviceOrientationManager getDeviceOrientationManager() { return cameraFeatures.getSensorOrientation().getDeviceOrientationManager(); } /** * Sets zoom level from dart. * * @param result Flutter result. * @param zoom new value. */ public void setZoomLevel(@NonNull final Result result, float zoom) throws CameraAccessException { final ZoomLevelFeature zoomLevel = cameraFeatures.getZoomLevel(); float maxZoom = zoomLevel.getMaximumZoomLevel(); float minZoom = zoomLevel.getMinimumZoomLevel(); if (zoom > maxZoom || zoom < minZoom) { String errorMessage = String.format( Locale.ENGLISH, "Zoom level out of bounds (zoom level should be between %f and %f).", minZoom, maxZoom); result.error("ZOOM_ERROR", errorMessage, null); return; } zoomLevel.setValue(zoom); zoomLevel.updateBuilder(previewRequestBuilder); refreshPreviewCaptureSession( () -> result.success(null), (code, message) -> result.error("setZoomLevelFailed", "Could not set zoom level.", null)); } /** * Lock capture orientation from dart. * * @param orientation new orientation. */ public void lockCaptureOrientation(PlatformChannel.DeviceOrientation orientation) { cameraFeatures.getSensorOrientation().lockCaptureOrientation(orientation); } /** Unlock capture orientation from dart. */ public void unlockCaptureOrientation() { cameraFeatures.getSensorOrientation().unlockCaptureOrientation(); } /** Pause the preview from dart. */ public void pausePreview() throws CameraAccessException { this.pausedPreview = true; this.captureSession.stopRepeating(); } /** Resume the preview from dart. */ public void resumePreview() { this.pausedPreview = false; this.refreshPreviewCaptureSession( null, (code, message) -> dartMessenger.sendCameraErrorEvent(message)); } public void startPreview() throws CameraAccessException { if (pictureImageReader == null || pictureImageReader.getSurface() == null) return; Log.i(TAG, "startPreview"); createCaptureSession(CameraDevice.TEMPLATE_PREVIEW, pictureImageReader.getSurface()); } public void startPreviewWithImageStream(EventChannel imageStreamChannel) throws CameraAccessException { setStreamHandler(imageStreamChannel); startCapture(false, true); Log.i(TAG, "startPreviewWithImageStream"); } /** * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a * still image is ready to be saved. */ @Override public void onImageAvailable(ImageReader reader) { Log.i(TAG, "onImageAvailable"); backgroundHandler.post( new ImageSaver( // Use acquireNextImage since image reader is only for one image. reader.acquireNextImage(), captureFile, new ImageSaver.Callback() { @Override public void onComplete(String absolutePath) { dartMessenger.finish(flutterResult, absolutePath); } @Override public void onError(String errorCode, String errorMessage) { dartMessenger.error(flutterResult, errorCode, errorMessage, null); } })); cameraCaptureCallback.setCameraState(CameraState.STATE_PREVIEW); } private void prepareRecording(@NonNull Result result) { final File outputDir = applicationContext.getCacheDir(); try { captureFile = File.createTempFile("REC", ".mp4", outputDir); } catch (IOException | SecurityException e) { result.error("cannotCreateFile", e.getMessage(), null); return; } try { prepareMediaRecorder(captureFile.getAbsolutePath()); } catch (IOException e) { recordingVideo = false; captureFile = null; result.error("videoRecordingFailed", e.getMessage(), null); return; } // Re-create autofocus feature so it's using video focus mode now. cameraFeatures.setAutoFocus( cameraFeatureFactory.createAutoFocusFeature(cameraProperties, true)); } private void setStreamHandler(EventChannel imageStreamChannel) { imageStreamChannel.setStreamHandler( new EventChannel.StreamHandler() { @Override public void onListen(Object o, EventChannel.EventSink imageStreamSink) { setImageStreamImageAvailableListener(imageStreamSink); } @Override public void onCancel(Object o) { imageStreamReader.setOnImageAvailableListener(null, backgroundHandler); } }); } private void setImageStreamImageAvailableListener(final EventChannel.EventSink imageStreamSink) { imageStreamReader.setOnImageAvailableListener( reader -> { Image img = reader.acquireNextImage(); // Use acquireNextImage since image reader is only for one image. if (img == null) return; List> planes = new ArrayList<>(); for (Image.Plane plane : img.getPlanes()) { ByteBuffer buffer = plane.getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes, 0, bytes.length); Map planeBuffer = new HashMap<>(); planeBuffer.put("bytesPerRow", plane.getRowStride()); planeBuffer.put("bytesPerPixel", plane.getPixelStride()); planeBuffer.put("bytes", bytes); planes.add(planeBuffer); } Map imageBuffer = new HashMap<>(); imageBuffer.put("width", img.getWidth()); imageBuffer.put("height", img.getHeight()); imageBuffer.put("format", img.getFormat()); imageBuffer.put("planes", planes); imageBuffer.put("lensAperture", this.captureProps.getLastLensAperture()); imageBuffer.put("sensorExposureTime", this.captureProps.getLastSensorExposureTime()); Integer sensorSensitivity = this.captureProps.getLastSensorSensitivity(); imageBuffer.put( "sensorSensitivity", sensorSensitivity == null ? null : (double) sensorSensitivity); final Handler handler = new Handler(Looper.getMainLooper()); handler.post(() -> imageStreamSink.success(imageBuffer)); img.close(); }, backgroundHandler); } private void closeCaptureSession() { if (captureSession != null) { Log.i(TAG, "closeCaptureSession"); captureSession.close(); captureSession = null; } } public void close() { Log.i(TAG, "close"); if (cameraDevice != null) { cameraDevice.close(); cameraDevice = null; // Closing the CameraDevice without closing the CameraCaptureSession is recommended // for quickly closing the camera: // https://developer.android.com/reference/android/hardware/camera2/CameraCaptureSession#close() captureSession = null; } else { closeCaptureSession(); } if (pictureImageReader != null) { pictureImageReader.close(); pictureImageReader = null; } if (imageStreamReader != null) { imageStreamReader.close(); imageStreamReader = null; } if (mediaRecorder != null) { mediaRecorder.reset(); mediaRecorder.release(); mediaRecorder = null; } stopBackgroundThread(); } public void dispose() { Log.i(TAG, "dispose"); close(); flutterTexture.release(); getDeviceOrientationManager().stop(); } /** Factory class that assists in creating a {@link HandlerThread} instance. */ static class HandlerThreadFactory { /** * Creates a new instance of the {@link HandlerThread} class. * *

This method is visible for testing purposes only and should never be used outside this * * class. * * @param name to give to the HandlerThread. * @return new instance of the {@link HandlerThread} class. */ @VisibleForTesting public static HandlerThread create(String name) { return new HandlerThread(name); } } /** Factory class that assists in creating a {@link Handler} instance. */ static class HandlerFactory { /** * Creates a new instance of the {@link Handler} class. * *

This method is visible for testing purposes only and should never be used outside this * * class. * * @param looper to give to the Handler. * @return new instance of the {@link Handler} class. */ @VisibleForTesting public static Handler create(Looper looper) { return new Handler(looper); } } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraCaptureCallback.java ================================================ // Copyright 2013 The Flutter 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.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCaptureSession.CaptureCallback; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.TotalCaptureResult; import android.util.Log; import androidx.annotation.NonNull; import io.flutter.plugins.camera.types.CameraCaptureProperties; import io.flutter.plugins.camera.types.CaptureTimeoutsWrapper; /** * A callback object for tracking the progress of a {@link android.hardware.camera2.CaptureRequest} * submitted to the camera device. */ class CameraCaptureCallback extends CaptureCallback { private static final String TAG = "CameraCaptureCallback"; private final CameraCaptureStateListener cameraStateListener; private CameraState cameraState; private final CaptureTimeoutsWrapper captureTimeouts; private final CameraCaptureProperties captureProps; private CameraCaptureCallback( @NonNull CameraCaptureStateListener cameraStateListener, @NonNull CaptureTimeoutsWrapper captureTimeouts, @NonNull CameraCaptureProperties captureProps) { cameraState = CameraState.STATE_PREVIEW; this.cameraStateListener = cameraStateListener; this.captureTimeouts = captureTimeouts; this.captureProps = captureProps; } /** * Creates a new instance of the {@link CameraCaptureCallback} class. * * @param cameraStateListener instance which will be called when the camera state changes. * @param captureTimeouts specifying the different timeout counters that should be taken into * account. * @return a configured instance of the {@link CameraCaptureCallback} class. */ public static CameraCaptureCallback create( @NonNull CameraCaptureStateListener cameraStateListener, @NonNull CaptureTimeoutsWrapper captureTimeouts, @NonNull CameraCaptureProperties captureProps) { return new CameraCaptureCallback(cameraStateListener, captureTimeouts, captureProps); } /** * Gets the current {@link CameraState}. * * @return the current {@link CameraState}. */ public CameraState getCameraState() { return cameraState; } /** * Sets the {@link CameraState}. * * @param state the camera is currently in. */ public void setCameraState(@NonNull CameraState state) { cameraState = state; } private void process(CaptureResult result) { Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); // Update capture properties if (result instanceof TotalCaptureResult) { Float lensAperture = result.get(CaptureResult.LENS_APERTURE); Long sensorExposureTime = result.get(CaptureResult.SENSOR_EXPOSURE_TIME); Integer sensorSensitivity = result.get(CaptureResult.SENSOR_SENSITIVITY); this.captureProps.setLastLensAperture(lensAperture); this.captureProps.setLastSensorExposureTime(sensorExposureTime); this.captureProps.setLastSensorSensitivity(sensorSensitivity); } if (cameraState != CameraState.STATE_PREVIEW) { Log.d( TAG, "CameraCaptureCallback | state: " + cameraState + " | afState: " + afState + " | aeState: " + aeState); } switch (cameraState) { case STATE_PREVIEW: { // We have nothing to do when the camera preview is working normally. break; } case STATE_WAITING_FOCUS: { if (afState == null) { return; } else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { handleWaitingFocusState(aeState); } else if (captureTimeouts.getPreCaptureFocusing().getIsExpired()) { Log.w(TAG, "Focus timeout, moving on with capture"); handleWaitingFocusState(aeState); } break; } case STATE_WAITING_PRECAPTURE_START: { // CONTROL_AE_STATE can be null on some devices if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) { setCameraState(CameraState.STATE_WAITING_PRECAPTURE_DONE); } else if (captureTimeouts.getPreCaptureMetering().getIsExpired()) { Log.w(TAG, "Metering timeout waiting for pre-capture to start, moving on with capture"); setCameraState(CameraState.STATE_WAITING_PRECAPTURE_DONE); } break; } case STATE_WAITING_PRECAPTURE_DONE: { // CONTROL_AE_STATE can be null on some devices if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { cameraStateListener.onConverged(); } else if (captureTimeouts.getPreCaptureMetering().getIsExpired()) { Log.w( TAG, "Metering timeout waiting for pre-capture to finish, moving on with capture"); cameraStateListener.onConverged(); } break; } } } private void handleWaitingFocusState(Integer aeState) { // CONTROL_AE_STATE can be null on some devices if (aeState == null || aeState == CaptureRequest.CONTROL_AE_STATE_CONVERGED) { cameraStateListener.onConverged(); } else { cameraStateListener.onPrecapture(); } } @Override public void onCaptureProgressed( @NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) { process(partialResult); } @Override public void onCaptureCompleted( @NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { process(result); } /** An interface that describes the different state changes implementers can be informed about. */ interface CameraCaptureStateListener { /** Called when the {@link android.hardware.camera2.CaptureRequest} has been converged. */ void onConverged(); /** * Called when the {@link android.hardware.camera2.CaptureRequest} enters the pre-capture state. */ void onPrecapture(); } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java ================================================ // Copyright 2013 The Flutter 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.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 CameraPermissions { 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; } } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java ================================================ // Copyright 2013 The Flutter 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.app.Activity; import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.Nullable; 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.plugins.camera.CameraPermissions.PermissionsRegistry; import io.flutter.view.TextureRegistry; /** * Platform implementation of the camera_plugin. * *

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

Call {@link #registerWith(io.flutter.plugin.common.PluginRegistry.Registrar)} to register an * implementation of this that uses the stable {@code io.flutter.plugin.common} package. */ public final class CameraPlugin implements FlutterPlugin, ActivityAware { private static final String TAG = "CameraPlugin"; private @Nullable FlutterPluginBinding flutterPluginBinding; private @Nullable MethodCallHandlerImpl methodCallHandler; /** * Initialize this within the {@code #configureFlutterEngine} of a Flutter activity or fragment. * *

See {@code io.flutter.plugins.camera.MainActivity} for an example. */ public CameraPlugin() {} /** * Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common} * package. * *

Calling this automatically initializes the plugin. However plugins initialized this way * won't react to changes in activity or context, unlike {@link CameraPlugin}. */ @SuppressWarnings("deprecation") public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { CameraPlugin plugin = new CameraPlugin(); plugin.maybeStartListening( registrar.activity(), registrar.messenger(), registrar::addRequestPermissionsResultListener, registrar.view()); } @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { this.flutterPluginBinding = binding; } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { this.flutterPluginBinding = null; } @Override public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { maybeStartListening( binding.getActivity(), flutterPluginBinding.getBinaryMessenger(), binding::addRequestPermissionsResultListener, flutterPluginBinding.getTextureRegistry()); } @Override public void onDetachedFromActivity() { // Could be on too low of an SDK to have started listening originally. if (methodCallHandler != null) { methodCallHandler.stopListening(); methodCallHandler = null; } } @Override public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { onAttachedToActivity(binding); } @Override public void onDetachedFromActivityForConfigChanges() { onDetachedFromActivity(); } private void maybeStartListening( Activity activity, BinaryMessenger messenger, PermissionsRegistry permissionsRegistry, TextureRegistry textureRegistry) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { // If the sdk is less than 21 (min sdk for Camera2) we don't register the plugin. return; } methodCallHandler = new MethodCallHandlerImpl( activity, messenger, new CameraPermissions(), permissionsRegistry, textureRegistry); } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraProperties.java ================================================ // Copyright 2013 The Flutter 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 android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraManager; import android.os.Build.VERSION_CODES; import android.util.Range; import android.util.Rational; import android.util.Size; import androidx.annotation.RequiresApi; /** An interface allowing access to the different characteristics of the device's camera. */ public interface CameraProperties { /** * Returns the name (or identifier) of the camera device. * * @return String The name of the camera device. */ String getCameraName(); /** * Returns the list of frame rate ranges for @see android.control.aeTargetFpsRange supported by * this camera device. * *

By default maps to the @see * android.hardware.camera2.CameraCharacteristics#CONTROL_AE_TARGET_FPS_RANGE key. * * @return android.util.Range[] List of frame rate ranges supported by this camera * device. */ Range[] getControlAutoExposureAvailableTargetFpsRanges(); /** * Returns the maximum and minimum exposure compensation values for @see * android.control.aeExposureCompensation, in counts of @see android.control.aeCompensationStep, * that are supported by this camera device. * *

By default maps to the @see * android.hardware.camera2.CameraCharacteristics#CONTROL_AE_COMPENSATION_RANGE key. * * @return android.util.Range Maximum and minimum exposure compensation supported by this * camera device. */ Range getControlAutoExposureCompensationRange(); /** * Returns the smallest step by which the exposure compensation can be changed. * *

By default maps to the @see * android.hardware.camera2.CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP key. * * @return double Smallest step by which the exposure compensation can be changed. */ double getControlAutoExposureCompensationStep(); /** * Returns a list of auto-focus modes for @see android.control.afMode that are supported by this * camera device. * *

By default maps to the @see * android.hardware.camera2.CameraCharacteristics#CONTROL_AF_AVAILABLE_MODES key. * * @return int[] List of auto-focus modes supported by this camera device. */ int[] getControlAutoFocusAvailableModes(); /** * Returns the maximum number of metering regions that can be used by the auto-exposure routine. * *

By default maps to the @see * android.hardware.camera2.CameraCharacteristics#CONTROL_MAX_REGIONS_AE key. * * @return Integer Maximum number of metering regions that can be used by the auto-exposure * routine. */ Integer getControlMaxRegionsAutoExposure(); /** * Returns the maximum number of metering regions that can be used by the auto-focus routine. * *

By default maps to the @see * android.hardware.camera2.CameraCharacteristics#CONTROL_MAX_REGIONS_AF key. * * @return Integer Maximum number of metering regions that can be used by the auto-focus routine. */ Integer getControlMaxRegionsAutoFocus(); /** * Returns a list of distortion correction modes for @see android.distortionCorrection.mode that * are supported by this camera device. * *

By default maps to the @see * android.hardware.camera2.CameraCharacteristics#DISTORTION_CORRECTION_AVAILABLE_MODES key. * * @return int[] List of distortion correction modes supported by this camera device. */ @RequiresApi(api = VERSION_CODES.P) int[] getDistortionCorrectionAvailableModes(); /** * Returns whether this camera device has a flash unit. * *

By default maps to the @see * android.hardware.camera2.CameraCharacteristics#FLASH_INFO_AVAILABLE key. * * @return Boolean Whether this camera device has a flash unit. */ Boolean getFlashInfoAvailable(); /** * Returns the direction the camera faces relative to device screen. * *

Possible values: * *

    *
  • @see android.hardware.camera2.CameraMetadata.LENS_FACING_FRONT *
  • @see android.hardware.camera2.CameraMetadata.LENS_FACING_BACK *
  • @see android.hardware.camera2.CameraMetadata.LENS_FACING_EXTERNAL *
* *

By default maps to the @see android.hardware.camera2.CameraCharacteristics.LENS_FACING key. * * @return int Direction the camera faces relative to device screen. */ int getLensFacing(); /** * Returns the shortest distance from front most surface of the lens that can be brought into * sharp focus. * *

By default maps to the @see * android.hardware.camera2.CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE key. * * @return Float Shortest distance from front most surface of the lens that can be brought into * sharp focus. */ Float getLensInfoMinimumFocusDistance(); /** * Returns the maximum ratio between both active area width and crop region width, and active area * height and crop region height, for @see android.scaler.cropRegion. * *

By default maps to the @see * 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. */ 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. * *

By default maps to the @see * android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE key. * * @return android.graphics.Rect area of the image sensor which corresponds to active pixels after * any geometric distortion correction has been applied. */ Rect getSensorInfoActiveArraySize(); /** * Returns the dimensions of the full pixel array, possibly including black calibration pixels. * *

By default maps to the @see * android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE key. * * @return android.util.Size Dimensions of the full pixel array, possibly including black * calibration pixels. */ Size getSensorInfoPixelArraySize(); /** * Returns the area of the image sensor which corresponds to active pixels prior to the * application of any geometric distortion correction. * *

By default maps to the @see * android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE * key. * * @return android.graphics.Rect Area of the image sensor which corresponds to active pixels prior * to the application of any geometric distortion correction. */ @RequiresApi(api = VERSION_CODES.M) Rect getSensorInfoPreCorrectionActiveArraySize(); /** * Returns the clockwise angle through which the output image needs to be rotated to be upright on * the device screen in its native orientation. * *

By default maps to the @see * android.hardware.camera2.CameraCharacteristics#SENSOR_ORIENTATION key. * * @return int Clockwise angle through which the output image needs to be rotated to be upright on * the device screen in its native orientation. */ int getSensorOrientation(); /** * Returns a level which generally classifies the overall set of the camera device functionality. * *

Possible values: * *

    *
  • @see android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY *
  • @see android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED *
  • @see android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL *
  • @see android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEVEL_3 *
  • @see android.hardware.camera2.CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL *
* *

By default maps to the @see * android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL key. * * @return int Level which generally classifies the overall set of the camera device * functionality. */ int getHardwareLevel(); /** * Returns a list of noise reduction modes for @see android.noiseReduction.mode that are supported * by this camera device. * *

By default maps to the @see * android.hardware.camera2.CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES * key. * * @return int[] List of noise reduction modes that are supported by this camera device. */ int[] getAvailableNoiseReductionModes(); } /** * Implementation of the @see CameraProperties interface using the @see * android.hardware.camera2.CameraCharacteristics class to access the different characteristics. */ class CameraPropertiesImpl implements CameraProperties { private final CameraCharacteristics cameraCharacteristics; private final String cameraName; public CameraPropertiesImpl(String cameraName, CameraManager cameraManager) throws CameraAccessException { this.cameraName = cameraName; this.cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraName); } @Override public String getCameraName() { return cameraName; } @Override public Range[] getControlAutoExposureAvailableTargetFpsRanges() { return cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); } @Override public Range getControlAutoExposureCompensationRange() { return cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); } @Override public double getControlAutoExposureCompensationStep() { Rational rational = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP); return rational == null ? 0.0 : rational.doubleValue(); } @Override public int[] getControlAutoFocusAvailableModes() { return cameraCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); } @Override public Integer getControlMaxRegionsAutoExposure() { return cameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE); } @Override public Integer getControlMaxRegionsAutoFocus() { return cameraCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF); } @RequiresApi(api = VERSION_CODES.P) @Override public int[] getDistortionCorrectionAvailableModes() { return cameraCharacteristics.get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES); } @Override public Boolean getFlashInfoAvailable() { return cameraCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); } @Override public int getLensFacing() { return cameraCharacteristics.get(CameraCharacteristics.LENS_FACING); } @Override public Float getLensInfoMinimumFocusDistance() { return cameraCharacteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE); } @Override 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); } @Override public Size getSensorInfoPixelArraySize() { return cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE); } @RequiresApi(api = VERSION_CODES.M) @Override public Rect getSensorInfoPreCorrectionActiveArraySize() { return cameraCharacteristics.get( CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE); } @Override public int getSensorOrientation() { return cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); } @Override public int getHardwareLevel() { return cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); } @Override public int[] getAvailableNoiseReductionModes() { return cameraCharacteristics.get( CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES); } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraRegionUtils.java ================================================ // Copyright 2013 The Flutter 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.annotation.TargetApi; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.MeteringRectangle; import android.os.Build; import android.util.Size; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import java.util.Arrays; /** * Utility class offering functions to calculate values regarding the camera boundaries. * *

The functions are used to calculate focus and exposure settings. */ public final class CameraRegionUtils { /** * Obtains the boundaries for the currently active camera, that can be used for calculating * MeteringRectangle instances required for setting focus or exposure settings. * * @param cameraProperties - Collection of the characteristics for the current camera device. * @param requestBuilder - The request builder for the current capture request. * @return The boundaries for the current camera device. */ public static Size getCameraBoundaries( @NonNull CameraProperties cameraProperties, @NonNull CaptureRequest.Builder requestBuilder) { if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && supportsDistortionCorrection(cameraProperties)) { // Get the current distortion correction mode. Integer distortionCorrectionMode = requestBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE); // Return the correct boundaries depending on the mode. android.graphics.Rect rect; if (distortionCorrectionMode == null || distortionCorrectionMode == CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) { rect = cameraProperties.getSensorInfoPreCorrectionActiveArraySize(); } else { rect = cameraProperties.getSensorInfoActiveArraySize(); } return SizeFactory.create(rect.width(), rect.height()); } else { // No distortion correction support. return cameraProperties.getSensorInfoPixelArraySize(); } } /** * Converts a point into a {@link MeteringRectangle} with the supplied coordinates as the center * point. * *

Since the Camera API (due to cross-platform constraints) only accepts a point when * configuring a specific focus or exposure area and Android requires a rectangle to configure * these settings there is a need to convert the point into a rectangle. This method will create * the required rectangle with an arbitrarily size that is a 10th of the current viewport and the * coordinates as the center point. * * @param boundaries - The camera boundaries to calculate the metering rectangle for. * @param x x - 1 >= coordinate >= 0. * @param y y - 1 >= coordinate >= 0. * @return The dimensions of the metering rectangle based on the supplied coordinates and * boundaries. */ public static MeteringRectangle convertPointToMeteringRectangle( @NonNull Size boundaries, double x, double y, @NonNull PlatformChannel.DeviceOrientation orientation) { assert (boundaries.getWidth() > 0 && boundaries.getHeight() > 0); assert (x >= 0 && x <= 1); assert (y >= 0 && y <= 1); // Rotate the coordinates to match the device orientation. double oldX = x, oldY = y; switch (orientation) { case PORTRAIT_UP: // 90 ccw. y = 1 - oldX; x = oldY; break; case PORTRAIT_DOWN: // 90 cw. x = 1 - oldY; y = oldX; break; case LANDSCAPE_LEFT: // No rotation required. break; case LANDSCAPE_RIGHT: // 180. x = 1 - x; y = 1 - y; break; } // Interpolate the target coordinate. int targetX = (int) Math.round(x * ((double) (boundaries.getWidth() - 1))); int targetY = (int) Math.round(y * ((double) (boundaries.getHeight() - 1))); // Determine the dimensions of the metering rectangle (10th of the viewport). int targetWidth = (int) Math.round(((double) boundaries.getWidth()) / 10d); int targetHeight = (int) Math.round(((double) boundaries.getHeight()) / 10d); // Adjust target coordinate to represent top-left corner of metering rectangle. targetX -= targetWidth / 2; targetY -= targetHeight / 2; // Adjust target coordinate as to not fall out of bounds. if (targetX < 0) { targetX = 0; } if (targetY < 0) { targetY = 0; } int maxTargetX = boundaries.getWidth() - 1 - targetWidth; int maxTargetY = boundaries.getHeight() - 1 - targetHeight; if (targetX > maxTargetX) { targetX = maxTargetX; } if (targetY > maxTargetY) { targetY = maxTargetY; } // Build the metering rectangle. return MeteringRectangleFactory.create(targetX, targetY, targetWidth, targetHeight, 1); } @TargetApi(Build.VERSION_CODES.P) private static boolean supportsDistortionCorrection(CameraProperties cameraProperties) { int[] availableDistortionCorrectionModes = cameraProperties.getDistortionCorrectionAvailableModes(); if (availableDistortionCorrectionModes == null) { availableDistortionCorrectionModes = new int[0]; } long nonOffModesSupported = Arrays.stream(availableDistortionCorrectionModes) .filter((value) -> value != CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) .count(); return nonOffModesSupported > 0; } /** Factory class that assists in creating a {@link MeteringRectangle} instance. */ static class MeteringRectangleFactory { /** * Creates a new instance of the {@link MeteringRectangle} class. * *

This method is visible for testing purposes only and should never be used outside this * * class. * * @param x coordinate >= 0. * @param y coordinate >= 0. * @param width width >= 0. * @param height height >= 0. * @param meteringWeight weight between {@value MeteringRectangle#METERING_WEIGHT_MIN} and * {@value MeteringRectangle#METERING_WEIGHT_MAX} inclusively. * @return new instance of the {@link MeteringRectangle} class. * @throws IllegalArgumentException if any of the parameters were negative. */ @VisibleForTesting public static MeteringRectangle create( int x, int y, int width, int height, int meteringWeight) { return new MeteringRectangle(x, y, width, height, meteringWeight); } } /** Factory class that assists in creating a {@link Size} instance. */ static class SizeFactory { /** * Creates a new instance of the {@link Size} class. * *

This method is visible for testing purposes only and should never be used outside this * * class. * * @param width width >= 0. * @param height height >= 0. * @return new instance of the {@link Size} class. */ @VisibleForTesting public static Size create(int width, int height) { return new Size(width, height); } } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraState.java ================================================ // Copyright 2013 The Flutter 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; /** * These are the states that the camera can be in. The camera can only take one photo at a time so * this state describes the state of the camera itself. The camera works like a pipeline where we * feed it requests through. It can only process one tasks at a time. */ public enum CameraState { /** Idle, showing preview and not capturing anything. */ STATE_PREVIEW, /** Starting and waiting for autofocus to complete. */ STATE_WAITING_FOCUS, /** Start performing autoexposure. */ STATE_WAITING_PRECAPTURE_START, /** waiting for autoexposure to complete. */ STATE_WAITING_PRECAPTURE_DONE, /** Capturing an image. */ STATE_CAPTURING, } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java ================================================ // Copyright 2013 The Flutter 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.app.Activity; import android.content.Context; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraMetadata; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** Provides various utilities for camera. */ public final class CameraUtils { private CameraUtils() {} /** * Gets the {@link CameraManager} singleton. * * @param context The context to get the {@link CameraManager} singleton from. * @return The {@link CameraManager} singleton. */ static CameraManager getCameraManager(Context context) { return (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); } /** * Serializes the {@link PlatformChannel.DeviceOrientation} to a string value. * * @param orientation The orientation to serialize. * @return The serialized orientation. * @throws UnsupportedOperationException when the provided orientation not have a corresponding * string value. */ static String serializeDeviceOrientation(PlatformChannel.DeviceOrientation orientation) { if (orientation == null) throw new UnsupportedOperationException("Could not serialize null device orientation."); switch (orientation) { case PORTRAIT_UP: return "portraitUp"; case PORTRAIT_DOWN: return "portraitDown"; case LANDSCAPE_LEFT: return "landscapeLeft"; case LANDSCAPE_RIGHT: return "landscapeRight"; default: throw new UnsupportedOperationException( "Could not serialize device orientation: " + orientation.toString()); } } /** * Deserializes a string value to its corresponding {@link PlatformChannel.DeviceOrientation} * value. * * @param orientation The string value to deserialize. * @return The deserialized orientation. * @throws UnsupportedOperationException when the provided string value does not have a * corresponding {@link PlatformChannel.DeviceOrientation}. */ static PlatformChannel.DeviceOrientation deserializeDeviceOrientation(String orientation) { if (orientation == null) throw new UnsupportedOperationException("Could not deserialize null device orientation."); switch (orientation) { case "portraitUp": return PlatformChannel.DeviceOrientation.PORTRAIT_UP; case "portraitDown": return PlatformChannel.DeviceOrientation.PORTRAIT_DOWN; case "landscapeLeft": return PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT; case "landscapeRight": return PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT; default: throw new UnsupportedOperationException( "Could not deserialize device orientation: " + orientation); } } /** * Gets all the available cameras for the device. * * @param activity The current Android activity. * @return A map of all the available cameras, with their name as their key. * @throws CameraAccessException when the camera could not be accessed. */ public static List> getAvailableCameras(Activity activity) throws CameraAccessException { CameraManager cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); String[] cameraNames = cameraManager.getCameraIdList(); List> cameras = new ArrayList<>(); for (String cameraName : cameraNames) { int cameraId; try { cameraId = Integer.parseInt(cameraName, 10); } catch (NumberFormatException e) { cameraId = -1; } if (cameraId < 0) { continue; } HashMap details = new HashMap<>(); CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraName); details.put("name", cameraName); int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); details.put("sensorOrientation", sensorOrientation); int lensFacing = characteristics.get(CameraCharacteristics.LENS_FACING); switch (lensFacing) { case CameraMetadata.LENS_FACING_FRONT: details.put("lensFacing", "front"); break; case CameraMetadata.LENS_FACING_BACK: details.put("lensFacing", "back"); break; case CameraMetadata.LENS_FACING_EXTERNAL: details.put("lensFacing", "external"); break; } cameras.add(details); } return cameras; } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/DartMessenger.java ================================================ // Copyright 2013 The Flutter 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.os.Handler; import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugins.camera.features.autofocus.FocusMode; import io.flutter.plugins.camera.features.exposurelock.ExposureMode; import java.util.HashMap; import java.util.Map; /** Utility class that facilitates communication to the Flutter client */ public class DartMessenger { @NonNull private final Handler handler; @Nullable private MethodChannel cameraChannel; @Nullable private MethodChannel deviceChannel; /** Specifies the different device related message types. */ enum DeviceEventType { /** Indicates the device's orientation has changed. */ ORIENTATION_CHANGED("orientation_changed"); private final String method; DeviceEventType(String method) { this.method = method; } } /** Specifies the different camera related message types. */ enum CameraEventType { /** Indicates that an error occurred while interacting with the camera. */ ERROR("error"), /** Indicates that the camera is closing. */ CLOSING("camera_closing"), /** Indicates that the camera is initialized. */ INITIALIZED("initialized"); private final String method; /** * Converts the supplied method name to the matching {@link CameraEventType}. * * @param method name to be converted into a {@link CameraEventType}. */ CameraEventType(String method) { this.method = method; } } /** * Creates a new instance of the {@link DartMessenger} class. * * @param messenger is the {@link BinaryMessenger} that is used to communicate with Flutter. * @param cameraId identifies the camera which is the source of the communication. * @param handler the handler used to manage the thread's message queue. This should always be a * handler managing the main thread since communication with Flutter should always happen on * the main thread. The handler is mainly supplied so it will be easier test this class. */ DartMessenger(BinaryMessenger messenger, long cameraId, @NonNull Handler handler) { cameraChannel = new MethodChannel(messenger, "plugins.flutter.io/camera_android/camera" + cameraId); deviceChannel = new MethodChannel(messenger, "plugins.flutter.io/camera_android/fromPlatform"); this.handler = handler; } /** * Sends a message to the Flutter client informing the orientation of the device has been changed. * * @param orientation specifies the new orientation of the device. */ public void sendDeviceOrientationChangeEvent(PlatformChannel.DeviceOrientation orientation) { assert (orientation != null); this.send( DeviceEventType.ORIENTATION_CHANGED, new HashMap() { { put("orientation", CameraUtils.serializeDeviceOrientation(orientation)); } }); } /** * Sends a message to the Flutter client informing that the camera has been initialized. * * @param previewWidth describes the preview width that is supported by the camera. * @param previewHeight describes the preview height that is supported by the camera. * @param exposureMode describes the current exposure mode that is set on the camera. * @param focusMode describes the current focus mode that is set on the camera. * @param exposurePointSupported indicates if the camera supports setting an exposure point. * @param focusPointSupported indicates if the camera supports setting a focus point. */ void sendCameraInitializedEvent( Integer previewWidth, Integer previewHeight, ExposureMode exposureMode, FocusMode focusMode, Boolean exposurePointSupported, Boolean focusPointSupported) { assert (previewWidth != null); assert (previewHeight != null); assert (exposureMode != null); assert (focusMode != null); assert (exposurePointSupported != null); assert (focusPointSupported != null); this.send( CameraEventType.INITIALIZED, new HashMap() { { put("previewWidth", previewWidth.doubleValue()); put("previewHeight", previewHeight.doubleValue()); put("exposureMode", exposureMode.toString()); put("focusMode", focusMode.toString()); put("exposurePointSupported", exposurePointSupported); put("focusPointSupported", focusPointSupported); } }); } /** Sends a message to the Flutter client informing that the camera is closing. */ void sendCameraClosingEvent() { send(CameraEventType.CLOSING); } /** * Sends a message to the Flutter client informing that an error occurred while interacting with * the camera. * * @param description contains details regarding the error that occurred. */ void sendCameraErrorEvent(@Nullable String description) { this.send( CameraEventType.ERROR, new HashMap() { { if (!TextUtils.isEmpty(description)) put("description", description); } }); } private void send(CameraEventType eventType) { send(eventType, new HashMap<>()); } private void send(CameraEventType eventType, Map args) { if (cameraChannel == null) { return; } handler.post( new Runnable() { @Override public void run() { cameraChannel.invokeMethod(eventType.method, args); } }); } private void send(DeviceEventType eventType) { send(eventType, new HashMap<>()); } private void send(DeviceEventType eventType, Map args) { if (deviceChannel == null) { return; } handler.post( new Runnable() { @Override public void run() { deviceChannel.invokeMethod(eventType.method, args); } }); } /** * Send a success payload to a {@link MethodChannel.Result} on the main thread. * * @param payload The payload to send. */ public void finish(MethodChannel.Result result, Object payload) { handler.post(() -> result.success(payload)); } /** * Send an error payload to a {@link MethodChannel.Result} on the main thread. * * @param errorCode error code. * @param errorMessage error message. * @param errorDetails error details. */ public void error( MethodChannel.Result result, String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) { handler.post(() -> result.error(errorCode, errorMessage, errorDetails)); } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/ImageSaver.java ================================================ // Copyright 2013 The Flutter 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.media.Image; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; /** Saves a JPEG {@link Image} into the specified {@link File}. */ public class ImageSaver implements Runnable { /** The JPEG image */ private final Image image; /** The file we save the image into. */ private final File file; /** Used to report the status of the save action. */ private final Callback callback; /** * Creates an instance of the ImageSaver runnable * * @param image - The image to save * @param file - The file to save the image to * @param callback - The callback that is run on completion, or when an error is encountered. */ ImageSaver(@NonNull Image image, @NonNull File file, @NonNull Callback callback) { this.image = image; this.file = file; this.callback = callback; } @Override public void run() { ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); FileOutputStream output = null; try { output = FileOutputStreamFactory.create(file); output.write(bytes); callback.onComplete(file.getAbsolutePath()); } catch (IOException e) { callback.onError("IOError", "Failed saving image"); } finally { image.close(); if (null != output) { try { output.close(); } catch (IOException e) { callback.onError("cameraAccess", e.getMessage()); } } } } /** * The interface for the callback that is passed to ImageSaver, for detecting completion or * failure of the image saving task. */ public interface Callback { /** * Called when the image file has been saved successfully. * * @param absolutePath - The absolute path of the file that was saved. */ void onComplete(String absolutePath); /** * Called when an error is encountered while saving the image file. * * @param errorCode - The error code. * @param errorMessage - The human readable error message. */ void onError(String errorCode, String errorMessage); } /** Factory class that assists in creating a {@link FileOutputStream} instance. */ static class FileOutputStreamFactory { /** * Creates a new instance of the {@link FileOutputStream} class. * *

This method is visible for testing purposes only and should never be used outside this * * class. * * @param file - The file to create the output stream for * @return new instance of the {@link FileOutputStream} class. * @throws FileNotFoundException when the supplied file could not be found. */ @VisibleForTesting public static FileOutputStream create(File file) throws FileNotFoundException { return new FileOutputStream(file); } } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java ================================================ // Copyright 2013 The Flutter 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.app.Activity; import android.hardware.camera2.CameraAccessException; import android.os.Handler; import android.os.Looper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.EventChannel; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry; import io.flutter.plugins.camera.features.CameraFeatureFactoryImpl; import io.flutter.plugins.camera.features.Point; import io.flutter.plugins.camera.features.autofocus.FocusMode; import io.flutter.plugins.camera.features.exposurelock.ExposureMode; import io.flutter.plugins.camera.features.flash.FlashMode; import io.flutter.plugins.camera.features.resolution.ResolutionPreset; import io.flutter.view.TextureRegistry; import java.util.HashMap; import java.util.Map; import java.util.Objects; final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { private final Activity activity; private final BinaryMessenger messenger; private final CameraPermissions cameraPermissions; private final PermissionsRegistry permissionsRegistry; private final TextureRegistry textureRegistry; private final MethodChannel methodChannel; private final EventChannel imageStreamChannel; private @Nullable Camera camera; MethodCallHandlerImpl( Activity activity, BinaryMessenger messenger, CameraPermissions cameraPermissions, PermissionsRegistry permissionsAdder, TextureRegistry textureRegistry) { this.activity = activity; this.messenger = messenger; this.cameraPermissions = cameraPermissions; this.permissionsRegistry = permissionsAdder; this.textureRegistry = textureRegistry; methodChannel = new MethodChannel(messenger, "plugins.flutter.io/camera_android"); imageStreamChannel = new EventChannel(messenger, "plugins.flutter.io/camera_android/imageStream"); methodChannel.setMethodCallHandler(this); } @Override public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) { switch (call.method) { case "availableCameras": try { result.success(CameraUtils.getAvailableCameras(activity)); } catch (Exception e) { handleException(e, result); } break; case "create": { if (camera != null) { camera.close(); } cameraPermissions.requestPermissions( activity, permissionsRegistry, call.argument("enableAudio"), (String errCode, String errDesc) -> { if (errCode == null) { try { instantiateCamera(call, result); } catch (Exception e) { handleException(e, result); } } else { result.error(errCode, errDesc, null); } }); break; } case "initialize": { if (camera != null) { try { camera.open(call.argument("imageFormatGroup")); result.success(null); } catch (Exception e) { handleException(e, result); } } else { result.error( "cameraNotFound", "Camera not found. Please call the 'create' method before calling 'initialize'.", null); } break; } case "takePicture": { camera.takePicture(result); break; } case "prepareForVideoRecording": { // This optimization is not required for Android. result.success(null); break; } case "startVideoRecording": { camera.startVideoRecording( result, Objects.equals(call.argument("enableStream"), true) ? imageStreamChannel : null); break; } case "stopVideoRecording": { camera.stopVideoRecording(result); break; } case "pauseVideoRecording": { camera.pauseVideoRecording(result); break; } case "resumeVideoRecording": { camera.resumeVideoRecording(result); break; } case "setFlashMode": { String modeStr = call.argument("mode"); FlashMode mode = FlashMode.getValueForString(modeStr); if (mode == null) { result.error("setFlashModeFailed", "Unknown flash mode " + modeStr, null); return; } try { camera.setFlashMode(result, mode); } catch (Exception e) { handleException(e, result); } break; } case "setExposureMode": { String modeStr = call.argument("mode"); ExposureMode mode = ExposureMode.getValueForString(modeStr); if (mode == null) { result.error("setExposureModeFailed", "Unknown exposure mode " + modeStr, null); return; } try { camera.setExposureMode(result, mode); } catch (Exception e) { handleException(e, result); } break; } case "setExposurePoint": { Boolean reset = call.argument("reset"); Double x = null; Double y = null; if (reset == null || !reset) { x = call.argument("x"); y = call.argument("y"); } try { camera.setExposurePoint(result, new Point(x, y)); } catch (Exception e) { handleException(e, result); } break; } case "getMinExposureOffset": { try { result.success(camera.getMinExposureOffset()); } catch (Exception e) { handleException(e, result); } break; } case "getMaxExposureOffset": { try { result.success(camera.getMaxExposureOffset()); } catch (Exception e) { handleException(e, result); } break; } case "getExposureOffsetStepSize": { try { result.success(camera.getExposureOffsetStepSize()); } catch (Exception e) { handleException(e, result); } break; } case "setExposureOffset": { try { camera.setExposureOffset(result, call.argument("offset")); } catch (Exception e) { handleException(e, result); } break; } case "setFocusMode": { String modeStr = call.argument("mode"); FocusMode mode = FocusMode.getValueForString(modeStr); if (mode == null) { result.error("setFocusModeFailed", "Unknown focus mode " + modeStr, null); return; } try { camera.setFocusMode(result, mode); } catch (Exception e) { handleException(e, result); } break; } case "setFocusPoint": { Boolean reset = call.argument("reset"); Double x = null; Double y = null; if (reset == null || !reset) { x = call.argument("x"); y = call.argument("y"); } try { camera.setFocusPoint(result, new Point(x, y)); } catch (Exception e) { handleException(e, result); } break; } case "startImageStream": { try { camera.startPreviewWithImageStream(imageStreamChannel); result.success(null); } catch (Exception e) { handleException(e, result); } break; } case "stopImageStream": { try { camera.startPreview(); result.success(null); } catch (Exception e) { handleException(e, result); } break; } case "getMaxZoomLevel": { assert camera != null; try { float maxZoomLevel = camera.getMaxZoomLevel(); result.success(maxZoomLevel); } catch (Exception e) { handleException(e, result); } break; } case "getMinZoomLevel": { assert camera != null; try { float minZoomLevel = camera.getMinZoomLevel(); result.success(minZoomLevel); } catch (Exception e) { handleException(e, result); } break; } case "setZoomLevel": { assert camera != null; Double zoom = call.argument("zoom"); if (zoom == null) { result.error( "ZOOM_ERROR", "setZoomLevel is called without specifying a zoom level.", null); return; } try { camera.setZoomLevel(result, zoom.floatValue()); } catch (Exception e) { handleException(e, result); } break; } case "lockCaptureOrientation": { PlatformChannel.DeviceOrientation orientation = CameraUtils.deserializeDeviceOrientation(call.argument("orientation")); try { camera.lockCaptureOrientation(orientation); result.success(null); } catch (Exception e) { handleException(e, result); } break; } case "unlockCaptureOrientation": { try { camera.unlockCaptureOrientation(); result.success(null); } catch (Exception e) { handleException(e, result); } break; } case "pausePreview": { try { camera.pausePreview(); result.success(null); } catch (Exception e) { handleException(e, result); } break; } case "resumePreview": { camera.resumePreview(); result.success(null); break; } case "dispose": { if (camera != null) { camera.dispose(); } result.success(null); break; } default: result.notImplemented(); break; } } void stopListening() { methodChannel.setMethodCallHandler(null); } private void instantiateCamera(MethodCall call, Result result) throws CameraAccessException { String cameraName = call.argument("cameraName"); String preset = call.argument("resolutionPreset"); boolean enableAudio = call.argument("enableAudio"); TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture = textureRegistry.createSurfaceTexture(); DartMessenger dartMessenger = new DartMessenger( messenger, flutterSurfaceTexture.id(), new Handler(Looper.getMainLooper())); CameraProperties cameraProperties = new CameraPropertiesImpl(cameraName, CameraUtils.getCameraManager(activity)); ResolutionPreset resolutionPreset = ResolutionPreset.valueOf(preset); camera = new Camera( activity, flutterSurfaceTexture, new CameraFeatureFactoryImpl(), dartMessenger, cameraProperties, resolutionPreset, enableAudio); Map reply = new HashMap<>(); reply.put("cameraId", flutterSurfaceTexture.id()); result.success(reply); } // We move catching CameraAccessException out of onMethodCall because it causes a crash // on plugin registration for sdks incompatible with Camera2 (< 21). We want this plugin to // to be able to compile with <21 sdks for apps that want the camera and support earlier version. @SuppressWarnings("ConstantConditions") private void handleException(Exception exception, Result result) { if (exception instanceof CameraAccessException) { result.error("CameraAccess", exception.getMessage(), null); return; } // CameraAccessException can not be cast to a RuntimeException. throw (RuntimeException) exception; } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/CameraFeature.java ================================================ // Copyright 2013 The Flutter 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.features; import android.hardware.camera2.CaptureRequest; import androidx.annotation.NonNull; import io.flutter.plugins.camera.CameraProperties; /** * An interface describing a feature in the camera. This holds a setting value of type T and must * implement a means to check if this setting is supported by the current camera properties. It also * must implement a builder update method which will update a given capture request builder for this * feature's current setting value. * * @param */ public abstract class CameraFeature { protected final CameraProperties cameraProperties; protected CameraFeature(@NonNull CameraProperties cameraProperties) { this.cameraProperties = cameraProperties; } /** Debug name for this feature. */ public abstract String getDebugName(); /** * Gets the current value of this feature's setting. * * @return Current value of this feature's setting. */ public abstract T getValue(); /** * Sets a new value for this feature's setting. * * @param value New value for this feature's setting. */ public abstract void setValue(T value); /** * Returns whether or not this feature is supported. * *

When the feature is not supported any {@see #value} is simply ignored by the camera plugin. * * @return boolean Whether or not this feature is supported. */ public abstract boolean checkIsSupported(); /** * Updates the setting in a provided {@see android.hardware.camera2.CaptureRequest.Builder}. * * @param requestBuilder A {@see android.hardware.camera2.CaptureRequest.Builder} instance used to * configure the settings and outputs needed to capture a single image from the camera device. */ public abstract void updateBuilder(CaptureRequest.Builder requestBuilder); } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactory.java ================================================ // Copyright 2013 The Flutter 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.features; import android.app.Activity; import androidx.annotation.NonNull; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.DartMessenger; import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature; import io.flutter.plugins.camera.features.exposurelock.ExposureLockFeature; import io.flutter.plugins.camera.features.exposureoffset.ExposureOffsetFeature; import io.flutter.plugins.camera.features.exposurepoint.ExposurePointFeature; import io.flutter.plugins.camera.features.flash.FlashFeature; import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature; import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature; import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionPreset; import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature; import io.flutter.plugins.camera.features.zoomlevel.ZoomLevelFeature; /** * Factory for creating the supported feature implementation controlling different aspects of the * {@link android.hardware.camera2.CaptureRequest}. */ public interface CameraFeatureFactory { /** * Creates a new instance of the auto focus feature. * * @param cameraProperties instance of the CameraProperties class containing information about the * cameras features. * @param recordingVideo indicates if the camera is currently recording. * @return newly created instance of the AutoFocusFeature class. */ AutoFocusFeature createAutoFocusFeature( @NonNull CameraProperties cameraProperties, boolean recordingVideo); /** * Creates a new instance of the exposure lock feature. * * @param cameraProperties instance of the CameraProperties class containing information about the * cameras features. * @return newly created instance of the ExposureLockFeature class. */ ExposureLockFeature createExposureLockFeature(@NonNull CameraProperties cameraProperties); /** * Creates a new instance of the exposure offset feature. * * @param cameraProperties instance of the CameraProperties class containing information about the * cameras features. * @return newly created instance of the ExposureOffsetFeature class. */ ExposureOffsetFeature createExposureOffsetFeature(@NonNull CameraProperties cameraProperties); /** * Creates a new instance of the flash feature. * * @param cameraProperties instance of the CameraProperties class containing information about the * cameras features. * @return newly created instance of the FlashFeature class. */ FlashFeature createFlashFeature(@NonNull CameraProperties cameraProperties); /** * Creates a new instance of the resolution feature. * * @param cameraProperties instance of the CameraProperties class containing information about the * cameras features. * @param initialSetting initial resolution preset. * @param cameraName the name of the camera which can be used to identify the camera device. * @return newly created instance of the ResolutionFeature class. */ ResolutionFeature createResolutionFeature( @NonNull CameraProperties cameraProperties, ResolutionPreset initialSetting, String cameraName); /** * Creates a new instance of the focus point feature. * * @param cameraProperties instance of the CameraProperties class containing information about the * cameras features. * @param sensorOrientationFeature instance of the SensorOrientationFeature class containing * information about the sensor and device orientation. * @return newly created instance of the FocusPointFeature class. */ FocusPointFeature createFocusPointFeature( @NonNull CameraProperties cameraProperties, @NonNull SensorOrientationFeature sensorOrientationFeature); /** * Creates a new instance of the FPS range feature. * * @param cameraProperties instance of the CameraProperties class containing information about the * cameras features. * @return newly created instance of the FpsRangeFeature class. */ FpsRangeFeature createFpsRangeFeature(@NonNull CameraProperties cameraProperties); /** * Creates a new instance of the sensor orientation feature. * * @param cameraProperties instance of the CameraProperties class containing information about the * cameras features. * @param activity current activity associated with the camera plugin. * @param dartMessenger instance of the DartMessenger class, used to send state updates back to * Dart. * @return newly created instance of the SensorOrientationFeature class. */ SensorOrientationFeature createSensorOrientationFeature( @NonNull CameraProperties cameraProperties, @NonNull Activity activity, @NonNull DartMessenger dartMessenger); /** * Creates a new instance of the zoom level feature. * * @param cameraProperties instance of the CameraProperties class containing information about the * cameras features. * @return newly created instance of the ZoomLevelFeature class. */ ZoomLevelFeature createZoomLevelFeature(@NonNull CameraProperties cameraProperties); /** * Creates a new instance of the exposure point feature. * * @param cameraProperties instance of the CameraProperties class containing information about the * cameras features. * @param sensorOrientationFeature instance of the SensorOrientationFeature class containing * information about the sensor and device orientation. * @return newly created instance of the ExposurePointFeature class. */ ExposurePointFeature createExposurePointFeature( @NonNull CameraProperties cameraProperties, @NonNull SensorOrientationFeature sensorOrientationFeature); /** * Creates a new instance of the noise reduction feature. * * @param cameraProperties instance of the CameraProperties class containing information about the * cameras features. * @return newly created instance of the NoiseReductionFeature class. */ NoiseReductionFeature createNoiseReductionFeature(@NonNull CameraProperties cameraProperties); } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatureFactoryImpl.java ================================================ // Copyright 2013 The Flutter 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.features; import android.app.Activity; import androidx.annotation.NonNull; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.DartMessenger; import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature; import io.flutter.plugins.camera.features.exposurelock.ExposureLockFeature; import io.flutter.plugins.camera.features.exposureoffset.ExposureOffsetFeature; import io.flutter.plugins.camera.features.exposurepoint.ExposurePointFeature; import io.flutter.plugins.camera.features.flash.FlashFeature; import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature; import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature; import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionPreset; import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature; import io.flutter.plugins.camera.features.zoomlevel.ZoomLevelFeature; /** * Implementation of the {@link CameraFeatureFactory} interface creating the supported feature * implementation controlling different aspects of the {@link * android.hardware.camera2.CaptureRequest}. */ public class CameraFeatureFactoryImpl implements CameraFeatureFactory { @Override public AutoFocusFeature createAutoFocusFeature( @NonNull CameraProperties cameraProperties, boolean recordingVideo) { return new AutoFocusFeature(cameraProperties, recordingVideo); } @Override public ExposureLockFeature createExposureLockFeature(@NonNull CameraProperties cameraProperties) { return new ExposureLockFeature(cameraProperties); } @Override public ExposureOffsetFeature createExposureOffsetFeature( @NonNull CameraProperties cameraProperties) { return new ExposureOffsetFeature(cameraProperties); } @Override public FlashFeature createFlashFeature(@NonNull CameraProperties cameraProperties) { return new FlashFeature(cameraProperties); } @Override public ResolutionFeature createResolutionFeature( @NonNull CameraProperties cameraProperties, ResolutionPreset initialSetting, String cameraName) { return new ResolutionFeature(cameraProperties, initialSetting, cameraName); } @Override public FocusPointFeature createFocusPointFeature( @NonNull CameraProperties cameraProperties, @NonNull SensorOrientationFeature sensorOrientationFeature) { return new FocusPointFeature(cameraProperties, sensorOrientationFeature); } @Override public FpsRangeFeature createFpsRangeFeature(@NonNull CameraProperties cameraProperties) { return new FpsRangeFeature(cameraProperties); } @Override public SensorOrientationFeature createSensorOrientationFeature( @NonNull CameraProperties cameraProperties, @NonNull Activity activity, @NonNull DartMessenger dartMessenger) { return new SensorOrientationFeature(cameraProperties, activity, dartMessenger); } @Override public ZoomLevelFeature createZoomLevelFeature(@NonNull CameraProperties cameraProperties) { return new ZoomLevelFeature(cameraProperties); } @Override public ExposurePointFeature createExposurePointFeature( @NonNull CameraProperties cameraProperties, @NonNull SensorOrientationFeature sensorOrientationFeature) { return new ExposurePointFeature(cameraProperties, sensorOrientationFeature); } @Override public NoiseReductionFeature createNoiseReductionFeature( @NonNull CameraProperties cameraProperties) { return new NoiseReductionFeature(cameraProperties); } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/CameraFeatures.java ================================================ // Copyright 2013 The Flutter 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.features; import android.app.Activity; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.DartMessenger; import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature; import io.flutter.plugins.camera.features.exposurelock.ExposureLockFeature; import io.flutter.plugins.camera.features.exposureoffset.ExposureOffsetFeature; import io.flutter.plugins.camera.features.exposurepoint.ExposurePointFeature; import io.flutter.plugins.camera.features.flash.FlashFeature; import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature; import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature; import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionPreset; import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature; import io.flutter.plugins.camera.features.zoomlevel.ZoomLevelFeature; import java.util.Collection; import java.util.HashMap; import java.util.Map; /** * These are all of our available features in the camera. Used in the Camera to access all features * in a simpler way. */ public class CameraFeatures { private static final String AUTO_FOCUS = "AUTO_FOCUS"; private static final String EXPOSURE_LOCK = "EXPOSURE_LOCK"; private static final String EXPOSURE_OFFSET = "EXPOSURE_OFFSET"; private static final String EXPOSURE_POINT = "EXPOSURE_POINT"; private static final String FLASH = "FLASH"; private static final String FOCUS_POINT = "FOCUS_POINT"; private static final String FPS_RANGE = "FPS_RANGE"; private static final String NOISE_REDUCTION = "NOISE_REDUCTION"; private static final String REGION_BOUNDARIES = "REGION_BOUNDARIES"; private static final String RESOLUTION = "RESOLUTION"; private static final String SENSOR_ORIENTATION = "SENSOR_ORIENTATION"; private static final String ZOOM_LEVEL = "ZOOM_LEVEL"; public static CameraFeatures init( CameraFeatureFactory cameraFeatureFactory, CameraProperties cameraProperties, Activity activity, DartMessenger dartMessenger, ResolutionPreset resolutionPreset) { CameraFeatures cameraFeatures = new CameraFeatures(); cameraFeatures.setAutoFocus( cameraFeatureFactory.createAutoFocusFeature(cameraProperties, false)); cameraFeatures.setExposureLock( cameraFeatureFactory.createExposureLockFeature(cameraProperties)); cameraFeatures.setExposureOffset( cameraFeatureFactory.createExposureOffsetFeature(cameraProperties)); SensorOrientationFeature sensorOrientationFeature = cameraFeatureFactory.createSensorOrientationFeature( cameraProperties, activity, dartMessenger); cameraFeatures.setSensorOrientation(sensorOrientationFeature); cameraFeatures.setExposurePoint( cameraFeatureFactory.createExposurePointFeature( cameraProperties, sensorOrientationFeature)); cameraFeatures.setFlash(cameraFeatureFactory.createFlashFeature(cameraProperties)); cameraFeatures.setFocusPoint( cameraFeatureFactory.createFocusPointFeature(cameraProperties, sensorOrientationFeature)); cameraFeatures.setFpsRange(cameraFeatureFactory.createFpsRangeFeature(cameraProperties)); cameraFeatures.setNoiseReduction( cameraFeatureFactory.createNoiseReductionFeature(cameraProperties)); cameraFeatures.setResolution( cameraFeatureFactory.createResolutionFeature( cameraProperties, resolutionPreset, cameraProperties.getCameraName())); cameraFeatures.setZoomLevel(cameraFeatureFactory.createZoomLevelFeature(cameraProperties)); return cameraFeatures; } private Map featureMap = new HashMap<>(); /** * Gets a collection of all features that have been set. * * @return A collection of all features that have been set. */ public Collection getAllFeatures() { return this.featureMap.values(); } /** * Gets the auto focus feature if it has been set. * * @return the auto focus feature. */ public AutoFocusFeature getAutoFocus() { return (AutoFocusFeature) featureMap.get(AUTO_FOCUS); } /** * Sets the instance of the auto focus feature. * * @param autoFocus the {@link AutoFocusFeature} instance to set. */ public void setAutoFocus(AutoFocusFeature autoFocus) { this.featureMap.put(AUTO_FOCUS, autoFocus); } /** * Gets the exposure lock feature if it has been set. * * @return the exposure lock feature. */ public ExposureLockFeature getExposureLock() { return (ExposureLockFeature) featureMap.get(EXPOSURE_LOCK); } /** * Sets the instance of the exposure lock feature. * * @param exposureLock the {@link ExposureLockFeature} instance to set. */ public void setExposureLock(ExposureLockFeature exposureLock) { this.featureMap.put(EXPOSURE_LOCK, exposureLock); } /** * Gets the exposure offset feature if it has been set. * * @return the exposure offset feature. */ public ExposureOffsetFeature getExposureOffset() { return (ExposureOffsetFeature) featureMap.get(EXPOSURE_OFFSET); } /** * Sets the instance of the exposure offset feature. * * @param exposureOffset the {@link ExposureOffsetFeature} instance to set. */ public void setExposureOffset(ExposureOffsetFeature exposureOffset) { this.featureMap.put(EXPOSURE_OFFSET, exposureOffset); } /** * Gets the exposure point feature if it has been set. * * @return the exposure point feature. */ public ExposurePointFeature getExposurePoint() { return (ExposurePointFeature) featureMap.get(EXPOSURE_POINT); } /** * Sets the instance of the exposure point feature. * * @param exposurePoint the {@link ExposurePointFeature} instance to set. */ public void setExposurePoint(ExposurePointFeature exposurePoint) { this.featureMap.put(EXPOSURE_POINT, exposurePoint); } /** * Gets the flash feature if it has been set. * * @return the flash feature. */ public FlashFeature getFlash() { return (FlashFeature) featureMap.get(FLASH); } /** * Sets the instance of the flash feature. * * @param flash the {@link FlashFeature} instance to set. */ public void setFlash(FlashFeature flash) { this.featureMap.put(FLASH, flash); } /** * Gets the focus point feature if it has been set. * * @return the focus point feature. */ public FocusPointFeature getFocusPoint() { return (FocusPointFeature) featureMap.get(FOCUS_POINT); } /** * Sets the instance of the focus point feature. * * @param focusPoint the {@link FocusPointFeature} instance to set. */ public void setFocusPoint(FocusPointFeature focusPoint) { this.featureMap.put(FOCUS_POINT, focusPoint); } /** * Gets the fps range feature if it has been set. * * @return the fps range feature. */ public FpsRangeFeature getFpsRange() { return (FpsRangeFeature) featureMap.get(FPS_RANGE); } /** * Sets the instance of the fps range feature. * * @param fpsRange the {@link FpsRangeFeature} instance to set. */ public void setFpsRange(FpsRangeFeature fpsRange) { this.featureMap.put(FPS_RANGE, fpsRange); } /** * Gets the noise reduction feature if it has been set. * * @return the noise reduction feature. */ public NoiseReductionFeature getNoiseReduction() { return (NoiseReductionFeature) featureMap.get(NOISE_REDUCTION); } /** * Sets the instance of the noise reduction feature. * * @param noiseReduction the {@link NoiseReductionFeature} instance to set. */ public void setNoiseReduction(NoiseReductionFeature noiseReduction) { this.featureMap.put(NOISE_REDUCTION, noiseReduction); } /** * Gets the resolution feature if it has been set. * * @return the resolution feature. */ public ResolutionFeature getResolution() { return (ResolutionFeature) featureMap.get(RESOLUTION); } /** * Sets the instance of the resolution feature. * * @param resolution the {@link ResolutionFeature} instance to set. */ public void setResolution(ResolutionFeature resolution) { this.featureMap.put(RESOLUTION, resolution); } /** * Gets the sensor orientation feature if it has been set. * * @return the sensor orientation feature. */ public SensorOrientationFeature getSensorOrientation() { return (SensorOrientationFeature) featureMap.get(SENSOR_ORIENTATION); } /** * Sets the instance of the sensor orientation feature. * * @param sensorOrientation the {@link SensorOrientationFeature} instance to set. */ public void setSensorOrientation(SensorOrientationFeature sensorOrientation) { this.featureMap.put(SENSOR_ORIENTATION, sensorOrientation); } /** * Gets the zoom level feature if it has been set. * * @return the zoom level feature. */ public ZoomLevelFeature getZoomLevel() { return (ZoomLevelFeature) featureMap.get(ZOOM_LEVEL); } /** * Sets the instance of the zoom level feature. * * @param zoomLevel the {@link ZoomLevelFeature} instance to set. */ public void setZoomLevel(ZoomLevelFeature zoomLevel) { this.featureMap.put(ZOOM_LEVEL, zoomLevel); } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/Point.java ================================================ // Copyright 2013 The Flutter 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.features; /** Represents a point on an x/y axis. */ public class Point { public final Double x; public final Double y; public Point(Double x, Double y) { this.x = x; this.y = y; } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/autofocus/AutoFocusFeature.java ================================================ // Copyright 2013 The Flutter 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.features.autofocus; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureRequest; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.features.CameraFeature; /** Controls the auto focus configuration on the {@see anddroid.hardware.camera2} API. */ public class AutoFocusFeature extends CameraFeature { private FocusMode currentSetting = FocusMode.auto; // When switching recording modes this feature is re-created with the appropriate setting here. private final boolean recordingVideo; /** * Creates a new instance of the {@see AutoFocusFeature}. * * @param cameraProperties Collection of the characteristics for the current camera device. * @param recordingVideo Indicates whether the camera is currently recording video. */ public AutoFocusFeature(CameraProperties cameraProperties, boolean recordingVideo) { super(cameraProperties); this.recordingVideo = recordingVideo; } @Override public String getDebugName() { return "AutoFocusFeature"; } @Override public FocusMode getValue() { return currentSetting; } @Override public void setValue(FocusMode value) { this.currentSetting = value; } @Override public boolean checkIsSupported() { int[] modes = cameraProperties.getControlAutoFocusAvailableModes(); final Float minFocus = cameraProperties.getLensInfoMinimumFocusDistance(); // Check if the focal length of the lens is fixed. If the minimum focus distance == 0, then the // focal length is fixed. The minimum focus distance can be null on some devices: https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE boolean isFixedLength = minFocus == null || minFocus == 0; return !isFixedLength && !(modes.length == 0 || (modes.length == 1 && modes[0] == CameraCharacteristics.CONTROL_AF_MODE_OFF)); } @Override public void updateBuilder(CaptureRequest.Builder requestBuilder) { if (!checkIsSupported()) { return; } switch (currentSetting) { case locked: // When locking the auto-focus the camera device should do a one-time focus and afterwards // set the auto-focus to idle. This is accomplished by setting the CONTROL_AF_MODE to // CONTROL_AF_MODE_AUTO. requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); break; case auto: requestBuilder.set( CaptureRequest.CONTROL_AF_MODE, recordingVideo ? CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO : CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); default: break; } } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/autofocus/FocusMode.java ================================================ // Copyright 2013 The Flutter 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.features.autofocus; // Mirrors focus_mode.dart public enum FocusMode { auto("auto"), locked("locked"); private final String strValue; FocusMode(String strValue) { this.strValue = strValue; } public static FocusMode getValueForString(String modeStr) { for (FocusMode value : values()) { if (value.strValue.equals(modeStr)) { return value; } } return null; } @Override public String toString() { return strValue; } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeature.java ================================================ // Copyright 2013 The Flutter 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.features.exposurelock; import android.hardware.camera2.CaptureRequest; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.features.CameraFeature; /** Controls whether or not the exposure mode is currently locked or automatically metering. */ public class ExposureLockFeature extends CameraFeature { private ExposureMode currentSetting = ExposureMode.auto; /** * Creates a new instance of the {@see ExposureLockFeature}. * * @param cameraProperties Collection of the characteristics for the current camera device. */ public ExposureLockFeature(CameraProperties cameraProperties) { super(cameraProperties); } @Override public String getDebugName() { return "ExposureLockFeature"; } @Override public ExposureMode getValue() { return currentSetting; } @Override public void setValue(ExposureMode value) { this.currentSetting = value; } // Available on all devices. @Override public boolean checkIsSupported() { return true; } @Override public void updateBuilder(CaptureRequest.Builder requestBuilder) { if (!checkIsSupported()) { return; } requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, currentSetting == ExposureMode.locked); } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/exposurelock/ExposureMode.java ================================================ // Copyright 2013 The Flutter 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.features.exposurelock; // Mirrors exposure_mode.dart public enum ExposureMode { auto("auto"), locked("locked"); private final String strValue; ExposureMode(String strValue) { this.strValue = strValue; } /** * Tries to convert the supplied string into an {@see ExposureMode} enum value. * *

When the supplied string doesn't match a valid {@see ExposureMode} enum value, null is * returned. * * @param modeStr String value to convert into an {@see ExposureMode} enum value. * @return Matching {@see ExposureMode} enum value, or null if no match is found. */ public static ExposureMode getValueForString(String modeStr) { for (ExposureMode value : values()) { if (value.strValue.equals(modeStr)) { return value; } } return null; } @Override public String toString() { return strValue; } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeature.java ================================================ // Copyright 2013 The Flutter 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.features.exposureoffset; import android.hardware.camera2.CaptureRequest; import android.util.Range; import androidx.annotation.NonNull; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.features.CameraFeature; /** Controls the exposure offset making the resulting image brighter or darker. */ public class ExposureOffsetFeature extends CameraFeature { private double currentSetting = 0; /** * Creates a new instance of the {@link ExposureOffsetFeature}. * * @param cameraProperties Collection of the characteristics for the current camera device. */ public ExposureOffsetFeature(CameraProperties cameraProperties) { super(cameraProperties); } @Override public String getDebugName() { return "ExposureOffsetFeature"; } @Override public Double getValue() { return currentSetting; } @Override public void setValue(@NonNull Double value) { double stepSize = getExposureOffsetStepSize(); this.currentSetting = value / stepSize; } // Available on all devices. @Override public boolean checkIsSupported() { return true; } @Override public void updateBuilder(CaptureRequest.Builder requestBuilder) { if (!checkIsSupported()) { return; } requestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, (int) currentSetting); } /** * Returns the minimum exposure offset. * * @return double Minimum exposure offset. */ public double getMinExposureOffset() { Range range = cameraProperties.getControlAutoExposureCompensationRange(); double minStepped = range == null ? 0 : range.getLower(); double stepSize = getExposureOffsetStepSize(); return minStepped * stepSize; } /** * Returns the maximum exposure offset. * * @return double Maximum exposure offset. */ public double getMaxExposureOffset() { Range range = cameraProperties.getControlAutoExposureCompensationRange(); double maxStepped = range == null ? 0 : range.getUpper(); double stepSize = getExposureOffsetStepSize(); return maxStepped * stepSize; } /** * Returns the smallest step by which the exposure compensation can be changed. * *

Example: if this has a value of 0.5, then an aeExposureCompensation setting of -2 means that * the actual AE offset is -1. More details can be found in the official Android documentation: * https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#CONTROL_AE_COMPENSATION_STEP * * @return double Smallest step by which the exposure compensation can be changed. */ public double getExposureOffsetStepSize() { return cameraProperties.getControlAutoExposureCompensationStep(); } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeature.java ================================================ // Copyright 2013 The Flutter 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.features.exposurepoint; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.MeteringRectangle; import android.util.Size; import androidx.annotation.NonNull; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.CameraRegionUtils; import io.flutter.plugins.camera.features.CameraFeature; import io.flutter.plugins.camera.features.Point; import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature; /** Exposure point controls where in the frame exposure metering will come from. */ public class ExposurePointFeature extends CameraFeature { private Size cameraBoundaries; private Point exposurePoint; private MeteringRectangle exposureRectangle; private final SensorOrientationFeature sensorOrientationFeature; /** * Creates a new instance of the {@link ExposurePointFeature}. * * @param cameraProperties Collection of the characteristics for the current camera device. */ public ExposurePointFeature( CameraProperties cameraProperties, SensorOrientationFeature sensorOrientationFeature) { super(cameraProperties); this.sensorOrientationFeature = sensorOrientationFeature; } /** * Sets the camera boundaries that are required for the exposure point feature to function. * * @param cameraBoundaries - The camera boundaries to set. */ public void setCameraBoundaries(@NonNull Size cameraBoundaries) { this.cameraBoundaries = cameraBoundaries; this.buildExposureRectangle(); } @Override public String getDebugName() { return "ExposurePointFeature"; } @Override public Point getValue() { return exposurePoint; } @Override public void setValue(Point value) { this.exposurePoint = (value == null || value.x == null || value.y == null) ? null : value; this.buildExposureRectangle(); } // Whether or not this camera can set the exposure point. @Override public boolean checkIsSupported() { Integer supportedRegions = cameraProperties.getControlMaxRegionsAutoExposure(); return supportedRegions != null && supportedRegions > 0; } @Override public void updateBuilder(CaptureRequest.Builder requestBuilder) { if (!checkIsSupported()) { return; } requestBuilder.set( CaptureRequest.CONTROL_AE_REGIONS, exposureRectangle == null ? null : new MeteringRectangle[] {exposureRectangle}); } private void buildExposureRectangle() { if (this.cameraBoundaries == null) { throw new AssertionError( "The cameraBoundaries should be set (using `ExposurePointFeature.setCameraBoundaries(Size)`) before updating the exposure point."); } if (this.exposurePoint == null) { this.exposureRectangle = null; } else { PlatformChannel.DeviceOrientation orientation = this.sensorOrientationFeature.getLockedCaptureOrientation(); if (orientation == null) { orientation = this.sensorOrientationFeature.getDeviceOrientationManager().getLastUIOrientation(); } this.exposureRectangle = CameraRegionUtils.convertPointToMeteringRectangle( this.cameraBoundaries, this.exposurePoint.x, this.exposurePoint.y, orientation); } } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/flash/FlashFeature.java ================================================ // Copyright 2013 The Flutter 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.features.flash; import android.hardware.camera2.CaptureRequest; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.features.CameraFeature; /** Controls the flash configuration on the {@link android.hardware.camera2} API. */ public class FlashFeature extends CameraFeature { private FlashMode currentSetting = FlashMode.auto; /** * Creates a new instance of the {@link FlashFeature}. * * @param cameraProperties Collection of characteristics for the current camera device. */ public FlashFeature(CameraProperties cameraProperties) { super(cameraProperties); } @Override public String getDebugName() { return "FlashFeature"; } @Override public FlashMode getValue() { return currentSetting; } @Override public void setValue(FlashMode value) { this.currentSetting = value; } @Override public boolean checkIsSupported() { Boolean available = cameraProperties.getFlashInfoAvailable(); return available != null && available; } @Override public void updateBuilder(CaptureRequest.Builder requestBuilder) { if (!checkIsSupported()) { return; } switch (currentSetting) { case off: requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); break; case always: requestBuilder.set( CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH); requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); break; case torch: requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH); break; case auto: requestBuilder.set( CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); break; } } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/flash/FlashMode.java ================================================ // Copyright 2013 The Flutter 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.features.flash; // Mirrors flash_mode.dart public enum FlashMode { off("off"), auto("auto"), always("always"), torch("torch"); private final String strValue; FlashMode(String strValue) { this.strValue = strValue; } /** * Tries to convert the supplied string into a {@see FlashMode} enum value. * *

When the supplied string doesn't match a valid {@see FlashMode} enum value, null is * returned. * * @param modeStr String value to convert into an {@see FlashMode} enum value. * @return Matching {@see FlashMode} enum value, or null if no match is found. */ public static FlashMode getValueForString(String modeStr) { for (FlashMode value : values()) { if (value.strValue.equals(modeStr)) return value; } return null; } @Override public String toString() { return strValue; } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeature.java ================================================ // Copyright 2013 The Flutter 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.features.focuspoint; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.MeteringRectangle; import android.util.Size; import androidx.annotation.NonNull; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.CameraRegionUtils; import io.flutter.plugins.camera.features.CameraFeature; import io.flutter.plugins.camera.features.Point; import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature; /** Focus point controls where in the frame focus will come from. */ public class FocusPointFeature extends CameraFeature { private Size cameraBoundaries; private Point focusPoint; private MeteringRectangle focusRectangle; private final SensorOrientationFeature sensorOrientationFeature; /** * Creates a new instance of the {@link FocusPointFeature}. * * @param cameraProperties Collection of the characteristics for the current camera device. */ public FocusPointFeature( CameraProperties cameraProperties, SensorOrientationFeature sensorOrientationFeature) { super(cameraProperties); this.sensorOrientationFeature = sensorOrientationFeature; } /** * Sets the camera boundaries that are required for the focus point feature to function. * * @param cameraBoundaries - The camera boundaries to set. */ public void setCameraBoundaries(@NonNull Size cameraBoundaries) { this.cameraBoundaries = cameraBoundaries; this.buildFocusRectangle(); } @Override public String getDebugName() { return "FocusPointFeature"; } @Override public Point getValue() { return focusPoint; } @Override public void setValue(Point value) { this.focusPoint = value == null || value.x == null || value.y == null ? null : value; this.buildFocusRectangle(); } // Whether or not this camera can set the focus point. @Override public boolean checkIsSupported() { Integer supportedRegions = cameraProperties.getControlMaxRegionsAutoFocus(); return supportedRegions != null && supportedRegions > 0; } @Override public void updateBuilder(CaptureRequest.Builder requestBuilder) { if (!checkIsSupported()) { return; } requestBuilder.set( CaptureRequest.CONTROL_AF_REGIONS, focusRectangle == null ? null : new MeteringRectangle[] {focusRectangle}); } private void buildFocusRectangle() { if (this.cameraBoundaries == null) { throw new AssertionError( "The cameraBoundaries should be set (using `FocusPointFeature.setCameraBoundaries(Size)`) before updating the focus point."); } if (this.focusPoint == null) { this.focusRectangle = null; } else { PlatformChannel.DeviceOrientation orientation = this.sensorOrientationFeature.getLockedCaptureOrientation(); if (orientation == null) { orientation = this.sensorOrientationFeature.getDeviceOrientationManager().getLastUIOrientation(); } this.focusRectangle = CameraRegionUtils.convertPointToMeteringRectangle( this.cameraBoundaries, this.focusPoint.x, this.focusPoint.y, orientation); } } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeature.java ================================================ // Copyright 2013 The Flutter 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.features.fpsrange; import android.hardware.camera2.CaptureRequest; import android.os.Build; import android.util.Range; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.features.CameraFeature; /** * Controls the frames per seconds (FPS) range configuration on the {@link android.hardware.camera2} * API. */ public class FpsRangeFeature extends CameraFeature> { private static final Range MAX_PIXEL4A_RANGE = new Range<>(30, 30); private Range currentSetting; /** * Creates a new instance of the {@link FpsRangeFeature}. * * @param cameraProperties Collection of characteristics for the current camera device. */ public FpsRangeFeature(CameraProperties cameraProperties) { super(cameraProperties); if (isPixel4A()) { // HACK: There is a bug in the Pixel 4A where it cannot support 60fps modes // even though they are reported as supported by // `getControlAutoExposureAvailableTargetFpsRanges`. // For max device compatibility we will keep FPS under 60 even if they report they are // capable of achieving 60 fps. Highest working FPS is 30. // https://issuetracker.google.com/issues/189237151 currentSetting = MAX_PIXEL4A_RANGE; } else { Range[] ranges = cameraProperties.getControlAutoExposureAvailableTargetFpsRanges(); if (ranges != null) { for (Range range : ranges) { int upper = range.getUpper(); if (upper >= 10) { if (currentSetting == null || upper > currentSetting.getUpper()) { currentSetting = range; } } } } } } private boolean isPixel4A() { return Build.BRAND.equals("google") && Build.MODEL.equals("Pixel 4a"); } @Override public String getDebugName() { return "FpsRangeFeature"; } @Override public Range getValue() { return currentSetting; } @Override public void setValue(Range value) { this.currentSetting = value; } // Always supported @Override public boolean checkIsSupported() { return true; } @Override public void updateBuilder(CaptureRequest.Builder requestBuilder) { if (!checkIsSupported()) { return; } requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, currentSetting); } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionFeature.java ================================================ // Copyright 2013 The Flutter 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.features.noisereduction; import android.hardware.camera2.CaptureRequest; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.util.Log; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.features.CameraFeature; import java.util.HashMap; /** * This can either be enabled or disabled. Only full capability devices can set this to off. Legacy * and full support the fast mode. * https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES */ public class NoiseReductionFeature extends CameraFeature { private NoiseReductionMode currentSetting = NoiseReductionMode.fast; private final HashMap NOISE_REDUCTION_MODES = new HashMap<>(); /** * Creates a new instance of the {@link NoiseReductionFeature}. * * @param cameraProperties Collection of the characteristics for the current camera device. */ public NoiseReductionFeature(CameraProperties cameraProperties) { super(cameraProperties); NOISE_REDUCTION_MODES.put(NoiseReductionMode.off, CaptureRequest.NOISE_REDUCTION_MODE_OFF); NOISE_REDUCTION_MODES.put(NoiseReductionMode.fast, CaptureRequest.NOISE_REDUCTION_MODE_FAST); NOISE_REDUCTION_MODES.put( NoiseReductionMode.highQuality, CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY); if (VERSION.SDK_INT >= VERSION_CODES.M) { NOISE_REDUCTION_MODES.put( NoiseReductionMode.minimal, CaptureRequest.NOISE_REDUCTION_MODE_MINIMAL); NOISE_REDUCTION_MODES.put( NoiseReductionMode.zeroShutterLag, CaptureRequest.NOISE_REDUCTION_MODE_ZERO_SHUTTER_LAG); } } @Override public String getDebugName() { return "NoiseReductionFeature"; } @Override public NoiseReductionMode getValue() { return currentSetting; } @Override public void setValue(NoiseReductionMode value) { this.currentSetting = value; } @Override public boolean checkIsSupported() { /* * Available settings: public static final int NOISE_REDUCTION_MODE_FAST = 1; public static * final int NOISE_REDUCTION_MODE_HIGH_QUALITY = 2; public static final int * NOISE_REDUCTION_MODE_MINIMAL = 3; public static final int NOISE_REDUCTION_MODE_OFF = 0; * public static final int NOISE_REDUCTION_MODE_ZERO_SHUTTER_LAG = 4; * *

Full-capability camera devices will always support OFF and FAST. Camera devices that * support YUV_REPROCESSING or PRIVATE_REPROCESSING will support ZERO_SHUTTER_LAG. * Legacy-capability camera devices will only support FAST mode. */ // Can be null on some devices. int[] modes = cameraProperties.getAvailableNoiseReductionModes(); /// If there's at least one mode available then we are supported. return modes != null && modes.length > 0; } @Override public void updateBuilder(CaptureRequest.Builder requestBuilder) { if (!checkIsSupported()) { return; } Log.i("Camera", "updateNoiseReduction | currentSetting: " + currentSetting); // Always use fast mode. requestBuilder.set( CaptureRequest.NOISE_REDUCTION_MODE, NOISE_REDUCTION_MODES.get(currentSetting)); } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionMode.java ================================================ // Copyright 2013 The Flutter 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.features.noisereduction; /** Only supports fast mode for now. */ public enum NoiseReductionMode { off("off"), fast("fast"), highQuality("highQuality"), minimal("minimal"), zeroShutterLag("zeroShutterLag"); private final String strValue; NoiseReductionMode(String strValue) { this.strValue = strValue; } /** * Tries to convert the supplied string into a {@see NoiseReductionMode} enum value. * *

When the supplied string doesn't match a valid {@see NoiseReductionMode} enum value, null is * returned. * * @param modeStr String value to convert into an {@see NoiseReductionMode} enum value. * @return Matching {@see NoiseReductionMode} enum value, or null if no match is found. */ public static NoiseReductionMode getValueForString(String modeStr) { for (NoiseReductionMode value : values()) { if (value.strValue.equals(modeStr)) return value; } return null; } @Override public String toString() { return strValue; } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionFeature.java ================================================ // Copyright 2013 The Flutter 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.features.resolution; import android.annotation.TargetApi; import android.hardware.camera2.CaptureRequest; import android.media.CamcorderProfile; import android.media.EncoderProfiles; import android.os.Build; import android.util.Size; import androidx.annotation.VisibleForTesting; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.features.CameraFeature; import java.util.List; /** * Controls the resolutions configuration on the {@link android.hardware.camera2} API. * *

The {@link ResolutionFeature} is responsible for converting the platform independent {@link * ResolutionPreset} into a {@link android.media.CamcorderProfile} which contains all the properties * required to configure the resolution using the {@link android.hardware.camera2} API. */ public class ResolutionFeature extends CameraFeature { private Size captureSize; private Size previewSize; private CamcorderProfile recordingProfileLegacy; private EncoderProfiles recordingProfile; private ResolutionPreset currentSetting; private int cameraId; /** * Creates a new instance of the {@link ResolutionFeature}. * * @param cameraProperties Collection of characteristics for the current camera device. * @param resolutionPreset Platform agnostic enum containing resolution information. * @param cameraName Camera identifier of the camera for which to configure the resolution. */ public ResolutionFeature( CameraProperties cameraProperties, ResolutionPreset resolutionPreset, String cameraName) { super(cameraProperties); this.currentSetting = resolutionPreset; try { this.cameraId = Integer.parseInt(cameraName, 10); } catch (NumberFormatException e) { this.cameraId = -1; return; } configureResolution(resolutionPreset, cameraId); } /** * Gets the {@link android.media.CamcorderProfile} containing the information to configure the * resolution using the {@link android.hardware.camera2} API. * * @return Resolution information to configure the {@link android.hardware.camera2} API. */ public CamcorderProfile getRecordingProfileLegacy() { return this.recordingProfileLegacy; } public EncoderProfiles getRecordingProfile() { return this.recordingProfile; } /** * Gets the optimal preview size based on the configured resolution. * * @return The optimal preview size. */ public Size getPreviewSize() { return this.previewSize; } /** * Gets the optimal capture size based on the configured resolution. * * @return The optimal capture size. */ public Size getCaptureSize() { return this.captureSize; } @Override public String getDebugName() { return "ResolutionFeature"; } @Override public ResolutionPreset getValue() { return currentSetting; } @Override public void setValue(ResolutionPreset value) { this.currentSetting = value; configureResolution(currentSetting, cameraId); } @Override public boolean checkIsSupported() { return cameraId >= 0; } @Override public void updateBuilder(CaptureRequest.Builder requestBuilder) { // No-op: when setting a resolution there is no need to update the request builder. } @VisibleForTesting static Size computeBestPreviewSize(int cameraId, ResolutionPreset preset) throws IndexOutOfBoundsException { if (preset.ordinal() > ResolutionPreset.high.ordinal()) { preset = ResolutionPreset.high; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { EncoderProfiles profile = getBestAvailableCamcorderProfileForResolutionPreset(cameraId, preset); List videoProfiles = profile.getVideoProfiles(); EncoderProfiles.VideoProfile defaultVideoProfile = videoProfiles.get(0); 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); } /** * Gets the best possible {@link android.media.CamcorderProfile} for the supplied {@link * ResolutionPreset}. Supports SDK < 31. * * @param cameraId Camera identifier which indicates the device's camera for which to select a * {@link android.media.CamcorderProfile}. * @param preset The {@link ResolutionPreset} for which is to be translated to a {@link * android.media.CamcorderProfile}. * @return The best possible {@link android.media.CamcorderProfile} that matches the supplied * {@link ResolutionPreset}. */ public static CamcorderProfile getBestAvailableCamcorderProfileForResolutionPresetLegacy( int cameraId, ResolutionPreset preset) { if (cameraId < 0) { throw new AssertionError( "getBestAvailableCamcorderProfileForResolutionPreset can only be used with valid (>=0) camera identifiers."); } switch (preset) { // All of these cases deliberately fall through to get the best available profile. case max: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_HIGH)) { return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH); } case ultraHigh: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_2160P)) { return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_2160P); } case veryHigh: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) { return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_1080P); } case high: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) { return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_720P); } case medium: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) { return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_480P); } case low: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_QVGA)) { return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_QVGA); } default: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_LOW)) { return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW); } else { throw new IllegalArgumentException( "No capture session available for current capture session."); } } } @TargetApi(Build.VERSION_CODES.S) public static EncoderProfiles getBestAvailableCamcorderProfileForResolutionPreset( int cameraId, ResolutionPreset preset) { if (cameraId < 0) { throw new AssertionError( "getBestAvailableCamcorderProfileForResolutionPreset can only be used with valid (>=0) camera identifiers."); } String cameraIdString = Integer.toString(cameraId); switch (preset) { // All of these cases deliberately fall through to get the best available profile. case max: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_HIGH)) { return CamcorderProfile.getAll(cameraIdString, CamcorderProfile.QUALITY_HIGH); } case ultraHigh: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_2160P)) { return CamcorderProfile.getAll(cameraIdString, CamcorderProfile.QUALITY_2160P); } case veryHigh: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) { return CamcorderProfile.getAll(cameraIdString, CamcorderProfile.QUALITY_1080P); } case high: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) { return CamcorderProfile.getAll(cameraIdString, CamcorderProfile.QUALITY_720P); } case medium: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) { return CamcorderProfile.getAll(cameraIdString, CamcorderProfile.QUALITY_480P); } case low: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_QVGA)) { return CamcorderProfile.getAll(cameraIdString, CamcorderProfile.QUALITY_QVGA); } default: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_LOW)) { return CamcorderProfile.getAll(cameraIdString, CamcorderProfile.QUALITY_LOW); } throw new IllegalArgumentException( "No capture session available for current capture session."); } } private void configureResolution(ResolutionPreset resolutionPreset, int cameraId) throws IndexOutOfBoundsException { if (!checkIsSupported()) { return; } boolean captureSizeCalculated = false; 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); if (defaultVideoProfile != null) { captureSizeCalculated = true; captureSize = new Size(defaultVideoProfile.getWidth(), defaultVideoProfile.getHeight()); } } if (!captureSizeCalculated) { recordingProfile = null; @SuppressWarnings("deprecation") CamcorderProfile camcorderProfile = getBestAvailableCamcorderProfileForResolutionPresetLegacy(cameraId, resolutionPreset); recordingProfileLegacy = camcorderProfile; captureSize = new Size(recordingProfileLegacy.videoFrameWidth, recordingProfileLegacy.videoFrameHeight); } previewSize = computeBestPreviewSize(cameraId, resolutionPreset); } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/resolution/ResolutionPreset.java ================================================ // Copyright 2013 The Flutter 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.features.resolution; // Mirrors camera.dart public enum ResolutionPreset { low, medium, high, veryHigh, ultraHigh, max, } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/sensororientation/DeviceOrientationManager.java ================================================ // Copyright 2013 The Flutter 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.features.sensororientation; 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; import io.flutter.plugins.camera.DartMessenger; /** * Support class to help to determine the media orientation based on the orientation of the device. */ public class DeviceOrientationManager { private static final IntentFilter orientationIntentFilter = new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED); private final Activity activity; private final DartMessenger messenger; private final boolean isFrontFacing; private final int sensorOrientation; private PlatformChannel.DeviceOrientation lastOrientation; private BroadcastReceiver broadcastReceiver; /** Factory method to create a device orientation manager. */ public static DeviceOrientationManager create( @NonNull Activity activity, @NonNull DartMessenger messenger, boolean isFrontFacing, int sensorOrientation) { return new DeviceOrientationManager(activity, messenger, isFrontFacing, sensorOrientation); } private DeviceOrientationManager( @NonNull Activity activity, @NonNull DartMessenger messenger, boolean isFrontFacing, int sensorOrientation) { this.activity = activity; this.messenger = messenger; this.isFrontFacing = isFrontFacing; this.sensorOrientation = sensorOrientation; } /** * Starts listening to the device's sensors or UI for orientation updates. * *

When orientation information is updated the new orientation is send to the client using the * {@link DartMessenger}. 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, messenger); 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, DartMessenger messenger) { if (!newOrientation.equals(previousOrientation)) { messenger.sendDeviceOrientationChangeEvent(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(); } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/sensororientation/SensorOrientationFeature.java ================================================ // Copyright 2013 The Flutter 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.features.sensororientation; import android.app.Activity; import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CaptureRequest; import androidx.annotation.NonNull; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.DartMessenger; import io.flutter.plugins.camera.features.CameraFeature; import io.flutter.plugins.camera.features.resolution.ResolutionFeature; /** Provides access to the sensor orientation of the camera devices. */ public class SensorOrientationFeature extends CameraFeature { private Integer currentSetting = 0; private final DeviceOrientationManager deviceOrientationListener; private PlatformChannel.DeviceOrientation lockedCaptureOrientation; /** * Creates a new instance of the {@link ResolutionFeature}. * * @param cameraProperties Collection of characteristics for the current camera device. * @param activity Current Android {@link android.app.Activity}, used to detect UI orientation * changes. * @param dartMessenger Instance of a {@link DartMessenger} used to communicate orientation * updates back to the client. */ public SensorOrientationFeature( @NonNull CameraProperties cameraProperties, @NonNull Activity activity, @NonNull DartMessenger dartMessenger) { super(cameraProperties); setValue(cameraProperties.getSensorOrientation()); boolean isFrontFacing = cameraProperties.getLensFacing() == CameraMetadata.LENS_FACING_FRONT; deviceOrientationListener = DeviceOrientationManager.create(activity, dartMessenger, isFrontFacing, currentSetting); deviceOrientationListener.start(); } @Override public String getDebugName() { return "SensorOrientationFeature"; } @Override public Integer getValue() { return currentSetting; } @Override public void setValue(Integer value) { this.currentSetting = value; } @Override public boolean checkIsSupported() { return true; } @Override public void updateBuilder(CaptureRequest.Builder requestBuilder) { // Noop: when setting the sensor orientation there is no need to update the request builder. } /** * Gets the instance of the {@link DeviceOrientationManager} used to detect orientation changes. * * @return The instance of the {@link DeviceOrientationManager}. */ public DeviceOrientationManager getDeviceOrientationManager() { return this.deviceOrientationListener; } /** * Lock the capture orientation, indicating that the device orientation should not influence the * capture orientation. * * @param orientation The orientation in which to lock the capture orientation. */ public void lockCaptureOrientation(PlatformChannel.DeviceOrientation orientation) { this.lockedCaptureOrientation = orientation; } /** * Unlock the capture orientation, indicating that the device orientation should be used to * configure the capture orientation. */ public void unlockCaptureOrientation() { this.lockedCaptureOrientation = null; } /** * Gets the configured locked capture orientation. * * @return The configured locked capture orientation. */ public PlatformChannel.DeviceOrientation getLockedCaptureOrientation() { return this.lockedCaptureOrientation; } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeature.java ================================================ // Copyright 2013 The Flutter 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.features.zoomlevel; 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 DEFAULT_ZOOM_LEVEL = 1.0f; private final boolean hasSupport; private final Rect sensorArraySize; private Float currentSetting = DEFAULT_ZOOM_LEVEL; private Float minimumZoomLevel = currentSetting; private Float maximumZoomLevel; /** * Creates a new instance of the {@link ZoomLevelFeature}. * * @param cameraProperties Collection of characteristics for the current camera device. */ public ZoomLevelFeature(CameraProperties cameraProperties) { super(cameraProperties); sensorArraySize = cameraProperties.getSensorInfoActiveArraySize(); if (sensorArraySize == null) { 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; } hasSupport = (Float.compare(maximumZoomLevel, minimumZoomLevel) > 0); } @Override public String getDebugName() { return "ZoomLevelFeature"; } @Override public Float getValue() { return currentSetting; } @Override public void setValue(Float value) { currentSetting = value; } @Override public boolean checkIsSupported() { return hasSupport; } @Override public void updateBuilder(CaptureRequest.Builder requestBuilder) { if (!checkIsSupported()) { return; } // 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); } } /** * Gets the minimum supported zoom level. * * @return The minimum zoom level. */ public float getMinimumZoomLevel() { return minimumZoomLevel; } /** * Gets the maximum supported zoom level. * * @return The maximum zoom level. */ public float getMaximumZoomLevel() { return maximumZoomLevel; } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtils.java ================================================ // Copyright 2013 The Flutter 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.features.zoomlevel; import android.graphics.Rect; import androidx.annotation.NonNull; import androidx.core.math.MathUtils; /** * Utility class containing methods that assist with zoom features in the {@link * android.hardware.camera2} API. */ 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. 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. * @param minimumZoomLevel The minimum supported zoom level. * @param maximumZoomLevel The maximim supported zoom level. * @return An image sensor area based on the supplied zoom settings */ static Rect computeZoomRect( float zoom, @NonNull Rect sensorArraySize, float minimumZoomLevel, float maximumZoomLevel) { final float newZoom = computeZoomRatio(zoom, minimumZoomLevel, maximumZoomLevel); final int centerX = sensorArraySize.width() / 2; final int centerY = sensorArraySize.height() / 2; final int deltaX = (int) ((0.5f * sensorArraySize.width()) / newZoom); final int deltaY = (int) ((0.5f * sensorArraySize.height()) / newZoom); 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); } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/MediaRecorderBuilder.java ================================================ // Copyright 2013 The Flutter 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.media; import android.media.CamcorderProfile; import android.media.EncoderProfiles; import android.media.MediaRecorder; import android.os.Build; import androidx.annotation.NonNull; import java.io.IOException; public class MediaRecorderBuilder { @SuppressWarnings("deprecation") static class MediaRecorderFactory { MediaRecorder makeMediaRecorder() { return new MediaRecorder(); } } private final String outputFilePath; private final CamcorderProfile camcorderProfile; private final EncoderProfiles encoderProfiles; private final MediaRecorderFactory recorderFactory; private boolean enableAudio; private int mediaOrientation; public MediaRecorderBuilder( @NonNull CamcorderProfile camcorderProfile, @NonNull String outputFilePath) { this(camcorderProfile, outputFilePath, new MediaRecorderFactory()); } public MediaRecorderBuilder( @NonNull EncoderProfiles encoderProfiles, @NonNull String outputFilePath) { this(encoderProfiles, outputFilePath, new MediaRecorderFactory()); } MediaRecorderBuilder( @NonNull CamcorderProfile camcorderProfile, @NonNull String outputFilePath, MediaRecorderFactory helper) { this.outputFilePath = outputFilePath; this.camcorderProfile = camcorderProfile; this.encoderProfiles = null; this.recorderFactory = helper; } MediaRecorderBuilder( @NonNull EncoderProfiles encoderProfiles, @NonNull String outputFilePath, MediaRecorderFactory helper) { this.outputFilePath = outputFilePath; this.encoderProfiles = encoderProfiles; this.camcorderProfile = null; this.recorderFactory = helper; } public MediaRecorderBuilder setEnableAudio(boolean enableAudio) { this.enableAudio = enableAudio; return this; } public MediaRecorderBuilder setMediaOrientation(int orientation) { this.mediaOrientation = orientation; return this; } public MediaRecorder build() throws IOException, NullPointerException, IndexOutOfBoundsException { MediaRecorder mediaRecorder = recorderFactory.makeMediaRecorder(); // There's a fixed order that mediaRecorder expects. Only change these functions accordingly. // You can find the specifics here: https://developer.android.com/reference/android/media/MediaRecorder. if (enableAudio) mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); 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); mediaRecorder.setOutputFormat(encoderProfiles.getRecommendedFileFormat()); if (enableAudio) { mediaRecorder.setAudioEncoder(audioProfile.getCodec()); mediaRecorder.setAudioEncodingBitRate(audioProfile.getBitrate()); mediaRecorder.setAudioSamplingRate(audioProfile.getSampleRate()); } mediaRecorder.setVideoEncoder(videoProfile.getCodec()); mediaRecorder.setVideoEncodingBitRate(videoProfile.getBitrate()); mediaRecorder.setVideoFrameRate(videoProfile.getFrameRate()); mediaRecorder.setVideoSize(videoProfile.getWidth(), videoProfile.getHeight()); mediaRecorder.setVideoSize(videoProfile.getWidth(), videoProfile.getHeight()); } else { mediaRecorder.setOutputFormat(camcorderProfile.fileFormat); if (enableAudio) { mediaRecorder.setAudioEncoder(camcorderProfile.audioCodec); mediaRecorder.setAudioEncodingBitRate(camcorderProfile.audioBitRate); mediaRecorder.setAudioSamplingRate(camcorderProfile.audioSampleRate); } mediaRecorder.setVideoEncoder(camcorderProfile.videoCodec); mediaRecorder.setVideoEncodingBitRate(camcorderProfile.videoBitRate); mediaRecorder.setVideoFrameRate(camcorderProfile.videoFrameRate); mediaRecorder.setVideoSize( camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight); } mediaRecorder.setOutputFile(outputFilePath); mediaRecorder.setOrientationHint(this.mediaOrientation); mediaRecorder.prepare(); return mediaRecorder; } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/types/CameraCaptureProperties.java ================================================ // Copyright 2013 The Flutter 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.types; public class CameraCaptureProperties { private Float lastLensAperture; private Long lastSensorExposureTime; private Integer lastSensorSensitivity; /** * Gets the last known lens aperture. (As f-stop value) * * @return the last known lens aperture. (As f-stop value) */ public Float getLastLensAperture() { return lastLensAperture; } /** * Sets the last known lens aperture. (As f-stop value) * * @param lastLensAperture - The last known lens aperture to set. (As f-stop value) */ public void setLastLensAperture(Float lastLensAperture) { this.lastLensAperture = lastLensAperture; } /** * Gets the last known sensor exposure time in nanoseconds. * * @return the last known sensor exposure time in nanoseconds. */ public Long getLastSensorExposureTime() { return lastSensorExposureTime; } /** * Sets the last known sensor exposure time in nanoseconds. * * @param lastSensorExposureTime - The last known sensor exposure time to set, in nanoseconds. */ public void setLastSensorExposureTime(Long lastSensorExposureTime) { this.lastSensorExposureTime = lastSensorExposureTime; } /** * Gets the last known sensor sensitivity in ISO arithmetic units. * * @return the last known sensor sensitivity in ISO arithmetic units. */ public Integer getLastSensorSensitivity() { return lastSensorSensitivity; } /** * Sets the last known sensor sensitivity in ISO arithmetic units. * * @param lastSensorSensitivity - The last known sensor sensitivity to set, in ISO arithmetic * units. */ public void setLastSensorSensitivity(Integer lastSensorSensitivity) { this.lastSensorSensitivity = lastSensorSensitivity; } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/types/CaptureTimeoutsWrapper.java ================================================ // Copyright 2013 The Flutter 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.types; /** * Wrapper class that provides a container for all {@link Timeout} instances that are required for * the capture flow. */ public class CaptureTimeoutsWrapper { private Timeout preCaptureFocusing; private Timeout preCaptureMetering; private final long preCaptureFocusingTimeoutMs; private final long preCaptureMeteringTimeoutMs; /** * Create a new wrapper instance with the specified timeout values. * * @param preCaptureFocusingTimeoutMs focusing timeout milliseconds. * @param preCaptureMeteringTimeoutMs metering timeout milliseconds. */ public CaptureTimeoutsWrapper( long preCaptureFocusingTimeoutMs, long preCaptureMeteringTimeoutMs) { this.preCaptureFocusingTimeoutMs = preCaptureFocusingTimeoutMs; this.preCaptureMeteringTimeoutMs = preCaptureMeteringTimeoutMs; } /** Reset all timeouts to the current timestamp. */ public void reset() { this.preCaptureFocusing = Timeout.create(preCaptureFocusingTimeoutMs); this.preCaptureMetering = Timeout.create(preCaptureMeteringTimeoutMs); } /** * Returns the timeout instance related to precapture focusing. * * @return - The timeout object */ public Timeout getPreCaptureFocusing() { return preCaptureFocusing; } /** * Returns the timeout instance related to precapture metering. * * @return - The timeout object */ public Timeout getPreCaptureMetering() { return preCaptureMetering; } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/types/ExposureMode.java ================================================ // Copyright 2013 The Flutter 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.types; // Mirrors exposure_mode.dart public enum ExposureMode { auto("auto"), locked("locked"); private final String strValue; ExposureMode(String strValue) { this.strValue = strValue; } public static ExposureMode getValueForString(String modeStr) { for (ExposureMode value : values()) { if (value.strValue.equals(modeStr)) return value; } return null; } @Override public String toString() { return strValue; } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/types/FlashMode.java ================================================ // Copyright 2013 The Flutter 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.types; // Mirrors flash_mode.dart public enum FlashMode { off("off"), auto("auto"), always("always"), torch("torch"); private final String strValue; FlashMode(String strValue) { this.strValue = strValue; } public static FlashMode getValueForString(String modeStr) { for (FlashMode value : values()) { if (value.strValue.equals(modeStr)) return value; } return null; } @Override public String toString() { return strValue; } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/types/FocusMode.java ================================================ // Copyright 2013 The Flutter 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.types; // Mirrors focus_mode.dart public enum FocusMode { auto("auto"), locked("locked"); private final String strValue; FocusMode(String strValue) { this.strValue = strValue; } public static FocusMode getValueForString(String modeStr) { for (FocusMode value : values()) { if (value.strValue.equals(modeStr)) return value; } return null; } @Override public String toString() { return strValue; } } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/types/ResolutionPreset.java ================================================ // Copyright 2013 The Flutter 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.types; // Mirrors camera.dart public enum ResolutionPreset { low, medium, high, veryHigh, ultraHigh, max, } ================================================ FILE: packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/types/Timeout.java ================================================ // Copyright 2013 The Flutter 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.types; import android.os.SystemClock; /** * This is a simple class for managing a timeout. In the camera we generally keep two timeouts: one * for focusing and one for pre-capture metering. * *

We use timeouts to ensure a picture is always captured within a reasonable amount of time even * if the settings don't converge and focus can't be locked. * *

You generally check the status of the timeout in the CameraCaptureCallback during the capture * sequence and use it to move to the next state if the timeout has passed. */ public class Timeout { /** The timeout time in milliseconds */ private final long timeoutMs; /** When this timeout was started. Will be used later to check if the timeout has expired yet. */ private final long timeStarted; /** * Factory method to create a new Timeout. * * @param timeoutMs timeout to use. * @return returns a new Timeout. */ public static Timeout create(long timeoutMs) { return new Timeout(timeoutMs); } /** * Create a new timeout. * * @param timeoutMs the time in milliseconds for this timeout to lapse. */ private Timeout(long timeoutMs) { this.timeoutMs = timeoutMs; this.timeStarted = SystemClock.elapsedRealtime(); } /** Will return true when the timeout period has lapsed. */ public boolean getIsExpired() { return (SystemClock.elapsedRealtime() - timeStarted) > timeoutMs; } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraCaptureCallbackStatesTest.java ================================================ // Copyright 2013 The Flutter 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.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.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.CaptureResult.Key; import android.hardware.camera2.TotalCaptureResult; import io.flutter.plugins.camera.CameraCaptureCallback.CameraCaptureStateListener; import io.flutter.plugins.camera.types.CameraCaptureProperties; import io.flutter.plugins.camera.types.CaptureTimeoutsWrapper; import io.flutter.plugins.camera.types.Timeout; import io.flutter.plugins.camera.utils.TestUtils; import java.util.HashMap; import java.util.Map; import junit.framework.TestCase; import junit.framework.TestSuite; import org.mockito.MockedStatic; public class CameraCaptureCallbackStatesTest extends TestCase { private final Integer aeState; private final Integer afState; private final CameraState cameraState; private final boolean isTimedOut; private Runnable validate; private CameraCaptureCallback cameraCaptureCallback; private CameraCaptureStateListener mockCaptureStateListener; private CameraCaptureSession mockCameraCaptureSession; private CaptureRequest mockCaptureRequest; private CaptureResult mockPartialCaptureResult; private CaptureTimeoutsWrapper mockCaptureTimeouts; private CameraCaptureProperties mockCaptureProps; private TotalCaptureResult mockTotalCaptureResult; private MockedStatic mockedStaticTimeout; private Timeout mockTimeout; public static TestSuite suite() { TestSuite suite = new TestSuite(); setUpPreviewStateTest(suite); setUpWaitingFocusTests(suite); setUpWaitingPreCaptureStartTests(suite); setUpWaitingPreCaptureDoneTests(suite); return suite; } protected CameraCaptureCallbackStatesTest( String name, CameraState cameraState, Integer afState, Integer aeState) { this(name, cameraState, afState, aeState, false); } protected CameraCaptureCallbackStatesTest( String name, CameraState cameraState, Integer afState, Integer aeState, boolean isTimedOut) { super(name); this.aeState = aeState; this.afState = afState; this.cameraState = cameraState; this.isTimedOut = isTimedOut; } @Override @SuppressWarnings("unchecked") protected void setUp() throws Exception { super.setUp(); mockedStaticTimeout = mockStatic(Timeout.class); mockCaptureStateListener = mock(CameraCaptureStateListener.class); mockCameraCaptureSession = mock(CameraCaptureSession.class); mockCaptureRequest = mock(CaptureRequest.class); mockPartialCaptureResult = mock(CaptureResult.class); mockTotalCaptureResult = mock(TotalCaptureResult.class); mockTimeout = mock(Timeout.class); mockCaptureTimeouts = mock(CaptureTimeoutsWrapper.class); mockCaptureProps = mock(CameraCaptureProperties.class); when(mockCaptureTimeouts.getPreCaptureFocusing()).thenReturn(mockTimeout); when(mockCaptureTimeouts.getPreCaptureMetering()).thenReturn(mockTimeout); Key mockAeStateKey = mock(Key.class); Key mockAfStateKey = mock(Key.class); TestUtils.setFinalStatic(CaptureResult.class, "CONTROL_AE_STATE", mockAeStateKey); TestUtils.setFinalStatic(CaptureResult.class, "CONTROL_AF_STATE", mockAfStateKey); mockedStaticTimeout.when(() -> Timeout.create(1000)).thenReturn(mockTimeout); cameraCaptureCallback = CameraCaptureCallback.create( mockCaptureStateListener, mockCaptureTimeouts, mockCaptureProps); } @Override protected void tearDown() throws Exception { super.tearDown(); mockedStaticTimeout.close(); TestUtils.setFinalStatic(CaptureResult.class, "CONTROL_AE_STATE", null); TestUtils.setFinalStatic(CaptureResult.class, "CONTROL_AF_STATE", null); } @Override protected void runTest() throws Throwable { when(mockPartialCaptureResult.get(CaptureResult.CONTROL_AF_STATE)).thenReturn(afState); when(mockPartialCaptureResult.get(CaptureResult.CONTROL_AE_STATE)).thenReturn(aeState); when(mockTotalCaptureResult.get(CaptureResult.CONTROL_AF_STATE)).thenReturn(afState); when(mockTotalCaptureResult.get(CaptureResult.CONTROL_AE_STATE)).thenReturn(aeState); cameraCaptureCallback.setCameraState(cameraState); if (isTimedOut) { when(mockTimeout.getIsExpired()).thenReturn(true); cameraCaptureCallback.onCaptureCompleted( mockCameraCaptureSession, mockCaptureRequest, mockTotalCaptureResult); } else { cameraCaptureCallback.onCaptureProgressed( mockCameraCaptureSession, mockCaptureRequest, mockPartialCaptureResult); } validate.run(); } private static void setUpPreviewStateTest(TestSuite suite) { CameraCaptureCallbackStatesTest previewStateTest = new CameraCaptureCallbackStatesTest( "process_should_not_converge_or_pre_capture_when_state_is_preview", CameraState.STATE_PREVIEW, null, null); previewStateTest.validate = () -> { verify(previewStateTest.mockCaptureStateListener, never()).onConverged(); verify(previewStateTest.mockCaptureStateListener, never()).onConverged(); assertEquals( CameraState.STATE_PREVIEW, previewStateTest.cameraCaptureCallback.getCameraState()); }; suite.addTest(previewStateTest); } private static void setUpWaitingFocusTests(TestSuite suite) { Integer[] actionableAfStates = new Integer[] { CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED, CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED }; Integer[] nonActionableAfStates = new Integer[] { CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN, CaptureResult.CONTROL_AF_STATE_INACTIVE, CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED, CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN, CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED }; Map aeStatesConvergeMap = new HashMap() { { put(null, true); put(CaptureResult.CONTROL_AE_STATE_CONVERGED, true); put(CaptureResult.CONTROL_AE_STATE_PRECAPTURE, false); put(CaptureResult.CONTROL_AE_STATE_LOCKED, false); put(CaptureResult.CONTROL_AE_STATE_SEARCHING, false); put(CaptureResult.CONTROL_AE_STATE_INACTIVE, false); put(CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED, false); } }; CameraCaptureCallbackStatesTest nullStateTest = new CameraCaptureCallbackStatesTest( "process_should_not_converge_or_pre_capture_when_afstate_is_null", CameraState.STATE_WAITING_FOCUS, null, null); nullStateTest.validate = () -> { verify(nullStateTest.mockCaptureStateListener, never()).onConverged(); verify(nullStateTest.mockCaptureStateListener, never()).onConverged(); assertEquals( CameraState.STATE_WAITING_FOCUS, nullStateTest.cameraCaptureCallback.getCameraState()); }; suite.addTest(nullStateTest); for (Integer afState : actionableAfStates) { aeStatesConvergeMap.forEach( (aeState, shouldConverge) -> { CameraCaptureCallbackStatesTest focusLockedTest = new CameraCaptureCallbackStatesTest( "process_should_converge_when_af_state_is_" + afState + "_and_ae_state_is_" + aeState, CameraState.STATE_WAITING_FOCUS, afState, aeState); focusLockedTest.validate = () -> { if (shouldConverge) { verify(focusLockedTest.mockCaptureStateListener, times(1)).onConverged(); verify(focusLockedTest.mockCaptureStateListener, never()).onPrecapture(); } else { verify(focusLockedTest.mockCaptureStateListener, times(1)).onPrecapture(); verify(focusLockedTest.mockCaptureStateListener, never()).onConverged(); } assertEquals( CameraState.STATE_WAITING_FOCUS, focusLockedTest.cameraCaptureCallback.getCameraState()); }; suite.addTest(focusLockedTest); }); } for (Integer afState : nonActionableAfStates) { CameraCaptureCallbackStatesTest focusLockedTest = new CameraCaptureCallbackStatesTest( "process_should_do_nothing_when_af_state_is_" + afState, CameraState.STATE_WAITING_FOCUS, afState, null); focusLockedTest.validate = () -> { verify(focusLockedTest.mockCaptureStateListener, never()).onConverged(); verify(focusLockedTest.mockCaptureStateListener, never()).onPrecapture(); assertEquals( CameraState.STATE_WAITING_FOCUS, focusLockedTest.cameraCaptureCallback.getCameraState()); }; suite.addTest(focusLockedTest); } for (Integer afState : nonActionableAfStates) { aeStatesConvergeMap.forEach( (aeState, shouldConverge) -> { CameraCaptureCallbackStatesTest focusLockedTest = new CameraCaptureCallbackStatesTest( "process_should_converge_when_af_state_is_" + afState + "_and_ae_state_is_" + aeState, CameraState.STATE_WAITING_FOCUS, afState, aeState, true); focusLockedTest.validate = () -> { if (shouldConverge) { verify(focusLockedTest.mockCaptureStateListener, times(1)).onConverged(); verify(focusLockedTest.mockCaptureStateListener, never()).onPrecapture(); } else { verify(focusLockedTest.mockCaptureStateListener, times(1)).onPrecapture(); verify(focusLockedTest.mockCaptureStateListener, never()).onConverged(); } assertEquals( CameraState.STATE_WAITING_FOCUS, focusLockedTest.cameraCaptureCallback.getCameraState()); }; suite.addTest(focusLockedTest); }); } } private static void setUpWaitingPreCaptureStartTests(TestSuite suite) { Map cameraStateMap = new HashMap() { { put(null, CameraState.STATE_WAITING_PRECAPTURE_DONE); put( CaptureResult.CONTROL_AE_STATE_INACTIVE, CameraState.STATE_WAITING_PRECAPTURE_START); put( CaptureResult.CONTROL_AE_STATE_SEARCHING, CameraState.STATE_WAITING_PRECAPTURE_START); put( CaptureResult.CONTROL_AE_STATE_CONVERGED, CameraState.STATE_WAITING_PRECAPTURE_DONE); put(CaptureResult.CONTROL_AE_STATE_LOCKED, CameraState.STATE_WAITING_PRECAPTURE_START); put( CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED, CameraState.STATE_WAITING_PRECAPTURE_DONE); put( CaptureResult.CONTROL_AE_STATE_PRECAPTURE, CameraState.STATE_WAITING_PRECAPTURE_DONE); } }; cameraStateMap.forEach( (aeState, cameraState) -> { CameraCaptureCallbackStatesTest testCase = new CameraCaptureCallbackStatesTest( "process_should_update_camera_state_to_waiting_pre_capture_done_when_ae_state_is_" + aeState, CameraState.STATE_WAITING_PRECAPTURE_START, null, aeState); testCase.validate = () -> assertEquals(cameraState, testCase.cameraCaptureCallback.getCameraState()); suite.addTest(testCase); }); cameraStateMap.forEach( (aeState, cameraState) -> { if (cameraState == CameraState.STATE_WAITING_PRECAPTURE_DONE) { return; } CameraCaptureCallbackStatesTest testCase = new CameraCaptureCallbackStatesTest( "process_should_update_camera_state_to_waiting_pre_capture_done_when_ae_state_is_" + aeState, CameraState.STATE_WAITING_PRECAPTURE_START, null, aeState, true); testCase.validate = () -> assertEquals( CameraState.STATE_WAITING_PRECAPTURE_DONE, testCase.cameraCaptureCallback.getCameraState()); suite.addTest(testCase); }); } private static void setUpWaitingPreCaptureDoneTests(TestSuite suite) { Integer[] onConvergeStates = new Integer[] { null, CaptureResult.CONTROL_AE_STATE_CONVERGED, CaptureResult.CONTROL_AE_STATE_LOCKED, CaptureResult.CONTROL_AE_STATE_SEARCHING, CaptureResult.CONTROL_AE_STATE_INACTIVE, CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED, }; for (Integer aeState : onConvergeStates) { CameraCaptureCallbackStatesTest shouldConvergeTest = new CameraCaptureCallbackStatesTest( "process_should_converge_when_ae_state_is_" + aeState, CameraState.STATE_WAITING_PRECAPTURE_DONE, null, null); shouldConvergeTest.validate = () -> verify(shouldConvergeTest.mockCaptureStateListener, times(1)).onConverged(); suite.addTest(shouldConvergeTest); } CameraCaptureCallbackStatesTest shouldNotConvergeTest = new CameraCaptureCallbackStatesTest( "process_should_not_converge_when_ae_state_is_pre_capture", CameraState.STATE_WAITING_PRECAPTURE_DONE, null, CaptureResult.CONTROL_AE_STATE_PRECAPTURE); shouldNotConvergeTest.validate = () -> verify(shouldNotConvergeTest.mockCaptureStateListener, never()).onConverged(); suite.addTest(shouldNotConvergeTest); CameraCaptureCallbackStatesTest shouldConvergeWhenTimedOutTest = new CameraCaptureCallbackStatesTest( "process_should_not_converge_when_ae_state_is_pre_capture", CameraState.STATE_WAITING_PRECAPTURE_DONE, null, CaptureResult.CONTROL_AE_STATE_PRECAPTURE, true); shouldConvergeWhenTimedOutTest.validate = () -> verify(shouldConvergeWhenTimedOutTest.mockCaptureStateListener, times(1)).onConverged(); suite.addTest(shouldConvergeWhenTimedOutTest); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraCaptureCallbackTest.java ================================================ // Copyright 2013 The Flutter 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.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.mock; 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.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.TotalCaptureResult; import io.flutter.plugins.camera.types.CameraCaptureProperties; import io.flutter.plugins.camera.types.CaptureTimeoutsWrapper; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class CameraCaptureCallbackTest { private CameraCaptureCallback cameraCaptureCallback; private CameraCaptureProperties mockCaptureProps; @Before public void setUp() { CameraCaptureCallback.CameraCaptureStateListener mockCaptureStateListener = mock(CameraCaptureCallback.CameraCaptureStateListener.class); CaptureTimeoutsWrapper mockCaptureTimeouts = mock(CaptureTimeoutsWrapper.class); mockCaptureProps = mock(CameraCaptureProperties.class); cameraCaptureCallback = CameraCaptureCallback.create( mockCaptureStateListener, mockCaptureTimeouts, mockCaptureProps); } @Test public void onCaptureProgressed_doesNotUpdateCameraCaptureProperties() { CameraCaptureSession mockSession = mock(CameraCaptureSession.class); CaptureRequest mockRequest = mock(CaptureRequest.class); CaptureResult mockResult = mock(CaptureResult.class); cameraCaptureCallback.onCaptureProgressed(mockSession, mockRequest, mockResult); verify(mockCaptureProps, never()).setLastLensAperture(anyFloat()); verify(mockCaptureProps, never()).setLastSensorExposureTime(anyLong()); verify(mockCaptureProps, never()).setLastSensorSensitivity(anyInt()); } @Test public void onCaptureCompleted_updatesCameraCaptureProperties() { CameraCaptureSession mockSession = mock(CameraCaptureSession.class); CaptureRequest mockRequest = mock(CaptureRequest.class); TotalCaptureResult mockResult = mock(TotalCaptureResult.class); when(mockResult.get(CaptureResult.LENS_APERTURE)).thenReturn(1.0f); when(mockResult.get(CaptureResult.SENSOR_EXPOSURE_TIME)).thenReturn(2L); when(mockResult.get(CaptureResult.SENSOR_SENSITIVITY)).thenReturn(3); cameraCaptureCallback.onCaptureCompleted(mockSession, mockRequest, mockResult); verify(mockCaptureProps, times(1)).setLastLensAperture(1.0f); verify(mockCaptureProps, times(1)).setLastSensorExposureTime(2L); verify(mockCaptureProps, times(1)).setLastSensorSensitivity(3); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraPermissionsTest.java ================================================ // Copyright 2013 The Flutter 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 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.camera.CameraPermissions.CameraRequestPermissionsListener; import io.flutter.plugins.camera.CameraPermissions.ResultCallback; import org.junit.Test; public class CameraPermissionsTest { @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."); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraPropertiesImplTest.java ================================================ // Copyright 2013 The Flutter 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.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; 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.graphics.Rect; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraManager; import android.util.Range; import android.util.Rational; import android.util.Size; import org.junit.Before; import org.junit.Test; public class CameraPropertiesImplTest { private static final String CAMERA_NAME = "test_camera"; private final CameraCharacteristics mockCharacteristics = mock(CameraCharacteristics.class); private final CameraManager mockCameraManager = mock(CameraManager.class); private CameraPropertiesImpl cameraProperties; @Before public void before() { try { when(mockCameraManager.getCameraCharacteristics(CAMERA_NAME)).thenReturn(mockCharacteristics); cameraProperties = new CameraPropertiesImpl(CAMERA_NAME, mockCameraManager); } catch (CameraAccessException e) { fail(); } } @Test public void ctor_shouldReturnValidInstance() throws CameraAccessException { verify(mockCameraManager, times(1)).getCameraCharacteristics(CAMERA_NAME); assertNotNull(cameraProperties); } @Test @SuppressWarnings("unchecked") public void getControlAutoExposureAvailableTargetFpsRangesTest() { Range mockRange = mock(Range.class); Range[] mockRanges = new Range[] {mockRange}; when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES)) .thenReturn(mockRanges); Range[] actualRanges = cameraProperties.getControlAutoExposureAvailableTargetFpsRanges(); verify(mockCharacteristics, times(1)) .get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); assertArrayEquals(actualRanges, mockRanges); } @Test @SuppressWarnings("unchecked") public void getControlAutoExposureCompensationRangeTest() { Range mockRange = mock(Range.class); when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE)) .thenReturn(mockRange); Range actualRange = cameraProperties.getControlAutoExposureCompensationRange(); verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE); assertEquals(actualRange, mockRange); } @Test public void getControlAutoExposureCompensationStep_shouldReturnDoubleWhenRationalIsNotNull() { double expectedStep = 3.1415926535; Rational mockRational = mock(Rational.class); when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP)) .thenReturn(mockRational); when(mockRational.doubleValue()).thenReturn(expectedStep); double actualSteps = cameraProperties.getControlAutoExposureCompensationStep(); verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP); assertEquals(actualSteps, expectedStep, 0); } @Test public void getControlAutoExposureCompensationStep_shouldReturnZeroWhenRationalIsNull() { double expectedStep = 0.0; when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP)) .thenReturn(null); double actualSteps = cameraProperties.getControlAutoExposureCompensationStep(); verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP); assertEquals(actualSteps, expectedStep, 0); } @Test public void getControlAutoFocusAvailableModesTest() { int[] expectedAutoFocusModes = new int[] {0, 1, 2}; when(mockCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES)) .thenReturn(expectedAutoFocusModes); int[] actualAutoFocusModes = cameraProperties.getControlAutoFocusAvailableModes(); verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); assertEquals(actualAutoFocusModes, expectedAutoFocusModes); } @Test public void getControlMaxRegionsAutoExposureTest() { int expectedRegions = 42; when(mockCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE)) .thenReturn(expectedRegions); int actualRegions = cameraProperties.getControlMaxRegionsAutoExposure(); verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_MAX_REGIONS_AE); assertEquals(actualRegions, expectedRegions); } @Test public void getControlMaxRegionsAutoFocusTest() { int expectedRegions = 42; when(mockCharacteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF)) .thenReturn(expectedRegions); int actualRegions = cameraProperties.getControlMaxRegionsAutoFocus(); verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF); assertEquals(actualRegions, expectedRegions); } @Test public void getDistortionCorrectionAvailableModesTest() { int[] expectedCorrectionModes = new int[] {0, 1, 2}; when(mockCharacteristics.get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES)) .thenReturn(expectedCorrectionModes); int[] actualCorrectionModes = cameraProperties.getDistortionCorrectionAvailableModes(); verify(mockCharacteristics, times(1)) .get(CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES); assertEquals(actualCorrectionModes, expectedCorrectionModes); } @Test public void getFlashInfoAvailableTest() { boolean expectedAvailability = true; when(mockCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE)) .thenReturn(expectedAvailability); boolean actualAvailability = cameraProperties.getFlashInfoAvailable(); verify(mockCharacteristics, times(1)).get(CameraCharacteristics.FLASH_INFO_AVAILABLE); assertEquals(actualAvailability, expectedAvailability); } @Test public void getLensFacingTest() { int expectedFacing = 42; when(mockCharacteristics.get(CameraCharacteristics.LENS_FACING)).thenReturn(expectedFacing); int actualFacing = cameraProperties.getLensFacing(); verify(mockCharacteristics, times(1)).get(CameraCharacteristics.LENS_FACING); assertEquals(actualFacing, expectedFacing); } @Test public void getLensInfoMinimumFocusDistanceTest() { Float expectedFocusDistance = new Float(3.14); when(mockCharacteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE)) .thenReturn(expectedFocusDistance); Float actualFocusDistance = cameraProperties.getLensInfoMinimumFocusDistance(); verify(mockCharacteristics, times(1)) .get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE); assertEquals(actualFocusDistance, expectedFocusDistance); } @Test public void getScalerAvailableMaxDigitalZoomTest() { Float expectedDigitalZoom = new Float(3.14); when(mockCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM)) .thenReturn(expectedDigitalZoom); Float actualDigitalZoom = cameraProperties.getScalerAvailableMaxDigitalZoom(); verify(mockCharacteristics, times(1)) .get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM); 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); when(mockCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)) .thenReturn(expectedArraySize); Rect actualArraySize = cameraProperties.getSensorInfoActiveArraySize(); verify(mockCharacteristics, times(1)).get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); assertEquals(actualArraySize, expectedArraySize); } @Test public void getSensorInfoPixelArraySizeTest() { Size expectedArraySize = mock(Size.class); when(mockCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE)) .thenReturn(expectedArraySize); Size actualArraySize = cameraProperties.getSensorInfoPixelArraySize(); verify(mockCharacteristics, times(1)).get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE); assertEquals(actualArraySize, expectedArraySize); } @Test public void getSensorInfoPreCorrectionActiveArraySize() { Rect expectedArraySize = mock(Rect.class); when(mockCharacteristics.get( CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE)) .thenReturn(expectedArraySize); Rect actualArraySize = cameraProperties.getSensorInfoPreCorrectionActiveArraySize(); verify(mockCharacteristics, times(1)) .get(CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE); assertEquals(actualArraySize, expectedArraySize); } @Test public void getSensorOrientationTest() { int expectedOrientation = 42; when(mockCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)) .thenReturn(expectedOrientation); int actualOrientation = cameraProperties.getSensorOrientation(); verify(mockCharacteristics, times(1)).get(CameraCharacteristics.SENSOR_ORIENTATION); assertEquals(actualOrientation, expectedOrientation); } @Test public void getHardwareLevelTest() { int expectedLevel = 42; when(mockCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)) .thenReturn(expectedLevel); int actualLevel = cameraProperties.getHardwareLevel(); verify(mockCharacteristics, times(1)).get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); assertEquals(actualLevel, expectedLevel); } @Test public void getAvailableNoiseReductionModesTest() { int[] expectedReductionModes = new int[] {0, 1, 2}; when(mockCharacteristics.get( CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES)) .thenReturn(expectedReductionModes); int[] actualReductionModes = cameraProperties.getAvailableNoiseReductionModes(); verify(mockCharacteristics, times(1)) .get(CameraCharacteristics.NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES); assertEquals(actualReductionModes, expectedReductionModes); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtils_convertPointToMeteringRectangleTest.java ================================================ // Copyright 2013 The Flutter 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.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; import android.hardware.camera2.params.MeteringRectangle; import android.util.Size; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; public class CameraRegionUtils_convertPointToMeteringRectangleTest { private MockedStatic mockedMeteringRectangleFactory; private Size mockCameraBoundaries; @Before public void setUp() { this.mockCameraBoundaries = mock(Size.class); when(this.mockCameraBoundaries.getWidth()).thenReturn(100); when(this.mockCameraBoundaries.getHeight()).thenReturn(100); mockedMeteringRectangleFactory = mockStatic(CameraRegionUtils.MeteringRectangleFactory.class); mockedMeteringRectangleFactory .when( () -> CameraRegionUtils.MeteringRectangleFactory.create( anyInt(), anyInt(), anyInt(), anyInt(), anyInt())) .thenAnswer( new Answer() { @Override public MeteringRectangle answer(InvocationOnMock createInvocation) throws Throwable { MeteringRectangle mockMeteringRectangle = mock(MeteringRectangle.class); when(mockMeteringRectangle.getX()).thenReturn(createInvocation.getArgument(0)); when(mockMeteringRectangle.getY()).thenReturn(createInvocation.getArgument(1)); when(mockMeteringRectangle.getWidth()).thenReturn(createInvocation.getArgument(2)); when(mockMeteringRectangle.getHeight()).thenReturn(createInvocation.getArgument(3)); when(mockMeteringRectangle.getMeteringWeight()) .thenReturn(createInvocation.getArgument(4)); when(mockMeteringRectangle.equals(any())) .thenAnswer( new Answer() { @Override public Boolean answer(InvocationOnMock equalsInvocation) throws Throwable { MeteringRectangle otherMockMeteringRectangle = equalsInvocation.getArgument(0); return mockMeteringRectangle.getX() == otherMockMeteringRectangle.getX() && mockMeteringRectangle.getY() == otherMockMeteringRectangle.getY() && mockMeteringRectangle.getWidth() == otherMockMeteringRectangle.getWidth() && mockMeteringRectangle.getHeight() == otherMockMeteringRectangle.getHeight() && mockMeteringRectangle.getMeteringWeight() == otherMockMeteringRectangle.getMeteringWeight(); } }); return mockMeteringRectangle; } }); } @After public void tearDown() { mockedMeteringRectangleFactory.close(); } @Test public void convertPointToMeteringRectangle_shouldReturnValidMeteringRectangleForCenterCoord() { MeteringRectangle r = CameraRegionUtils.convertPointToMeteringRectangle( this.mockCameraBoundaries, 0.5, 0.5, PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT); assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(45, 45, 10, 10, 1).equals(r)); } @Test public void convertPointToMeteringRectangle_shouldReturnValidMeteringRectangleForTopLeftCoord() { MeteringRectangle r = CameraRegionUtils.convertPointToMeteringRectangle( this.mockCameraBoundaries, 0, 0, PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT); assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(0, 0, 10, 10, 1).equals(r)); } @Test public void convertPointToMeteringRectangle_ShouldReturnValidMeteringRectangleForTopRightCoord() { MeteringRectangle r = CameraRegionUtils.convertPointToMeteringRectangle( this.mockCameraBoundaries, 1, 0, PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT); assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(89, 0, 10, 10, 1).equals(r)); } @Test public void convertPointToMeteringRectangle_shouldReturnValidMeteringRectangleForBottomLeftCoord() { MeteringRectangle r = CameraRegionUtils.convertPointToMeteringRectangle( this.mockCameraBoundaries, 0, 1, PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT); assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(0, 89, 10, 10, 1).equals(r)); } @Test public void convertPointToMeteringRectangle_shouldReturnValidMeteringRectangleForBottomRightCoord() { MeteringRectangle r = CameraRegionUtils.convertPointToMeteringRectangle( this.mockCameraBoundaries, 1, 1, PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT); assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(89, 89, 10, 10, 1).equals(r)); } @Test(expected = AssertionError.class) public void convertPointToMeteringRectangle_shouldThrowForXUpperBound() { CameraRegionUtils.convertPointToMeteringRectangle( this.mockCameraBoundaries, 1.5, 0, PlatformChannel.DeviceOrientation.PORTRAIT_UP); } @Test(expected = AssertionError.class) public void convertPointToMeteringRectangle_shouldThrowForXLowerBound() { CameraRegionUtils.convertPointToMeteringRectangle( this.mockCameraBoundaries, -0.5, 0, PlatformChannel.DeviceOrientation.PORTRAIT_UP); } @Test(expected = AssertionError.class) public void convertPointToMeteringRectangle_shouldThrowForYUpperBound() { CameraRegionUtils.convertPointToMeteringRectangle( this.mockCameraBoundaries, 0, 1.5, PlatformChannel.DeviceOrientation.PORTRAIT_UP); } @Test(expected = AssertionError.class) public void convertPointToMeteringRectangle_shouldThrowForYLowerBound() { CameraRegionUtils.convertPointToMeteringRectangle( this.mockCameraBoundaries, 0, -0.5, PlatformChannel.DeviceOrientation.PORTRAIT_UP); } @Test() public void convertPointToMeteringRectangle_shouldRotateMeteringRectangleAccordingToUiOrientationForPortraitUp() { MeteringRectangle r = CameraRegionUtils.convertPointToMeteringRectangle( this.mockCameraBoundaries, 1, 1, PlatformChannel.DeviceOrientation.PORTRAIT_UP); assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(89, 0, 10, 10, 1).equals(r)); } @Test() public void convertPointToMeteringRectangle_shouldRotateMeteringRectangleAccordingToUiOrientationForPortraitDown() { MeteringRectangle r = CameraRegionUtils.convertPointToMeteringRectangle( this.mockCameraBoundaries, 1, 1, PlatformChannel.DeviceOrientation.PORTRAIT_DOWN); assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(0, 89, 10, 10, 1).equals(r)); } @Test() public void convertPointToMeteringRectangle_shouldRotateMeteringRectangleAccordingToUiOrientationForLandscapeLeft() { MeteringRectangle r = CameraRegionUtils.convertPointToMeteringRectangle( this.mockCameraBoundaries, 1, 1, PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT); assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(89, 89, 10, 10, 1).equals(r)); } @Test() public void convertPointToMeteringRectangle_shouldRotateMeteringRectangleAccordingToUiOrientationForLandscapeRight() { MeteringRectangle r = CameraRegionUtils.convertPointToMeteringRectangle( this.mockCameraBoundaries, 1, 1, PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT); assertTrue(CameraRegionUtils.MeteringRectangleFactory.create(0, 0, 10, 10, 1).equals(r)); } @Test(expected = AssertionError.class) public void convertPointToMeteringRectangle_shouldThrowFor0WidthBoundary() { Size mockCameraBoundaries = mock(Size.class); when(mockCameraBoundaries.getWidth()).thenReturn(0); when(mockCameraBoundaries.getHeight()).thenReturn(50); CameraRegionUtils.convertPointToMeteringRectangle( mockCameraBoundaries, 0, -0.5, PlatformChannel.DeviceOrientation.PORTRAIT_UP); } @Test(expected = AssertionError.class) public void convertPointToMeteringRectangle_shouldThrowFor0HeightBoundary() { Size mockCameraBoundaries = mock(Size.class); when(mockCameraBoundaries.getWidth()).thenReturn(50); when(mockCameraBoundaries.getHeight()).thenReturn(0); CameraRegionUtils.convertPointToMeteringRectangle( this.mockCameraBoundaries, 0, -0.5, PlatformChannel.DeviceOrientation.PORTRAIT_UP); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraRegionUtils_getCameraBoundariesTest.java ================================================ // Copyright 2013 The Flutter 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.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.graphics.Rect; import android.hardware.camera2.CaptureRequest; import android.os.Build; import android.util.Size; import io.flutter.plugins.camera.utils.TestUtils; import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.stubbing.Answer; public class CameraRegionUtils_getCameraBoundariesTest { Size mockCameraBoundaries; @Before public void setUp() { this.mockCameraBoundaries = mock(Size.class); when(this.mockCameraBoundaries.getWidth()).thenReturn(100); when(this.mockCameraBoundaries.getHeight()).thenReturn(100); } @Test public void getCameraBoundaries_shouldReturnSensorInfoPixelArraySizeWhenRunningPreAndroidP() { updateSdkVersion(Build.VERSION_CODES.O_MR1); try { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); when(mockCameraProperties.getSensorInfoPixelArraySize()).thenReturn(mockCameraBoundaries); Size result = CameraRegionUtils.getCameraBoundaries(mockCameraProperties, mockBuilder); assertEquals(mockCameraBoundaries, result); verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); } finally { updateSdkVersion(0); } } @Test public void getCameraBoundaries_shouldReturnSensorInfoPixelArraySizeWhenDistortionCorrectionIsNull() { updateSdkVersion(Build.VERSION_CODES.P); try { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); when(mockCameraProperties.getDistortionCorrectionAvailableModes()).thenReturn(null); when(mockCameraProperties.getSensorInfoPixelArraySize()).thenReturn(mockCameraBoundaries); Size result = CameraRegionUtils.getCameraBoundaries(mockCameraProperties, mockBuilder); assertEquals(mockCameraBoundaries, result); verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); } finally { updateSdkVersion(0); } } @Test public void getCameraBoundaries_shouldReturnSensorInfoPixelArraySizeWhenDistortionCorrectionIsOff() { updateSdkVersion(Build.VERSION_CODES.P); try { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); when(mockCameraProperties.getDistortionCorrectionAvailableModes()) .thenReturn(new int[] {CaptureRequest.DISTORTION_CORRECTION_MODE_OFF}); when(mockCameraProperties.getSensorInfoPixelArraySize()).thenReturn(mockCameraBoundaries); Size result = CameraRegionUtils.getCameraBoundaries(mockCameraProperties, mockBuilder); assertEquals(mockCameraBoundaries, result); verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); } finally { updateSdkVersion(0); } } @Test public void getCameraBoundaries_shouldReturnInfoPreCorrectionActiveArraySizeWhenDistortionCorrectionModeIsSetToNull() { updateSdkVersion(Build.VERSION_CODES.P); try { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); Rect mockSensorInfoPreCorrectionActiveArraySize = mock(Rect.class); when(mockSensorInfoPreCorrectionActiveArraySize.width()).thenReturn(100); when(mockSensorInfoPreCorrectionActiveArraySize.height()).thenReturn(100); when(mockCameraProperties.getDistortionCorrectionAvailableModes()) .thenReturn( new int[] { CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, CaptureRequest.DISTORTION_CORRECTION_MODE_FAST }); when(mockBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE)).thenReturn(null); when(mockCameraProperties.getSensorInfoPreCorrectionActiveArraySize()) .thenReturn(mockSensorInfoPreCorrectionActiveArraySize); try (MockedStatic mockedSizeFactory = mockStatic(CameraRegionUtils.SizeFactory.class)) { mockedSizeFactory .when(() -> CameraRegionUtils.SizeFactory.create(anyInt(), anyInt())) .thenAnswer( (Answer) invocation -> { Size mockSize = mock(Size.class); when(mockSize.getWidth()).thenReturn(invocation.getArgument(0)); when(mockSize.getHeight()).thenReturn(invocation.getArgument(1)); return mockSize; }); Size result = CameraRegionUtils.getCameraBoundaries(mockCameraProperties, mockBuilder); assertEquals(100, result.getWidth()); assertEquals(100, result.getHeight()); verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); } } finally { updateSdkVersion(0); } } @Test public void getCameraBoundaries_shouldReturnInfoPreCorrectionActiveArraySizeWhenDistortionCorrectionModeIsSetToOff() { updateSdkVersion(Build.VERSION_CODES.P); try { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); Rect mockSensorInfoPreCorrectionActiveArraySize = mock(Rect.class); when(mockSensorInfoPreCorrectionActiveArraySize.width()).thenReturn(100); when(mockSensorInfoPreCorrectionActiveArraySize.height()).thenReturn(100); when(mockCameraProperties.getDistortionCorrectionAvailableModes()) .thenReturn( new int[] { CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, CaptureRequest.DISTORTION_CORRECTION_MODE_FAST }); when(mockBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE)) .thenReturn(CaptureRequest.DISTORTION_CORRECTION_MODE_OFF); when(mockCameraProperties.getSensorInfoPreCorrectionActiveArraySize()) .thenReturn(mockSensorInfoPreCorrectionActiveArraySize); try (MockedStatic mockedSizeFactory = mockStatic(CameraRegionUtils.SizeFactory.class)) { mockedSizeFactory .when(() -> CameraRegionUtils.SizeFactory.create(anyInt(), anyInt())) .thenAnswer( (Answer) invocation -> { Size mockSize = mock(Size.class); when(mockSize.getWidth()).thenReturn(invocation.getArgument(0)); when(mockSize.getHeight()).thenReturn(invocation.getArgument(1)); return mockSize; }); Size result = CameraRegionUtils.getCameraBoundaries(mockCameraProperties, mockBuilder); assertEquals(100, result.getWidth()); assertEquals(100, result.getHeight()); verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); verify(mockCameraProperties, never()).getSensorInfoActiveArraySize(); } } finally { updateSdkVersion(0); } } @Test public void getCameraBoundaries_shouldReturnSensorInfoActiveArraySizeWhenDistortionCorrectionModeIsSet() { updateSdkVersion(Build.VERSION_CODES.P); try { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); Rect mockSensorInfoActiveArraySize = mock(Rect.class); when(mockSensorInfoActiveArraySize.width()).thenReturn(100); when(mockSensorInfoActiveArraySize.height()).thenReturn(100); when(mockCameraProperties.getDistortionCorrectionAvailableModes()) .thenReturn( new int[] { CaptureRequest.DISTORTION_CORRECTION_MODE_OFF, CaptureRequest.DISTORTION_CORRECTION_MODE_FAST }); when(mockBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE)) .thenReturn(CaptureRequest.DISTORTION_CORRECTION_MODE_FAST); when(mockCameraProperties.getSensorInfoActiveArraySize()) .thenReturn(mockSensorInfoActiveArraySize); try (MockedStatic mockedSizeFactory = mockStatic(CameraRegionUtils.SizeFactory.class)) { mockedSizeFactory .when(() -> CameraRegionUtils.SizeFactory.create(anyInt(), anyInt())) .thenAnswer( (Answer) invocation -> { Size mockSize = mock(Size.class); when(mockSize.getWidth()).thenReturn(invocation.getArgument(0)); when(mockSize.getHeight()).thenReturn(invocation.getArgument(1)); return mockSize; }); Size result = CameraRegionUtils.getCameraBoundaries(mockCameraProperties, mockBuilder); assertEquals(100, result.getWidth()); assertEquals(100, result.getHeight()); verify(mockCameraProperties, never()).getSensorInfoPixelArraySize(); verify(mockCameraProperties, never()).getSensorInfoPreCorrectionActiveArraySize(); } } finally { updateSdkVersion(0); } } private static void updateSdkVersion(int version) { TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", version); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest.java ================================================ // Copyright 2013 The Flutter 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.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; 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.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.SessionConfiguration; import android.media.ImageReader; import android.media.MediaRecorder; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.util.Size; import android.view.Surface; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.LifecycleObserver; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugins.camera.features.CameraFeatureFactory; import io.flutter.plugins.camera.features.CameraFeatures; import io.flutter.plugins.camera.features.Point; import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature; import io.flutter.plugins.camera.features.autofocus.FocusMode; import io.flutter.plugins.camera.features.exposurelock.ExposureLockFeature; import io.flutter.plugins.camera.features.exposurelock.ExposureMode; import io.flutter.plugins.camera.features.exposureoffset.ExposureOffsetFeature; import io.flutter.plugins.camera.features.exposurepoint.ExposurePointFeature; import io.flutter.plugins.camera.features.flash.FlashFeature; import io.flutter.plugins.camera.features.flash.FlashMode; import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature; import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature; import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionPreset; import io.flutter.plugins.camera.features.sensororientation.DeviceOrientationManager; import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature; import io.flutter.plugins.camera.features.zoomlevel.ZoomLevelFeature; import io.flutter.plugins.camera.utils.TestUtils; import io.flutter.view.TextureRegistry; import java.util.ArrayList; import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; class FakeCameraDeviceWrapper implements CameraDeviceWrapper { final List captureRequests; FakeCameraDeviceWrapper(List captureRequests) { this.captureRequests = captureRequests; } @NonNull @Override public CaptureRequest.Builder createCaptureRequest(int var1) { return captureRequests.remove(0); } @Override public void createCaptureSession(SessionConfiguration config) {} @Override public void createCaptureSession( @NonNull List outputs, @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler) {} @Override public void close() {} } public class CameraTest { private CameraProperties mockCameraProperties; private CameraFeatureFactory mockCameraFeatureFactory; private DartMessenger mockDartMessenger; private Camera camera; private CameraCaptureSession mockCaptureSession; private CaptureRequest.Builder mockPreviewRequestBuilder; private MockedStatic mockHandlerThreadFactory; private HandlerThread mockHandlerThread; private MockedStatic mockHandlerFactory; private Handler mockHandler; @Before public void before() { mockCameraProperties = mock(CameraProperties.class); mockCameraFeatureFactory = new TestCameraFeatureFactory(); mockDartMessenger = mock(DartMessenger.class); mockCaptureSession = mock(CameraCaptureSession.class); mockPreviewRequestBuilder = mock(CaptureRequest.Builder.class); mockHandlerThreadFactory = mockStatic(Camera.HandlerThreadFactory.class); mockHandlerThread = mock(HandlerThread.class); mockHandlerFactory = mockStatic(Camera.HandlerFactory.class); mockHandler = mock(Handler.class); final Activity mockActivity = mock(Activity.class); final TextureRegistry.SurfaceTextureEntry mockFlutterTexture = mock(TextureRegistry.SurfaceTextureEntry.class); final String cameraName = "1"; final ResolutionPreset resolutionPreset = ResolutionPreset.high; final boolean enableAudio = false; when(mockCameraProperties.getCameraName()).thenReturn(cameraName); mockHandlerFactory.when(() -> Camera.HandlerFactory.create(any())).thenReturn(mockHandler); mockHandlerThreadFactory .when(() -> Camera.HandlerThreadFactory.create(any())) .thenReturn(mockHandlerThread); camera = new Camera( mockActivity, mockFlutterTexture, mockCameraFeatureFactory, mockDartMessenger, mockCameraProperties, resolutionPreset, enableAudio); TestUtils.setPrivateField(camera, "captureSession", mockCaptureSession); TestUtils.setPrivateField(camera, "previewRequestBuilder", mockPreviewRequestBuilder); } @After public void after() { TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 0); mockHandlerThreadFactory.close(); mockHandlerFactory.close(); } @Test public void shouldNotImplementLifecycleObserverInterface() { Class cameraClass = Camera.class; assertFalse(LifecycleObserver.class.isAssignableFrom(cameraClass)); } @Test public void shouldCreateCameraPluginAndSetAllFeatures() { final Activity mockActivity = mock(Activity.class); final TextureRegistry.SurfaceTextureEntry mockFlutterTexture = mock(TextureRegistry.SurfaceTextureEntry.class); final CameraFeatureFactory mockCameraFeatureFactory = mock(CameraFeatureFactory.class); final String cameraName = "1"; final ResolutionPreset resolutionPreset = ResolutionPreset.high; final boolean enableAudio = false; when(mockCameraProperties.getCameraName()).thenReturn(cameraName); SensorOrientationFeature mockSensorOrientationFeature = mock(SensorOrientationFeature.class); when(mockCameraFeatureFactory.createSensorOrientationFeature(any(), any(), any())) .thenReturn(mockSensorOrientationFeature); Camera camera = new Camera( mockActivity, mockFlutterTexture, mockCameraFeatureFactory, mockDartMessenger, mockCameraProperties, resolutionPreset, enableAudio); verify(mockCameraFeatureFactory, times(1)) .createSensorOrientationFeature(mockCameraProperties, mockActivity, mockDartMessenger); verify(mockCameraFeatureFactory, times(1)).createAutoFocusFeature(mockCameraProperties, false); verify(mockCameraFeatureFactory, times(1)).createExposureLockFeature(mockCameraProperties); verify(mockCameraFeatureFactory, times(1)) .createExposurePointFeature(eq(mockCameraProperties), eq(mockSensorOrientationFeature)); verify(mockCameraFeatureFactory, times(1)).createExposureOffsetFeature(mockCameraProperties); verify(mockCameraFeatureFactory, times(1)).createFlashFeature(mockCameraProperties); verify(mockCameraFeatureFactory, times(1)) .createFocusPointFeature(eq(mockCameraProperties), eq(mockSensorOrientationFeature)); verify(mockCameraFeatureFactory, times(1)).createFpsRangeFeature(mockCameraProperties); verify(mockCameraFeatureFactory, times(1)).createNoiseReductionFeature(mockCameraProperties); verify(mockCameraFeatureFactory, times(1)) .createResolutionFeature(mockCameraProperties, resolutionPreset, cameraName); verify(mockCameraFeatureFactory, times(1)).createZoomLevelFeature(mockCameraProperties); assertNotNull("should create a camera", camera); } @Test public void getDeviceOrientationManager() { SensorOrientationFeature mockSensorOrientationFeature = mockCameraFeatureFactory.createSensorOrientationFeature(mockCameraProperties, null, null); DeviceOrientationManager mockDeviceOrientationManager = mock(DeviceOrientationManager.class); when(mockSensorOrientationFeature.getDeviceOrientationManager()) .thenReturn(mockDeviceOrientationManager); DeviceOrientationManager actualDeviceOrientationManager = camera.getDeviceOrientationManager(); verify(mockSensorOrientationFeature, times(1)).getDeviceOrientationManager(); assertEquals(mockDeviceOrientationManager, actualDeviceOrientationManager); } @Test public void getExposureOffsetStepSize() { ExposureOffsetFeature mockExposureOffsetFeature = mockCameraFeatureFactory.createExposureOffsetFeature(mockCameraProperties); double stepSize = 2.3; when(mockExposureOffsetFeature.getExposureOffsetStepSize()).thenReturn(stepSize); double actualSize = camera.getExposureOffsetStepSize(); verify(mockExposureOffsetFeature, times(1)).getExposureOffsetStepSize(); assertEquals(stepSize, actualSize, 0); } @Test public void getMaxExposureOffset() { ExposureOffsetFeature mockExposureOffsetFeature = mockCameraFeatureFactory.createExposureOffsetFeature(mockCameraProperties); double expectedMaxOffset = 42.0; when(mockExposureOffsetFeature.getMaxExposureOffset()).thenReturn(expectedMaxOffset); double actualMaxOffset = camera.getMaxExposureOffset(); verify(mockExposureOffsetFeature, times(1)).getMaxExposureOffset(); assertEquals(expectedMaxOffset, actualMaxOffset, 0); } @Test public void getMinExposureOffset() { ExposureOffsetFeature mockExposureOffsetFeature = mockCameraFeatureFactory.createExposureOffsetFeature(mockCameraProperties); double expectedMinOffset = 21.5; when(mockExposureOffsetFeature.getMinExposureOffset()).thenReturn(21.5); double actualMinOffset = camera.getMinExposureOffset(); verify(mockExposureOffsetFeature, times(1)).getMinExposureOffset(); assertEquals(expectedMinOffset, actualMinOffset, 0); } @Test public void getMaxZoomLevel() { ZoomLevelFeature mockZoomLevelFeature = mockCameraFeatureFactory.createZoomLevelFeature(mockCameraProperties); float expectedMaxZoomLevel = 4.2f; when(mockZoomLevelFeature.getMaximumZoomLevel()).thenReturn(expectedMaxZoomLevel); float actualMaxZoomLevel = camera.getMaxZoomLevel(); verify(mockZoomLevelFeature, times(1)).getMaximumZoomLevel(); assertEquals(expectedMaxZoomLevel, actualMaxZoomLevel, 0); } @Test public void getMinZoomLevel() { ZoomLevelFeature mockZoomLevelFeature = mockCameraFeatureFactory.createZoomLevelFeature(mockCameraProperties); float expectedMinZoomLevel = 4.2f; when(mockZoomLevelFeature.getMinimumZoomLevel()).thenReturn(expectedMinZoomLevel); float actualMinZoomLevel = camera.getMinZoomLevel(); verify(mockZoomLevelFeature, times(1)).getMinimumZoomLevel(); assertEquals(expectedMinZoomLevel, actualMinZoomLevel, 0); } @Test public void setExposureMode_shouldUpdateExposureLockFeature() { ExposureLockFeature mockExposureLockFeature = mockCameraFeatureFactory.createExposureLockFeature(mockCameraProperties); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); ExposureMode exposureMode = ExposureMode.locked; camera.setExposureMode(mockResult, exposureMode); verify(mockExposureLockFeature, times(1)).setValue(exposureMode); verify(mockResult, never()).error(any(), any(), any()); verify(mockResult, times(1)).success(null); } @Test public void setExposureMode_shouldUpdateBuilder() { ExposureLockFeature mockExposureLockFeature = mockCameraFeatureFactory.createExposureLockFeature(mockCameraProperties); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); ExposureMode exposureMode = ExposureMode.locked; camera.setExposureMode(mockResult, exposureMode); verify(mockExposureLockFeature, times(1)).updateBuilder(any()); } @Test public void setExposureMode_shouldCallErrorOnResultOnCameraAccessException() throws CameraAccessException { MethodChannel.Result mockResult = mock(MethodChannel.Result.class); ExposureMode exposureMode = ExposureMode.locked; when(mockCaptureSession.setRepeatingRequest(any(), any(), any())) .thenThrow(new CameraAccessException(0, "")); camera.setExposureMode(mockResult, exposureMode); verify(mockResult, never()).success(any()); verify(mockResult, times(1)) .error("setExposureModeFailed", "Could not set exposure mode.", null); } @Test public void setExposurePoint_shouldUpdateExposurePointFeature() { SensorOrientationFeature mockSensorOrientationFeature = mock(SensorOrientationFeature.class); ExposurePointFeature mockExposurePointFeature = mockCameraFeatureFactory.createExposurePointFeature( mockCameraProperties, mockSensorOrientationFeature); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); Point point = new Point(42d, 42d); camera.setExposurePoint(mockResult, point); verify(mockExposurePointFeature, times(1)).setValue(point); verify(mockResult, never()).error(any(), any(), any()); verify(mockResult, times(1)).success(null); } @Test public void setExposurePoint_shouldUpdateBuilder() { SensorOrientationFeature mockSensorOrientationFeature = mock(SensorOrientationFeature.class); ExposurePointFeature mockExposurePointFeature = mockCameraFeatureFactory.createExposurePointFeature( mockCameraProperties, mockSensorOrientationFeature); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); Point point = new Point(42d, 42d); camera.setExposurePoint(mockResult, point); verify(mockExposurePointFeature, times(1)).updateBuilder(any()); } @Test public void setExposurePoint_shouldCallErrorOnResultOnCameraAccessException() throws CameraAccessException { MethodChannel.Result mockResult = mock(MethodChannel.Result.class); Point point = new Point(42d, 42d); when(mockCaptureSession.setRepeatingRequest(any(), any(), any())) .thenThrow(new CameraAccessException(0, "")); camera.setExposurePoint(mockResult, point); verify(mockResult, never()).success(any()); verify(mockResult, times(1)) .error("setExposurePointFailed", "Could not set exposure point.", null); } @Test public void setFlashMode_shouldUpdateFlashFeature() { FlashFeature mockFlashFeature = mockCameraFeatureFactory.createFlashFeature(mockCameraProperties); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); FlashMode flashMode = FlashMode.always; camera.setFlashMode(mockResult, flashMode); verify(mockFlashFeature, times(1)).setValue(flashMode); verify(mockResult, never()).error(any(), any(), any()); verify(mockResult, times(1)).success(null); } @Test public void setFlashMode_shouldUpdateBuilder() { FlashFeature mockFlashFeature = mockCameraFeatureFactory.createFlashFeature(mockCameraProperties); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); FlashMode flashMode = FlashMode.always; camera.setFlashMode(mockResult, flashMode); verify(mockFlashFeature, times(1)).updateBuilder(any()); } @Test public void setFlashMode_shouldCallErrorOnResultOnCameraAccessException() throws CameraAccessException { MethodChannel.Result mockResult = mock(MethodChannel.Result.class); FlashMode flashMode = FlashMode.always; when(mockCaptureSession.setRepeatingRequest(any(), any(), any())) .thenThrow(new CameraAccessException(0, "")); camera.setFlashMode(mockResult, flashMode); verify(mockResult, never()).success(any()); verify(mockResult, times(1)).error("setFlashModeFailed", "Could not set flash mode.", null); } @Test public void setFocusPoint_shouldUpdateFocusPointFeature() { SensorOrientationFeature mockSensorOrientationFeature = mock(SensorOrientationFeature.class); FocusPointFeature mockFocusPointFeature = mockCameraFeatureFactory.createFocusPointFeature( mockCameraProperties, mockSensorOrientationFeature); AutoFocusFeature mockAutoFocusFeature = mockCameraFeatureFactory.createAutoFocusFeature(mockCameraProperties, false); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); Point point = new Point(42d, 42d); when(mockAutoFocusFeature.getValue()).thenReturn(FocusMode.auto); camera.setFocusPoint(mockResult, point); verify(mockFocusPointFeature, times(1)).setValue(point); verify(mockResult, never()).error(any(), any(), any()); verify(mockResult, times(1)).success(null); } @Test public void setFocusPoint_shouldUpdateBuilder() { SensorOrientationFeature mockSensorOrientationFeature = mock(SensorOrientationFeature.class); FocusPointFeature mockFocusPointFeature = mockCameraFeatureFactory.createFocusPointFeature( mockCameraProperties, mockSensorOrientationFeature); AutoFocusFeature mockAutoFocusFeature = mockCameraFeatureFactory.createAutoFocusFeature(mockCameraProperties, false); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); Point point = new Point(42d, 42d); when(mockAutoFocusFeature.getValue()).thenReturn(FocusMode.auto); camera.setFocusPoint(mockResult, point); verify(mockFocusPointFeature, times(1)).updateBuilder(any()); } @Test public void setFocusPoint_shouldCallErrorOnResultOnCameraAccessException() throws CameraAccessException { AutoFocusFeature mockAutoFocusFeature = mockCameraFeatureFactory.createAutoFocusFeature(mockCameraProperties, false); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); Point point = new Point(42d, 42d); when(mockAutoFocusFeature.getValue()).thenReturn(FocusMode.auto); when(mockCaptureSession.setRepeatingRequest(any(), any(), any())) .thenThrow(new CameraAccessException(0, "")); camera.setFocusPoint(mockResult, point); verify(mockResult, never()).success(any()); verify(mockResult, times(1)).error("setFocusPointFailed", "Could not set focus point.", null); } @Test public void setZoomLevel_shouldUpdateZoomLevelFeature() throws CameraAccessException { ZoomLevelFeature mockZoomLevelFeature = mockCameraFeatureFactory.createZoomLevelFeature(mockCameraProperties); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); float zoomLevel = 1.0f; when(mockZoomLevelFeature.getValue()).thenReturn(zoomLevel); when(mockZoomLevelFeature.getMinimumZoomLevel()).thenReturn(0f); when(mockZoomLevelFeature.getMaximumZoomLevel()).thenReturn(2f); camera.setZoomLevel(mockResult, zoomLevel); verify(mockZoomLevelFeature, times(1)).setValue(zoomLevel); verify(mockResult, never()).error(any(), any(), any()); verify(mockResult, times(1)).success(null); } @Test public void setZoomLevel_shouldUpdateBuilder() throws CameraAccessException { ZoomLevelFeature mockZoomLevelFeature = mockCameraFeatureFactory.createZoomLevelFeature(mockCameraProperties); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); float zoomLevel = 1.0f; when(mockZoomLevelFeature.getValue()).thenReturn(zoomLevel); when(mockZoomLevelFeature.getMinimumZoomLevel()).thenReturn(0f); when(mockZoomLevelFeature.getMaximumZoomLevel()).thenReturn(2f); camera.setZoomLevel(mockResult, zoomLevel); verify(mockZoomLevelFeature, times(1)).updateBuilder(any()); } @Test public void setZoomLevel_shouldCallErrorOnResultOnCameraAccessException() throws CameraAccessException { ZoomLevelFeature mockZoomLevelFeature = mockCameraFeatureFactory.createZoomLevelFeature(mockCameraProperties); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); float zoomLevel = 1.0f; when(mockZoomLevelFeature.getValue()).thenReturn(zoomLevel); when(mockZoomLevelFeature.getMinimumZoomLevel()).thenReturn(0f); when(mockZoomLevelFeature.getMaximumZoomLevel()).thenReturn(2f); when(mockCaptureSession.setRepeatingRequest(any(), any(), any())) .thenThrow(new CameraAccessException(0, "")); camera.setZoomLevel(mockResult, zoomLevel); verify(mockResult, never()).success(any()); verify(mockResult, times(1)).error("setZoomLevelFailed", "Could not set zoom level.", null); } @Test public void pauseVideoRecording_shouldSendNullResultWhenNotRecording() { TestUtils.setPrivateField(camera, "recordingVideo", false); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); camera.pauseVideoRecording(mockResult); verify(mockResult, times(1)).success(null); verify(mockResult, never()).error(any(), any(), any()); } @Test public void pauseVideoRecording_shouldCallPauseWhenRecordingAndOnAPIN() { MethodChannel.Result mockResult = mock(MethodChannel.Result.class); MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); TestUtils.setPrivateField(camera, "mediaRecorder", mockMediaRecorder); TestUtils.setPrivateField(camera, "recordingVideo", true); TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 24); camera.pauseVideoRecording(mockResult); verify(mockMediaRecorder, times(1)).pause(); verify(mockResult, times(1)).success(null); verify(mockResult, never()).error(any(), any(), any()); } @Test public void pauseVideoRecording_shouldSendVideoRecordingFailedErrorWhenVersionCodeSmallerThenN() { TestUtils.setPrivateField(camera, "recordingVideo", true); TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 23); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); camera.pauseVideoRecording(mockResult); verify(mockResult, times(1)) .error("videoRecordingFailed", "pauseVideoRecording requires Android API +24.", null); verify(mockResult, never()).success(any()); } @Test public void pauseVideoRecording_shouldSendVideoRecordingFailedErrorWhenMediaRecorderPauseThrowsIllegalStateException() { MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); TestUtils.setPrivateField(camera, "mediaRecorder", mockMediaRecorder); TestUtils.setPrivateField(camera, "recordingVideo", true); TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 24); IllegalStateException expectedException = new IllegalStateException("Test error message"); doThrow(expectedException).when(mockMediaRecorder).pause(); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); camera.pauseVideoRecording(mockResult); verify(mockResult, times(1)).error("videoRecordingFailed", "Test error message", null); verify(mockResult, never()).success(any()); } @Test public void resumeVideoRecording_shouldSendNullResultWhenNotRecording() { MethodChannel.Result mockResult = mock(MethodChannel.Result.class); TestUtils.setPrivateField(camera, "recordingVideo", false); camera.resumeVideoRecording(mockResult); verify(mockResult, times(1)).success(null); verify(mockResult, never()).error(any(), any(), any()); } @Test public void resumeVideoRecording_shouldCallPauseWhenRecordingAndOnAPIN() { MethodChannel.Result mockResult = mock(MethodChannel.Result.class); MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); TestUtils.setPrivateField(camera, "mediaRecorder", mockMediaRecorder); TestUtils.setPrivateField(camera, "recordingVideo", true); TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 24); camera.resumeVideoRecording(mockResult); verify(mockMediaRecorder, times(1)).resume(); verify(mockResult, times(1)).success(null); verify(mockResult, never()).error(any(), any(), any()); } @Test public void resumeVideoRecording_shouldSendVideoRecordingFailedErrorWhenVersionCodeSmallerThanN() { TestUtils.setPrivateField(camera, "recordingVideo", true); TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 23); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); camera.resumeVideoRecording(mockResult); verify(mockResult, times(1)) .error("videoRecordingFailed", "resumeVideoRecording requires Android API +24.", null); verify(mockResult, never()).success(any()); } @Test public void resumeVideoRecording_shouldSendVideoRecordingFailedErrorWhenMediaRecorderPauseThrowsIllegalStateException() { MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); TestUtils.setPrivateField(camera, "mediaRecorder", mockMediaRecorder); TestUtils.setPrivateField(camera, "recordingVideo", true); TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", 24); IllegalStateException expectedException = new IllegalStateException("Test error message"); doThrow(expectedException).when(mockMediaRecorder).resume(); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); camera.resumeVideoRecording(mockResult); verify(mockResult, times(1)).error("videoRecordingFailed", "Test error message", null); verify(mockResult, never()).success(any()); } @Test public void setFocusMode_shouldUpdateAutoFocusFeature() { AutoFocusFeature mockAutoFocusFeature = mockCameraFeatureFactory.createAutoFocusFeature(mockCameraProperties, false); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); camera.setFocusMode(mockResult, FocusMode.auto); verify(mockAutoFocusFeature, times(1)).setValue(FocusMode.auto); verify(mockResult, never()).error(any(), any(), any()); verify(mockResult, times(1)).success(null); } @Test public void setFocusMode_shouldUpdateBuilder() { AutoFocusFeature mockAutoFocusFeature = mockCameraFeatureFactory.createAutoFocusFeature(mockCameraProperties, false); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); camera.setFocusMode(mockResult, FocusMode.auto); verify(mockAutoFocusFeature, times(1)).updateBuilder(any()); } @Test public void setFocusMode_shouldUnlockAutoFocusForAutoMode() { camera.setFocusMode(mock(MethodChannel.Result.class), FocusMode.auto); verify(mockPreviewRequestBuilder, times(1)) .set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); verify(mockPreviewRequestBuilder, times(1)) .set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE); } @Test public void setFocusMode_shouldSkipUnlockAutoFocusWhenNullCaptureSession() { TestUtils.setPrivateField(camera, "captureSession", null); camera.setFocusMode(mock(MethodChannel.Result.class), FocusMode.auto); verify(mockPreviewRequestBuilder, never()) .set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); verify(mockPreviewRequestBuilder, never()) .set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE); } @Test public void setFocusMode_shouldSendErrorEventOnUnlockAutoFocusCameraAccessException() throws CameraAccessException { when(mockCaptureSession.capture(any(), any(), any())) .thenThrow(new CameraAccessException(0, "")); camera.setFocusMode(mock(MethodChannel.Result.class), FocusMode.auto); verify(mockDartMessenger, times(1)).sendCameraErrorEvent(any()); } @Test public void setFocusMode_shouldLockAutoFocusForLockedMode() throws CameraAccessException { camera.setFocusMode(mock(MethodChannel.Result.class), FocusMode.locked); verify(mockPreviewRequestBuilder, times(1)) .set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START); verify(mockCaptureSession, times(1)).capture(any(), any(), any()); verify(mockCaptureSession, times(1)).setRepeatingRequest(any(), any(), any()); } @Test public void setFocusMode_shouldSkipLockAutoFocusWhenNullCaptureSession() { TestUtils.setPrivateField(camera, "captureSession", null); camera.setFocusMode(mock(MethodChannel.Result.class), FocusMode.locked); verify(mockPreviewRequestBuilder, never()) .set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); } @Test public void setFocusMode_shouldSendErrorEventOnLockAutoFocusCameraAccessException() throws CameraAccessException { when(mockCaptureSession.capture(any(), any(), any())) .thenThrow(new CameraAccessException(0, "")); camera.setFocusMode(mock(MethodChannel.Result.class), FocusMode.locked); verify(mockDartMessenger, times(1)).sendCameraErrorEvent(any()); } @Test public void setFocusMode_shouldCallErrorOnResultOnCameraAccessException() throws CameraAccessException { MethodChannel.Result mockResult = mock(MethodChannel.Result.class); when(mockCaptureSession.setRepeatingRequest(any(), any(), any())) .thenThrow(new CameraAccessException(0, "")); camera.setFocusMode(mockResult, FocusMode.locked); verify(mockResult, never()).success(any()); verify(mockResult, times(1)) .error("setFocusModeFailed", "Error setting focus mode: null", null); } @Test public void setExposureOffset_shouldUpdateExposureOffsetFeature() { ExposureOffsetFeature mockExposureOffsetFeature = mockCameraFeatureFactory.createExposureOffsetFeature(mockCameraProperties); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); when(mockExposureOffsetFeature.getValue()).thenReturn(1.0); camera.setExposureOffset(mockResult, 1.0); verify(mockExposureOffsetFeature, times(1)).setValue(1.0); verify(mockResult, never()).error(any(), any(), any()); verify(mockResult, times(1)).success(1.0); } @Test public void setExposureOffset_shouldAndUpdateBuilder() { ExposureOffsetFeature mockExposureOffsetFeature = mockCameraFeatureFactory.createExposureOffsetFeature(mockCameraProperties); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); camera.setExposureOffset(mockResult, 1.0); verify(mockExposureOffsetFeature, times(1)).updateBuilder(any()); } @Test public void setExposureOffset_shouldCallErrorOnResultOnCameraAccessException() throws CameraAccessException { MethodChannel.Result mockResult = mock(MethodChannel.Result.class); when(mockCaptureSession.setRepeatingRequest(any(), any(), any())) .thenThrow(new CameraAccessException(0, "")); camera.setExposureOffset(mockResult, 1.0); verify(mockResult, never()).success(any()); verify(mockResult, times(1)) .error("setExposureOffsetFailed", "Could not set exposure offset.", null); } @Test public void lockCaptureOrientation_shouldLockCaptureOrientation() { final Activity mockActivity = mock(Activity.class); SensorOrientationFeature mockSensorOrientationFeature = mockCameraFeatureFactory.createSensorOrientationFeature( mockCameraProperties, mockActivity, mockDartMessenger); camera.lockCaptureOrientation(PlatformChannel.DeviceOrientation.PORTRAIT_UP); verify(mockSensorOrientationFeature, times(1)) .lockCaptureOrientation(PlatformChannel.DeviceOrientation.PORTRAIT_UP); } @Test public void unlockCaptureOrientation_shouldUnlockCaptureOrientation() { final Activity mockActivity = mock(Activity.class); SensorOrientationFeature mockSensorOrientationFeature = mockCameraFeatureFactory.createSensorOrientationFeature( mockCameraProperties, mockActivity, mockDartMessenger); camera.unlockCaptureOrientation(); verify(mockSensorOrientationFeature, times(1)).unlockCaptureOrientation(); } @Test public void pausePreview_shouldPausePreview() throws CameraAccessException { camera.pausePreview(); assertEquals(TestUtils.getPrivateField(camera, "pausedPreview"), true); verify(mockCaptureSession, times(1)).stopRepeating(); } @Test public void resumePreview_shouldResumePreview() throws CameraAccessException { camera.resumePreview(); assertEquals(TestUtils.getPrivateField(camera, "pausedPreview"), false); verify(mockCaptureSession, times(1)).setRepeatingRequest(any(), any(), any()); } @Test public void resumePreview_shouldSendErrorEventOnCameraAccessException() throws CameraAccessException { when(mockCaptureSession.setRepeatingRequest(any(), any(), any())) .thenThrow(new CameraAccessException(0)); camera.resumePreview(); verify(mockDartMessenger, times(1)).sendCameraErrorEvent(any()); } @Test public void startBackgroundThread_shouldStartNewThread() { camera.startBackgroundThread(); verify(mockHandlerThread, times(1)).start(); assertEquals(mockHandler, TestUtils.getPrivateField(camera, "backgroundHandler")); } @Test public void startBackgroundThread_shouldNotStartNewThreadWhenAlreadyCreated() { camera.startBackgroundThread(); camera.startBackgroundThread(); verify(mockHandlerThread, times(1)).start(); } @Test public void stopBackgroundThread_quitsSafely() throws InterruptedException { camera.startBackgroundThread(); camera.stopBackgroundThread(); verify(mockHandlerThread).quitSafely(); verify(mockHandlerThread, never()).join(); } @Test public void onConverge_shouldTakePictureWithoutAbortingSession() throws CameraAccessException { ArrayList mockRequestBuilders = new ArrayList<>(); mockRequestBuilders.add(mock(CaptureRequest.Builder.class)); CameraDeviceWrapper fakeCamera = new FakeCameraDeviceWrapper(mockRequestBuilders); // Stub out other features used by the flow. TestUtils.setPrivateField(camera, "cameraDevice", fakeCamera); TestUtils.setPrivateField(camera, "pictureImageReader", mock(ImageReader.class)); SensorOrientationFeature mockSensorOrientationFeature = mockCameraFeatureFactory.createSensorOrientationFeature(mockCameraProperties, null, null); DeviceOrientationManager mockDeviceOrientationManager = mock(DeviceOrientationManager.class); when(mockSensorOrientationFeature.getDeviceOrientationManager()) .thenReturn(mockDeviceOrientationManager); // Simulate a post-precapture flow. camera.onConverged(); // A picture should be taken. verify(mockCaptureSession, times(1)).capture(any(), any(), any()); // The session shuold not be aborted as part of this flow, as this breaks capture on some // devices, and causes delays on others. verify(mockCaptureSession, never()).abortCaptures(); } @Test public void createCaptureSession_doesNotCloseCaptureSession() throws CameraAccessException { Surface mockSurface = mock(Surface.class); SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class); ResolutionFeature mockResolutionFeature = mock(ResolutionFeature.class); Size mockSize = mock(Size.class); ArrayList mockRequestBuilders = new ArrayList<>(); mockRequestBuilders.add(mock(CaptureRequest.Builder.class)); CameraDeviceWrapper fakeCamera = new FakeCameraDeviceWrapper(mockRequestBuilders); TestUtils.setPrivateField(camera, "cameraDevice", fakeCamera); TextureRegistry.SurfaceTextureEntry cameraFlutterTexture = (TextureRegistry.SurfaceTextureEntry) TestUtils.getPrivateField(camera, "flutterTexture"); CameraFeatures cameraFeatures = (CameraFeatures) TestUtils.getPrivateField(camera, "cameraFeatures"); ResolutionFeature resolutionFeature = (ResolutionFeature) TestUtils.getPrivateField(mockCameraFeatureFactory, "mockResolutionFeature"); when(cameraFlutterTexture.surfaceTexture()).thenReturn(mockSurfaceTexture); when(resolutionFeature.getPreviewSize()).thenReturn(mockSize); camera.createCaptureSession(CameraDevice.TEMPLATE_PREVIEW, mockSurface); verify(mockCaptureSession, never()).close(); } @Test public void close_doesCloseCaptureSessionWhenCameraDeviceNull() { camera.close(); verify(mockCaptureSession).close(); } @Test public void close_doesNotCloseCaptureSessionWhenCameraDeviceNonNull() { ArrayList mockRequestBuilders = new ArrayList<>(); mockRequestBuilders.add(mock(CaptureRequest.Builder.class)); CameraDeviceWrapper fakeCamera = new FakeCameraDeviceWrapper(mockRequestBuilders); TestUtils.setPrivateField(camera, "cameraDevice", fakeCamera); camera.close(); verify(mockCaptureSession, never()).close(); } private static class TestCameraFeatureFactory implements CameraFeatureFactory { private final AutoFocusFeature mockAutoFocusFeature; private final ExposureLockFeature mockExposureLockFeature; private final ExposureOffsetFeature mockExposureOffsetFeature; private final ExposurePointFeature mockExposurePointFeature; private final FlashFeature mockFlashFeature; private final FocusPointFeature mockFocusPointFeature; private final FpsRangeFeature mockFpsRangeFeature; private final NoiseReductionFeature mockNoiseReductionFeature; private final ResolutionFeature mockResolutionFeature; private final SensorOrientationFeature mockSensorOrientationFeature; private final ZoomLevelFeature mockZoomLevelFeature; public TestCameraFeatureFactory() { this.mockAutoFocusFeature = mock(AutoFocusFeature.class); this.mockExposureLockFeature = mock(ExposureLockFeature.class); this.mockExposureOffsetFeature = mock(ExposureOffsetFeature.class); this.mockExposurePointFeature = mock(ExposurePointFeature.class); this.mockFlashFeature = mock(FlashFeature.class); this.mockFocusPointFeature = mock(FocusPointFeature.class); this.mockFpsRangeFeature = mock(FpsRangeFeature.class); this.mockNoiseReductionFeature = mock(NoiseReductionFeature.class); this.mockResolutionFeature = mock(ResolutionFeature.class); this.mockSensorOrientationFeature = mock(SensorOrientationFeature.class); this.mockZoomLevelFeature = mock(ZoomLevelFeature.class); } @Override public AutoFocusFeature createAutoFocusFeature( @NonNull CameraProperties cameraProperties, boolean recordingVideo) { return mockAutoFocusFeature; } @Override public ExposureLockFeature createExposureLockFeature( @NonNull CameraProperties cameraProperties) { return mockExposureLockFeature; } @Override public ExposureOffsetFeature createExposureOffsetFeature( @NonNull CameraProperties cameraProperties) { return mockExposureOffsetFeature; } @Override public FlashFeature createFlashFeature(@NonNull CameraProperties cameraProperties) { return mockFlashFeature; } @Override public ResolutionFeature createResolutionFeature( @NonNull CameraProperties cameraProperties, ResolutionPreset initialSetting, String cameraName) { return mockResolutionFeature; } @Override public FocusPointFeature createFocusPointFeature( @NonNull CameraProperties cameraProperties, @NonNull SensorOrientationFeature sensorOrienttionFeature) { return mockFocusPointFeature; } @Override public FpsRangeFeature createFpsRangeFeature(@NonNull CameraProperties cameraProperties) { return mockFpsRangeFeature; } @Override public SensorOrientationFeature createSensorOrientationFeature( @NonNull CameraProperties cameraProperties, @NonNull Activity activity, @NonNull DartMessenger dartMessenger) { return mockSensorOrientationFeature; } @Override public ZoomLevelFeature createZoomLevelFeature(@NonNull CameraProperties cameraProperties) { return mockZoomLevelFeature; } @Override public ExposurePointFeature createExposurePointFeature( @NonNull CameraProperties cameraProperties, @NonNull SensorOrientationFeature sensorOrientationFeature) { return mockExposurePointFeature; } @Override public NoiseReductionFeature createNoiseReductionFeature( @NonNull CameraProperties cameraProperties) { return mockNoiseReductionFeature; } } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraTest_getRecordingProfileTest.java ================================================ // Copyright 2013 The Flutter 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.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 android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CaptureRequest; import android.media.CamcorderProfile; import android.media.EncoderProfiles; import android.os.Handler; import android.os.HandlerThread; import androidx.annotation.NonNull; import io.flutter.plugins.camera.features.CameraFeatureFactory; import io.flutter.plugins.camera.features.autofocus.AutoFocusFeature; import io.flutter.plugins.camera.features.exposurelock.ExposureLockFeature; import io.flutter.plugins.camera.features.exposureoffset.ExposureOffsetFeature; import io.flutter.plugins.camera.features.exposurepoint.ExposurePointFeature; import io.flutter.plugins.camera.features.flash.FlashFeature; import io.flutter.plugins.camera.features.focuspoint.FocusPointFeature; import io.flutter.plugins.camera.features.fpsrange.FpsRangeFeature; import io.flutter.plugins.camera.features.noisereduction.NoiseReductionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionFeature; import io.flutter.plugins.camera.features.resolution.ResolutionPreset; import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature; import io.flutter.plugins.camera.features.zoomlevel.ZoomLevelFeature; import io.flutter.view.TextureRegistry; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockedStatic; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) public class CameraTest_getRecordingProfileTest { private CameraProperties mockCameraProperties; private CameraFeatureFactory mockCameraFeatureFactory; private DartMessenger mockDartMessenger; private Camera camera; private CameraCaptureSession mockCaptureSession; private CaptureRequest.Builder mockPreviewRequestBuilder; private MockedStatic mockHandlerThreadFactory; private HandlerThread mockHandlerThread; private MockedStatic mockHandlerFactory; private Handler mockHandler; @Before public void before() { mockCameraProperties = mock(CameraProperties.class); mockCameraFeatureFactory = new TestCameraFeatureFactory(); mockDartMessenger = mock(DartMessenger.class); final Activity mockActivity = mock(Activity.class); final TextureRegistry.SurfaceTextureEntry mockFlutterTexture = mock(TextureRegistry.SurfaceTextureEntry.class); final ResolutionPreset resolutionPreset = ResolutionPreset.high; final boolean enableAudio = false; camera = new Camera( mockActivity, mockFlutterTexture, mockCameraFeatureFactory, mockDartMessenger, mockCameraProperties, resolutionPreset, enableAudio); } @Config(maxSdk = 30) @Test public void getRecordingProfileLegacy() { ResolutionFeature mockResolutionFeature = mockCameraFeatureFactory.createResolutionFeature(mockCameraProperties, null, null); CamcorderProfile mockCamcorderProfile = mock(CamcorderProfile.class); when(mockResolutionFeature.getRecordingProfileLegacy()).thenReturn(mockCamcorderProfile); CamcorderProfile actualRecordingProfile = camera.getRecordingProfileLegacy(); verify(mockResolutionFeature, times(1)).getRecordingProfileLegacy(); assertEquals(mockCamcorderProfile, actualRecordingProfile); } @Config(minSdk = 31) @Test public void getRecordingProfile() { ResolutionFeature mockResolutionFeature = mockCameraFeatureFactory.createResolutionFeature(mockCameraProperties, null, null); EncoderProfiles mockRecordingProfile = mock(EncoderProfiles.class); when(mockResolutionFeature.getRecordingProfile()).thenReturn(mockRecordingProfile); EncoderProfiles actualRecordingProfile = camera.getRecordingProfile(); verify(mockResolutionFeature, times(1)).getRecordingProfile(); assertEquals(mockRecordingProfile, actualRecordingProfile); } private static class TestCameraFeatureFactory implements CameraFeatureFactory { private final AutoFocusFeature mockAutoFocusFeature; private final ExposureLockFeature mockExposureLockFeature; private final ExposureOffsetFeature mockExposureOffsetFeature; private final ExposurePointFeature mockExposurePointFeature; private final FlashFeature mockFlashFeature; private final FocusPointFeature mockFocusPointFeature; private final FpsRangeFeature mockFpsRangeFeature; private final NoiseReductionFeature mockNoiseReductionFeature; private final ResolutionFeature mockResolutionFeature; private final SensorOrientationFeature mockSensorOrientationFeature; private final ZoomLevelFeature mockZoomLevelFeature; public TestCameraFeatureFactory() { this.mockAutoFocusFeature = mock(AutoFocusFeature.class); this.mockExposureLockFeature = mock(ExposureLockFeature.class); this.mockExposureOffsetFeature = mock(ExposureOffsetFeature.class); this.mockExposurePointFeature = mock(ExposurePointFeature.class); this.mockFlashFeature = mock(FlashFeature.class); this.mockFocusPointFeature = mock(FocusPointFeature.class); this.mockFpsRangeFeature = mock(FpsRangeFeature.class); this.mockNoiseReductionFeature = mock(NoiseReductionFeature.class); this.mockResolutionFeature = mock(ResolutionFeature.class); this.mockSensorOrientationFeature = mock(SensorOrientationFeature.class); this.mockZoomLevelFeature = mock(ZoomLevelFeature.class); } @Override public AutoFocusFeature createAutoFocusFeature( @NonNull CameraProperties cameraProperties, boolean recordingVideo) { return mockAutoFocusFeature; } @Override public ExposureLockFeature createExposureLockFeature( @NonNull CameraProperties cameraProperties) { return mockExposureLockFeature; } @Override public ExposureOffsetFeature createExposureOffsetFeature( @NonNull CameraProperties cameraProperties) { return mockExposureOffsetFeature; } @Override public FlashFeature createFlashFeature(@NonNull CameraProperties cameraProperties) { return mockFlashFeature; } @Override public ResolutionFeature createResolutionFeature( @NonNull CameraProperties cameraProperties, ResolutionPreset initialSetting, String cameraName) { return mockResolutionFeature; } @Override public FocusPointFeature createFocusPointFeature( @NonNull CameraProperties cameraProperties, @NonNull SensorOrientationFeature sensorOrienttionFeature) { return mockFocusPointFeature; } @Override public FpsRangeFeature createFpsRangeFeature(@NonNull CameraProperties cameraProperties) { return mockFpsRangeFeature; } @Override public SensorOrientationFeature createSensorOrientationFeature( @NonNull CameraProperties cameraProperties, @NonNull Activity activity, @NonNull DartMessenger dartMessenger) { return mockSensorOrientationFeature; } @Override public ZoomLevelFeature createZoomLevelFeature(@NonNull CameraProperties cameraProperties) { return mockZoomLevelFeature; } @Override public ExposurePointFeature createExposurePointFeature( @NonNull CameraProperties cameraProperties, @NonNull SensorOrientationFeature sensorOrientationFeature) { return mockExposurePointFeature; } @Override public NoiseReductionFeature createNoiseReductionFeature( @NonNull CameraProperties cameraProperties) { return mockNoiseReductionFeature; } } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/CameraUtilsTest.java ================================================ // Copyright 2013 The Flutter 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.mockito.Mockito.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.app.Activity; import android.content.Context; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraMetadata; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import java.util.List; import java.util.Map; import org.junit.Test; public class CameraUtilsTest { @Test public void serializeDeviceOrientation_serializesCorrectly() { assertEquals( "portraitUp", CameraUtils.serializeDeviceOrientation(PlatformChannel.DeviceOrientation.PORTRAIT_UP)); assertEquals( "portraitDown", CameraUtils.serializeDeviceOrientation(PlatformChannel.DeviceOrientation.PORTRAIT_DOWN)); assertEquals( "landscapeLeft", CameraUtils.serializeDeviceOrientation(PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT)); assertEquals( "landscapeRight", CameraUtils.serializeDeviceOrientation(PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT)); } @Test(expected = UnsupportedOperationException.class) public void serializeDeviceOrientation_throws_for_null() { CameraUtils.serializeDeviceOrientation(null); } @Test public void deserializeDeviceOrientation_deserializesCorrectly() { assertEquals( PlatformChannel.DeviceOrientation.PORTRAIT_UP, CameraUtils.deserializeDeviceOrientation("portraitUp")); assertEquals( PlatformChannel.DeviceOrientation.PORTRAIT_DOWN, CameraUtils.deserializeDeviceOrientation("portraitDown")); assertEquals( PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT, CameraUtils.deserializeDeviceOrientation("landscapeLeft")); assertEquals( PlatformChannel.DeviceOrientation.LANDSCAPE_RIGHT, CameraUtils.deserializeDeviceOrientation("landscapeRight")); } @Test(expected = UnsupportedOperationException.class) public void deserializeDeviceOrientation_throwsForNull() { CameraUtils.deserializeDeviceOrientation(null); } @Test public void getAvailableCameras_retrievesValidCameras() throws CameraAccessException, NumberFormatException { final Activity mockActivity = mock(Activity.class); final CameraManager mockCameraManager = mock(CameraManager.class); final CameraCharacteristics mockCameraCharacteristics = mock(CameraCharacteristics.class); final String[] mockCameraIds = {"1394902", "-192930", "0283835", "foobar"}; final int mockSensorOrientation0 = 90; final int mockSensorOrientation2 = 270; final int mockLensFacing0 = CameraMetadata.LENS_FACING_FRONT; final int mockLensFacing2 = CameraMetadata.LENS_FACING_EXTERNAL; when(mockActivity.getSystemService(Context.CAMERA_SERVICE)).thenReturn(mockCameraManager); when(mockCameraManager.getCameraIdList()).thenReturn(mockCameraIds); when(mockCameraManager.getCameraCharacteristics(anyString())) .thenReturn(mockCameraCharacteristics); when(mockCameraCharacteristics.get(any())) .thenReturn(mockSensorOrientation0) .thenReturn(mockLensFacing0) .thenReturn(mockSensorOrientation2) .thenReturn(mockLensFacing2); List> availableCameras = CameraUtils.getAvailableCameras(mockActivity); assertEquals(availableCameras.size(), 2); assertEquals(availableCameras.get(0).get("name"), "1394902"); assertEquals(availableCameras.get(0).get("sensorOrientation"), mockSensorOrientation0); assertEquals(availableCameras.get(0).get("lensFacing"), "front"); assertEquals(availableCameras.get(1).get("name"), "0283835"); assertEquals(availableCameras.get(1).get("sensorOrientation"), mockSensorOrientation2); assertEquals(availableCameras.get(1).get("lensFacing"), "external"); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/DartMessengerTest.java ================================================ // Copyright 2013 The Flutter 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 junit.framework.TestCase.assertNull; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import android.os.Handler; import androidx.annotation.NonNull; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.StandardMethodCodec; import io.flutter.plugins.camera.features.autofocus.FocusMode; import io.flutter.plugins.camera.features.exposurelock.ExposureMode; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; public class DartMessengerTest { /** A {@link BinaryMessenger} implementation that does nothing but save its messages. */ private static class FakeBinaryMessenger implements BinaryMessenger { private final List sentMessages = new ArrayList<>(); @Override public void send(@NonNull String channel, ByteBuffer message) { sentMessages.add(message); } @Override public void send(@NonNull String channel, ByteBuffer message, BinaryReply callback) { send(channel, message); } @Override public void setMessageHandler(@NonNull String channel, BinaryMessageHandler handler) {} List getMessages() { return new ArrayList<>(sentMessages); } } private Handler mockHandler; private DartMessenger dartMessenger; private FakeBinaryMessenger fakeBinaryMessenger; @Before public void setUp() { mockHandler = mock(Handler.class); fakeBinaryMessenger = new FakeBinaryMessenger(); dartMessenger = new DartMessenger(fakeBinaryMessenger, 0, mockHandler); } @Test public void sendCameraErrorEvent_includesErrorDescriptions() { doAnswer(createPostHandlerAnswer()).when(mockHandler).post(any(Runnable.class)); dartMessenger.sendCameraErrorEvent("error description"); List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); MethodCall call = decodeSentMessage(sentMessages.get(0)); assertEquals("error", call.method); assertEquals("error description", call.argument("description")); } @Test public void sendCameraInitializedEvent_includesPreviewSize() { doAnswer(createPostHandlerAnswer()).when(mockHandler).post(any(Runnable.class)); dartMessenger.sendCameraInitializedEvent(0, 0, ExposureMode.auto, FocusMode.auto, true, true); List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); MethodCall call = decodeSentMessage(sentMessages.get(0)); assertEquals("initialized", call.method); assertEquals(0, (double) call.argument("previewWidth"), 0); assertEquals(0, (double) call.argument("previewHeight"), 0); assertEquals("ExposureMode auto", call.argument("exposureMode"), "auto"); assertEquals("FocusMode continuous", call.argument("focusMode"), "auto"); assertEquals("exposurePointSupported", call.argument("exposurePointSupported"), true); assertEquals("focusPointSupported", call.argument("focusPointSupported"), true); } @Test public void sendCameraClosingEvent() { doAnswer(createPostHandlerAnswer()).when(mockHandler).post(any(Runnable.class)); dartMessenger.sendCameraClosingEvent(); List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); MethodCall call = decodeSentMessage(sentMessages.get(0)); assertEquals("camera_closing", call.method); assertNull(call.argument("description")); } @Test public void sendDeviceOrientationChangedEvent() { doAnswer(createPostHandlerAnswer()).when(mockHandler).post(any(Runnable.class)); dartMessenger.sendDeviceOrientationChangeEvent(PlatformChannel.DeviceOrientation.PORTRAIT_UP); List sentMessages = fakeBinaryMessenger.getMessages(); assertEquals(1, sentMessages.size()); MethodCall call = decodeSentMessage(sentMessages.get(0)); assertEquals("orientation_changed", call.method); assertEquals(call.argument("orientation"), "portraitUp"); } private static Answer createPostHandlerAnswer() { return new Answer() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { Runnable runnable = invocation.getArgument(0, Runnable.class); if (runnable != null) { runnable.run(); } return true; } }; } private MethodCall decodeSentMessage(ByteBuffer sentMessage) { sentMessage.position(0); return StandardMethodCodec.INSTANCE.decodeMethodCall(sentMessage); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/ImageSaverTests.java ================================================ // Copyright 2013 The Flutter 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.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; 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.media.Image; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; public class ImageSaverTests { Image mockImage; File mockFile; ImageSaver.Callback mockCallback; ImageSaver imageSaver; Image.Plane mockPlane; ByteBuffer mockBuffer; MockedStatic mockFileOutputStreamFactory; FileOutputStream mockFileOutputStream; @Before public void setup() { // Set up mocked file dependency mockFile = mock(File.class); when(mockFile.getAbsolutePath()).thenReturn("absolute/path"); mockPlane = mock(Image.Plane.class); mockBuffer = mock(ByteBuffer.class); when(mockBuffer.remaining()).thenReturn(3); when(mockBuffer.get(any())) .thenAnswer( new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { byte[] bytes = invocation.getArgument(0); bytes[0] = 0x42; bytes[1] = 0x00; bytes[2] = 0x13; return mockBuffer; } }); // Set up mocked image dependency mockImage = mock(Image.class); when(mockPlane.getBuffer()).thenReturn(mockBuffer); when(mockImage.getPlanes()).thenReturn(new Image.Plane[] {mockPlane}); // Set up mocked FileOutputStream mockFileOutputStreamFactory = mockStatic(ImageSaver.FileOutputStreamFactory.class); mockFileOutputStream = mock(FileOutputStream.class); mockFileOutputStreamFactory .when(() -> ImageSaver.FileOutputStreamFactory.create(any())) .thenReturn(mockFileOutputStream); // Set up testable ImageSaver instance mockCallback = mock(ImageSaver.Callback.class); imageSaver = new ImageSaver(mockImage, mockFile, mockCallback); } @After public void teardown() { mockFileOutputStreamFactory.close(); } @Test public void runWritesBytesToFileAndFinishesWithPath() throws IOException { imageSaver.run(); verify(mockFileOutputStream, times(1)).write(new byte[] {0x42, 0x00, 0x13}); verify(mockCallback, times(1)).onComplete("absolute/path"); verify(mockCallback, never()).onError(any(), any()); } @Test public void runCallsErrorOnWriteIoexception() throws IOException { doThrow(new IOException()).when(mockFileOutputStream).write(any()); imageSaver.run(); verify(mockCallback, times(1)).onError("IOError", "Failed saving image"); verify(mockCallback, never()).onComplete(any()); } @Test public void runCallsErrorOnCloseIoexception() throws IOException { doThrow(new IOException("message")).when(mockFileOutputStream).close(); imageSaver.run(); verify(mockCallback, times(1)).onError("cameraAccess", "message"); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/MethodCallHandlerImplTest.java ================================================ // Copyright 2013 The Flutter 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.assertFalse; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.Activity; import android.hardware.camera2.CameraAccessException; import androidx.lifecycle.LifecycleObserver; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugins.camera.utils.TestUtils; import io.flutter.view.TextureRegistry; import org.junit.Before; import org.junit.Test; public class MethodCallHandlerImplTest { MethodChannel.MethodCallHandler handler; MethodChannel.Result mockResult; Camera mockCamera; @Before public void setUp() { handler = new MethodCallHandlerImpl( mock(Activity.class), mock(BinaryMessenger.class), mock(CameraPermissions.class), mock(CameraPermissions.PermissionsRegistry.class), mock(TextureRegistry.class)); mockResult = mock(MethodChannel.Result.class); mockCamera = mock(Camera.class); TestUtils.setPrivateField(handler, "camera", mockCamera); } @Test public void shouldNotImplementLifecycleObserverInterface() { Class methodCallHandlerClass = MethodCallHandlerImpl.class; assertFalse(LifecycleObserver.class.isAssignableFrom(methodCallHandlerClass)); } @Test public void onMethodCall_pausePreview_shouldPausePreviewAndSendSuccessResult() throws CameraAccessException { handler.onMethodCall(new MethodCall("pausePreview", null), mockResult); verify(mockCamera, times(1)).pausePreview(); verify(mockResult, times(1)).success(null); } @Test public void onMethodCall_pausePreview_shouldSendErrorResultOnCameraAccessException() throws CameraAccessException { doThrow(new CameraAccessException(0)).when(mockCamera).pausePreview(); handler.onMethodCall(new MethodCall("pausePreview", null), mockResult); verify(mockResult, times(1)).error("CameraAccess", null, null); } @Test public void onMethodCall_resumePreview_shouldResumePreviewAndSendSuccessResult() { handler.onMethodCall(new MethodCall("resumePreview", null), mockResult); verify(mockCamera, times(1)).resumePreview(); verify(mockResult, times(1)).success(null); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/autofocus/AutoFocusFeatureTest.java ================================================ // Copyright 2013 The Flutter 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.features.autofocus; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; 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.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureRequest; import io.flutter.plugins.camera.CameraProperties; import org.junit.Test; public class AutoFocusFeatureTest { private static final int[] FOCUS_MODES_ONLY_OFF = new int[] {CameraCharacteristics.CONTROL_AF_MODE_OFF}; private static final int[] FOCUS_MODES = new int[] { CameraCharacteristics.CONTROL_AF_MODE_OFF, CameraCharacteristics.CONTROL_AF_MODE_AUTO }; @Test public void getDebugName_shouldReturnTheNameOfTheFeature() { CameraProperties mockCameraProperties = mock(CameraProperties.class); AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false); assertEquals("AutoFocusFeature", autoFocusFeature.getDebugName()); } @Test public void getValue_shouldReturnAutoIfNotSet() { CameraProperties mockCameraProperties = mock(CameraProperties.class); AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false); assertEquals(FocusMode.auto, autoFocusFeature.getValue()); } @Test public void getValue_shouldEchoTheSetValue() { CameraProperties mockCameraProperties = mock(CameraProperties.class); AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false); FocusMode expectedValue = FocusMode.locked; autoFocusFeature.setValue(expectedValue); FocusMode actualValue = autoFocusFeature.getValue(); assertEquals(expectedValue, actualValue); } @Test public void checkIsSupported_shouldReturnFalseWhenMinimumFocusDistanceIsZero() { CameraProperties mockCameraProperties = mock(CameraProperties.class); AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false); when(mockCameraProperties.getControlAutoFocusAvailableModes()).thenReturn(FOCUS_MODES); when(mockCameraProperties.getLensInfoMinimumFocusDistance()).thenReturn(0.0F); assertFalse(autoFocusFeature.checkIsSupported()); } @Test public void checkIsSupported_shouldReturnFalseWhenMinimumFocusDistanceIsNull() { CameraProperties mockCameraProperties = mock(CameraProperties.class); AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false); when(mockCameraProperties.getControlAutoFocusAvailableModes()).thenReturn(FOCUS_MODES); when(mockCameraProperties.getLensInfoMinimumFocusDistance()).thenReturn(null); assertFalse(autoFocusFeature.checkIsSupported()); } @Test public void checkIsSupport_shouldReturnFalseWhenNoFocusModesAreAvailable() { CameraProperties mockCameraProperties = mock(CameraProperties.class); AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false); when(mockCameraProperties.getControlAutoFocusAvailableModes()).thenReturn(new int[] {}); when(mockCameraProperties.getLensInfoMinimumFocusDistance()).thenReturn(1.0F); assertFalse(autoFocusFeature.checkIsSupported()); } @Test public void checkIsSupport_shouldReturnFalseWhenOnlyFocusOffIsAvailable() { CameraProperties mockCameraProperties = mock(CameraProperties.class); AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false); when(mockCameraProperties.getControlAutoFocusAvailableModes()).thenReturn(FOCUS_MODES_ONLY_OFF); when(mockCameraProperties.getLensInfoMinimumFocusDistance()).thenReturn(1.0F); assertFalse(autoFocusFeature.checkIsSupported()); } @Test public void checkIsSupport_shouldReturnTrueWhenOnlyMultipleFocusModesAreAvailable() { CameraProperties mockCameraProperties = mock(CameraProperties.class); AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false); when(mockCameraProperties.getControlAutoFocusAvailableModes()).thenReturn(FOCUS_MODES); when(mockCameraProperties.getLensInfoMinimumFocusDistance()).thenReturn(1.0F); assertTrue(autoFocusFeature.checkIsSupported()); } @Test public void updateBuilderShouldReturnWhenCheckIsSupportedIsFalse() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false); when(mockCameraProperties.getControlAutoFocusAvailableModes()).thenReturn(FOCUS_MODES); when(mockCameraProperties.getLensInfoMinimumFocusDistance()).thenReturn(0.0F); autoFocusFeature.updateBuilder(mockBuilder); verify(mockBuilder, never()).set(any(), any()); } @Test public void updateBuilder_shouldSetControlModeToAutoWhenFocusIsLocked() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false); when(mockCameraProperties.getControlAutoFocusAvailableModes()).thenReturn(FOCUS_MODES); when(mockCameraProperties.getLensInfoMinimumFocusDistance()).thenReturn(1.0F); autoFocusFeature.setValue(FocusMode.locked); autoFocusFeature.updateBuilder(mockBuilder); verify(mockBuilder, times(1)) .set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); } @Test public void updateBuilder_shouldSetControlModeToContinuousVideoWhenFocusIsAutoAndRecordingVideo() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, true); when(mockCameraProperties.getControlAutoFocusAvailableModes()).thenReturn(FOCUS_MODES); when(mockCameraProperties.getLensInfoMinimumFocusDistance()).thenReturn(1.0F); autoFocusFeature.setValue(FocusMode.auto); autoFocusFeature.updateBuilder(mockBuilder); verify(mockBuilder, times(1)) .set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO); } @Test public void updateBuilder_shouldSetControlModeToContinuousVideoWhenFocusIsAutoAndNotRecordingVideo() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); AutoFocusFeature autoFocusFeature = new AutoFocusFeature(mockCameraProperties, false); when(mockCameraProperties.getControlAutoFocusAvailableModes()).thenReturn(FOCUS_MODES); when(mockCameraProperties.getLensInfoMinimumFocusDistance()).thenReturn(1.0F); autoFocusFeature.setValue(FocusMode.auto); autoFocusFeature.updateBuilder(mockBuilder); verify(mockBuilder, times(1)) .set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/autofocus/FocusModeTest.java ================================================ // Copyright 2013 The Flutter 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.features.autofocus; import static org.junit.Assert.assertEquals; import org.junit.Test; public class FocusModeTest { @Test public void getValueForString_returnsCorrectValues() { assertEquals( "Returns FocusMode.auto for 'auto'", FocusMode.getValueForString("auto"), FocusMode.auto); assertEquals( "Returns FocusMode.locked for 'locked'", FocusMode.getValueForString("locked"), FocusMode.locked); } @Test public void getValueForString_returnsNullForNonexistantValue() { assertEquals( "Returns null for 'nonexistant'", FocusMode.getValueForString("nonexistant"), null); } @Test public void toString_returnsCorrectValue() { assertEquals("Returns 'auto' for FocusMode.auto", FocusMode.auto.toString(), "auto"); assertEquals("Returns 'locked' for FocusMode.locked", FocusMode.locked.toString(), "locked"); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/exposurelock/ExposureLockFeatureTest.java ================================================ // Copyright 2013 The Flutter 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.features.exposurelock; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.hardware.camera2.CaptureRequest; import io.flutter.plugins.camera.CameraProperties; import org.junit.Test; public class ExposureLockFeatureTest { @Test public void getDebugName_shouldReturnTheNameOfTheFeature() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposureLockFeature exposureLockFeature = new ExposureLockFeature(mockCameraProperties); assertEquals("ExposureLockFeature", exposureLockFeature.getDebugName()); } @Test public void getValue_shouldReturnAutoIfNotSet() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposureLockFeature exposureLockFeature = new ExposureLockFeature(mockCameraProperties); assertEquals(ExposureMode.auto, exposureLockFeature.getValue()); } @Test public void getValue_shouldEchoTheSetValue() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposureLockFeature exposureLockFeature = new ExposureLockFeature(mockCameraProperties); ExposureMode expectedValue = ExposureMode.locked; exposureLockFeature.setValue(expectedValue); ExposureMode actualValue = exposureLockFeature.getValue(); assertEquals(expectedValue, actualValue); } @Test public void checkIsSupported_shouldReturnTrue() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposureLockFeature exposureLockFeature = new ExposureLockFeature(mockCameraProperties); assertTrue(exposureLockFeature.checkIsSupported()); } @Test public void updateBuilder_shouldSetControlAeLockToFalseWhenAutoExposureIsSetToAuto() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); ExposureLockFeature exposureLockFeature = new ExposureLockFeature(mockCameraProperties); exposureLockFeature.setValue(ExposureMode.auto); exposureLockFeature.updateBuilder(mockBuilder); verify(mockBuilder, times(1)).set(CaptureRequest.CONTROL_AE_LOCK, false); } @Test public void updateBuilder_shouldSetControlAeLockToFalseWhenAutoExposureIsSetToLocked() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); ExposureLockFeature exposureLockFeature = new ExposureLockFeature(mockCameraProperties); exposureLockFeature.setValue(ExposureMode.locked); exposureLockFeature.updateBuilder(mockBuilder); verify(mockBuilder, times(1)).set(CaptureRequest.CONTROL_AE_LOCK, true); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/exposurelock/ExposureModeTest.java ================================================ // Copyright 2013 The Flutter 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.features.exposurelock; import static org.junit.Assert.assertEquals; import org.junit.Test; public class ExposureModeTest { @Test public void getValueForString_returnsCorrectValues() { assertEquals( "Returns ExposureMode.auto for 'auto'", ExposureMode.getValueForString("auto"), ExposureMode.auto); assertEquals( "Returns ExposureMode.locked for 'locked'", ExposureMode.getValueForString("locked"), ExposureMode.locked); } @Test public void getValueForString_returnsNullForNonexistantValue() { assertEquals( "Returns null for 'nonexistant'", ExposureMode.getValueForString("nonexistant"), null); } @Test public void toString_returnsCorrectValue() { assertEquals("Returns 'auto' for ExposureMode.auto", ExposureMode.auto.toString(), "auto"); assertEquals( "Returns 'locked' for ExposureMode.locked", ExposureMode.locked.toString(), "locked"); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/exposureoffset/ExposureOffsetFeatureTest.java ================================================ // Copyright 2013 The Flutter 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.features.exposureoffset; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; 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.hardware.camera2.CaptureRequest; import io.flutter.plugins.camera.CameraProperties; import org.junit.Test; public class ExposureOffsetFeatureTest { @Test public void getDebugName_shouldReturnTheNameOfTheFeature() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties); assertEquals("ExposureOffsetFeature", exposureOffsetFeature.getDebugName()); } @Test public void getValue_shouldReturnZeroIfNotSet() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties); final double actualValue = exposureOffsetFeature.getValue(); assertEquals(0.0, actualValue, 0); } @Test public void getValue_shouldEchoTheSetValue() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties); double expectedValue = 4.0; when(mockCameraProperties.getControlAutoExposureCompensationStep()).thenReturn(0.5); exposureOffsetFeature.setValue(2.0); double actualValue = exposureOffsetFeature.getValue(); assertEquals(expectedValue, actualValue, 0); } @Test public void getExposureOffsetStepSize_shouldReturnTheControlExposureCompensationStepValue() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties); when(mockCameraProperties.getControlAutoExposureCompensationStep()).thenReturn(0.5); assertEquals(0.5, exposureOffsetFeature.getExposureOffsetStepSize(), 0); } @Test public void checkIsSupported_shouldReturnTrue() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties); assertTrue(exposureOffsetFeature.checkIsSupported()); } @Test public void updateBuilder_shouldSetControlAeExposureCompensationToOffset() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); ExposureOffsetFeature exposureOffsetFeature = new ExposureOffsetFeature(mockCameraProperties); when(mockCameraProperties.getControlAutoExposureCompensationStep()).thenReturn(0.5); exposureOffsetFeature.setValue(2.0); exposureOffsetFeature.updateBuilder(mockBuilder); verify(mockBuilder, times(1)).set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 4); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/exposurepoint/ExposurePointFeatureTest.java ================================================ // Copyright 2013 The Flutter 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.features.exposurepoint; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; 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.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.MeteringRectangle; import android.util.Size; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.CameraRegionUtils; import io.flutter.plugins.camera.features.Point; import io.flutter.plugins.camera.features.sensororientation.DeviceOrientationManager; import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature; import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; public class ExposurePointFeatureTest { Size mockCameraBoundaries; SensorOrientationFeature mockSensorOrientationFeature; DeviceOrientationManager mockDeviceOrientationManager; @Before public void setUp() { this.mockCameraBoundaries = mock(Size.class); when(this.mockCameraBoundaries.getWidth()).thenReturn(100); when(this.mockCameraBoundaries.getHeight()).thenReturn(100); mockSensorOrientationFeature = mock(SensorOrientationFeature.class); mockDeviceOrientationManager = mock(DeviceOrientationManager.class); when(mockSensorOrientationFeature.getDeviceOrientationManager()) .thenReturn(mockDeviceOrientationManager); when(mockDeviceOrientationManager.getLastUIOrientation()) .thenReturn(PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT); } @Test public void getDebugName_shouldReturnTheNameOfTheFeature() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature); assertEquals("ExposurePointFeature", exposurePointFeature.getDebugName()); } @Test public void getValue_shouldReturnNullIfNotSet() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature); assertNull(exposurePointFeature.getValue()); } @Test public void getValue_shouldEchoTheSetValue() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature); exposurePointFeature.setCameraBoundaries(this.mockCameraBoundaries); Point expectedPoint = new Point(0.0, 0.0); exposurePointFeature.setValue(expectedPoint); Point actualPoint = exposurePointFeature.getValue(); assertEquals(expectedPoint, actualPoint); } @Test public void setValue_shouldResetPointWhenXCoordIsNull() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature); exposurePointFeature.setCameraBoundaries(this.mockCameraBoundaries); exposurePointFeature.setValue(new Point(null, 0.0)); assertNull(exposurePointFeature.getValue()); } @Test public void setValue_shouldResetPointWhenYCoordIsNull() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature); exposurePointFeature.setCameraBoundaries(this.mockCameraBoundaries); exposurePointFeature.setValue(new Point(0.0, null)); assertNull(exposurePointFeature.getValue()); } @Test public void setValue_shouldSetPointWhenValidCoordsAreSupplied() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature); exposurePointFeature.setCameraBoundaries(this.mockCameraBoundaries); Point point = new Point(0.0, 0.0); exposurePointFeature.setValue(point); assertEquals(point, exposurePointFeature.getValue()); } @Test public void setValue_shouldDetermineMeteringRectangleWhenValidBoundariesAndCoordsAreSupplied() { CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature); Size mockedCameraBoundaries = mock(Size.class); exposurePointFeature.setCameraBoundaries(mockedCameraBoundaries); try (MockedStatic mockedCameraRegionUtils = Mockito.mockStatic(CameraRegionUtils.class)) { exposurePointFeature.setValue(new Point(0.5, 0.5)); mockedCameraRegionUtils.verify( () -> CameraRegionUtils.convertPointToMeteringRectangle( mockedCameraBoundaries, 0.5, 0.5, PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT), times(1)); } } @Test(expected = AssertionError.class) public void setValue_shouldThrowAssertionErrorWhenNoValidBoundariesAreSet() { CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature); try (MockedStatic mockedCameraRegionUtils = Mockito.mockStatic(CameraRegionUtils.class)) { exposurePointFeature.setValue(new Point(0.5, 0.5)); } } @Test public void setValue_shouldNotDetermineMeteringRectangleWhenNullCoordsAreSet() { CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature); Size mockedCameraBoundaries = mock(Size.class); exposurePointFeature.setCameraBoundaries(mockedCameraBoundaries); try (MockedStatic mockedCameraRegionUtils = Mockito.mockStatic(CameraRegionUtils.class)) { exposurePointFeature.setValue(null); exposurePointFeature.setValue(new Point(null, 0.5)); exposurePointFeature.setValue(new Point(0.5, null)); mockedCameraRegionUtils.verifyNoInteractions(); } } @Test public void setCameraBoundaries_shouldDetermineMeteringRectangleWhenValidBoundariesAndCoordsAreSupplied() { CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature); exposurePointFeature.setCameraBoundaries(this.mockCameraBoundaries); exposurePointFeature.setValue(new Point(0.5, 0.5)); Size mockedCameraBoundaries = mock(Size.class); try (MockedStatic mockedCameraRegionUtils = Mockito.mockStatic(CameraRegionUtils.class)) { exposurePointFeature.setCameraBoundaries(mockedCameraBoundaries); mockedCameraRegionUtils.verify( () -> CameraRegionUtils.convertPointToMeteringRectangle( mockedCameraBoundaries, 0.5, 0.5, PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT), times(1)); } } @Test public void checkIsSupported_shouldReturnFalseWhenMaxRegionsIsNull() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature); exposurePointFeature.setCameraBoundaries(new Size(100, 100)); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(null); assertFalse(exposurePointFeature.checkIsSupported()); } @Test public void checkIsSupported_shouldReturnFalseWhenMaxRegionsIsZero() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature); exposurePointFeature.setCameraBoundaries(new Size(100, 100)); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(0); assertFalse(exposurePointFeature.checkIsSupported()); } @Test public void checkIsSupported_shouldReturnTrueWhenMaxRegionsIsBiggerThenZero() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature); exposurePointFeature.setCameraBoundaries(new Size(100, 100)); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); assertTrue(exposurePointFeature.checkIsSupported()); } @Test public void updateBuilder_shouldReturnWhenCheckIsSupportedIsFalse() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(0); exposurePointFeature.updateBuilder(mockCaptureRequestBuilder); verify(mockCaptureRequestBuilder, never()).set(any(), any()); } @Test public void updateBuilder_shouldSetMeteringRectangleWhenValidBoundariesAndCoordsAreSupplied() { CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature); Size mockedCameraBoundaries = mock(Size.class); MeteringRectangle mockedMeteringRectangle = mock(MeteringRectangle.class); try (MockedStatic mockedCameraRegionUtils = Mockito.mockStatic(CameraRegionUtils.class)) { mockedCameraRegionUtils .when( () -> CameraRegionUtils.convertPointToMeteringRectangle( mockedCameraBoundaries, 0.5, 0.5, PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT)) .thenReturn(mockedMeteringRectangle); exposurePointFeature.setCameraBoundaries(mockedCameraBoundaries); exposurePointFeature.setValue(new Point(0.5, 0.5)); exposurePointFeature.updateBuilder(mockCaptureRequestBuilder); } verify(mockCaptureRequestBuilder, times(1)) .set(CaptureRequest.CONTROL_AE_REGIONS, new MeteringRectangle[] {mockedMeteringRectangle}); } @Test public void updateBuilder_shouldNotSetMeteringRectangleWhenNoValidBoundariesAreSupplied() { CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature); exposurePointFeature.updateBuilder(mockCaptureRequestBuilder); verify(mockCaptureRequestBuilder, times(1)).set(any(), isNull()); } @Test public void updateBuilder_shouldNotSetMeteringRectangleWhenNoValidCoordsAreSupplied() { CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoExposure()).thenReturn(1); CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class); ExposurePointFeature exposurePointFeature = new ExposurePointFeature(mockCameraProperties, mockSensorOrientationFeature); exposurePointFeature.setCameraBoundaries(this.mockCameraBoundaries); exposurePointFeature.setValue(null); exposurePointFeature.updateBuilder(mockCaptureRequestBuilder); exposurePointFeature.setValue(new Point(0d, null)); exposurePointFeature.updateBuilder(mockCaptureRequestBuilder); exposurePointFeature.setValue(new Point(null, 0d)); exposurePointFeature.updateBuilder(mockCaptureRequestBuilder); verify(mockCaptureRequestBuilder, times(3)).set(any(), isNull()); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/flash/FlashFeatureTest.java ================================================ // Copyright 2013 The Flutter 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.features.flash; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; 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.hardware.camera2.CaptureRequest; import io.flutter.plugins.camera.CameraProperties; import org.junit.Test; public class FlashFeatureTest { @Test public void getDebugName_shouldReturnTheNameOfTheFeature() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FlashFeature flashFeature = new FlashFeature(mockCameraProperties); assertEquals("FlashFeature", flashFeature.getDebugName()); } @Test public void getValue_shouldReturnAutoIfNotSet() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FlashFeature flashFeature = new FlashFeature(mockCameraProperties); assertEquals(FlashMode.auto, flashFeature.getValue()); } @Test public void getValue_shouldEchoTheSetValue() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FlashFeature flashFeature = new FlashFeature(mockCameraProperties); FlashMode expectedValue = FlashMode.torch; flashFeature.setValue(expectedValue); FlashMode actualValue = flashFeature.getValue(); assertEquals(expectedValue, actualValue); } @Test public void checkIsSupported_shouldReturnFalseWhenFlashInfoAvailableIsNull() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FlashFeature flashFeature = new FlashFeature(mockCameraProperties); when(mockCameraProperties.getFlashInfoAvailable()).thenReturn(null); assertFalse(flashFeature.checkIsSupported()); } @Test public void checkIsSupported_shouldReturnFalseWhenFlashInfoAvailableIsFalse() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FlashFeature flashFeature = new FlashFeature(mockCameraProperties); when(mockCameraProperties.getFlashInfoAvailable()).thenReturn(false); assertFalse(flashFeature.checkIsSupported()); } @Test public void checkIsSupported_shouldReturnTrueWhenFlashInfoAvailableIsTrue() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FlashFeature flashFeature = new FlashFeature(mockCameraProperties); when(mockCameraProperties.getFlashInfoAvailable()).thenReturn(true); assertTrue(flashFeature.checkIsSupported()); } @Test public void updateBuilder_shouldReturnWhenCheckIsSupportedIsFalse() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); FlashFeature flashFeature = new FlashFeature(mockCameraProperties); when(mockCameraProperties.getFlashInfoAvailable()).thenReturn(false); flashFeature.updateBuilder(mockBuilder); verify(mockBuilder, never()).set(any(), any()); } @Test public void updateBuilder_shouldSetAeModeAndFlashModeWhenFlashModeIsOff() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); FlashFeature flashFeature = new FlashFeature(mockCameraProperties); when(mockCameraProperties.getFlashInfoAvailable()).thenReturn(true); flashFeature.setValue(FlashMode.off); flashFeature.updateBuilder(mockBuilder); verify(mockBuilder, times(1)) .set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); verify(mockBuilder, times(1)).set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); } @Test public void updateBuilder_shouldSetAeModeAndFlashModeWhenFlashModeIsAlways() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); FlashFeature flashFeature = new FlashFeature(mockCameraProperties); when(mockCameraProperties.getFlashInfoAvailable()).thenReturn(true); flashFeature.setValue(FlashMode.always); flashFeature.updateBuilder(mockBuilder); verify(mockBuilder, times(1)) .set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH); verify(mockBuilder, times(1)).set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); } @Test public void updateBuilder_shouldSetAeModeAndFlashModeWhenFlashModeIsTorch() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); FlashFeature flashFeature = new FlashFeature(mockCameraProperties); when(mockCameraProperties.getFlashInfoAvailable()).thenReturn(true); flashFeature.setValue(FlashMode.torch); flashFeature.updateBuilder(mockBuilder); verify(mockBuilder, times(1)) .set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); verify(mockBuilder, times(1)).set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH); } @Test public void updateBuilder_shouldSetAeModeAndFlashModeWhenFlashModeIsAuto() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); FlashFeature flashFeature = new FlashFeature(mockCameraProperties); when(mockCameraProperties.getFlashInfoAvailable()).thenReturn(true); flashFeature.setValue(FlashMode.auto); flashFeature.updateBuilder(mockBuilder); verify(mockBuilder, times(1)) .set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); verify(mockBuilder, times(1)).set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/focuspoint/FocusPointFeatureTest.java ================================================ // Copyright 2013 The Flutter 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.features.focuspoint; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; 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.hardware.camera2.CaptureRequest; import android.hardware.camera2.params.MeteringRectangle; import android.util.Size; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.CameraRegionUtils; import io.flutter.plugins.camera.features.Point; import io.flutter.plugins.camera.features.sensororientation.DeviceOrientationManager; import io.flutter.plugins.camera.features.sensororientation.SensorOrientationFeature; import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; public class FocusPointFeatureTest { Size mockCameraBoundaries; SensorOrientationFeature mockSensorOrientationFeature; DeviceOrientationManager mockDeviceOrientationManager; @Before public void setUp() { this.mockCameraBoundaries = mock(Size.class); when(this.mockCameraBoundaries.getWidth()).thenReturn(100); when(this.mockCameraBoundaries.getHeight()).thenReturn(100); mockSensorOrientationFeature = mock(SensorOrientationFeature.class); mockDeviceOrientationManager = mock(DeviceOrientationManager.class); when(mockSensorOrientationFeature.getDeviceOrientationManager()) .thenReturn(mockDeviceOrientationManager); when(mockDeviceOrientationManager.getLastUIOrientation()) .thenReturn(PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT); } @Test public void getDebugName_shouldReturnTheNameOfTheFeature() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature); assertEquals("FocusPointFeature", focusPointFeature.getDebugName()); } @Test public void getValue_shouldReturnNullIfNotSet() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature); Point actualPoint = focusPointFeature.getValue(); assertNull(focusPointFeature.getValue()); } @Test public void getValue_shouldEchoTheSetValue() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature); focusPointFeature.setCameraBoundaries(this.mockCameraBoundaries); Point expectedPoint = new Point(0.0, 0.0); focusPointFeature.setValue(expectedPoint); Point actualPoint = focusPointFeature.getValue(); assertEquals(expectedPoint, actualPoint); } @Test public void setValue_shouldResetPointWhenXCoordIsNull() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature); focusPointFeature.setCameraBoundaries(this.mockCameraBoundaries); focusPointFeature.setValue(new Point(null, 0.0)); assertNull(focusPointFeature.getValue()); } @Test public void setValue_shouldResetPointWhenYCoordIsNull() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature); focusPointFeature.setCameraBoundaries(this.mockCameraBoundaries); focusPointFeature.setValue(new Point(0.0, null)); assertNull(focusPointFeature.getValue()); } @Test public void setValue_shouldSetPointWhenValidCoordsAreSupplied() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature); focusPointFeature.setCameraBoundaries(this.mockCameraBoundaries); Point point = new Point(0.0, 0.0); focusPointFeature.setValue(point); assertEquals(point, focusPointFeature.getValue()); } @Test public void setValue_shouldDetermineMeteringRectangleWhenValidBoundariesAndCoordsAreSupplied() { CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature); Size mockedCameraBoundaries = mock(Size.class); focusPointFeature.setCameraBoundaries(mockedCameraBoundaries); try (MockedStatic mockedCameraRegionUtils = Mockito.mockStatic(CameraRegionUtils.class)) { focusPointFeature.setValue(new Point(0.5, 0.5)); mockedCameraRegionUtils.verify( () -> CameraRegionUtils.convertPointToMeteringRectangle( mockedCameraBoundaries, 0.5, 0.5, PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT), times(1)); } } @Test(expected = AssertionError.class) public void setValue_shouldThrowAssertionErrorWhenNoValidBoundariesAreSet() { CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature); try (MockedStatic mockedCameraRegionUtils = Mockito.mockStatic(CameraRegionUtils.class)) { focusPointFeature.setValue(new Point(0.5, 0.5)); } } @Test public void setValue_shouldNotDetermineMeteringRectangleWhenNullCoordsAreSet() { CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature); Size mockedCameraBoundaries = mock(Size.class); focusPointFeature.setCameraBoundaries(mockedCameraBoundaries); try (MockedStatic mockedCameraRegionUtils = Mockito.mockStatic(CameraRegionUtils.class)) { focusPointFeature.setValue(null); focusPointFeature.setValue(new Point(null, 0.5)); focusPointFeature.setValue(new Point(0.5, null)); mockedCameraRegionUtils.verifyNoInteractions(); } } @Test public void setCameraBoundaries_shouldDetermineMeteringRectangleWhenValidBoundariesAndCoordsAreSupplied() { CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature); focusPointFeature.setCameraBoundaries(this.mockCameraBoundaries); focusPointFeature.setValue(new Point(0.5, 0.5)); Size mockedCameraBoundaries = mock(Size.class); try (MockedStatic mockedCameraRegionUtils = Mockito.mockStatic(CameraRegionUtils.class)) { focusPointFeature.setCameraBoundaries(mockedCameraBoundaries); mockedCameraRegionUtils.verify( () -> CameraRegionUtils.convertPointToMeteringRectangle( mockedCameraBoundaries, 0.5, 0.5, PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT), times(1)); } } @Test public void checkIsSupported_shouldReturnFalseWhenMaxRegionsIsNull() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature); focusPointFeature.setCameraBoundaries(new Size(100, 100)); when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(null); assertFalse(focusPointFeature.checkIsSupported()); } @Test public void checkIsSupported_shouldReturnFalseWhenMaxRegionsIsZero() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature); focusPointFeature.setCameraBoundaries(new Size(100, 100)); when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(0); assertFalse(focusPointFeature.checkIsSupported()); } @Test public void checkIsSupported_shouldReturnTrueWhenMaxRegionsIsBiggerThenZero() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature); focusPointFeature.setCameraBoundaries(new Size(100, 100)); when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); assertTrue(focusPointFeature.checkIsSupported()); } @Test public void updateBuilder_shouldReturnWhenCheckIsSupportedIsFalse() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature); when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(0); focusPointFeature.updateBuilder(mockCaptureRequestBuilder); verify(mockCaptureRequestBuilder, never()).set(any(), any()); } @Test public void updateBuilder_shouldSetMeteringRectangleWhenValidBoundariesAndCoordsAreSupplied() { CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature); Size mockedCameraBoundaries = mock(Size.class); MeteringRectangle mockedMeteringRectangle = mock(MeteringRectangle.class); try (MockedStatic mockedCameraRegionUtils = Mockito.mockStatic(CameraRegionUtils.class)) { mockedCameraRegionUtils .when( () -> CameraRegionUtils.convertPointToMeteringRectangle( mockedCameraBoundaries, 0.5, 0.5, PlatformChannel.DeviceOrientation.LANDSCAPE_LEFT)) .thenReturn(mockedMeteringRectangle); focusPointFeature.setCameraBoundaries(mockedCameraBoundaries); focusPointFeature.setValue(new Point(0.5, 0.5)); focusPointFeature.updateBuilder(mockCaptureRequestBuilder); } verify(mockCaptureRequestBuilder, times(1)) .set(CaptureRequest.CONTROL_AE_REGIONS, new MeteringRectangle[] {mockedMeteringRectangle}); } @Test public void updateBuilder_shouldNotSetMeteringRectangleWhenNoValidBoundariesAreSupplied() { CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature); MeteringRectangle mockedMeteringRectangle = mock(MeteringRectangle.class); focusPointFeature.updateBuilder(mockCaptureRequestBuilder); verify(mockCaptureRequestBuilder, times(1)).set(any(), isNull()); } @Test public void updateBuilder_shouldNotSetMeteringRectangleWhenNoValidCoordsAreSupplied() { CameraProperties mockCameraProperties = mock(CameraProperties.class); when(mockCameraProperties.getControlMaxRegionsAutoFocus()).thenReturn(1); CaptureRequest.Builder mockCaptureRequestBuilder = mock(CaptureRequest.Builder.class); FocusPointFeature focusPointFeature = new FocusPointFeature(mockCameraProperties, mockSensorOrientationFeature); focusPointFeature.setCameraBoundaries(this.mockCameraBoundaries); focusPointFeature.setValue(null); focusPointFeature.updateBuilder(mockCaptureRequestBuilder); focusPointFeature.setValue(new Point(0d, null)); focusPointFeature.updateBuilder(mockCaptureRequestBuilder); focusPointFeature.setValue(new Point(null, 0d)); focusPointFeature.updateBuilder(mockCaptureRequestBuilder); verify(mockCaptureRequestBuilder, times(3)).set(any(), isNull()); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeaturePixel4aTest.java ================================================ // Copyright 2013 The Flutter 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.features.fpsrange; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import android.os.Build; import android.util.Range; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.utils.TestUtils; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class FpsRangeFeaturePixel4aTest { @Test public void ctor_shouldInitializeFpsRangeWith30WhenDeviceIsPixel4a() { TestUtils.setFinalStatic(Build.class, "BRAND", "google"); TestUtils.setFinalStatic(Build.class, "MODEL", "Pixel 4a"); FpsRangeFeature fpsRangeFeature = new FpsRangeFeature(mock(CameraProperties.class)); Range range = fpsRangeFeature.getValue(); assertEquals(30, (int) range.getLower()); assertEquals(30, (int) range.getUpper()); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/fpsrange/FpsRangeFeatureTest.java ================================================ // Copyright 2013 The Flutter 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.features.fpsrange; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.hardware.camera2.CaptureRequest; import android.os.Build; import android.util.Range; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.utils.TestUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; public class FpsRangeFeatureTest { @Before public void before() { TestUtils.setFinalStatic(Build.class, "BRAND", "Test Brand"); TestUtils.setFinalStatic(Build.class, "MODEL", "Test Model"); } @After public void after() { TestUtils.setFinalStatic(Build.class, "BRAND", null); TestUtils.setFinalStatic(Build.class, "MODEL", null); } @Test public void ctor_shouldInitializeFpsRangeWithHighestUpperValueFromRangeArray() { FpsRangeFeature fpsRangeFeature = createTestInstance(); assertEquals(13, (int) fpsRangeFeature.getValue().getUpper()); } @Test public void getDebugName_shouldReturnTheNameOfTheFeature() { FpsRangeFeature fpsRangeFeature = createTestInstance(); assertEquals("FpsRangeFeature", fpsRangeFeature.getDebugName()); } @Test public void getValue_shouldReturnHighestUpperRangeIfNotSet() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FpsRangeFeature fpsRangeFeature = createTestInstance(); assertEquals(13, (int) fpsRangeFeature.getValue().getUpper()); } @Test public void getValue_shouldEchoTheSetValue() { CameraProperties mockCameraProperties = mock(CameraProperties.class); FpsRangeFeature fpsRangeFeature = new FpsRangeFeature(mockCameraProperties); @SuppressWarnings("unchecked") Range expectedValue = mock(Range.class); fpsRangeFeature.setValue(expectedValue); Range actualValue = fpsRangeFeature.getValue(); assertEquals(expectedValue, actualValue); } @Test public void checkIsSupported_shouldReturnTrue() { FpsRangeFeature fpsRangeFeature = createTestInstance(); assertTrue(fpsRangeFeature.checkIsSupported()); } @Test @SuppressWarnings("unchecked") public void updateBuilder_shouldSetAeTargetFpsRange() { CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); FpsRangeFeature fpsRangeFeature = createTestInstance(); fpsRangeFeature.updateBuilder(mockBuilder); verify(mockBuilder).set(eq(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE), any(Range.class)); } private static FpsRangeFeature createTestInstance() { @SuppressWarnings("unchecked") Range rangeOne = mock(Range.class); @SuppressWarnings("unchecked") Range rangeTwo = mock(Range.class); @SuppressWarnings("unchecked") Range rangeThree = mock(Range.class); when(rangeOne.getUpper()).thenReturn(11); when(rangeTwo.getUpper()).thenReturn(12); when(rangeThree.getUpper()).thenReturn(13); @SuppressWarnings("unchecked") Range[] ranges = new Range[] {rangeOne, rangeTwo, rangeThree}; CameraProperties cameraProperties = mock(CameraProperties.class); when(cameraProperties.getControlAutoExposureAvailableTargetFpsRanges()).thenReturn(ranges); return new FpsRangeFeature(cameraProperties); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/noisereduction/NoiseReductionFeatureTest.java ================================================ // Copyright 2013 The Flutter 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.features.noisereduction; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; 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.hardware.camera2.CaptureRequest; import android.os.Build.VERSION; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.utils.TestUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; public class NoiseReductionFeatureTest { @Before public void before() { // Make sure the VERSION.SDK_INT field returns 23, to allow using all available // noise reduction modes in tests. TestUtils.setFinalStatic(VERSION.class, "SDK_INT", 23); } @After public void after() { // Make sure we reset the VERSION.SDK_INT field to it's original value. TestUtils.setFinalStatic(VERSION.class, "SDK_INT", 0); } @Test public void getDebugName_shouldReturnTheNameOfTheFeature() { CameraProperties mockCameraProperties = mock(CameraProperties.class); NoiseReductionFeature noiseReductionFeature = new NoiseReductionFeature(mockCameraProperties); assertEquals("NoiseReductionFeature", noiseReductionFeature.getDebugName()); } @Test public void getValue_shouldReturnFastIfNotSet() { CameraProperties mockCameraProperties = mock(CameraProperties.class); NoiseReductionFeature noiseReductionFeature = new NoiseReductionFeature(mockCameraProperties); assertEquals(NoiseReductionMode.fast, noiseReductionFeature.getValue()); } @Test public void getValue_shouldEchoTheSetValue() { CameraProperties mockCameraProperties = mock(CameraProperties.class); NoiseReductionFeature noiseReductionFeature = new NoiseReductionFeature(mockCameraProperties); NoiseReductionMode expectedValue = NoiseReductionMode.fast; noiseReductionFeature.setValue(expectedValue); NoiseReductionMode actualValue = noiseReductionFeature.getValue(); assertEquals(expectedValue, actualValue); } @Test public void checkIsSupported_shouldReturnFalseWhenAvailableNoiseReductionModesIsNull() { CameraProperties mockCameraProperties = mock(CameraProperties.class); NoiseReductionFeature noiseReductionFeature = new NoiseReductionFeature(mockCameraProperties); when(mockCameraProperties.getAvailableNoiseReductionModes()).thenReturn(null); assertFalse(noiseReductionFeature.checkIsSupported()); } @Test public void checkIsSupported_shouldReturnFalseWhenAvailableNoiseReductionModesReturnsAnEmptyArray() { CameraProperties mockCameraProperties = mock(CameraProperties.class); NoiseReductionFeature noiseReductionFeature = new NoiseReductionFeature(mockCameraProperties); when(mockCameraProperties.getAvailableNoiseReductionModes()).thenReturn(new int[] {}); assertFalse(noiseReductionFeature.checkIsSupported()); } @Test public void checkIsSupported_shouldReturnTrueWhenAvailableNoiseReductionModesReturnsAtLeastOneItem() { CameraProperties mockCameraProperties = mock(CameraProperties.class); NoiseReductionFeature noiseReductionFeature = new NoiseReductionFeature(mockCameraProperties); when(mockCameraProperties.getAvailableNoiseReductionModes()).thenReturn(new int[] {1}); assertTrue(noiseReductionFeature.checkIsSupported()); } @Test public void updateBuilder_shouldReturnWhenCheckIsSupportedIsFalse() { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); NoiseReductionFeature noiseReductionFeature = new NoiseReductionFeature(mockCameraProperties); when(mockCameraProperties.getAvailableNoiseReductionModes()).thenReturn(new int[] {}); noiseReductionFeature.updateBuilder(mockBuilder); verify(mockBuilder, never()).set(any(), any()); } @Test public void updateBuilder_shouldSetNoiseReductionModeOffWhenOff() { testUpdateBuilderWith(NoiseReductionMode.off, CaptureRequest.NOISE_REDUCTION_MODE_OFF); } @Test public void updateBuilder_shouldSetNoiseReductionModeFastWhenFast() { testUpdateBuilderWith(NoiseReductionMode.fast, CaptureRequest.NOISE_REDUCTION_MODE_FAST); } @Test public void updateBuilder_shouldSetNoiseReductionModeHighQualityWhenHighQuality() { testUpdateBuilderWith( NoiseReductionMode.highQuality, CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY); } @Test public void updateBuilder_shouldSetNoiseReductionModeMinimalWhenMinimal() { testUpdateBuilderWith(NoiseReductionMode.minimal, CaptureRequest.NOISE_REDUCTION_MODE_MINIMAL); } @Test public void updateBuilder_shouldSetNoiseReductionModeZeroShutterLagWhenZeroShutterLag() { testUpdateBuilderWith( NoiseReductionMode.zeroShutterLag, CaptureRequest.NOISE_REDUCTION_MODE_ZERO_SHUTTER_LAG); } private static void testUpdateBuilderWith(NoiseReductionMode mode, int expectedResult) { CameraProperties mockCameraProperties = mock(CameraProperties.class); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); NoiseReductionFeature noiseReductionFeature = new NoiseReductionFeature(mockCameraProperties); when(mockCameraProperties.getAvailableNoiseReductionModes()).thenReturn(new int[] {1}); noiseReductionFeature.setValue(mode); noiseReductionFeature.updateBuilder(mockBuilder); verify(mockBuilder, times(1)).set(CaptureRequest.NOISE_REDUCTION_MODE, expectedResult); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/resolution/ResolutionFeatureTest.java ================================================ // Copyright 2013 The Flutter 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.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; @RunWith(RobolectricTestRunner.class) public class ResolutionFeatureTest { private static final String cameraName = "1"; private CamcorderProfile mockProfileLowLegacy; private EncoderProfiles mockProfileLow; private MockedStatic mockedStaticProfile; @Before @SuppressWarnings("deprecation") public void beforeLegacy() { mockedStaticProfile = mockStatic(CamcorderProfile.class); mockProfileLowLegacy = mock(CamcorderProfile.class); CamcorderProfile mockProfileLegacy = mock(CamcorderProfile.class); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_HIGH)) .thenReturn(true); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_2160P)) .thenReturn(true); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_1080P)) .thenReturn(true); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_720P)) .thenReturn(true); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_480P)) .thenReturn(true); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_QVGA)) .thenReturn(true); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_LOW)) .thenReturn(true); mockedStaticProfile .when(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_HIGH)) .thenReturn(mockProfileLegacy); mockedStaticProfile .when(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_2160P)) .thenReturn(mockProfileLegacy); mockedStaticProfile .when(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_1080P)) .thenReturn(mockProfileLegacy); mockedStaticProfile .when(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_720P)) .thenReturn(mockProfileLegacy); mockedStaticProfile .when(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_480P)) .thenReturn(mockProfileLegacy); mockedStaticProfile .when(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_QVGA)) .thenReturn(mockProfileLegacy); mockedStaticProfile .when(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_LOW)) .thenReturn(mockProfileLowLegacy); } public void before() { mockProfileLow = mock(EncoderProfiles.class); EncoderProfiles mockProfile = mock(EncoderProfiles.class); EncoderProfiles.VideoProfile mockVideoProfile = mock(EncoderProfiles.VideoProfile.class); List mockVideoProfilesList = List.of(mockVideoProfile); mockedStaticProfile .when(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_HIGH)) .thenReturn(mockProfile); mockedStaticProfile .when(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_2160P)) .thenReturn(mockProfile); mockedStaticProfile .when(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_1080P)) .thenReturn(mockProfile); mockedStaticProfile .when(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_720P)) .thenReturn(mockProfile); mockedStaticProfile .when(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_480P)) .thenReturn(mockProfile); mockedStaticProfile .when(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_QVGA)) .thenReturn(mockProfile); mockedStaticProfile .when(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_LOW)) .thenReturn(mockProfileLow); when(mockProfile.getVideoProfiles()).thenReturn(mockVideoProfilesList); when(mockVideoProfile.getHeight()).thenReturn(100); when(mockVideoProfile.getWidth()).thenReturn(100); } @After public void after() { mockedStaticProfile.reset(); mockedStaticProfile.close(); } @Test public void getDebugName_shouldReturnTheNameOfTheFeature() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ResolutionFeature resolutionFeature = new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraName); assertEquals("ResolutionFeature", resolutionFeature.getDebugName()); } @Test public void getValue_shouldReturnInitialValueWhenNotSet() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ResolutionFeature resolutionFeature = new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraName); assertEquals(ResolutionPreset.max, resolutionFeature.getValue()); } @Test public void getValue_shouldEchoSetValue() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ResolutionFeature resolutionFeature = new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraName); resolutionFeature.setValue(ResolutionPreset.high); assertEquals(ResolutionPreset.high, resolutionFeature.getValue()); } @Test public void checkIsSupport_returnsTrue() { CameraProperties mockCameraProperties = mock(CameraProperties.class); ResolutionFeature resolutionFeature = new ResolutionFeature(mockCameraProperties, ResolutionPreset.max, cameraName); assertTrue(resolutionFeature.checkIsSupported()); } @Config(maxSdk = 30) @SuppressWarnings("deprecation") @Test public void getBestAvailableCamcorderProfileForResolutionPreset_shouldFallThroughLegacy() { mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_HIGH)) .thenReturn(false); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_2160P)) .thenReturn(false); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_1080P)) .thenReturn(false); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_720P)) .thenReturn(false); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_480P)) .thenReturn(false); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_QVGA)) .thenReturn(false); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_LOW)) .thenReturn(true); assertEquals( mockProfileLowLegacy, ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPresetLegacy( 1, ResolutionPreset.max)); } @Config(minSdk = 31) @Test public void getBestAvailableCamcorderProfileForResolutionPreset_shouldFallThrough() { mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_HIGH)) .thenReturn(false); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_2160P)) .thenReturn(false); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_1080P)) .thenReturn(false); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_720P)) .thenReturn(false); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_480P)) .thenReturn(false); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_QVGA)) .thenReturn(false); mockedStaticProfile .when(() -> CamcorderProfile.hasProfile(1, CamcorderProfile.QUALITY_LOW)) .thenReturn(true); assertEquals( mockProfileLow, ResolutionFeature.getBestAvailableCamcorderProfileForResolutionPreset( 1, ResolutionPreset.max)); } @Config(maxSdk = 30) @SuppressWarnings("deprecation") @Test public void computeBestPreviewSize_shouldUse720PWhenResolutionPresetMaxLegacy() { ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.max); mockedStaticProfile.verify(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_720P)); } @Config(minSdk = 31) @Test public void computeBestPreviewSize_shouldUse720PWhenResolutionPresetMax() { before(); ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.max); mockedStaticProfile.verify(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_720P)); } @Config(maxSdk = 30) @SuppressWarnings("deprecation") @Test public void computeBestPreviewSize_shouldUse720PWhenResolutionPresetUltraHighLegacy() { ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.ultraHigh); mockedStaticProfile.verify(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_720P)); } @Config(minSdk = 31) @Test public void computeBestPreviewSize_shouldUse720PWhenResolutionPresetUltraHigh() { before(); ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.ultraHigh); mockedStaticProfile.verify(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_720P)); } @Config(maxSdk = 30) @SuppressWarnings("deprecation") @Test public void computeBestPreviewSize_shouldUse720PWhenResolutionPresetVeryHighLegacy() { ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.veryHigh); mockedStaticProfile.verify(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_720P)); } @Config(minSdk = 31) @SuppressWarnings("deprecation") @Test public void computeBestPreviewSize_shouldUse720PWhenResolutionPresetVeryHigh() { before(); ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.veryHigh); mockedStaticProfile.verify(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_720P)); } @Config(maxSdk = 30) @SuppressWarnings("deprecation") @Test public void computeBestPreviewSize_shouldUse720PWhenResolutionPresetHighLegacy() { ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.high); mockedStaticProfile.verify(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_720P)); } @Config(minSdk = 31) @Test public void computeBestPreviewSize_shouldUse720PWhenResolutionPresetHigh() { before(); ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.high); mockedStaticProfile.verify(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_720P)); } @Config(maxSdk = 30) @SuppressWarnings("deprecation") @Test public void computeBestPreviewSize_shouldUse480PWhenResolutionPresetMediumLegacy() { ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.medium); mockedStaticProfile.verify(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_480P)); } @Config(minSdk = 31) @Test public void computeBestPreviewSize_shouldUse480PWhenResolutionPresetMedium() { before(); ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.medium); mockedStaticProfile.verify(() -> CamcorderProfile.getAll("1", CamcorderProfile.QUALITY_480P)); } @Config(maxSdk = 30) @SuppressWarnings("deprecation") @Test public void computeBestPreviewSize_shouldUseQVGAWhenResolutionPresetLowLegacy() { ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.low); mockedStaticProfile.verify(() -> CamcorderProfile.get(1, CamcorderProfile.QUALITY_QVGA)); } @Config(minSdk = 31) @Test public void computeBestPreviewSize_shouldUseQVGAWhenResolutionPresetLow() { before(); ResolutionFeature.computeBestPreviewSize(1, ResolutionPreset.low); 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()); } } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/sensororientation/DeviceOrientationManagerTest.java ================================================ // Copyright 2013 The Flutter 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.features.sensororientation; 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.camera.DartMessenger; import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; public class DeviceOrientationManagerTest { private Activity mockActivity; private DartMessenger mockDartMessenger; private WindowManager mockWindowManager; private Display mockDisplay; private DeviceOrientationManager deviceOrientationManager; @Before @SuppressWarnings("deprecation") public void before() { mockActivity = mock(Activity.class); mockDartMessenger = mock(DartMessenger.class); mockDisplay = mock(Display.class); mockWindowManager = mock(WindowManager.class); when(mockActivity.getSystemService(Context.WINDOW_SERVICE)).thenReturn(mockWindowManager); when(mockWindowManager.getDefaultDisplay()).thenReturn(mockDisplay); deviceOrientationManager = DeviceOrientationManager.create(mockActivity, mockDartMessenger, false, 0); } @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 = DeviceOrientationManager.create(mockActivity, mockDartMessenger, false, 90); 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 = DeviceOrientationManager.create(mockActivity, mockDartMessenger, false, 90); 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 = DeviceOrientationManager.create(mockActivity, mockDartMessenger, false, 90); 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(mockDartMessenger, times(1)) .sendDeviceOrientationChangeEvent(DeviceOrientation.LANDSCAPE_LEFT); } @Test public void handleOrientationChange_shouldSendMessageWhenOrientationIsUpdated() { DeviceOrientation previousOrientation = DeviceOrientation.PORTRAIT_UP; DeviceOrientation newOrientation = DeviceOrientation.LANDSCAPE_LEFT; DeviceOrientationManager.handleOrientationChange( newOrientation, previousOrientation, mockDartMessenger); verify(mockDartMessenger, times(1)).sendDeviceOrientationChangeEvent(newOrientation); } @Test public void handleOrientationChange_shouldNotSendMessageWhenOrientationIsNotUpdated() { DeviceOrientation previousOrientation = DeviceOrientation.PORTRAIT_UP; DeviceOrientation newOrientation = DeviceOrientation.PORTRAIT_UP; DeviceOrientationManager.handleOrientationChange( newOrientation, previousOrientation, mockDartMessenger); verify(mockDartMessenger, never()).sendDeviceOrientationChangeEvent(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); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/sensororientation/SensorOrientationFeatureTest.java ================================================ // Copyright 2013 The Flutter 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.features.sensororientation; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Activity; import android.hardware.camera2.CameraMetadata; import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation; import io.flutter.plugins.camera.CameraProperties; import io.flutter.plugins.camera.DartMessenger; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; public class SensorOrientationFeatureTest { private MockedStatic mockedStaticDeviceOrientationManager; private Activity mockActivity; private CameraProperties mockCameraProperties; private DartMessenger mockDartMessenger; private DeviceOrientationManager mockDeviceOrientationManager; @Before public void before() { mockedStaticDeviceOrientationManager = mockStatic(DeviceOrientationManager.class); mockActivity = mock(Activity.class); mockCameraProperties = mock(CameraProperties.class); mockDartMessenger = mock(DartMessenger.class); mockDeviceOrientationManager = mock(DeviceOrientationManager.class); when(mockCameraProperties.getSensorOrientation()).thenReturn(0); when(mockCameraProperties.getLensFacing()).thenReturn(CameraMetadata.LENS_FACING_BACK); mockedStaticDeviceOrientationManager .when(() -> DeviceOrientationManager.create(mockActivity, mockDartMessenger, false, 0)) .thenReturn(mockDeviceOrientationManager); } @After public void after() { mockedStaticDeviceOrientationManager.close(); } @Test public void ctor_shouldStartDeviceOrientationManager() { SensorOrientationFeature sensorOrientationFeature = new SensorOrientationFeature(mockCameraProperties, mockActivity, mockDartMessenger); verify(mockDeviceOrientationManager, times(1)).start(); } @Test public void getDebugName_shouldReturnTheNameOfTheFeature() { SensorOrientationFeature sensorOrientationFeature = new SensorOrientationFeature(mockCameraProperties, mockActivity, mockDartMessenger); assertEquals("SensorOrientationFeature", sensorOrientationFeature.getDebugName()); } @Test public void getValue_shouldReturnNullIfNotSet() { SensorOrientationFeature sensorOrientationFeature = new SensorOrientationFeature(mockCameraProperties, mockActivity, mockDartMessenger); assertEquals(0, (int) sensorOrientationFeature.getValue()); } @Test public void getValue_shouldEchoSetValue() { SensorOrientationFeature sensorOrientationFeature = new SensorOrientationFeature(mockCameraProperties, mockActivity, mockDartMessenger); sensorOrientationFeature.setValue(90); assertEquals(90, (int) sensorOrientationFeature.getValue()); } @Test public void checkIsSupport_returnsTrue() { SensorOrientationFeature sensorOrientationFeature = new SensorOrientationFeature(mockCameraProperties, mockActivity, mockDartMessenger); assertTrue(sensorOrientationFeature.checkIsSupported()); } @Test public void getDeviceOrientationManager_shouldReturnInitializedDartOrientationManagerInstance() { SensorOrientationFeature sensorOrientationFeature = new SensorOrientationFeature(mockCameraProperties, mockActivity, mockDartMessenger); assertEquals( mockDeviceOrientationManager, sensorOrientationFeature.getDeviceOrientationManager()); } @Test public void lockCaptureOrientation_shouldLockToSpecifiedOrientation() { SensorOrientationFeature sensorOrientationFeature = new SensorOrientationFeature(mockCameraProperties, mockActivity, mockDartMessenger); sensorOrientationFeature.lockCaptureOrientation(DeviceOrientation.PORTRAIT_DOWN); assertEquals( DeviceOrientation.PORTRAIT_DOWN, sensorOrientationFeature.getLockedCaptureOrientation()); } @Test public void unlockCaptureOrientation_shouldSetLockToNull() { SensorOrientationFeature sensorOrientationFeature = new SensorOrientationFeature(mockCameraProperties, mockActivity, mockDartMessenger); sensorOrientationFeature.unlockCaptureOrientation(); assertNull(sensorOrientationFeature.getLockedCaptureOrientation()); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomLevelFeatureTest.java ================================================ // Copyright 2013 The Flutter 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.features.zoomlevel; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; 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.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; import org.mockito.MockedStatic; public class ZoomLevelFeatureTest { private MockedStatic mockedStaticCameraZoom; private CameraProperties mockCameraProperties; private ZoomUtils mockCameraZoom; private Rect mockZoomArea; private Rect mockSensorArray; @Before public void before() { mockedStaticCameraZoom = mockStatic(ZoomUtils.class); mockCameraProperties = mock(CameraProperties.class); mockCameraZoom = mock(ZoomUtils.class); mockZoomArea = mock(Rect.class); mockSensorArray = mock(Rect.class); mockedStaticCameraZoom .when(() -> ZoomUtils.computeZoomRect(anyFloat(), any(), anyFloat(), anyFloat())) .thenReturn(mockZoomArea); } @After public void after() { mockedStaticCameraZoom.close(); } @Test public void ctor_whenParametersAreValid() { when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray); when(mockCameraProperties.getScalerAvailableMaxDigitalZoom()).thenReturn(42f); final ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties); verify(mockCameraProperties, times(1)).getSensorInfoActiveArraySize(); verify(mockCameraProperties, times(1)).getScalerAvailableMaxDigitalZoom(); assertNotNull(zoomLevelFeature); assertEquals(42f, zoomLevelFeature.getMaximumZoomLevel(), 0); } @Test public void ctor_whenSensorSizeIsNull() { when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(null); when(mockCameraProperties.getScalerAvailableMaxDigitalZoom()).thenReturn(42f); final ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties); verify(mockCameraProperties, times(1)).getSensorInfoActiveArraySize(); verify(mockCameraProperties, never()).getScalerAvailableMaxDigitalZoom(); assertNotNull(zoomLevelFeature); assertFalse(zoomLevelFeature.checkIsSupported()); assertEquals(zoomLevelFeature.getMaximumZoomLevel(), 1.0f, 0); } @Test public void ctor_whenMaxZoomIsNull() { when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray); when(mockCameraProperties.getScalerAvailableMaxDigitalZoom()).thenReturn(null); final ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties); verify(mockCameraProperties, times(1)).getSensorInfoActiveArraySize(); verify(mockCameraProperties, times(1)).getScalerAvailableMaxDigitalZoom(); assertNotNull(zoomLevelFeature); assertFalse(zoomLevelFeature.checkIsSupported()); assertEquals(zoomLevelFeature.getMaximumZoomLevel(), 1.0f, 0); } @Test public void ctor_whenMaxZoomIsSmallerThenDefaultZoomFactor() { when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray); when(mockCameraProperties.getScalerAvailableMaxDigitalZoom()).thenReturn(0.5f); final ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties); verify(mockCameraProperties, times(1)).getSensorInfoActiveArraySize(); verify(mockCameraProperties, times(1)).getScalerAvailableMaxDigitalZoom(); assertNotNull(zoomLevelFeature); assertFalse(zoomLevelFeature.checkIsSupported()); assertEquals(zoomLevelFeature.getMaximumZoomLevel(), 1.0f, 0); } @Test public void getDebugName_shouldReturnTheNameOfTheFeature() { ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties); assertEquals("ZoomLevelFeature", zoomLevelFeature.getDebugName()); } @Test public void getValue_shouldReturnNullIfNotSet() { ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties); assertEquals(1.0, (float) zoomLevelFeature.getValue(), 0); } @Test public void getValue_shouldEchoSetValue() { ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties); zoomLevelFeature.setValue(2.3f); assertEquals(2.3f, (float) zoomLevelFeature.getValue(), 0); } @Test public void checkIsSupport_returnsFalseByDefault() { ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties); assertFalse(zoomLevelFeature.checkIsSupported()); } @Test public void updateBuilder_shouldSetScalarCropRegionWhenCheckIsSupportIsTrue() { when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray); when(mockCameraProperties.getScalerAvailableMaxDigitalZoom()).thenReturn(42f); ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties); CaptureRequest.Builder mockBuilder = mock(CaptureRequest.Builder.class); zoomLevelFeature.updateBuilder(mockBuilder); 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); assertEquals(1.0f, zoomLevelFeature.getMinimumZoomLevel(), 0); } @Test public void getMaximumZoomLevel() { when(mockCameraProperties.getSensorInfoActiveArraySize()).thenReturn(mockSensorArray); when(mockCameraProperties.getScalerAvailableMaxDigitalZoom()).thenReturn(42f); ZoomLevelFeature zoomLevelFeature = new ZoomLevelFeature(mockCameraProperties); 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); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtilsTest.java ================================================ // Copyright 2013 The Flutter 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.features.zoomlevel; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import android.graphics.Rect; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class ZoomUtilsTest { @Test public void setZoomRect_whenSensorSizeEqualsZeroShouldReturnCropRegionOfZero() { final Rect sensorSize = new Rect(0, 0, 0, 0); final Rect computedZoom = ZoomUtils.computeZoomRect(18f, sensorSize, 1f, 20f); assertNotNull(computedZoom); assertEquals(computedZoom.left, 0); assertEquals(computedZoom.top, 0); assertEquals(computedZoom.right, 0); assertEquals(computedZoom.bottom, 0); } @Test public void setZoomRect_whenSensorSizeIsValidShouldReturnCropRegion() { final Rect sensorSize = new Rect(0, 0, 100, 100); final Rect computedZoom = ZoomUtils.computeZoomRect(18f, sensorSize, 1f, 20f); assertNotNull(computedZoom); assertEquals(computedZoom.left, 48); assertEquals(computedZoom.top, 48); assertEquals(computedZoom.right, 52); assertEquals(computedZoom.bottom, 52); } @Test public void setZoomRect_whenZoomIsGreaterThenMaxZoomClampToMaxZoom() { final Rect sensorSize = new Rect(0, 0, 100, 100); final Rect computedZoom = ZoomUtils.computeZoomRect(25f, sensorSize, 1f, 10f); assertNotNull(computedZoom); assertEquals(computedZoom.left, 45); assertEquals(computedZoom.top, 45); assertEquals(computedZoom.right, 55); assertEquals(computedZoom.bottom, 55); } @Test public void setZoomRect_whenZoomIsSmallerThenMinZoomClampToMinZoom() { final Rect sensorSize = new Rect(0, 0, 100, 100); final Rect computedZoom = ZoomUtils.computeZoomRect(0.5f, sensorSize, 1f, 10f); assertNotNull(computedZoom); assertEquals(computedZoom.left, 0); assertEquals(computedZoom.top, 0); 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); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/media/MediaRecorderBuilderTest.java ================================================ // Copyright 2013 The Flutter 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.media; import static org.junit.Assert.assertNotNull; import static org.mockito.Mockito.*; import android.media.CamcorderProfile; import android.media.EncoderProfiles; import android.media.MediaRecorder; import java.io.IOException; import java.lang.reflect.Constructor; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InOrder; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) public class MediaRecorderBuilderTest { @Config(maxSdk = 30) @SuppressWarnings("deprecation") @Test public void ctor_testLegacy() { MediaRecorderBuilder builder = new MediaRecorderBuilder(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P), ""); assertNotNull(builder); } @Config(minSdk = 31) @Test public void ctor_test() { MediaRecorderBuilder builder = new MediaRecorderBuilder(CamcorderProfile.getAll("0", CamcorderProfile.QUALITY_1080P), ""); assertNotNull(builder); } @Config(maxSdk = 30) @SuppressWarnings("deprecation") @Test public void build_shouldSetValuesInCorrectOrderWhenAudioIsDisabledLegacy() throws IOException { CamcorderProfile recorderProfile = getEmptyCamcorderProfile(); MediaRecorderBuilder.MediaRecorderFactory mockFactory = mock(MediaRecorderBuilder.MediaRecorderFactory.class); MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); String outputFilePath = "mock_video_file_path"; int mediaOrientation = 1; MediaRecorderBuilder builder = new MediaRecorderBuilder(recorderProfile, outputFilePath, mockFactory) .setEnableAudio(false) .setMediaOrientation(mediaOrientation); when(mockFactory.makeMediaRecorder()).thenReturn(mockMediaRecorder); MediaRecorder recorder = builder.build(); InOrder inOrder = inOrder(recorder); inOrder.verify(recorder).setVideoSource(MediaRecorder.VideoSource.SURFACE); inOrder.verify(recorder).setOutputFormat(recorderProfile.fileFormat); inOrder.verify(recorder).setVideoEncoder(recorderProfile.videoCodec); inOrder.verify(recorder).setVideoEncodingBitRate(recorderProfile.videoBitRate); inOrder.verify(recorder).setVideoFrameRate(recorderProfile.videoFrameRate); inOrder .verify(recorder) .setVideoSize(recorderProfile.videoFrameWidth, recorderProfile.videoFrameHeight); inOrder.verify(recorder).setOutputFile(outputFilePath); inOrder.verify(recorder).setOrientationHint(mediaOrientation); inOrder.verify(recorder).prepare(); } @Config(minSdk = 31) @Test public void build_shouldSetValuesInCorrectOrderWhenAudioIsDisabled() throws IOException { EncoderProfiles recorderProfile = mock(EncoderProfiles.class); List mockVideoProfiles = List.of(mock(EncoderProfiles.VideoProfile.class)); List mockAudioProfiles = List.of(mock(EncoderProfiles.AudioProfile.class)); MediaRecorderBuilder.MediaRecorderFactory mockFactory = mock(MediaRecorderBuilder.MediaRecorderFactory.class); MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); String outputFilePath = "mock_video_file_path"; int mediaOrientation = 1; MediaRecorderBuilder builder = new MediaRecorderBuilder(recorderProfile, outputFilePath, mockFactory) .setEnableAudio(false) .setMediaOrientation(mediaOrientation); when(mockFactory.makeMediaRecorder()).thenReturn(mockMediaRecorder); when(recorderProfile.getVideoProfiles()).thenReturn(mockVideoProfiles); when(recorderProfile.getAudioProfiles()).thenReturn(mockAudioProfiles); MediaRecorder recorder = builder.build(); EncoderProfiles.VideoProfile videoProfile = mockVideoProfiles.get(0); InOrder inOrder = inOrder(recorder); inOrder.verify(recorder).setVideoSource(MediaRecorder.VideoSource.SURFACE); inOrder.verify(recorder).setOutputFormat(recorderProfile.getRecommendedFileFormat()); inOrder.verify(recorder).setVideoEncoder(videoProfile.getCodec()); inOrder.verify(recorder).setVideoEncodingBitRate(videoProfile.getBitrate()); inOrder.verify(recorder).setVideoFrameRate(videoProfile.getFrameRate()); inOrder.verify(recorder).setVideoSize(videoProfile.getWidth(), videoProfile.getHeight()); inOrder.verify(recorder).setOutputFile(outputFilePath); inOrder.verify(recorder).setOrientationHint(mediaOrientation); inOrder.verify(recorder).prepare(); } @Config(minSdk = 31) @Test(expected = IndexOutOfBoundsException.class) public void build_shouldThrowExceptionWithoutVideoOrAudioProfiles() throws IOException { EncoderProfiles recorderProfile = mock(EncoderProfiles.class); MediaRecorderBuilder.MediaRecorderFactory mockFactory = mock(MediaRecorderBuilder.MediaRecorderFactory.class); MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); String outputFilePath = "mock_video_file_path"; int mediaOrientation = 1; MediaRecorderBuilder builder = new MediaRecorderBuilder(recorderProfile, outputFilePath, mockFactory) .setEnableAudio(false) .setMediaOrientation(mediaOrientation); when(mockFactory.makeMediaRecorder()).thenReturn(mockMediaRecorder); MediaRecorder recorder = builder.build(); } @Config(maxSdk = 30) @SuppressWarnings("deprecation") @Test public void build_shouldSetValuesInCorrectOrderWhenAudioIsEnabledLegacy() throws IOException { CamcorderProfile recorderProfile = getEmptyCamcorderProfile(); MediaRecorderBuilder.MediaRecorderFactory mockFactory = mock(MediaRecorderBuilder.MediaRecorderFactory.class); MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); String outputFilePath = "mock_video_file_path"; int mediaOrientation = 1; MediaRecorderBuilder builder = new MediaRecorderBuilder(recorderProfile, outputFilePath, mockFactory) .setEnableAudio(true) .setMediaOrientation(mediaOrientation); when(mockFactory.makeMediaRecorder()).thenReturn(mockMediaRecorder); MediaRecorder recorder = builder.build(); InOrder inOrder = inOrder(recorder); inOrder.verify(recorder).setAudioSource(MediaRecorder.AudioSource.MIC); inOrder.verify(recorder).setVideoSource(MediaRecorder.VideoSource.SURFACE); inOrder.verify(recorder).setOutputFormat(recorderProfile.fileFormat); inOrder.verify(recorder).setAudioEncoder(recorderProfile.audioCodec); inOrder.verify(recorder).setAudioEncodingBitRate(recorderProfile.audioBitRate); inOrder.verify(recorder).setAudioSamplingRate(recorderProfile.audioSampleRate); inOrder.verify(recorder).setVideoEncoder(recorderProfile.videoCodec); inOrder.verify(recorder).setVideoEncodingBitRate(recorderProfile.videoBitRate); inOrder.verify(recorder).setVideoFrameRate(recorderProfile.videoFrameRate); inOrder .verify(recorder) .setVideoSize(recorderProfile.videoFrameWidth, recorderProfile.videoFrameHeight); inOrder.verify(recorder).setOutputFile(outputFilePath); inOrder.verify(recorder).setOrientationHint(mediaOrientation); inOrder.verify(recorder).prepare(); } @Config(minSdk = 31) @Test public void build_shouldSetValuesInCorrectOrderWhenAudioIsEnabled() throws IOException { EncoderProfiles recorderProfile = mock(EncoderProfiles.class); List mockVideoProfiles = List.of(mock(EncoderProfiles.VideoProfile.class)); List mockAudioProfiles = List.of(mock(EncoderProfiles.AudioProfile.class)); MediaRecorderBuilder.MediaRecorderFactory mockFactory = mock(MediaRecorderBuilder.MediaRecorderFactory.class); MediaRecorder mockMediaRecorder = mock(MediaRecorder.class); String outputFilePath = "mock_video_file_path"; int mediaOrientation = 1; MediaRecorderBuilder builder = new MediaRecorderBuilder(recorderProfile, outputFilePath, mockFactory) .setEnableAudio(true) .setMediaOrientation(mediaOrientation); when(mockFactory.makeMediaRecorder()).thenReturn(mockMediaRecorder); when(recorderProfile.getVideoProfiles()).thenReturn(mockVideoProfiles); when(recorderProfile.getAudioProfiles()).thenReturn(mockAudioProfiles); MediaRecorder recorder = builder.build(); EncoderProfiles.VideoProfile videoProfile = mockVideoProfiles.get(0); EncoderProfiles.AudioProfile audioProfile = mockAudioProfiles.get(0); InOrder inOrder = inOrder(recorder); inOrder.verify(recorder).setAudioSource(MediaRecorder.AudioSource.MIC); inOrder.verify(recorder).setVideoSource(MediaRecorder.VideoSource.SURFACE); inOrder.verify(recorder).setOutputFormat(recorderProfile.getRecommendedFileFormat()); inOrder.verify(recorder).setAudioEncoder(audioProfile.getCodec()); inOrder.verify(recorder).setAudioEncodingBitRate(audioProfile.getBitrate()); inOrder.verify(recorder).setAudioSamplingRate(audioProfile.getSampleRate()); inOrder.verify(recorder).setVideoEncoder(videoProfile.getCodec()); inOrder.verify(recorder).setVideoEncodingBitRate(videoProfile.getBitrate()); inOrder.verify(recorder).setVideoFrameRate(videoProfile.getFrameRate()); inOrder.verify(recorder).setVideoSize(videoProfile.getWidth(), videoProfile.getHeight()); inOrder.verify(recorder).setOutputFile(outputFilePath); inOrder.verify(recorder).setOrientationHint(mediaOrientation); inOrder.verify(recorder).prepare(); } private CamcorderProfile getEmptyCamcorderProfile() { try { Constructor constructor = CamcorderProfile.class.getDeclaredConstructor( int.class, int.class, int.class, int.class, int.class, int.class, int.class, int.class, int.class, int.class, int.class, int.class); constructor.setAccessible(true); return constructor.newInstance(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); } catch (Exception ignored) { } return null; } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/types/ExposureModeTest.java ================================================ // Copyright 2013 The Flutter 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.types; import static org.junit.Assert.assertEquals; import org.junit.Test; public class ExposureModeTest { @Test public void getValueForString_returnsCorrectValues() { assertEquals( "Returns ExposureMode.auto for 'auto'", ExposureMode.getValueForString("auto"), ExposureMode.auto); assertEquals( "Returns ExposureMode.locked for 'locked'", ExposureMode.getValueForString("locked"), ExposureMode.locked); } @Test public void getValueForString_returnsNullForNonexistantValue() { assertEquals( "Returns null for 'nonexistant'", ExposureMode.getValueForString("nonexistant"), null); } @Test public void toString_returnsCorrectValue() { assertEquals("Returns 'auto' for ExposureMode.auto", ExposureMode.auto.toString(), "auto"); assertEquals( "Returns 'locked' for ExposureMode.locked", ExposureMode.locked.toString(), "locked"); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/types/FlashModeTest.java ================================================ // Copyright 2013 The Flutter 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.types; import static org.junit.Assert.assertEquals; import org.junit.Test; public class FlashModeTest { @Test public void getValueForString_returnsCorrectValues() { assertEquals( "Returns FlashMode.off for 'off'", FlashMode.getValueForString("off"), FlashMode.off); assertEquals( "Returns FlashMode.auto for 'auto'", FlashMode.getValueForString("auto"), FlashMode.auto); assertEquals( "Returns FlashMode.always for 'always'", FlashMode.getValueForString("always"), FlashMode.always); assertEquals( "Returns FlashMode.torch for 'torch'", FlashMode.getValueForString("torch"), FlashMode.torch); } @Test public void getValueForString_returnsNullForNonexistantValue() { assertEquals( "Returns null for 'nonexistant'", FlashMode.getValueForString("nonexistant"), null); } @Test public void toString_returnsCorrectValue() { assertEquals("Returns 'off' for FlashMode.off", FlashMode.off.toString(), "off"); assertEquals("Returns 'auto' for FlashMode.auto", FlashMode.auto.toString(), "auto"); assertEquals("Returns 'always' for FlashMode.always", FlashMode.always.toString(), "always"); assertEquals("Returns 'torch' for FlashMode.torch", FlashMode.torch.toString(), "torch"); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/types/FocusModeTest.java ================================================ // Copyright 2013 The Flutter 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.types; import static org.junit.Assert.assertEquals; import org.junit.Test; public class FocusModeTest { @Test public void getValueForString_returnsCorrectValues() { assertEquals( "Returns FocusMode.auto for 'auto'", FocusMode.getValueForString("auto"), FocusMode.auto); assertEquals( "Returns FocusMode.locked for 'locked'", FocusMode.getValueForString("locked"), FocusMode.locked); } @Test public void getValueForString_returnsNullForNonexistantValue() { assertEquals( "Returns null for 'nonexistant'", FocusMode.getValueForString("nonexistant"), null); } @Test public void toString_returnsCorrectValue() { assertEquals("Returns 'auto' for FocusMode.auto", FocusMode.auto.toString(), "auto"); assertEquals("Returns 'locked' for FocusMode.locked", FocusMode.locked.toString(), "locked"); } } ================================================ FILE: packages/camera/camera_android/android/src/test/java/io/flutter/plugins/camera/utils/TestUtils.java ================================================ // Copyright 2013 The Flutter 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.utils; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import org.junit.Assert; public class TestUtils { public static void setFinalStatic(Class classToModify, String fieldName, Object newValue) { try { Field field = classToModify.getField(fieldName); field.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.set(null, newValue); } catch (Exception e) { Assert.fail("Unable to mock static field: " + fieldName); } } public static void setPrivateField(T instance, String fieldName, Object newValue) { try { Field field = instance.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(instance, newValue); } catch (Exception e) { Assert.fail("Unable to mock private field: " + fieldName); } } public static Object getPrivateField(T instance, String fieldName) { try { Field field = instance.getClass().getDeclaredField(fieldName); field.setAccessible(true); return field.get(instance); } catch (Exception e) { Assert.fail("Unable to mock private field: " + fieldName); return null; } } } ================================================ FILE: packages/camera/camera_android/android/src/test/resources/robolectric.properties ================================================ sdk=30 ================================================ FILE: packages/camera/camera_android/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 lintOptions { disable 'InvalidPackage' } defaultConfig { applicationId "io.flutter.plugins.cameraexample" minSdkVersion 21 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } profile { matchingFallbacks = ['debug', 'release'] } } } flutter { source '../..' } dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' } ================================================ FILE: packages/camera/camera_android/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/camera/camera_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/camera/camera_android/example/android/app/src/androidTest/java/io/flutter/plugins/cameraexample/FlutterActivityTest.java ================================================ // Copyright 2013 The Flutter 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.cameraexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class FlutterActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/camera/camera_android/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/camera/camera_android/example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: packages/camera/camera_android/example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: packages/camera/camera_android/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/camera/camera_android/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/camera/camera_android/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=false android.enableR8=true ================================================ FILE: packages/camera/camera_android/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withInputStream { stream -> plugins.load(stream) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/camera/camera_android/example/integration_test/camera_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:ui'; import 'package:camera_android/camera_android.dart'; import 'package:camera_example/camera_controller.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/painting.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:path_provider/path_provider.dart'; import 'package:video_player/video_player.dart'; void main() { late Directory testDir; IntegrationTestWidgetsFlutterBinding.ensureInitialized(); setUpAll(() async { CameraPlatform.instance = AndroidCamera(); final Directory extDir = await getTemporaryDirectory(); testDir = await Directory('${extDir.path}/test').create(recursive: true); }); tearDownAll(() async { await testDir.delete(recursive: true); }); final Map presetExpectedSizes = { ResolutionPreset.low: const Size(240, 320), ResolutionPreset.medium: const Size(480, 720), ResolutionPreset.high: const Size(720, 1280), ResolutionPreset.veryHigh: const Size(1080, 1920), ResolutionPreset.ultraHigh: const Size(2160, 3840), // Don't bother checking for max here since it could be anything. }; /// Verify that [actual] has dimensions that are at least as large as /// [expectedSize]. Allows for a mismatch in portrait vs landscape. Returns /// whether the dimensions exactly match. bool assertExpectedDimensions(Size expectedSize, Size actual) { expect(actual.shortestSide, lessThanOrEqualTo(expectedSize.shortestSide)); expect(actual.longestSide, lessThanOrEqualTo(expectedSize.longestSide)); return actual.shortestSide == expectedSize.shortestSide && actual.longestSide == expectedSize.longestSide; } // This tests that the capture is no bigger than the preset, since we have // automatic code to fall back to smaller sizes when we need to. Returns // whether the image is exactly the desired resolution. Future testCaptureImageResolution( CameraController controller, ResolutionPreset preset) async { final Size expectedSize = presetExpectedSizes[preset]!; // Take Picture final XFile file = await controller.takePicture(); // Load picture final File fileImage = File(file.path); final Image image = await decodeImageFromList(fileImage.readAsBytesSync()); // Verify image dimensions are as expected expect(image, isNotNull); return assertExpectedDimensions( expectedSize, Size(image.height.toDouble(), image.width.toDouble())); } testWidgets( 'Capture specific image resolutions', (WidgetTester tester) async { final List cameras = await CameraPlatform.instance.availableCameras(); if (cameras.isEmpty) { return; } for (final CameraDescription cameraDescription in cameras) { bool previousPresetExactlySupported = true; for (final MapEntry preset in presetExpectedSizes.entries) { final CameraController controller = CameraController(cameraDescription, preset.key); await controller.initialize(); final bool presetExactlySupported = await testCaptureImageResolution(controller, preset.key); assert(!(!previousPresetExactlySupported && presetExactlySupported), 'The camera took higher resolution pictures at a lower resolution.'); previousPresetExactlySupported = presetExactlySupported; await controller.dispose(); } } }, // TODO(egarciad): Fix https://github.com/flutter/flutter/issues/93686. skip: true, ); // This tests that the capture is no bigger than the preset, since we have // automatic code to fall back to smaller sizes when we need to. Returns // whether the image is exactly the desired resolution. Future testCaptureVideoResolution( CameraController controller, ResolutionPreset preset) async { final Size expectedSize = presetExpectedSizes[preset]!; // Take Video await controller.startVideoRecording(); sleep(const Duration(milliseconds: 300)); final XFile file = await controller.stopVideoRecording(); // Load video metadata final File videoFile = File(file.path); final VideoPlayerController videoController = VideoPlayerController.file(videoFile); await videoController.initialize(); final Size video = videoController.value.size; // Verify image dimensions are as expected expect(video, isNotNull); return assertExpectedDimensions( expectedSize, Size(video.height, video.width)); } testWidgets( 'Capture specific video resolutions', (WidgetTester tester) async { final List cameras = await CameraPlatform.instance.availableCameras(); if (cameras.isEmpty) { return; } for (final CameraDescription cameraDescription in cameras) { bool previousPresetExactlySupported = true; for (final MapEntry preset in presetExpectedSizes.entries) { final CameraController controller = CameraController(cameraDescription, preset.key); await controller.initialize(); await controller.prepareForVideoRecording(); final bool presetExactlySupported = await testCaptureVideoResolution(controller, preset.key); assert(!(!previousPresetExactlySupported && presetExactlySupported), 'The camera took higher resolution pictures at a lower resolution.'); previousPresetExactlySupported = presetExactlySupported; await controller.dispose(); } } }, // TODO(egarciad): Fix https://github.com/flutter/flutter/issues/93686. skip: true, ); testWidgets('Pause and resume video recording', (WidgetTester tester) async { final List cameras = await CameraPlatform.instance.availableCameras(); if (cameras.isEmpty) { return; } final CameraController controller = CameraController( cameras[0], ResolutionPreset.low, enableAudio: false, ); await controller.initialize(); await controller.prepareForVideoRecording(); int startPause; int timePaused = 0; await controller.startVideoRecording(); final int recordingStart = DateTime.now().millisecondsSinceEpoch; sleep(const Duration(milliseconds: 500)); await controller.pauseVideoRecording(); startPause = DateTime.now().millisecondsSinceEpoch; sleep(const Duration(milliseconds: 500)); await controller.resumeVideoRecording(); timePaused += DateTime.now().millisecondsSinceEpoch - startPause; sleep(const Duration(milliseconds: 500)); await controller.pauseVideoRecording(); startPause = DateTime.now().millisecondsSinceEpoch; sleep(const Duration(milliseconds: 500)); await controller.resumeVideoRecording(); timePaused += DateTime.now().millisecondsSinceEpoch - startPause; sleep(const Duration(milliseconds: 500)); final XFile file = await controller.stopVideoRecording(); final int recordingTime = DateTime.now().millisecondsSinceEpoch - recordingStart; final File videoFile = File(file.path); final VideoPlayerController videoController = VideoPlayerController.file( videoFile, ); await videoController.initialize(); final int duration = videoController.value.duration.inMilliseconds; await videoController.dispose(); expect(duration, lessThan(recordingTime - timePaused)); }); testWidgets( 'image streaming', (WidgetTester tester) async { final List cameras = await CameraPlatform.instance.availableCameras(); if (cameras.isEmpty) { return; } final CameraController controller = CameraController( cameras[0], ResolutionPreset.low, enableAudio: false, ); await controller.initialize(); bool isDetecting = false; await controller.startImageStream((CameraImageData image) { if (isDetecting) { return; } isDetecting = true; expectLater(image, isNotNull).whenComplete(() => isDetecting = false); }); expect(controller.value.isStreamingImages, true); sleep(const Duration(milliseconds: 500)); await controller.stopImageStream(); await controller.dispose(); }, ); testWidgets( 'recording with image stream', (WidgetTester tester) async { final List cameras = await CameraPlatform.instance.availableCameras(); if (cameras.isEmpty) { return; } final CameraController controller = CameraController( cameras[0], ResolutionPreset.low, enableAudio: false, ); await controller.initialize(); bool isDetecting = false; await controller.startVideoRecording( streamCallback: (CameraImageData image) { if (isDetecting) { return; } isDetecting = true; expectLater(image, isNotNull); }); expect(controller.value.isStreamingImages, true); // Stopping recording before anything is recorded will throw, per // https://developer.android.com/reference/android/media/MediaRecorder.html#stop() // so delay long enough to ensure that some data is recorded. await Future.delayed(const Duration(seconds: 2)); await controller.stopVideoRecording(); await controller.dispose(); expect(controller.value.isStreamingImages, false); }, ); } ================================================ FILE: packages/camera/camera_android/example/lib/camera_controller.dart ================================================ // Copyright 2013 The Flutter 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 'dart:collection'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; /// The state of a [CameraController]. class CameraValue { /// Creates a new camera controller state. const CameraValue({ required this.isInitialized, this.previewSize, required this.isRecordingVideo, required this.isTakingPicture, required this.isStreamingImages, required this.isRecordingPaused, required this.flashMode, required this.exposureMode, required this.focusMode, required this.deviceOrientation, this.lockedCaptureOrientation, this.recordingOrientation, this.isPreviewPaused = false, this.previewPauseOrientation, }); /// Creates a new camera controller state for an uninitialized controller. const CameraValue.uninitialized() : this( isInitialized: false, isRecordingVideo: false, isTakingPicture: false, isStreamingImages: false, isRecordingPaused: false, flashMode: FlashMode.auto, exposureMode: ExposureMode.auto, focusMode: FocusMode.auto, deviceOrientation: DeviceOrientation.portraitUp, isPreviewPaused: false, ); /// True after [CameraController.initialize] has completed successfully. final bool isInitialized; /// True when a picture capture request has been sent but as not yet returned. final bool isTakingPicture; /// True when the camera is recording (not the same as previewing). final bool isRecordingVideo; /// True when images from the camera are being streamed. final bool isStreamingImages; /// True when video recording is paused. final bool isRecordingPaused; /// True when the preview widget has been paused manually. final bool isPreviewPaused; /// Set to the orientation the preview was paused in, if it is currently paused. final DeviceOrientation? previewPauseOrientation; /// The size of the preview in pixels. /// /// Is `null` until [isInitialized] is `true`. final Size? previewSize; /// The flash mode the camera is currently set to. final FlashMode flashMode; /// The exposure mode the camera is currently set to. final ExposureMode exposureMode; /// The focus mode the camera is currently set to. final FocusMode focusMode; /// The current device UI orientation. final DeviceOrientation deviceOrientation; /// The currently locked capture orientation. final DeviceOrientation? lockedCaptureOrientation; /// Whether the capture orientation is currently locked. bool get isCaptureOrientationLocked => lockedCaptureOrientation != null; /// The orientation of the currently running video recording. final DeviceOrientation? recordingOrientation; /// Creates a modified copy of the object. /// /// Explicitly specified fields get the specified value, all other fields get /// the same value of the current object. CameraValue copyWith({ bool? isInitialized, bool? isRecordingVideo, bool? isTakingPicture, bool? isStreamingImages, Size? previewSize, bool? isRecordingPaused, FlashMode? flashMode, ExposureMode? exposureMode, FocusMode? focusMode, bool? exposurePointSupported, bool? focusPointSupported, DeviceOrientation? deviceOrientation, Optional? lockedCaptureOrientation, Optional? recordingOrientation, bool? isPreviewPaused, Optional? previewPauseOrientation, }) { return CameraValue( isInitialized: isInitialized ?? this.isInitialized, previewSize: previewSize ?? this.previewSize, isRecordingVideo: isRecordingVideo ?? this.isRecordingVideo, isTakingPicture: isTakingPicture ?? this.isTakingPicture, isStreamingImages: isStreamingImages ?? this.isStreamingImages, isRecordingPaused: isRecordingPaused ?? this.isRecordingPaused, flashMode: flashMode ?? this.flashMode, exposureMode: exposureMode ?? this.exposureMode, focusMode: focusMode ?? this.focusMode, deviceOrientation: deviceOrientation ?? this.deviceOrientation, lockedCaptureOrientation: lockedCaptureOrientation == null ? this.lockedCaptureOrientation : lockedCaptureOrientation.orNull, recordingOrientation: recordingOrientation == null ? this.recordingOrientation : recordingOrientation.orNull, isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, previewPauseOrientation: previewPauseOrientation == null ? this.previewPauseOrientation : previewPauseOrientation.orNull, ); } @override String toString() { return '${objectRuntimeType(this, 'CameraValue')}(' 'isRecordingVideo: $isRecordingVideo, ' 'isInitialized: $isInitialized, ' 'previewSize: $previewSize, ' 'isStreamingImages: $isStreamingImages, ' 'flashMode: $flashMode, ' 'exposureMode: $exposureMode, ' 'focusMode: $focusMode, ' 'deviceOrientation: $deviceOrientation, ' 'lockedCaptureOrientation: $lockedCaptureOrientation, ' 'recordingOrientation: $recordingOrientation, ' 'isPreviewPaused: $isPreviewPaused, ' 'previewPausedOrientation: $previewPauseOrientation)'; } } /// Controls a device camera. /// /// This is a stripped-down version of the app-facing controller to serve as a /// utility for the example and integration tests. It wraps only the calls that /// have state associated with them, to consolidate tracking of camera state /// outside of the overall example code. class CameraController extends ValueNotifier { /// Creates a new camera controller in an uninitialized state. CameraController( this.description, this.resolutionPreset, { this.enableAudio = true, this.imageFormatGroup, }) : super(const CameraValue.uninitialized()); /// The properties of the camera device controlled by this controller. final CameraDescription description; /// The resolution this controller is targeting. /// /// This resolution preset is not guaranteed to be available on the device, /// if unavailable a lower resolution will be used. /// /// See also: [ResolutionPreset]. final ResolutionPreset resolutionPreset; /// Whether to include audio when recording a video. final bool enableAudio; /// The [ImageFormatGroup] describes the output of the raw image format. /// /// When null the imageFormat will fallback to the platforms default. final ImageFormatGroup? imageFormatGroup; late int _cameraId; bool _isDisposed = false; StreamSubscription? _imageStreamSubscription; FutureOr? _initCalled; StreamSubscription? _deviceOrientationSubscription; /// The camera identifier with which the controller is associated. int get cameraId => _cameraId; /// Initializes the camera on the device. Future initialize() async { final Completer initializeCompleter = Completer(); _deviceOrientationSubscription = CameraPlatform.instance .onDeviceOrientationChanged() .listen((DeviceOrientationChangedEvent event) { value = value.copyWith( deviceOrientation: event.orientation, ); }); _cameraId = await CameraPlatform.instance.createCamera( description, resolutionPreset, enableAudio: enableAudio, ); CameraPlatform.instance .onCameraInitialized(_cameraId) .first .then((CameraInitializedEvent event) { initializeCompleter.complete(event); }); await CameraPlatform.instance.initializeCamera( _cameraId, imageFormatGroup: imageFormatGroup ?? ImageFormatGroup.unknown, ); value = value.copyWith( isInitialized: true, previewSize: await initializeCompleter.future .then((CameraInitializedEvent event) => Size( event.previewWidth, event.previewHeight, )), exposureMode: await initializeCompleter.future .then((CameraInitializedEvent event) => event.exposureMode), focusMode: await initializeCompleter.future .then((CameraInitializedEvent event) => event.focusMode), exposurePointSupported: await initializeCompleter.future .then((CameraInitializedEvent event) => event.exposurePointSupported), focusPointSupported: await initializeCompleter.future .then((CameraInitializedEvent event) => event.focusPointSupported), ); _initCalled = true; } /// Prepare the capture session for video recording. Future prepareForVideoRecording() async { await CameraPlatform.instance.prepareForVideoRecording(); } /// Pauses the current camera preview Future pausePreview() async { await CameraPlatform.instance.pausePreview(_cameraId); value = value.copyWith( isPreviewPaused: true, 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, previewPauseOrientation: const Optional.absent()); } /// Captures an image and returns the file where it was saved. /// /// Throws a [CameraException] if the capture fails. Future takePicture() async { value = value.copyWith(isTakingPicture: true); final XFile file = await CameraPlatform.instance.takePicture(_cameraId); value = value.copyWith(isTakingPicture: false); return file; } /// Start streaming images from platform camera. Future startImageStream( Function(CameraImageData image) onAvailable) async { _imageStreamSubscription = CameraPlatform.instance .onStreamedFrameAvailable(_cameraId) .listen((CameraImageData imageData) { onAvailable(imageData); }); value = value.copyWith(isStreamingImages: true); } /// Stop streaming images from platform camera. Future stopImageStream() async { value = value.copyWith(isStreamingImages: false); await _imageStreamSubscription?.cancel(); _imageStreamSubscription = null; } /// Start a video recording. /// /// The video is returned as a [XFile] after calling [stopVideoRecording]. /// Throws a [CameraException] if the capture fails. Future startVideoRecording( {Function(CameraImageData image)? streamCallback}) async { await CameraPlatform.instance.startVideoCapturing( VideoCaptureOptions(_cameraId, streamCallback: streamCallback)); value = value.copyWith( isRecordingVideo: true, isRecordingPaused: false, isStreamingImages: streamCallback != null, recordingOrientation: Optional.of( value.lockedCaptureOrientation ?? value.deviceOrientation)); } /// Stops the video recording and returns the file where it was saved. /// /// Throws a [CameraException] if the capture failed. Future stopVideoRecording() async { if (value.isStreamingImages) { await stopImageStream(); } final XFile file = await CameraPlatform.instance.stopVideoRecording(_cameraId); value = value.copyWith( isRecordingVideo: false, isRecordingPaused: false, recordingOrientation: const Optional.absent(), ); return file; } /// Pause video recording. /// /// This feature is only available on iOS and Android sdk 24+. Future pauseVideoRecording() async { await CameraPlatform.instance.pauseVideoRecording(_cameraId); value = value.copyWith(isRecordingPaused: true); } /// Resume video recording after pausing. /// /// This feature is only available on iOS and Android sdk 24+. Future resumeVideoRecording() async { await CameraPlatform.instance.resumeVideoRecording(_cameraId); value = value.copyWith(isRecordingPaused: false); } /// Returns a widget showing a live camera preview. Widget buildPreview() { return CameraPlatform.instance.buildPreview(_cameraId); } /// Sets the flash mode for taking pictures. Future setFlashMode(FlashMode mode) async { await CameraPlatform.instance.setFlashMode(_cameraId, mode); value = value.copyWith(flashMode: mode); } /// Sets the exposure mode for taking pictures. Future setExposureMode(ExposureMode mode) async { await CameraPlatform.instance.setExposureMode(_cameraId, mode); value = value.copyWith(exposureMode: mode); } /// Sets the exposure offset for the selected camera. Future setExposureOffset(double offset) async { // Check if offset is in range final List range = await Future.wait(>[ CameraPlatform.instance.getMinExposureOffset(_cameraId), CameraPlatform.instance.getMaxExposureOffset(_cameraId) ]); // Round to the closest step if needed final double stepSize = await CameraPlatform.instance.getExposureOffsetStepSize(_cameraId); if (stepSize > 0) { final double inv = 1.0 / stepSize; double roundedOffset = (offset * inv).roundToDouble() / inv; if (roundedOffset > range[1]) { roundedOffset = (offset * inv).floorToDouble() / inv; } else if (roundedOffset < range[0]) { roundedOffset = (offset * inv).ceilToDouble() / inv; } offset = roundedOffset; } return CameraPlatform.instance.setExposureOffset(_cameraId, offset); } /// Locks the capture orientation. /// /// If [orientation] is omitted, the current device orientation is used. Future lockCaptureOrientation() async { await CameraPlatform.instance .lockCaptureOrientation(_cameraId, 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. Future setFocusMode(FocusMode mode) async { await CameraPlatform.instance.setFocusMode(_cameraId, mode); value = value.copyWith(focusMode: mode); } /// Releases the resources of this camera. @override Future dispose() async { if (_isDisposed) { return; } _deviceOrientationSubscription?.cancel(); _isDisposed = true; super.dispose(); if (_initCalled != null) { await _initCalled; await CameraPlatform.instance.dispose(_cameraId); } } @override void removeListener(VoidCallback listener) { // Prevent ValueListenableBuilder in CameraPreview widget from causing an // exception to be thrown by attempting to remove its own listener after // the controller has already been disposed. if (!_isDisposed) { super.removeListener(listener); } } } /// 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 }'; } } ================================================ FILE: packages/camera/camera_android/example/lib/camera_preview.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'camera_controller.dart'; /// A widget showing a live camera preview. class CameraPreview extends StatelessWidget { /// Creates a preview widget for the given camera controller. const CameraPreview(this.controller, {Key? key, this.child}) : super(key: key); /// The controller for the camera that the preview is shown for. final CameraController controller; /// A widget to overlay on top of the camera preview final Widget? child; @override Widget build(BuildContext context) { return controller.value.isInitialized ? ValueListenableBuilder( valueListenable: controller, builder: (BuildContext context, Object? value, Widget? child) { final double cameraAspectRatio = controller.value.previewSize!.width / controller.value.previewSize!.height; return AspectRatio( aspectRatio: _isLandscape() ? cameraAspectRatio : (1 / cameraAspectRatio), child: Stack( fit: StackFit.expand, children: [ _wrapInRotatedBox(child: controller.buildPreview()), child ?? Container(), ], ), ); }, child: child, ) : Container(); } Widget _wrapInRotatedBox({required Widget child}) { if (kIsWeb || defaultTargetPlatform != TargetPlatform.android) { return child; } return RotatedBox( quarterTurns: _getQuarterTurns(), child: child, ); } bool _isLandscape() { return [ DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight ].contains(_getApplicableOrientation()); } int _getQuarterTurns() { final Map turns = { DeviceOrientation.portraitUp: 0, DeviceOrientation.landscapeRight: 1, DeviceOrientation.portraitDown: 2, DeviceOrientation.landscapeLeft: 3, }; return turns[_getApplicableOrientation()]!; } DeviceOrientation _getApplicableOrientation() { return controller.value.isRecordingVideo ? controller.value.recordingOrientation! : (controller.value.previewPauseOrientation ?? controller.value.lockedCaptureOrientation ?? controller.value.deviceOrientation); } } ================================================ FILE: packages/camera/camera_android/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; import 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:video_player/video_player.dart'; import 'camera_controller.dart'; import 'camera_preview.dart'; /// Camera example home widget. class CameraExampleHome extends StatefulWidget { /// Default Constructor const CameraExampleHome({Key? key}) : super(key: key); @override State createState() { return _CameraExampleHomeState(); } } /// Returns a suitable camera icon for [direction]. IconData getCameraLensIcon(CameraLensDirection direction) { switch (direction) { case CameraLensDirection.back: return Icons.camera_rear; case CameraLensDirection.front: return Icons.camera_front; case CameraLensDirection.external: return Icons.camera; } // 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) { // ignore: avoid_print print('Error: $code${message == null ? '' : '\nError Message: $message'}'); } class _CameraExampleHomeState extends State with WidgetsBindingObserver, TickerProviderStateMixin { CameraController? controller; XFile? imageFile; XFile? videoFile; VideoPlayerController? videoController; VoidCallback? videoPlayerListener; bool enableAudio = true; double _minAvailableExposureOffset = 0.0; double _maxAvailableExposureOffset = 0.0; double _currentExposureOffset = 0.0; late AnimationController _flashModeControlRowAnimationController; late Animation _flashModeControlRowAnimation; late AnimationController _exposureModeControlRowAnimationController; late Animation _exposureModeControlRowAnimation; late AnimationController _focusModeControlRowAnimationController; late Animation _focusModeControlRowAnimation; double _minAvailableZoom = 1.0; double _maxAvailableZoom = 1.0; double _currentScale = 1.0; double _baseScale = 1.0; // Counting pointers (number of user fingers on screen) int _pointers = 0; @override void initState() { super.initState(); _ambiguate(WidgetsBinding.instance)?.addObserver(this); _flashModeControlRowAnimationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _flashModeControlRowAnimation = CurvedAnimation( parent: _flashModeControlRowAnimationController, curve: Curves.easeInCubic, ); _exposureModeControlRowAnimationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _exposureModeControlRowAnimation = CurvedAnimation( parent: _exposureModeControlRowAnimationController, curve: Curves.easeInCubic, ); _focusModeControlRowAnimationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _focusModeControlRowAnimation = CurvedAnimation( parent: _focusModeControlRowAnimationController, curve: Curves.easeInCubic, ); } @override void dispose() { _ambiguate(WidgetsBinding.instance)?.removeObserver(this); _flashModeControlRowAnimationController.dispose(); _exposureModeControlRowAnimationController.dispose(); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { final CameraController? cameraController = controller; // App state changed before we got the chance to initialize. if (cameraController == null || !cameraController.value.isInitialized) { return; } if (state == AppLifecycleState.inactive) { cameraController.dispose(); } else if (state == AppLifecycleState.resumed) { onNewCameraSelected(cameraController.description); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Camera example'), ), body: Column( children: [ Expanded( child: Container( decoration: BoxDecoration( color: Colors.black, border: Border.all( color: controller != null && controller!.value.isRecordingVideo ? Colors.redAccent : Colors.grey, width: 3.0, ), ), child: Padding( padding: const EdgeInsets.all(1.0), child: Center( child: _cameraPreviewWidget(), ), ), ), ), _captureControlRowWidget(), _modeControlRowWidget(), Padding( padding: const EdgeInsets.all(5.0), child: Row( children: [ _cameraTogglesRowWidget(), _thumbnailWidget(), ], ), ), ], ), ); } /// Display the preview from the camera (or a message if the preview is not available). Widget _cameraPreviewWidget() { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { return const Text( 'Tap a camera', style: TextStyle( color: Colors.white, fontSize: 24.0, fontWeight: FontWeight.w900, ), ); } else { return Listener( onPointerDown: (_) => _pointers++, onPointerUp: (_) => _pointers--, child: CameraPreview( controller!, child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return GestureDetector( behavior: HitTestBehavior.opaque, onScaleStart: _handleScaleStart, onScaleUpdate: _handleScaleUpdate, onTapDown: (TapDownDetails details) => onViewFinderTap(details, constraints), ); }), ), ); } } void _handleScaleStart(ScaleStartDetails details) { _baseScale = _currentScale; } Future _handleScaleUpdate(ScaleUpdateDetails details) async { // When there are not exactly two fingers on screen don't scale if (controller == null || _pointers != 2) { return; } _currentScale = (_baseScale * details.scale) .clamp(_minAvailableZoom, _maxAvailableZoom); await CameraPlatform.instance .setZoomLevel(controller!.cameraId, _currentScale); } /// Display the thumbnail of the captured image or video. Widget _thumbnailWidget() { final VideoPlayerController? localVideoController = videoController; return Expanded( child: Align( alignment: Alignment.centerRight, child: Row( mainAxisSize: MainAxisSize.min, children: [ if (localVideoController == null && imageFile == null) Container() else SizedBox( width: 64.0, height: 64.0, child: (localVideoController == null) ? ( // The captured image on the web contains a network-accessible URL // pointing to a location within the browser. It may be displayed // either with Image.network or Image.memory after loading the image // bytes to memory. kIsWeb ? Image.network(imageFile!.path) : Image.file(File(imageFile!.path))) : Container( decoration: BoxDecoration( border: Border.all(color: Colors.pink)), child: Center( child: AspectRatio( aspectRatio: localVideoController.value.size != null ? localVideoController.value.aspectRatio : 1.0, child: VideoPlayer(localVideoController)), ), ), ), ], ), ), ); } /// Display a bar with buttons to change the flash and exposure modes Widget _modeControlRowWidget() { return Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( icon: const Icon(Icons.flash_on), color: Colors.blue, onPressed: controller != null ? onFlashModeButtonPressed : null, ), // The exposure and focus mode are currently not supported on the web. ...!kIsWeb ? [ IconButton( icon: const Icon(Icons.exposure), color: Colors.blue, onPressed: controller != null ? onExposureModeButtonPressed : null, ), IconButton( icon: const Icon(Icons.filter_center_focus), color: Colors.blue, onPressed: controller != null ? onFocusModeButtonPressed : null, ) ] : [], IconButton( icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute), color: Colors.blue, onPressed: controller != null ? onAudioModeButtonPressed : null, ), IconButton( icon: Icon(controller?.value.isCaptureOrientationLocked ?? false ? Icons.screen_lock_rotation : Icons.screen_rotation), color: Colors.blue, onPressed: controller != null ? onCaptureOrientationLockButtonPressed : null, ), ], ), _flashModeControlRowWidget(), _exposureModeControlRowWidget(), _focusModeControlRowWidget(), ], ); } Widget _flashModeControlRowWidget() { return SizeTransition( sizeFactor: _flashModeControlRowAnimation, child: ClipRect( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( icon: const Icon(Icons.flash_off), color: controller?.value.flashMode == FlashMode.off ? Colors.orange : Colors.blue, onPressed: controller != null ? () => onSetFlashModeButtonPressed(FlashMode.off) : null, ), IconButton( icon: const Icon(Icons.flash_auto), color: controller?.value.flashMode == FlashMode.auto ? Colors.orange : Colors.blue, onPressed: controller != null ? () => onSetFlashModeButtonPressed(FlashMode.auto) : null, ), IconButton( icon: const Icon(Icons.flash_on), color: controller?.value.flashMode == FlashMode.always ? Colors.orange : Colors.blue, onPressed: controller != null ? () => onSetFlashModeButtonPressed(FlashMode.always) : null, ), IconButton( icon: const Icon(Icons.highlight), color: controller?.value.flashMode == FlashMode.torch ? Colors.orange : Colors.blue, onPressed: controller != null ? () => onSetFlashModeButtonPressed(FlashMode.torch) : null, ), ], ), ), ); } Widget _exposureModeControlRowWidget() { final ButtonStyle styleAuto = TextButton.styleFrom( // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: controller?.value.exposureMode == ExposureMode.auto ? Colors.orange : Colors.blue, ); final ButtonStyle styleLocked = TextButton.styleFrom( // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: controller?.value.exposureMode == ExposureMode.locked ? Colors.orange : Colors.blue, ); return SizeTransition( sizeFactor: _exposureModeControlRowAnimation, child: ClipRect( child: Container( color: Colors.grey.shade50, child: Column( children: [ const Center( child: Text('Exposure Mode'), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ TextButton( style: styleAuto, onPressed: controller != null ? () => onSetExposureModeButtonPressed(ExposureMode.auto) : null, onLongPress: () { if (controller != null) { CameraPlatform.instance .setExposurePoint(controller!.cameraId, null); showInSnackBar('Resetting exposure point'); } }, child: const Text('AUTO'), ), TextButton( style: styleLocked, onPressed: controller != null ? () => onSetExposureModeButtonPressed(ExposureMode.locked) : null, child: const Text('LOCKED'), ), TextButton( style: styleLocked, onPressed: controller != null ? () => controller!.setExposureOffset(0.0) : null, child: const Text('RESET OFFSET'), ), ], ), const Center( child: Text('Exposure Offset'), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text(_minAvailableExposureOffset.toString()), Slider( value: _currentExposureOffset, min: _minAvailableExposureOffset, max: _maxAvailableExposureOffset, label: _currentExposureOffset.toString(), onChanged: _minAvailableExposureOffset == _maxAvailableExposureOffset ? null : setExposureOffset, ), Text(_maxAvailableExposureOffset.toString()), ], ), ], ), ), ), ); } Widget _focusModeControlRowWidget() { final ButtonStyle styleAuto = TextButton.styleFrom( // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: controller?.value.focusMode == FocusMode.auto ? Colors.orange : Colors.blue, ); final ButtonStyle styleLocked = TextButton.styleFrom( // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: controller?.value.focusMode == FocusMode.locked ? Colors.orange : Colors.blue, ); return SizeTransition( sizeFactor: _focusModeControlRowAnimation, child: ClipRect( child: Container( color: Colors.grey.shade50, child: Column( children: [ const Center( child: Text('Focus Mode'), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ TextButton( style: styleAuto, onPressed: controller != null ? () => onSetFocusModeButtonPressed(FocusMode.auto) : null, onLongPress: () { if (controller != null) { CameraPlatform.instance .setFocusPoint(controller!.cameraId, null); } showInSnackBar('Resetting focus point'); }, child: const Text('AUTO'), ), TextButton( style: styleLocked, onPressed: controller != null ? () => onSetFocusModeButtonPressed(FocusMode.locked) : null, child: const Text('LOCKED'), ), ], ), ], ), ), ), ); } /// Display the control bar with buttons to take pictures and record videos. Widget _captureControlRowWidget() { final CameraController? cameraController = controller; return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( icon: const Icon(Icons.camera_alt), color: Colors.blue, onPressed: cameraController != null && cameraController.value.isInitialized && !cameraController.value.isRecordingVideo ? onTakePictureButtonPressed : null, ), IconButton( icon: const Icon(Icons.videocam), color: Colors.blue, onPressed: cameraController != null && cameraController.value.isInitialized && !cameraController.value.isRecordingVideo ? onVideoRecordButtonPressed : null, ), IconButton( icon: cameraController != null && (!cameraController.value.isRecordingVideo || cameraController.value.isRecordingPaused) ? const Icon(Icons.play_arrow) : const Icon(Icons.pause), color: Colors.blue, onPressed: cameraController != null && cameraController.value.isInitialized && cameraController.value.isRecordingVideo ? (cameraController.value.isRecordingPaused) ? onResumeButtonPressed : onPauseButtonPressed : null, ), IconButton( icon: const Icon(Icons.stop), color: Colors.red, onPressed: cameraController != null && cameraController.value.isInitialized && cameraController.value.isRecordingVideo ? onStopButtonPressed : null, ), IconButton( icon: const Icon(Icons.pause_presentation), color: cameraController != null && cameraController.value.isPreviewPaused ? Colors.red : Colors.blue, onPressed: cameraController == null ? null : onPausePreviewButtonPressed, ), ], ); } /// Display a row of toggle to select the camera (or a message if no camera is available). Widget _cameraTogglesRowWidget() { final List toggles = []; void onChanged(CameraDescription? description) { if (description == null) { return; } onNewCameraSelected(description); } if (_cameras.isEmpty) { _ambiguate(SchedulerBinding.instance)?.addPostFrameCallback((_) async { showInSnackBar('No camera found.'); }); return const Text('None'); } else { for (final CameraDescription cameraDescription in _cameras) { toggles.add( SizedBox( width: 90.0, child: RadioListTile( title: Icon(getCameraLensIcon(cameraDescription.lensDirection)), groupValue: controller?.description, value: cameraDescription, onChanged: controller != null && controller!.value.isRecordingVideo ? null : onChanged, ), ), ); } } return Row(children: toggles); } String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); void showInSnackBar(String message) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(message))); } void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { if (controller == null) { return; } final CameraController cameraController = controller!; final Point point = Point( details.localPosition.dx / constraints.maxWidth, details.localPosition.dy / constraints.maxHeight, ); CameraPlatform.instance.setExposurePoint(cameraController.cameraId, point); CameraPlatform.instance.setFocusPoint(cameraController.cameraId, point); } Future onNewCameraSelected(CameraDescription cameraDescription) async { final CameraController? oldController = controller; if (oldController != null) { // `controller` needs to be set to null before getting disposed, // to avoid a race condition when we use the controller that is being // disposed. This happens when camera permission dialog shows up, // which triggers `didChangeAppLifecycleState`, which disposes and // re-creates the controller. controller = null; await oldController.dispose(); } final CameraController cameraController = CameraController( cameraDescription, kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, enableAudio: enableAudio, imageFormatGroup: ImageFormatGroup.jpeg, ); controller = cameraController; // If the controller is updated then update the UI. cameraController.addListener(() { if (mounted) { setState(() {}); } }); try { await cameraController.initialize(); await Future.wait(>[ // The exposure mode is currently not supported on the web. ...!kIsWeb ? >[ CameraPlatform.instance .getMinExposureOffset(cameraController.cameraId) .then( (double value) => _minAvailableExposureOffset = value), CameraPlatform.instance .getMaxExposureOffset(cameraController.cameraId) .then((double value) => _maxAvailableExposureOffset = value) ] : >[], CameraPlatform.instance .getMaxZoomLevel(cameraController.cameraId) .then((double value) => _maxAvailableZoom = value), CameraPlatform.instance .getMinZoomLevel(cameraController.cameraId) .then((double value) => _minAvailableZoom = value), ]); } on CameraException catch (e) { switch (e.code) { case 'CameraAccessDenied': showInSnackBar('You have denied camera access.'); break; case 'CameraAccessDeniedWithoutPrompt': // iOS only showInSnackBar('Please go to Settings app to enable camera access.'); break; case 'CameraAccessRestricted': // iOS only showInSnackBar('Camera access is restricted.'); break; case 'AudioAccessDenied': showInSnackBar('You have denied audio access.'); break; case 'AudioAccessDeniedWithoutPrompt': // iOS only showInSnackBar('Please go to Settings app to enable audio access.'); break; case 'AudioAccessRestricted': // iOS only showInSnackBar('Audio access is restricted.'); break; case 'cameraPermission': // Android & web only showInSnackBar('Unknown permission error.'); break; default: _showCameraException(e); break; } } if (mounted) { setState(() {}); } } void onTakePictureButtonPressed() { takePicture().then((XFile? file) { if (mounted) { setState(() { imageFile = file; videoController?.dispose(); videoController = null; }); if (file != null) { showInSnackBar('Picture saved to ${file.path}'); } } }); } void onFlashModeButtonPressed() { if (_flashModeControlRowAnimationController.value == 1) { _flashModeControlRowAnimationController.reverse(); } else { _flashModeControlRowAnimationController.forward(); _exposureModeControlRowAnimationController.reverse(); _focusModeControlRowAnimationController.reverse(); } } void onExposureModeButtonPressed() { if (_exposureModeControlRowAnimationController.value == 1) { _exposureModeControlRowAnimationController.reverse(); } else { _exposureModeControlRowAnimationController.forward(); _flashModeControlRowAnimationController.reverse(); _focusModeControlRowAnimationController.reverse(); } } void onFocusModeButtonPressed() { if (_focusModeControlRowAnimationController.value == 1) { _focusModeControlRowAnimationController.reverse(); } else { _focusModeControlRowAnimationController.forward(); _flashModeControlRowAnimationController.reverse(); _exposureModeControlRowAnimationController.reverse(); } } void onAudioModeButtonPressed() { enableAudio = !enableAudio; if (controller != null) { onNewCameraSelected(controller!.description); } } Future onCaptureOrientationLockButtonPressed() async { try { if (controller != null) { final CameraController cameraController = controller!; if (cameraController.value.isCaptureOrientationLocked) { await cameraController.unlockCaptureOrientation(); showInSnackBar('Capture orientation unlocked'); } else { await cameraController.lockCaptureOrientation(); showInSnackBar( 'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}'); } } } on CameraException catch (e) { _showCameraException(e); } } void onSetFlashModeButtonPressed(FlashMode mode) { setFlashMode(mode).then((_) { if (mounted) { setState(() {}); } showInSnackBar('Flash mode set to ${mode.toString().split('.').last}'); }); } void onSetExposureModeButtonPressed(ExposureMode mode) { setExposureMode(mode).then((_) { if (mounted) { setState(() {}); } showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}'); }); } void onSetFocusModeButtonPressed(FocusMode mode) { setFocusMode(mode).then((_) { if (mounted) { setState(() {}); } showInSnackBar('Focus mode set to ${mode.toString().split('.').last}'); }); } void onVideoRecordButtonPressed() { startVideoRecording().then((_) { if (mounted) { setState(() {}); } }); } void onStopButtonPressed() { stopVideoRecording().then((XFile? file) { if (mounted) { setState(() {}); } if (file != null) { showInSnackBar('Video recorded to ${file.path}'); videoFile = file; _startVideoPlayer(); } }); } Future onPausePreviewButtonPressed() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return; } if (cameraController.value.isPreviewPaused) { await cameraController.resumePreview(); } else { await cameraController.pausePreview(); } if (mounted) { setState(() {}); } } void onPauseButtonPressed() { pauseVideoRecording().then((_) { if (mounted) { setState(() {}); } showInSnackBar('Video recording paused'); }); } void onResumeButtonPressed() { resumeVideoRecording().then((_) { if (mounted) { setState(() {}); } showInSnackBar('Video recording resumed'); }); } Future startVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return; } if (cameraController.value.isRecordingVideo) { // A recording is already started, do nothing. return; } try { await cameraController.startVideoRecording(); } on CameraException catch (e) { _showCameraException(e); return; } } Future stopVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { return null; } try { return cameraController.stopVideoRecording(); } on CameraException catch (e) { _showCameraException(e); return null; } } Future pauseVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { return; } try { await cameraController.pauseVideoRecording(); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future resumeVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { return; } try { await cameraController.resumeVideoRecording(); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future setFlashMode(FlashMode mode) async { if (controller == null) { return; } try { await controller!.setFlashMode(mode); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future setExposureMode(ExposureMode mode) async { if (controller == null) { return; } try { await controller!.setExposureMode(mode); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future setExposureOffset(double offset) async { if (controller == null) { return; } setState(() { _currentExposureOffset = offset; }); try { offset = await controller!.setExposureOffset(offset); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future setFocusMode(FocusMode mode) async { if (controller == null) { return; } try { await controller!.setFocusMode(mode); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future _startVideoPlayer() async { if (videoFile == null) { return; } final VideoPlayerController vController = kIsWeb ? VideoPlayerController.network(videoFile!.path) : VideoPlayerController.file(File(videoFile!.path)); videoPlayerListener = () { if (videoController != null && videoController!.value.size != null) { // Refreshing the state to update video player with the correct ratio. if (mounted) { setState(() {}); } videoController!.removeListener(videoPlayerListener!); } }; vController.addListener(videoPlayerListener!); await vController.setLooping(true); await vController.initialize(); await videoController?.dispose(); if (mounted) { setState(() { imageFile = null; videoController = vController; }); } await vController.play(); } Future takePicture() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return null; } if (cameraController.value.isTakingPicture) { // A capture is already pending, do nothing. return null; } try { final XFile file = await cameraController.takePicture(); return file; } on CameraException catch (e) { _showCameraException(e); return null; } } void _showCameraException(CameraException e) { _logError(e.code, e.description); showInSnackBar('Error: ${e.code}\n${e.description}'); } } /// CameraApp is the Main Application. class CameraApp extends StatelessWidget { /// Default Constructor const CameraApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const MaterialApp( home: CameraExampleHome(), ); } } List _cameras = []; Future main() async { // Fetch the available cameras before initializing the app. try { WidgetsFlutterBinding.ensureInitialized(); _cameras = await CameraPlatform.instance.availableCameras(); } on CameraException catch (e) { _logError(e.code, e.description); } runApp(const CameraApp()); } /// 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; ================================================ FILE: packages/camera/camera_android/example/pubspec.yaml ================================================ name: camera_example description: Demonstrates how to use the camera plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: camera_android: # When depending on this package from a real application you should use: # camera_android: ^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: ../ camera_platform_interface: ^2.3.1 flutter: sdk: flutter path_provider: ^2.0.0 quiver: ^3.0.0 video_player: ^2.1.4 dev_dependencies: build_runner: ^2.1.10 flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/camera/camera_android/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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: avoid_print import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter_driver/flutter_driver.dart'; const String _examplePackage = 'io.flutter.plugins.cameraexample'; Future main() async { if (!(Platform.isLinux || Platform.isMacOS)) { print('This test must be run on a POSIX host. Skipping...'); exit(0); } final bool adbExists = Process.runSync('which', ['adb']).exitCode == 0; if (!adbExists) { print(r'This test needs ADB to exist on the $PATH. Skipping...'); exit(0); } print('Granting camera permissions...'); Process.runSync('adb', [ 'shell', 'pm', 'grant', _examplePackage, 'android.permission.CAMERA' ]); Process.runSync('adb', [ 'shell', 'pm', 'grant', _examplePackage, 'android.permission.RECORD_AUDIO' ]); print('Starting test.'); final FlutterDriver driver = await FlutterDriver.connect(); final String data = await driver.requestData( null, timeout: const Duration(minutes: 1), ); await driver.close(); print('Test finished. Revoking camera permissions...'); Process.runSync('adb', [ 'shell', 'pm', 'revoke', _examplePackage, 'android.permission.CAMERA' ]); Process.runSync('adb', [ 'shell', 'pm', 'revoke', _examplePackage, 'android.permission.RECORD_AUDIO' ]); final Map result = jsonDecode(data) as Map; exit(result['result'] == 'true' ? 0 : 1); } ================================================ FILE: packages/camera/camera_android/lib/camera_android.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'src/android_camera.dart'; ================================================ FILE: packages/camera/camera_android/lib/src/android_camera.dart ================================================ // Copyright 2013 The Flutter 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 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:stream_transform/stream_transform.dart'; import 'type_conversion.dart'; import 'utils.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera_android'); /// The Android implementation of [CameraPlatform] that uses method channels. class AndroidCamera extends CameraPlatform { /// Registers this class as the default instance of [CameraPlatform]. static void registerWith() { CameraPlatform.instance = AndroidCamera(); } final Map _channels = {}; /// The name of the channel that device events from the platform side are /// sent on. @visibleForTesting static const String deviceEventChannelName = 'plugins.flutter.io/camera_android/fromPlatform'; /// The controller we need to broadcast the different events coming /// from handleMethodCall, specific to camera events. /// /// It is a `broadcast` because multiple controllers will connect to /// different stream views of this Controller. /// This is only exposed for test purposes. It shouldn't be used by clients of /// the plugin as it may break or change at any time. @visibleForTesting final StreamController cameraEventStreamController = StreamController.broadcast(); /// The controller we need to broadcast the different events coming /// from handleMethodCall, specific to general device events. /// /// It is a `broadcast` because multiple controllers will connect to /// different stream views of this Controller. late final StreamController _deviceEventStreamController = _createDeviceEventStreamController(); StreamController _createDeviceEventStreamController() { // Set up the method handler lazily. const MethodChannel channel = MethodChannel(deviceEventChannelName); channel.setMethodCallHandler(_handleDeviceMethodCall); return StreamController.broadcast(); } // The stream to receive frames from the native code. StreamSubscription? _platformImageStreamSubscription; // The stream for vending frames to platform interface clients. StreamController? _frameStreamController; Stream _cameraEvents(int cameraId) => cameraEventStreamController.stream .where((CameraEvent event) => event.cameraId == cameraId); @override Future> availableCameras() async { try { final List>? cameras = await _channel .invokeListMethod>('availableCameras'); if (cameras == null) { return []; } return cameras.map((Map camera) { return CameraDescription( name: camera['name']! as String, lensDirection: parseCameraLensDirection(camera['lensFacing']! as String), sensorOrientation: camera['sensorOrientation']! as int, ); }).toList(); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } @override Future createCamera( CameraDescription cameraDescription, ResolutionPreset? resolutionPreset, { bool enableAudio = false, }) async { try { final Map? reply = await _channel .invokeMapMethod('create', { 'cameraName': cameraDescription.name, 'resolutionPreset': resolutionPreset != null ? _serializeResolutionPreset(resolutionPreset) : null, 'enableAudio': enableAudio, }); return reply!['cameraId']! as int; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } @override Future initializeCamera( int cameraId, { ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown, }) { _channels.putIfAbsent(cameraId, () { final MethodChannel channel = MethodChannel('plugins.flutter.io/camera_android/camera$cameraId'); channel.setMethodCallHandler( (MethodCall call) => handleCameraMethodCall(call, cameraId)); return channel; }); final Completer completer = Completer(); onCameraInitialized(cameraId).first.then((CameraInitializedEvent value) { completer.complete(); }); _channel.invokeMapMethod( 'initialize', { 'cameraId': cameraId, 'imageFormatGroup': imageFormatGroup.name(), }, ).catchError( // TODO(srawlins): This should return a value of the future's type. This // will fail upcoming analysis checks with // https://github.com/flutter/flutter/issues/105750. // ignore: body_might_complete_normally_catch_error (Object error, StackTrace stackTrace) { if (error is! PlatformException) { // ignore: only_throw_errors throw error; } completer.completeError( CameraException(error.code, error.message), stackTrace, ); }, ); return completer.future; } @override Future dispose(int cameraId) async { if (_channels.containsKey(cameraId)) { final MethodChannel? cameraChannel = _channels[cameraId]; cameraChannel?.setMethodCallHandler(null); _channels.remove(cameraId); } await _channel.invokeMethod( 'dispose', {'cameraId': cameraId}, ); } @override Stream onCameraInitialized(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onCameraResolutionChanged(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onCameraClosing(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onCameraError(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onVideoRecordedEvent(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onDeviceOrientationChanged() { return _deviceEventStreamController.stream .whereType(); } @override Future lockCaptureOrientation( int cameraId, DeviceOrientation orientation, ) async { await _channel.invokeMethod( 'lockCaptureOrientation', { 'cameraId': cameraId, 'orientation': serializeDeviceOrientation(orientation) }, ); } @override Future unlockCaptureOrientation(int cameraId) async { await _channel.invokeMethod( 'unlockCaptureOrientation', {'cameraId': cameraId}, ); } @override Future takePicture(int cameraId) async { final String? path = await _channel.invokeMethod( 'takePicture', {'cameraId': cameraId}, ); if (path == null) { throw CameraException( 'INVALID_PATH', 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', ); } return XFile(path); } @override Future prepareForVideoRecording() => _channel.invokeMethod('prepareForVideoRecording'); @override Future startVideoRecording(int cameraId, {Duration? maxVideoDuration}) async { return startVideoCapturing( VideoCaptureOptions(cameraId, maxDuration: maxVideoDuration)); } @override Future startVideoCapturing(VideoCaptureOptions options) async { await _channel.invokeMethod( 'startVideoRecording', { 'cameraId': options.cameraId, 'maxVideoDuration': options.maxDuration?.inMilliseconds, 'enableStream': options.streamCallback != null, }, ); if (options.streamCallback != null) { _installStreamController().stream.listen(options.streamCallback); _startStreamListener(); } } @override Future stopVideoRecording(int cameraId) async { final String? path = await _channel.invokeMethod( 'stopVideoRecording', {'cameraId': cameraId}, ); if (path == null) { throw CameraException( 'INVALID_PATH', 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', ); } return XFile(path); } @override Future pauseVideoRecording(int cameraId) => _channel.invokeMethod( 'pauseVideoRecording', {'cameraId': cameraId}, ); @override Future resumeVideoRecording(int cameraId) => _channel.invokeMethod( 'resumeVideoRecording', {'cameraId': cameraId}, ); @override Stream onStreamedFrameAvailable(int cameraId, {CameraImageStreamOptions? options}) { _installStreamController(onListen: _onFrameStreamListen); return _frameStreamController!.stream; } StreamController _installStreamController( {Function()? onListen}) { _frameStreamController = StreamController( onListen: onListen ?? () {}, onPause: _onFrameStreamPauseResume, onResume: _onFrameStreamPauseResume, onCancel: _onFrameStreamCancel, ); return _frameStreamController!; } void _onFrameStreamListen() { _startPlatformStream(); } Future _startPlatformStream() async { await _channel.invokeMethod('startImageStream'); _startStreamListener(); } void _startStreamListener() { const EventChannel cameraEventChannel = EventChannel('plugins.flutter.io/camera_android/imageStream'); _platformImageStreamSubscription = cameraEventChannel.receiveBroadcastStream().listen((dynamic imageData) { _frameStreamController! .add(cameraImageFromPlatformData(imageData as Map)); }); } FutureOr _onFrameStreamCancel() async { await _channel.invokeMethod('stopImageStream'); await _platformImageStreamSubscription?.cancel(); _platformImageStreamSubscription = null; _frameStreamController = null; } void _onFrameStreamPauseResume() { throw CameraException('InvalidCall', 'Pause and resume are not supported for onStreamedFrameAvailable'); } @override Future setFlashMode(int cameraId, FlashMode mode) => _channel.invokeMethod( 'setFlashMode', { 'cameraId': cameraId, 'mode': _serializeFlashMode(mode), }, ); @override Future setExposureMode(int cameraId, ExposureMode mode) => _channel.invokeMethod( 'setExposureMode', { 'cameraId': cameraId, 'mode': serializeExposureMode(mode), }, ); @override Future setExposurePoint(int cameraId, Point? point) { assert(point == null || point.x >= 0 && point.x <= 1); assert(point == null || point.y >= 0 && point.y <= 1); return _channel.invokeMethod( 'setExposurePoint', { 'cameraId': cameraId, 'reset': point == null, 'x': point?.x, 'y': point?.y, }, ); } @override Future getMinExposureOffset(int cameraId) async { final double? minExposureOffset = await _channel.invokeMethod( 'getMinExposureOffset', {'cameraId': cameraId}, ); return minExposureOffset!; } @override Future getMaxExposureOffset(int cameraId) async { final double? maxExposureOffset = await _channel.invokeMethod( 'getMaxExposureOffset', {'cameraId': cameraId}, ); return maxExposureOffset!; } @override Future getExposureOffsetStepSize(int cameraId) async { final double? stepSize = await _channel.invokeMethod( 'getExposureOffsetStepSize', {'cameraId': cameraId}, ); return stepSize!; } @override Future setExposureOffset(int cameraId, double offset) async { final double? appliedOffset = await _channel.invokeMethod( 'setExposureOffset', { 'cameraId': cameraId, 'offset': offset, }, ); return appliedOffset!; } @override Future setFocusMode(int cameraId, FocusMode mode) => _channel.invokeMethod( 'setFocusMode', { 'cameraId': cameraId, 'mode': serializeFocusMode(mode), }, ); @override Future setFocusPoint(int cameraId, Point? point) { assert(point == null || point.x >= 0 && point.x <= 1); assert(point == null || point.y >= 0 && point.y <= 1); return _channel.invokeMethod( 'setFocusPoint', { 'cameraId': cameraId, 'reset': point == null, 'x': point?.x, 'y': point?.y, }, ); } @override Future getMaxZoomLevel(int cameraId) async { final double? maxZoomLevel = await _channel.invokeMethod( 'getMaxZoomLevel', {'cameraId': cameraId}, ); return maxZoomLevel!; } @override Future getMinZoomLevel(int cameraId) async { final double? minZoomLevel = await _channel.invokeMethod( 'getMinZoomLevel', {'cameraId': cameraId}, ); return minZoomLevel!; } @override Future setZoomLevel(int cameraId, double zoom) async { try { await _channel.invokeMethod( 'setZoomLevel', { 'cameraId': cameraId, 'zoom': zoom, }, ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } @override Future pausePreview(int cameraId) async { await _channel.invokeMethod( 'pausePreview', {'cameraId': cameraId}, ); } @override Future resumePreview(int cameraId) async { await _channel.invokeMethod( 'resumePreview', {'cameraId': cameraId}, ); } @override Widget buildPreview(int cameraId) { return Texture(textureId: cameraId); } /// Returns the flash mode as a String. String _serializeFlashMode(FlashMode flashMode) { switch (flashMode) { case FlashMode.off: return 'off'; case FlashMode.auto: return 'auto'; case FlashMode.always: return 'always'; case FlashMode.torch: return 'torch'; } // The enum comes from a different package, which could get a new value at // any time, so provide a fallback that ensures this won't break when used // 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. String _serializeResolutionPreset(ResolutionPreset resolutionPreset) { switch (resolutionPreset) { case ResolutionPreset.max: return 'max'; case ResolutionPreset.ultraHigh: return 'ultraHigh'; case ResolutionPreset.veryHigh: return 'veryHigh'; case ResolutionPreset.high: return 'high'; case ResolutionPreset.medium: return 'medium'; case ResolutionPreset.low: return 'low'; } // The enum comes from a different package, which could get a new value at // any time, so provide a fallback that ensures this won't break when used // 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(arguments['orientation']! as String))); break; default: throw MissingPluginException(); } } /// Converts messages received from the native platform into camera events. /// /// This is only exposed for test purposes. It shouldn't be used by clients of /// the plugin as it may break or change at any time. @visibleForTesting Future handleCameraMethodCall(MethodCall call, int cameraId) async { switch (call.method) { case 'initialized': final Map arguments = _getArgumentDictionary(call); cameraEventStreamController.add(CameraInitializedEvent( cameraId, 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, arguments['captureWidth']! as double, arguments['captureHeight']! as double, )); break; case 'camera_closing': cameraEventStreamController.add(CameraClosingEvent( cameraId, )); break; case 'video_recorded': final Map arguments = _getArgumentDictionary(call); cameraEventStreamController.add(VideoRecordedEvent( cameraId, 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, 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(); } } ================================================ FILE: packages/camera/camera_android/lib/src/type_conversion.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:camera_platform_interface/camera_platform_interface.dart'; /// Converts method channel call [data] for `receivedImageStreamData` to a /// [CameraImageData]. CameraImageData cameraImageFromPlatformData(Map data) { return CameraImageData( format: _cameraImageFormatFromPlatformData(data['format']), height: data['height'] as int, width: data['width'] as int, lensAperture: data['lensAperture'] as double?, sensorExposureTime: data['sensorExposureTime'] as int?, sensorSensitivity: data['sensorSensitivity'] as double?, planes: List.unmodifiable( (data['planes'] as List).map( (dynamic planeData) => _cameraImagePlaneFromPlatformData( planeData as Map)))); } CameraImageFormat _cameraImageFormatFromPlatformData(dynamic data) { return CameraImageFormat(_imageFormatGroupFromPlatformData(data), raw: data); } ImageFormatGroup _imageFormatGroupFromPlatformData(dynamic data) { switch (data) { case 35: // android.graphics.ImageFormat.YUV_420_888 return ImageFormatGroup.yuv420; case 256: // android.graphics.ImageFormat.JPEG return ImageFormatGroup.jpeg; } return ImageFormatGroup.unknown; } CameraImagePlane _cameraImagePlaneFromPlatformData(Map data) { return CameraImagePlane( bytes: data['bytes'] as Uint8List, bytesPerPixel: data['bytesPerPixel'] as int?, bytesPerRow: data['bytesPerRow'] as int, height: data['height'] as int?, width: data['width'] as int?); } ================================================ FILE: packages/camera/camera_android/lib/src/utils.dart ================================================ // Copyright 2013 The Flutter 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_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart'; /// Parses a string into a corresponding CameraLensDirection. CameraLensDirection parseCameraLensDirection(String string) { switch (string) { case 'front': return CameraLensDirection.front; case 'back': return CameraLensDirection.back; case 'external': return CameraLensDirection.external; } throw ArgumentError('Unknown CameraLensDirection value'); } /// Returns the device orientation as a String. String serializeDeviceOrientation(DeviceOrientation orientation) { switch (orientation) { case DeviceOrientation.portraitUp: return 'portraitUp'; case DeviceOrientation.portraitDown: return 'portraitDown'; case DeviceOrientation.landscapeRight: return 'landscapeRight'; case DeviceOrientation.landscapeLeft: return 'landscapeLeft'; } // The enum comes from a different package, which could get a new value at // any time, so provide a fallback that ensures this won't break when used // 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. DeviceOrientation deserializeDeviceOrientation(String str) { switch (str) { case 'portraitUp': return DeviceOrientation.portraitUp; case 'portraitDown': return DeviceOrientation.portraitDown; case 'landscapeRight': return DeviceOrientation.landscapeRight; case 'landscapeLeft': return DeviceOrientation.landscapeLeft; default: throw ArgumentError('"$str" is not a valid DeviceOrientation value'); } } ================================================ FILE: packages/camera/camera_android/pubspec.yaml ================================================ 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.4 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: camera platforms: android: package: io.flutter.plugins.camera pluginClass: CameraPlugin dartPluginClass: AndroidCamera dependencies: camera_platform_interface: ^2.3.1 flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.2 stream_transform: ^2.0.0 dev_dependencies: async: ^2.5.0 flutter_driver: sdk: flutter flutter_test: sdk: flutter ================================================ FILE: packages/camera/camera_android/test/android_camera_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:math'; import 'package:async/async.dart'; import 'package:camera_android/src/android_camera.dart'; import 'package:camera_android/src/utils.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'method_channel_mock.dart'; const String _channelName = 'plugins.flutter.io/camera_android'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); test('registers instance', () async { AndroidCamera.registerWith(); expect(CameraPlatform.instance, isA()); }); test('registration does not set message handlers', () async { AndroidCamera.registerWith(); // Setting up a handler requires bindings to be initialized, and since // 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 _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .handlePlatformMessage( AndroidCamera.deviceEventChannelName, const StandardMethodCodec().encodeMethodCall(const MethodCall( 'orientation_changed', {'orientation': 'portraitDown'})), (ByteData? data) {}); expect(response, null); }); group('Creation, Initialization & Disposal Tests', () { test('Should send creation data and receive back a camera id', () async { // Arrange final MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: _channelName, methods: { 'create': { 'cameraId': 1, 'imageFormatGroup': 'unknown', } }); final AndroidCamera camera = AndroidCamera(); // Act final int cameraId = await camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0), ResolutionPreset.high, ); // Assert expect(cameraMockChannel.log, [ isMethodCall( 'create', arguments: { 'cameraName': 'Test', 'resolutionPreset': 'high', 'enableAudio': false }, ), ]); expect(cameraId, 1); }); test('Should throw CameraException when create throws a PlatformException', () { // Arrange MethodChannelMock(channelName: _channelName, methods: { 'create': PlatformException( code: 'TESTING_ERROR_CODE', message: 'Mock error message used during testing.', ) }); final AndroidCamera camera = AndroidCamera(); // Act expect( () => camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ), throwsA( isA() .having( (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') .having((CameraException e) => e.description, 'description', 'Mock error message used during testing.'), ), ); }); test('Should throw CameraException when create throws a PlatformException', () { // Arrange MethodChannelMock(channelName: _channelName, methods: { 'create': PlatformException( code: 'TESTING_ERROR_CODE', message: 'Mock error message used during testing.', ) }); final AndroidCamera camera = AndroidCamera(); // Act expect( () => camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ), throwsA( isA() .having( (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') .having((CameraException e) => e.description, 'description', 'Mock error message used during testing.'), ), ); }); test( 'Should throw CameraException when initialize throws a PlatformException', () { // Arrange MethodChannelMock( channelName: _channelName, methods: { 'initialize': PlatformException( code: 'TESTING_ERROR_CODE', message: 'Mock error message used during testing.', ) }, ); final AndroidCamera camera = AndroidCamera(); // Act expect( () => camera.initializeCamera(0), throwsA( isA() .having( (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') .having( (CameraException e) => e.description, 'description', 'Mock error message used during testing.', ), ), ); }, ); test('Should send initialization data', () async { // Arrange final MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: _channelName, methods: { 'create': { 'cameraId': 1, 'imageFormatGroup': 'unknown', }, 'initialize': null }); final AndroidCamera camera = AndroidCamera(); final int cameraId = await camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ); // Act final Future initializeFuture = camera.initializeCamera(cameraId); camera.cameraEventStreamController.add(CameraInitializedEvent( cameraId, 1920, 1080, ExposureMode.auto, true, FocusMode.auto, true, )); await initializeFuture; // Assert expect(cameraId, 1); expect(cameraMockChannel.log, [ anything, isMethodCall( 'initialize', arguments: { 'cameraId': 1, 'imageFormatGroup': 'unknown', }, ), ]); }); test('Should send a disposal call on dispose', () async { // Arrange final MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: _channelName, methods: { 'create': {'cameraId': 1}, 'initialize': null, 'dispose': {'cameraId': 1} }); final AndroidCamera camera = AndroidCamera(); final int cameraId = await camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ); final Future initializeFuture = camera.initializeCamera(cameraId); camera.cameraEventStreamController.add(CameraInitializedEvent( cameraId, 1920, 1080, ExposureMode.auto, true, FocusMode.auto, true, )); await initializeFuture; // Act await camera.dispose(cameraId); // Assert expect(cameraId, 1); expect(cameraMockChannel.log, [ anything, anything, isMethodCall( 'dispose', arguments: {'cameraId': 1}, ), ]); }); }); group('Event Tests', () { late AndroidCamera camera; late int cameraId; setUp(() async { MethodChannelMock( channelName: _channelName, methods: { 'create': {'cameraId': 1}, 'initialize': null }, ); camera = AndroidCamera(); cameraId = await camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ); final Future initializeFuture = camera.initializeCamera(cameraId); camera.cameraEventStreamController.add(CameraInitializedEvent( cameraId, 1920, 1080, ExposureMode.auto, true, FocusMode.auto, true, )); await initializeFuture; }); test('Should receive initialized event', () async { // Act final Stream eventStream = camera.onCameraInitialized(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); // Emit test events final CameraInitializedEvent event = CameraInitializedEvent( cameraId, 3840, 2160, ExposureMode.auto, true, FocusMode.auto, true, ); await camera.handleCameraMethodCall( MethodCall('initialized', event.toJson()), cameraId); // Assert expect(await streamQueue.next, event); // Clean up await streamQueue.cancel(); }); test('Should receive resolution changes', () async { // Act final Stream resolutionStream = camera.onCameraResolutionChanged(cameraId); final StreamQueue streamQueue = StreamQueue(resolutionStream); // Emit test events final CameraResolutionChangedEvent fhdEvent = CameraResolutionChangedEvent(cameraId, 1920, 1080); final CameraResolutionChangedEvent uhdEvent = CameraResolutionChangedEvent(cameraId, 3840, 2160); await camera.handleCameraMethodCall( MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); // Assert expect(await streamQueue.next, fhdEvent); expect(await streamQueue.next, uhdEvent); expect(await streamQueue.next, fhdEvent); expect(await streamQueue.next, uhdEvent); // Clean up await streamQueue.cancel(); }); test('Should receive camera closing events', () async { // Act final Stream eventStream = camera.onCameraClosing(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); // Emit test events final CameraClosingEvent event = CameraClosingEvent(cameraId); await camera.handleCameraMethodCall( MethodCall('camera_closing', event.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('camera_closing', event.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('camera_closing', event.toJson()), cameraId); // Assert expect(await streamQueue.next, event); expect(await streamQueue.next, event); expect(await streamQueue.next, event); // Clean up await streamQueue.cancel(); }); test('Should receive camera error events', () async { // Act final Stream errorStream = camera.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(errorStream); // Emit test events final CameraErrorEvent event = CameraErrorEvent(cameraId, 'Error Description'); await camera.handleCameraMethodCall( MethodCall('error', event.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('error', event.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('error', event.toJson()), cameraId); // Assert expect(await streamQueue.next, event); expect(await streamQueue.next, event); expect(await streamQueue.next, event); // Clean up await streamQueue.cancel(); }); test('Should receive device orientation change events', () async { // Act final Stream eventStream = camera.onDeviceOrientationChanged(); final StreamQueue streamQueue = StreamQueue(eventStream); // Emit test events const DeviceOrientationChangedEvent event = DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); for (int i = 0; i < 3; i++) { await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .handlePlatformMessage( AndroidCamera.deviceEventChannelName, const StandardMethodCodec().encodeMethodCall( MethodCall('orientation_changed', event.toJson())), null); } // Assert expect(await streamQueue.next, event); expect(await streamQueue.next, event); expect(await streamQueue.next, event); // Clean up await streamQueue.cancel(); }); }); group('Function Tests', () { late AndroidCamera camera; late int cameraId; setUp(() async { MethodChannelMock( channelName: _channelName, methods: { 'create': {'cameraId': 1}, 'initialize': null }, ); camera = AndroidCamera(); cameraId = await camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ); final Future initializeFuture = camera.initializeCamera(cameraId); camera.cameraEventStreamController.add( CameraInitializedEvent( cameraId, 1920, 1080, ExposureMode.auto, true, FocusMode.auto, true, ), ); await initializeFuture; }); test('Should fetch CameraDescription instances for available cameras', () async { // Arrange final List returnData = [ { 'name': 'Test 1', 'lensFacing': 'front', 'sensorOrientation': 1 }, { 'name': 'Test 2', 'lensFacing': 'back', 'sensorOrientation': 2 } ]; final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'availableCameras': returnData}, ); // Act final List cameras = await camera.availableCameras(); // Assert expect(channel.log, [ isMethodCall('availableCameras', arguments: null), ]); 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: typedData['name']! as String, lensDirection: parseCameraLensDirection(typedData['lensFacing']! as String), sensorOrientation: typedData['sensorOrientation']! as int, ); expect(cameras[i], cameraDescription); } }); test( 'Should throw CameraException when availableCameras throws a PlatformException', () { // Arrange MethodChannelMock(channelName: _channelName, methods: { 'availableCameras': PlatformException( code: 'TESTING_ERROR_CODE', message: 'Mock error message used during testing.', ) }); // Act expect( camera.availableCameras, throwsA( isA() .having( (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') .having((CameraException e) => e.description, 'description', 'Mock error message used during testing.'), ), ); }); test('Should take a picture and return an XFile instance', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'takePicture': '/test/path.jpg'}); // Act final XFile file = await camera.takePicture(cameraId); // Assert expect(channel.log, [ isMethodCall('takePicture', arguments: { 'cameraId': cameraId, }), ]); expect(file.path, '/test/path.jpg'); }); test('Should prepare for video recording', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'prepareForVideoRecording': null}, ); // Act await camera.prepareForVideoRecording(); // Assert expect(channel.log, [ isMethodCall('prepareForVideoRecording', arguments: null), ]); }); test('Should start recording a video', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'startVideoRecording': null}, ); // Act await camera.startVideoRecording(cameraId); // Assert expect(channel.log, [ isMethodCall('startVideoRecording', arguments: { 'cameraId': cameraId, 'maxVideoDuration': null, 'enableStream': false, }), ]); }); test('Should pass maxVideoDuration when starting recording a video', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'startVideoRecording': null}, ); // Act await camera.startVideoRecording( cameraId, maxVideoDuration: const Duration(seconds: 10), ); // Assert expect(channel.log, [ isMethodCall('startVideoRecording', arguments: { 'cameraId': cameraId, 'maxVideoDuration': 10000, 'enableStream': false, }), ]); }); test( 'Should pass enableStream if callback is passed when starting recording a video', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'startVideoRecording': null}, ); // Act await camera.startVideoCapturing( VideoCaptureOptions(cameraId, streamCallback: (CameraImageData imageData) {}), ); // Assert expect(channel.log, [ isMethodCall('startVideoRecording', arguments: { 'cameraId': cameraId, 'maxVideoDuration': null, 'enableStream': true, }), ]); }); test('Should stop a video recording and return the file', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'stopVideoRecording': '/test/path.mp4'}, ); // Act final XFile file = await camera.stopVideoRecording(cameraId); // Assert expect(channel.log, [ isMethodCall('stopVideoRecording', arguments: { 'cameraId': cameraId, }), ]); expect(file.path, '/test/path.mp4'); }); test('Should pause a video recording', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'pauseVideoRecording': null}, ); // Act await camera.pauseVideoRecording(cameraId); // Assert expect(channel.log, [ isMethodCall('pauseVideoRecording', arguments: { 'cameraId': cameraId, }), ]); }); test('Should resume a video recording', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'resumeVideoRecording': null}, ); // Act await camera.resumeVideoRecording(cameraId); // Assert expect(channel.log, [ isMethodCall('resumeVideoRecording', arguments: { 'cameraId': cameraId, }), ]); }); test('Should set the flash mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'setFlashMode': null}, ); // Act await camera.setFlashMode(cameraId, FlashMode.torch); await camera.setFlashMode(cameraId, FlashMode.always); await camera.setFlashMode(cameraId, FlashMode.auto); await camera.setFlashMode(cameraId, FlashMode.off); // Assert expect(channel.log, [ isMethodCall('setFlashMode', arguments: { 'cameraId': cameraId, 'mode': 'torch' }), isMethodCall('setFlashMode', arguments: { 'cameraId': cameraId, 'mode': 'always' }), isMethodCall('setFlashMode', arguments: {'cameraId': cameraId, 'mode': 'auto'}), isMethodCall('setFlashMode', arguments: {'cameraId': cameraId, 'mode': 'off'}), ]); }); test('Should set the exposure mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'setExposureMode': null}, ); // Act await camera.setExposureMode(cameraId, ExposureMode.auto); await camera.setExposureMode(cameraId, ExposureMode.locked); // Assert expect(channel.log, [ isMethodCall('setExposureMode', arguments: {'cameraId': cameraId, 'mode': 'auto'}), isMethodCall('setExposureMode', arguments: { 'cameraId': cameraId, 'mode': 'locked' }), ]); }); test('Should set the exposure point', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'setExposurePoint': null}, ); // Act await camera.setExposurePoint(cameraId, const Point(0.5, 0.5)); await camera.setExposurePoint(cameraId, null); // Assert expect(channel.log, [ isMethodCall('setExposurePoint', arguments: { 'cameraId': cameraId, 'x': 0.5, 'y': 0.5, 'reset': false }), isMethodCall('setExposurePoint', arguments: { 'cameraId': cameraId, 'x': null, 'y': null, 'reset': true }), ]); }); test('Should get the min exposure offset', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'getMinExposureOffset': 2.0}, ); // Act final double minExposureOffset = await camera.getMinExposureOffset(cameraId); // Assert expect(minExposureOffset, 2.0); expect(channel.log, [ isMethodCall('getMinExposureOffset', arguments: { 'cameraId': cameraId, }), ]); }); test('Should get the max exposure offset', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'getMaxExposureOffset': 2.0}, ); // Act final double maxExposureOffset = await camera.getMaxExposureOffset(cameraId); // Assert expect(maxExposureOffset, 2.0); expect(channel.log, [ isMethodCall('getMaxExposureOffset', arguments: { 'cameraId': cameraId, }), ]); }); test('Should get the exposure offset step size', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'getExposureOffsetStepSize': 0.25}, ); // Act final double stepSize = await camera.getExposureOffsetStepSize(cameraId); // Assert expect(stepSize, 0.25); expect(channel.log, [ isMethodCall('getExposureOffsetStepSize', arguments: { 'cameraId': cameraId, }), ]); }); test('Should set the exposure offset', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'setExposureOffset': 0.6}, ); // Act final double actualOffset = await camera.setExposureOffset(cameraId, 0.5); // Assert expect(actualOffset, 0.6); expect(channel.log, [ isMethodCall('setExposureOffset', arguments: { 'cameraId': cameraId, 'offset': 0.5, }), ]); }); test('Should set the focus mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'setFocusMode': null}, ); // Act await camera.setFocusMode(cameraId, FocusMode.auto); await camera.setFocusMode(cameraId, FocusMode.locked); // Assert expect(channel.log, [ isMethodCall('setFocusMode', arguments: {'cameraId': cameraId, 'mode': 'auto'}), isMethodCall('setFocusMode', arguments: { 'cameraId': cameraId, 'mode': 'locked' }), ]); }); test('Should set the exposure point', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'setFocusPoint': null}, ); // Act await camera.setFocusPoint(cameraId, const Point(0.5, 0.5)); await camera.setFocusPoint(cameraId, null); // Assert expect(channel.log, [ isMethodCall('setFocusPoint', arguments: { 'cameraId': cameraId, 'x': 0.5, 'y': 0.5, 'reset': false }), isMethodCall('setFocusPoint', arguments: { 'cameraId': cameraId, 'x': null, 'y': null, 'reset': true }), ]); }); test('Should build a texture widget as preview widget', () async { // Act final Widget widget = camera.buildPreview(cameraId); // Act expect(widget is Texture, isTrue); expect((widget as Texture).textureId, cameraId); }); test('Should throw MissingPluginException when handling unknown method', () { final AndroidCamera camera = AndroidCamera(); expect( () => camera.handleCameraMethodCall( const MethodCall('unknown_method'), 1), throwsA(isA())); }); test('Should get the max zoom level', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'getMaxZoomLevel': 10.0}, ); // Act final double maxZoomLevel = await camera.getMaxZoomLevel(cameraId); // Assert expect(maxZoomLevel, 10.0); expect(channel.log, [ isMethodCall('getMaxZoomLevel', arguments: { 'cameraId': cameraId, }), ]); }); test('Should get the min zoom level', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'getMinZoomLevel': 1.0}, ); // Act final double maxZoomLevel = await camera.getMinZoomLevel(cameraId); // Assert expect(maxZoomLevel, 1.0); expect(channel.log, [ isMethodCall('getMinZoomLevel', arguments: { 'cameraId': cameraId, }), ]); }); test('Should set the zoom level', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'setZoomLevel': null}, ); // Act await camera.setZoomLevel(cameraId, 2.0); // Assert expect(channel.log, [ isMethodCall('setZoomLevel', arguments: {'cameraId': cameraId, 'zoom': 2.0}), ]); }); test('Should throw CameraException when illegal zoom level is supplied', () async { // Arrange MethodChannelMock( channelName: _channelName, methods: { 'setZoomLevel': PlatformException( code: 'ZOOM_ERROR', message: 'Illegal zoom error', ) }, ); // Act & assert expect( () => camera.setZoomLevel(cameraId, -1.0), throwsA(isA() .having((CameraException e) => e.code, 'code', 'ZOOM_ERROR') .having((CameraException e) => e.description, 'description', 'Illegal zoom error'))); }); test('Should lock the capture orientation', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'lockCaptureOrientation': null}, ); // Act await camera.lockCaptureOrientation( cameraId, DeviceOrientation.portraitUp); // Assert expect(channel.log, [ isMethodCall('lockCaptureOrientation', arguments: { 'cameraId': cameraId, 'orientation': 'portraitUp' }), ]); }); test('Should unlock the capture orientation', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'unlockCaptureOrientation': null}, ); // Act await camera.unlockCaptureOrientation(cameraId); // Assert expect(channel.log, [ isMethodCall('unlockCaptureOrientation', arguments: {'cameraId': cameraId}), ]); }); test('Should pause the camera preview', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'pausePreview': null}, ); // Act await camera.pausePreview(cameraId); // Assert expect(channel.log, [ isMethodCall('pausePreview', arguments: {'cameraId': cameraId}), ]); }); test('Should resume the camera preview', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'resumePreview': null}, ); // Act await camera.resumePreview(cameraId); // Assert expect(channel.log, [ isMethodCall('resumePreview', arguments: {'cameraId': cameraId}), ]); }); test('Should start streaming', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: { 'startImageStream': null, 'stopImageStream': null, }, ); // Act final StreamSubscription subscription = camera .onStreamedFrameAvailable(cameraId) .listen((CameraImageData imageData) {}); // Assert expect(channel.log, [ isMethodCall('startImageStream', arguments: null), ]); subscription.cancel(); }); test('Should stop streaming', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: { 'startImageStream': null, 'stopImageStream': null, }, ); // Act final StreamSubscription subscription = camera .onStreamedFrameAvailable(cameraId) .listen((CameraImageData imageData) {}); subscription.cancel(); // Assert expect(channel.log, [ isMethodCall('startImageStream', arguments: null), isMethodCall('stopImageStream', arguments: null), ]); }); }); } /// 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; ================================================ FILE: packages/camera/camera_android/test/method_channel_mock.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; class MethodChannelMock { MethodChannelMock({ required String channelName, this.delay, required this.methods, }) : methodChannel = MethodChannel(channelName) { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(methodChannel, _handler); } final Duration? delay; final MethodChannel methodChannel; final Map methods; final List log = []; Future _handler(MethodCall methodCall) async { log.add(methodCall); if (!methods.containsKey(methodCall.method)) { throw MissingPluginException('No implementation found for method ' '${methodCall.method} on channel ${methodChannel.name}'); } return Future.delayed(delay ?? Duration.zero, () { final dynamic result = methods[methodCall.method]; if (result is Exception) { throw result; } return Future.value(result); }); } } /// 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; ================================================ FILE: packages/camera/camera_android/test/type_conversion_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:camera_android/src/type_conversion.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('CameraImageData can be created', () { final CameraImageData cameraImage = cameraImageFromPlatformData({ 'format': 1, 'height': 1, 'width': 4, 'lensAperture': 1.8, 'sensorExposureTime': 9991324, 'sensorSensitivity': 92.0, 'planes': [ { 'bytes': Uint8List.fromList([1, 2, 3, 4]), 'bytesPerPixel': 1, 'bytesPerRow': 4, 'height': 1, 'width': 4 } ] }); expect(cameraImage.height, 1); expect(cameraImage.width, 4); expect(cameraImage.format.group, ImageFormatGroup.unknown); expect(cameraImage.planes.length, 1); }); test('CameraImageData has ImageFormatGroup.yuv420', () { final CameraImageData cameraImage = cameraImageFromPlatformData({ 'format': 35, 'height': 1, 'width': 4, 'lensAperture': 1.8, 'sensorExposureTime': 9991324, 'sensorSensitivity': 92.0, 'planes': [ { 'bytes': Uint8List.fromList([1, 2, 3, 4]), 'bytesPerPixel': 1, 'bytesPerRow': 4, 'height': 1, 'width': 4 } ] }); expect(cameraImage.format.group, ImageFormatGroup.yuv420); }); } ================================================ FILE: packages/camera/camera_android/test/utils_test.dart ================================================ // Copyright 2013 The Flutter 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/src/utils.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('Utility methods', () { test( 'Should return CameraLensDirection when valid value is supplied when parsing camera lens direction', () { expect( parseCameraLensDirection('back'), CameraLensDirection.back, ); expect( parseCameraLensDirection('front'), CameraLensDirection.front, ); expect( parseCameraLensDirection('external'), CameraLensDirection.external, ); }); test( 'Should throw ArgumentException when invalid value is supplied when parsing camera lens direction', () { expect( () => parseCameraLensDirection('test'), throwsA(isArgumentError), ); }); test('serializeDeviceOrientation() should serialize correctly', () { expect(serializeDeviceOrientation(DeviceOrientation.portraitUp), 'portraitUp'); expect(serializeDeviceOrientation(DeviceOrientation.portraitDown), 'portraitDown'); expect(serializeDeviceOrientation(DeviceOrientation.landscapeRight), 'landscapeRight'); expect(serializeDeviceOrientation(DeviceOrientation.landscapeLeft), 'landscapeLeft'); }); test('deserializeDeviceOrientation() should deserialize correctly', () { expect(deserializeDeviceOrientation('portraitUp'), DeviceOrientation.portraitUp); expect(deserializeDeviceOrientation('portraitDown'), DeviceOrientation.portraitDown); expect(deserializeDeviceOrientation('landscapeRight'), DeviceOrientation.landscapeRight); expect(deserializeDeviceOrientation('landscapeLeft'), DeviceOrientation.landscapeLeft); }); }); } ================================================ FILE: packages/camera/camera_android_camerax/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled. version: revision: 6b04999e4aaa9dfafdcb5ca09e812df7379d9ee5 channel: spellcheck_1_1 project_type: plugin # Tracks metadata for the flutter migrate command migration: platforms: - platform: root create_revision: 6b04999e4aaa9dfafdcb5ca09e812df7379d9ee5 base_revision: 6b04999e4aaa9dfafdcb5ca09e812df7379d9ee5 - platform: android create_revision: 6b04999e4aaa9dfafdcb5ca09e812df7379d9ee5 base_revision: 6b04999e4aaa9dfafdcb5ca09e812df7379d9ee5 # User provided section # List of Local paths (relative to this file) that should be # ignored by the migrate tool. # # Files that are not part of the templates will be ignored by default. unmanaged_files: - 'lib/main.dart' - 'ios/Runner.xcodeproj/project.pbxproj' ================================================ FILE: packages/camera/camera_android_camerax/AUTHORS ================================================ # 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. ================================================ FILE: packages/camera/camera_android_camerax/CHANGELOG.md ================================================ ## NEXT * Creates camera_android_camerax plugin for development. * 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. * Adds Preview and Surface classes, along with other methods needed to implement camera preview. * Adds implementation of availableCameras(). * Implements camera preview, createCamera, initializeCamera, onCameraError, onDeviceOrientationChanged, and onCameraInitialized. * Adds integration test to plugin. ================================================ FILE: packages/camera/camera_android_camerax/LICENSE ================================================ 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. ================================================ FILE: packages/camera/camera_android_camerax/README.md ================================================ # camera_android_camerax An implementation of the camera plugin on Android using CameraX. ================================================ FILE: packages/camera/camera_android_camerax/android/build.gradle ================================================ group 'io.flutter.plugins.camerax' version '1.0' buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.3.0' } } rootProject.allprojects { repositories { google() mavenCentral() } } apply plugin: 'com.android.library' android { // CameraX dependencies require compilation against version 33 or later. compileSdkVersion 33 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { // Many of the CameraX APIs require API 21. minSdkVersion 21 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } testOptions { unitTests.includeAndroidResources = true unitTests.returnDefaultValues = true unitTests.all { testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" outputs.upToDateWhen {false} showStandardStreams = true } } } lintOptions { disable 'AndroidGradlePluginVersion' disable 'GradleDependency' } } dependencies { // CameraX core library using the camera2 implementation must use same version number. 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:5.0.0' testImplementation 'androidx.test:core:1.4.0' testImplementation 'org.robolectric:robolectric:4.8' } ================================================ FILE: packages/camera/camera_android_camerax/android/settings.gradle ================================================ rootProject.name = 'camera_android_camerax' ================================================ FILE: packages/camera/camera_android_camerax/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.camerax; import android.content.Context; import 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. * *

See {@code io.flutter.plugins.camera.MainActivity} for an example. */ public CameraAndroidCameraxPlugin() {} void setUp(BinaryMessenger binaryMessenger, Context context, TextureRegistry textureRegistry) { // Set up instance manager. instanceManager = InstanceManager.open( identifier -> { new GeneratedCameraXLibrary.JavaObjectFlutterApi(binaryMessenger) .dispose(identifier, reply -> {}); }); // Set up Host APIs. GeneratedCameraXLibrary.CameraInfoHostApi.setup( binaryMessenger, new CameraInfoHostApiImpl(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); GeneratedCameraXLibrary.PreviewHostApi.setup( binaryMessenger, new PreviewHostApiImpl(binaryMessenger, instanceManager, textureRegistry)); } @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { pluginBinding = flutterPluginBinding; } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { if (instanceManager != null) { instanceManager.close(); } } // Activity Lifecycle methods: @Override public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) { setUp( pluginBinding.getBinaryMessenger(), pluginBinding.getApplicationContext(), pluginBinding.getTextureRegistry()); updateContext(pluginBinding.getApplicationContext()); processCameraProviderHostApi.setLifecycleOwner( (LifecycleOwner) activityPluginBinding.getActivity()); systemServicesHostApi.setActivity(activityPluginBinding.getActivity()); systemServicesHostApi.setPermissionsRegistry( activityPluginBinding::addRequestPermissionsResultListener); } @Override public void onDetachedFromActivityForConfigChanges() { updateContext(pluginBinding.getApplicationContext()); } @Override public void onReattachedToActivityForConfigChanges( @NonNull ActivityPluginBinding activityPluginBinding) { updateContext(activityPluginBinding.getActivity()); } @Override public void onDetachedFromActivity() { updateContext(pluginBinding.getApplicationContext()); } /** * Updates context that is used to fetch the corresponding instance of a {@code * ProcessCameraProvider}. */ public void updateContext(Context context) { if (processCameraProviderHostApi != null) { processCameraProviderHostApi.setContext(context); } } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraFlutterApiImpl.java ================================================ // Copyright 2013 The Flutter 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); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoFlutterApiImpl.java ================================================ // Copyright 2013 The Flutter 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.CameraInfo; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraInfoFlutterApi; public class CameraInfoFlutterApiImpl extends CameraInfoFlutterApi { private final InstanceManager instanceManager; public CameraInfoFlutterApiImpl( BinaryMessenger binaryMessenger, InstanceManager instanceManager) { super(binaryMessenger); this.instanceManager = instanceManager; } void create(CameraInfo cameraInfo, Reply reply) { create(instanceManager.addHostCreatedInstance(cameraInfo), reply); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraInfoHostApiImpl.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.camerax; import androidx.annotation.NonNull; import androidx.camera.core.CameraInfo; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraInfoHostApi; import java.util.Objects; public class CameraInfoHostApiImpl implements CameraInfoHostApi { private final InstanceManager instanceManager; public CameraInfoHostApiImpl(InstanceManager instanceManager) { this.instanceManager = instanceManager; } @Override public Long getSensorRotationDegrees(@NonNull Long identifier) { CameraInfo cameraInfo = (CameraInfo) Objects.requireNonNull(instanceManager.getInstance(identifier)); return Long.valueOf(cameraInfo.getSensorRotationDegrees()); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraPermissionsManager.java ================================================ // Copyright 2013 The Flutter 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; } } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorFlutterApiImpl.java ================================================ // Copyright 2013 The Flutter 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.CameraSelector; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.CameraSelectorFlutterApi; public class CameraSelectorFlutterApiImpl extends CameraSelectorFlutterApi { private final InstanceManager instanceManager; public CameraSelectorFlutterApiImpl( BinaryMessenger binaryMessenger, InstanceManager instanceManager) { super(binaryMessenger); this.instanceManager = instanceManager; } void create(CameraSelector cameraSelector, Long lensFacing, Reply reply) { create(instanceManager.addHostCreatedInstance(cameraSelector), lensFacing, reply); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraSelectorHostApiImpl.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.camerax; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.camera.core.CameraInfo; import androidx.camera.core.CameraSelector; import io.flutter.plugin.common.BinaryMessenger; 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; private final InstanceManager instanceManager; @VisibleForTesting public CameraXProxy cameraXProxy = new CameraXProxy(); public CameraSelectorHostApiImpl( BinaryMessenger binaryMessenger, InstanceManager instanceManager) { this.binaryMessenger = binaryMessenger; this.instanceManager = instanceManager; } @Override public void create(@NonNull Long identifier, Long lensFacing) { CameraSelector.Builder cameraSelectorBuilder = cameraXProxy.createCameraSelectorBuilder(); CameraSelector cameraSelector; if (lensFacing != null) { cameraSelector = cameraSelectorBuilder.requireLensFacing(Math.toIntExact(lensFacing)).build(); } else { cameraSelector = cameraSelectorBuilder.build(); } instanceManager.addDartCreatedInstance(cameraSelector, identifier); } @Override public List filter(@NonNull Long identifier, @NonNull List cameraInfoIds) { CameraSelector cameraSelector = (CameraSelector) Objects.requireNonNull(instanceManager.getInstance(identifier)); List cameraInfosForFilter = new ArrayList(); for (Number cameraInfoAsNumber : cameraInfoIds) { Long cameraInfoId = cameraInfoAsNumber.longValue(); CameraInfo cameraInfo = (CameraInfo) Objects.requireNonNull(instanceManager.getInstance(cameraInfoId)); cameraInfosForFilter.add(cameraInfo); } List filteredCameraInfos = cameraSelector.filter(cameraInfosForFilter); List filteredCameraInfosIds = new ArrayList(); for (CameraInfo cameraInfo : filteredCameraInfos) { Long filteredCameraInfoId = instanceManager.getIdentifierForStrongReference(cameraInfo); filteredCameraInfosIds.add(filteredCameraInfoId); } return filteredCameraInfosIds; } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java ================================================ // Copyright 2013 The Flutter 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.graphics.SurfaceTexture; import android.view.Surface; import androidx.annotation.NonNull; import androidx.camera.core.CameraSelector; import androidx.camera.core.Preview; import io.flutter.plugin.common.BinaryMessenger; /** Utility class used to create CameraX-related objects primarily for testing purposes. */ public class CameraXProxy { public CameraSelector.Builder createCameraSelectorBuilder() { return new CameraSelector.Builder(); } public CameraPermissionsManager createCameraPermissionsManager() { return new CameraPermissionsManager(); } public DeviceOrientationManager createDeviceOrientationManager( @NonNull Activity activity, @NonNull Boolean isFrontFacing, @NonNull int sensorOrientation, @NonNull DeviceOrientationManager.DeviceOrientationChangeCallback callback) { return new DeviceOrientationManager(activity, isFrontFacing, sensorOrientation, callback); } public Preview.Builder createPreviewBuilder() { return new Preview.Builder(); } public Surface createSurface(@NonNull SurfaceTexture surfaceTexture) { return new Surface(surfaceTexture); } /** * Creates an instance of the {@code SystemServicesFlutterApiImpl}. * *

Included in this class to utilize the callback methods it provides, e.g. {@code * onCameraError(String)}. */ public SystemServicesFlutterApiImpl createSystemServicesFlutterApiImpl( @NonNull BinaryMessenger binaryMessenger) { return new SystemServicesFlutterApiImpl(binaryMessenger); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java ================================================ // Copyright 2013 The Flutter 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(); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java ================================================ // Copyright 2013 The Flutter 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.2.9), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.camerax; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.plugin.common.BasicMessageChannel; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MessageCodec; import io.flutter.plugin.common.StandardMessageCodec; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; /** Generated class from Pigeon. */ @SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"}) public class GeneratedCameraXLibrary { /** Generated class from Pigeon that represents data sent in messages. */ public static class ResolutionInfo { private @NonNull Long width; public @NonNull Long getWidth() { return width; } public void setWidth(@NonNull Long setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"width\" is null."); } this.width = setterArg; } private @NonNull Long height; public @NonNull Long getHeight() { return height; } public void setHeight(@NonNull Long setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"height\" is null."); } this.height = setterArg; } /** Constructor is private to enforce null safety; use Builder. */ private ResolutionInfo() {} public static final class Builder { private @Nullable Long width; public @NonNull Builder setWidth(@NonNull Long setterArg) { this.width = setterArg; return this; } private @Nullable Long height; public @NonNull Builder setHeight(@NonNull Long setterArg) { this.height = setterArg; return this; } public @NonNull ResolutionInfo build() { ResolutionInfo pigeonReturn = new ResolutionInfo(); pigeonReturn.setWidth(width); pigeonReturn.setHeight(height); return pigeonReturn; } } @NonNull Map toMap() { Map toMapResult = new HashMap<>(); toMapResult.put("width", width); toMapResult.put("height", height); return toMapResult; } static @NonNull ResolutionInfo fromMap(@NonNull Map map) { ResolutionInfo pigeonResult = new ResolutionInfo(); Object width = map.get("width"); pigeonResult.setWidth( (width == null) ? null : ((width instanceof Integer) ? (Integer) width : (Long) width)); Object height = map.get("height"); pigeonResult.setHeight( (height == null) ? null : ((height instanceof Integer) ? (Integer) height : (Long) height)); return pigeonResult; } } /** 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); void error(Throwable error); } private static class JavaObjectHostApiCodec extends StandardMessageCodec { public static final JavaObjectHostApiCodec INSTANCE = new JavaObjectHostApiCodec(); private JavaObjectHostApiCodec() {} } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface JavaObjectHostApi { void dispose(@NonNull Long identifier); /** The codec used by JavaObjectHostApi. */ static MessageCodec getCodec() { return JavaObjectHostApiCodec.INSTANCE; } /** * Sets up an instance of `JavaObjectHostApi` to handle messages through the `binaryMessenger`. */ static void setup(BinaryMessenger binaryMessenger, JavaObjectHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.JavaObjectHostApi.dispose", 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.dispose((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); } } } } private static class JavaObjectFlutterApiCodec extends StandardMessageCodec { public static final JavaObjectFlutterApiCodec INSTANCE = new JavaObjectFlutterApiCodec(); private JavaObjectFlutterApiCodec() {} } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class JavaObjectFlutterApi { private final BinaryMessenger binaryMessenger; public JavaObjectFlutterApi(BinaryMessenger argBinaryMessenger) { this.binaryMessenger = argBinaryMessenger; } public interface Reply { void reply(T reply); } static MessageCodec getCodec() { return JavaObjectFlutterApiCodec.INSTANCE; } public void dispose(@NonNull Long identifierArg, Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.JavaObjectFlutterApi.dispose", getCodec()); channel.send( new ArrayList(Arrays.asList(identifierArg)), channelReply -> { callback.reply(null); }); } } private static class CameraInfoHostApiCodec extends StandardMessageCodec { public static final CameraInfoHostApiCodec INSTANCE = new CameraInfoHostApiCodec(); private CameraInfoHostApiCodec() {} } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface CameraInfoHostApi { @NonNull Long getSensorRotationDegrees(@NonNull Long identifier); /** The codec used by CameraInfoHostApi. */ static MessageCodec getCodec() { return CameraInfoHostApiCodec.INSTANCE; } /** * Sets up an instance of `CameraInfoHostApi` to handle messages through the `binaryMessenger`. */ static void setup(BinaryMessenger binaryMessenger, CameraInfoHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.CameraInfoHostApi.getSensorRotationDegrees", 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."); } Long output = api.getSensorRotationDegrees( (identifierArg == null) ? null : identifierArg.longValue()); wrapped.put("result", output); } catch (Error | RuntimeException exception) { wrapped.put("error", wrapError(exception)); } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } } } private static class CameraInfoFlutterApiCodec extends StandardMessageCodec { public static final CameraInfoFlutterApiCodec INSTANCE = new CameraInfoFlutterApiCodec(); private CameraInfoFlutterApiCodec() {} } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class CameraInfoFlutterApi { private final BinaryMessenger binaryMessenger; public CameraInfoFlutterApi(BinaryMessenger argBinaryMessenger) { this.binaryMessenger = argBinaryMessenger; } public interface Reply { void reply(T reply); } static MessageCodec getCodec() { return CameraInfoFlutterApiCodec.INSTANCE; } public void create(@NonNull Long identifierArg, Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.CameraInfoFlutterApi.create", getCodec()); channel.send( new ArrayList(Arrays.asList(identifierArg)), channelReply -> { callback.reply(null); }); } } private static class CameraSelectorHostApiCodec extends StandardMessageCodec { public static final CameraSelectorHostApiCodec INSTANCE = new CameraSelectorHostApiCodec(); private CameraSelectorHostApiCodec() {} } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface CameraSelectorHostApi { void create(@NonNull Long identifier, @Nullable Long lensFacing); @NonNull List filter(@NonNull Long identifier, @NonNull List cameraInfoIds); /** The codec used by CameraSelectorHostApi. */ static MessageCodec getCodec() { return CameraSelectorHostApiCodec.INSTANCE; } /** * Sets up an instance of `CameraSelectorHostApi` to handle messages through the * `binaryMessenger`. */ static void setup(BinaryMessenger binaryMessenger, CameraSelectorHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.CameraSelectorHostApi.create", 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 lensFacingArg = (Number) args.get(1); api.create( (identifierArg == null) ? null : identifierArg.longValue(), (lensFacingArg == null) ? null : lensFacingArg.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.CameraSelectorHostApi.filter", 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 cameraInfoIdsArg = (List) args.get(1); if (cameraInfoIdsArg == null) { throw new NullPointerException("cameraInfoIdsArg unexpectedly null."); } List output = api.filter( (identifierArg == null) ? null : identifierArg.longValue(), cameraInfoIdsArg); wrapped.put("result", output); } catch (Error | RuntimeException exception) { wrapped.put("error", wrapError(exception)); } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } } } private static class CameraSelectorFlutterApiCodec extends StandardMessageCodec { public static final CameraSelectorFlutterApiCodec INSTANCE = new CameraSelectorFlutterApiCodec(); private CameraSelectorFlutterApiCodec() {} } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class CameraSelectorFlutterApi { private final BinaryMessenger binaryMessenger; public CameraSelectorFlutterApi(BinaryMessenger argBinaryMessenger) { this.binaryMessenger = argBinaryMessenger; } public interface Reply { void reply(T reply); } static MessageCodec getCodec() { return CameraSelectorFlutterApiCodec.INSTANCE; } public void create( @NonNull Long identifierArg, @Nullable Long lensFacingArg, Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.CameraSelectorFlutterApi.create", getCodec()); channel.send( new ArrayList(Arrays.asList(identifierArg, lensFacingArg)), channelReply -> { callback.reply(null); }); } } private static class ProcessCameraProviderHostApiCodec extends StandardMessageCodec { public static final ProcessCameraProviderHostApiCodec INSTANCE = new ProcessCameraProviderHostApiCodec(); private ProcessCameraProviderHostApiCodec() {} } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface ProcessCameraProviderHostApi { void getInstance(Result result); @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; } /** * Sets up an instance of `ProcessCameraProviderHostApi` to handle messages through the * `binaryMessenger`. */ static void setup(BinaryMessenger binaryMessenger, ProcessCameraProviderHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.ProcessCameraProviderHostApi.getInstance", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { Result resultCallback = new Result() { public void success(Long result) { wrapped.put("result", result); reply.reply(wrapped); } public void error(Throwable error) { wrapped.put("error", wrapError(error)); reply.reply(wrapped); } }; api.getInstance(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.ProcessCameraProviderHostApi.getAvailableCameraInfos", 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 output = api.getAvailableCameraInfos( (identifierArg == null) ? null : identifierArg.longValue()); 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.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); } } } } private static class ProcessCameraProviderFlutterApiCodec extends StandardMessageCodec { public static final ProcessCameraProviderFlutterApiCodec INSTANCE = new ProcessCameraProviderFlutterApiCodec(); private ProcessCameraProviderFlutterApiCodec() {} } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class ProcessCameraProviderFlutterApi { private final BinaryMessenger binaryMessenger; public ProcessCameraProviderFlutterApi(BinaryMessenger argBinaryMessenger) { this.binaryMessenger = argBinaryMessenger; } public interface Reply { void reply(T reply); } static MessageCodec getCodec() { return ProcessCameraProviderFlutterApiCodec.INSTANCE; } public void create(@NonNull Long identifierArg, Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.ProcessCameraProviderFlutterApi.create", getCodec()); channel.send( new ArrayList(Arrays.asList(identifierArg)), channelReply -> { callback.reply(null); }); } } 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); }); } public void onCameraError(@NonNull String errorDescriptionArg, Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.SystemServicesFlutterApi.onCameraError", getCodec()); channel.send( new ArrayList(Arrays.asList(errorDescriptionArg)), channelReply -> { callback.reply(null); }); } } private static class PreviewHostApiCodec extends StandardMessageCodec { public static final PreviewHostApiCodec INSTANCE = new PreviewHostApiCodec(); private PreviewHostApiCodec() {} @Override protected Object readValueOfType(byte type, ByteBuffer buffer) { switch (type) { case (byte) 128: return ResolutionInfo.fromMap((Map) readValue(buffer)); case (byte) 129: return ResolutionInfo.fromMap((Map) readValue(buffer)); default: return super.readValueOfType(type, buffer); } } @Override protected void writeValue(ByteArrayOutputStream stream, Object value) { if (value instanceof ResolutionInfo) { stream.write(128); writeValue(stream, ((ResolutionInfo) value).toMap()); } else if (value instanceof ResolutionInfo) { stream.write(129); writeValue(stream, ((ResolutionInfo) value).toMap()); } else { super.writeValue(stream, value); } } } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface PreviewHostApi { void create( @NonNull Long identifier, @Nullable Long rotation, @Nullable ResolutionInfo targetResolution); @NonNull Long setSurfaceProvider(@NonNull Long identifier); void releaseFlutterSurfaceTexture(); @NonNull ResolutionInfo getResolutionInfo(@NonNull Long identifier); /** The codec used by PreviewHostApi. */ static MessageCodec getCodec() { return PreviewHostApiCodec.INSTANCE; } /** Sets up an instance of `PreviewHostApi` to handle messages through the `binaryMessenger`. */ static void setup(BinaryMessenger binaryMessenger, PreviewHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.PreviewHostApi.create", 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 rotationArg = (Number) args.get(1); ResolutionInfo targetResolutionArg = (ResolutionInfo) args.get(2); api.create( (identifierArg == null) ? null : identifierArg.longValue(), (rotationArg == null) ? null : rotationArg.longValue(), targetResolutionArg); 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.PreviewHostApi.setSurfaceProvider", 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."); } Long output = api.setSurfaceProvider( (identifierArg == null) ? null : identifierArg.longValue()); 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.PreviewHostApi.releaseFlutterSurfaceTexture", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { api.releaseFlutterSurfaceTexture(); 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.PreviewHostApi.getResolutionInfo", 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."); } ResolutionInfo output = api.getResolutionInfo( (identifierArg == null) ? null : identifierArg.longValue()); wrapped.put("result", output); } catch (Error | RuntimeException exception) { wrapped.put("error", wrapError(exception)); } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } } } private static Map wrapError(Throwable exception) { Map errorMap = new HashMap<>(); errorMap.put("message", exception.toString()); errorMap.put("code", exception.getClass().getSimpleName()); errorMap.put( "details", "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); return errorMap; } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/InstanceManager.java ================================================ // Copyright 2013 The Flutter 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.os.Handler; import android.os.Looper; import androidx.annotation.Nullable; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.WeakHashMap; /** * Maintains instances used to communicate with the corresponding objects in Dart. * *

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

Added instances are added as a weak reference and a strong reference. When the strong * reference is removed with `{@link #remove(long)}` and the weak reference is deallocated, the * `finalizationListener` is made with the instance's identifier. However, if the strong reference * is removed and then the identifier is retrieved with the intention to pass the identifier to Dart * (e.g. calling {@link #getIdentifierForStrongReference(Object)}), the strong reference to the * instance is recreated. The strong reference will then need to be removed manually again. */ @SuppressWarnings("unchecked") public class InstanceManager { // Identifiers are locked to a specific range to avoid collisions with objects // created simultaneously from Dart. // Host uses identifiers >= 2^16 and Dart is expected to use values n where, // 0 <= n < 2^16. private static final long MIN_HOST_CREATED_IDENTIFIER = 65536; private static final long CLEAR_FINALIZED_WEAK_REFERENCES_INTERVAL = 30000; /** Interface for listening when a weak reference of an instance is removed from the manager. */ public interface FinalizationListener { void onFinalize(long identifier); } private final WeakHashMap identifiers = new WeakHashMap<>(); private final HashMap> weakInstances = new HashMap<>(); private final HashMap strongInstances = new HashMap<>(); private final ReferenceQueue referenceQueue = new ReferenceQueue<>(); private final HashMap, Long> weakReferencesToIdentifiers = new HashMap<>(); private final Handler handler = new Handler(Looper.getMainLooper()); private final FinalizationListener finalizationListener; private long nextIdentifier = MIN_HOST_CREATED_IDENTIFIER; private boolean isClosed = false; /** * Instantiate a new manager. * *

When the manager is no longer needed, {@link #close()} must be called. * * @param finalizationListener the listener for garbage collected weak references. * @return a new `InstanceManager`. */ public static InstanceManager open(FinalizationListener finalizationListener) { return new InstanceManager(finalizationListener); } private InstanceManager(FinalizationListener finalizationListener) { this.finalizationListener = finalizationListener; handler.postDelayed( this::releaseAllFinalizedInstances, CLEAR_FINALIZED_WEAK_REFERENCES_INTERVAL); } /** * Removes `identifier` and its associated strongly referenced instance, if present, from the * manager. * * @param identifier the identifier paired to an instance. * @param the expected return type. * @return the removed instance if the manager contains the given identifier, otherwise null. */ @Nullable public T remove(long identifier) { assertManagerIsNotClosed(); return (T) strongInstances.remove(identifier); } /** * Retrieves the identifier paired with an instance. * *

If the manager contains `instance`, as a strong or weak reference, the strong reference to * `instance` will be recreated and will need to be removed again with {@link #remove(long)}. * * @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. */ @Nullable public Long getIdentifierForStrongReference(Object instance) { assertManagerIsNotClosed(); final Long identifier = identifiers.get(instance); if (identifier != null) { strongInstances.put(identifier, instance); } return identifier; } /** * Adds a new instance that was instantiated from Dart. * *

If an instance or identifier has already been added, it will be replaced by the new values. * The Dart InstanceManager is considered the source of truth and has the capability to overwrite * stored pairs in response to hot restarts. * * @param instance the instance to be stored. * @param identifier the identifier to be paired with instance. This value must be >= 0. */ public void addDartCreatedInstance(Object instance, long identifier) { assertManagerIsNotClosed(); addInstance(instance, identifier); } /** * Adds a new instance that was instantiated from the host platform. * *

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(); final long identifier = nextIdentifier++; addInstance(instance, identifier); return identifier; } /** * Retrieves the instance associated with identifier. * * @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. */ @Nullable public T getInstance(long identifier) { assertManagerIsNotClosed(); final WeakReference instance = (WeakReference) weakInstances.get(identifier); if (instance != null) { return instance.get(); } return (T) strongInstances.get(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`. */ public boolean containsInstance(Object instance) { assertManagerIsNotClosed(); 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. */ public void close() { handler.removeCallbacks(this::releaseAllFinalizedInstances); isClosed = true; } private void releaseAllFinalizedInstances() { WeakReference reference; while ((reference = (WeakReference) referenceQueue.poll()) != null) { final Long identifier = weakReferencesToIdentifiers.remove(reference); if (identifier != null) { weakInstances.remove(identifier); strongInstances.remove(identifier); finalizationListener.onFinalize(identifier); } } handler.postDelayed( this::releaseAllFinalizedInstances, CLEAR_FINALIZED_WEAK_REFERENCES_INTERVAL); } private void addInstance(Object instance, long identifier) { if (identifier < 0) { throw new IllegalArgumentException("Identifier must be >= 0."); } final WeakReference weakReference = new WeakReference<>(instance, referenceQueue); identifiers.put(instance, identifier); weakInstances.put(identifier, weakReference); weakReferencesToIdentifiers.put(weakReference, identifier); strongInstances.put(identifier, instance); } private void assertManagerIsNotClosed() { if (isClosed) { throw new AssertionError("Manager has already been closed."); } } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/JavaObjectHostApiImpl.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.camerax; import androidx.annotation.NonNull; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.JavaObjectHostApi; /** * A pigeon Host API implementation that handles creating {@link Object}s and invoking its static * and instance methods. * *

{@link Object} instances created by {@link JavaObjectHostApiImpl} are used to intercommunicate * with a paired Dart object. */ public class JavaObjectHostApiImpl implements JavaObjectHostApi { private final InstanceManager instanceManager; /** * Constructs a {@link JavaObjectHostApiImpl}. * * @param instanceManager maintains instances stored to communicate with Dart objects */ public JavaObjectHostApiImpl(InstanceManager instanceManager) { this.instanceManager = instanceManager; } @Override public void dispose(@NonNull Long identifier) { instanceManager.remove(identifier); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/PreviewHostApiImpl.java ================================================ // Copyright 2013 The Flutter 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.graphics.SurfaceTexture; import android.util.Size; import android.view.Surface; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.camera.core.Preview; import androidx.camera.core.SurfaceRequest; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.PreviewHostApi; import io.flutter.view.TextureRegistry; import java.util.Objects; import java.util.concurrent.Executors; public class PreviewHostApiImpl implements PreviewHostApi { private final BinaryMessenger binaryMessenger; private final InstanceManager instanceManager; private final TextureRegistry textureRegistry; @VisibleForTesting public CameraXProxy cameraXProxy = new CameraXProxy(); @VisibleForTesting public TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture; public PreviewHostApiImpl( @NonNull BinaryMessenger binaryMessenger, @NonNull InstanceManager instanceManager, @NonNull TextureRegistry textureRegistry) { this.binaryMessenger = binaryMessenger; this.instanceManager = instanceManager; this.textureRegistry = textureRegistry; } /** Creates a {@link Preview} with the target rotation and resolution if specified. */ @Override public void create( @NonNull Long identifier, @Nullable Long rotation, @Nullable GeneratedCameraXLibrary.ResolutionInfo targetResolution) { Preview.Builder previewBuilder = cameraXProxy.createPreviewBuilder(); if (rotation != null) { previewBuilder.setTargetRotation(rotation.intValue()); } if (targetResolution != null) { previewBuilder.setTargetResolution( new Size( targetResolution.getWidth().intValue(), targetResolution.getHeight().intValue())); } Preview preview = previewBuilder.build(); instanceManager.addDartCreatedInstance(preview, identifier); } /** * Sets the {@link Preview.SurfaceProvider} that will be used to provide a {@code Surface} backed * by a Flutter {@link TextureRegistry.SurfaceTextureEntry} used to build the {@link Preview}. */ @Override public Long setSurfaceProvider(@NonNull Long identifier) { Preview preview = (Preview) Objects.requireNonNull(instanceManager.getInstance(identifier)); flutterSurfaceTexture = textureRegistry.createSurfaceTexture(); SurfaceTexture surfaceTexture = flutterSurfaceTexture.surfaceTexture(); Preview.SurfaceProvider surfaceProvider = createSurfaceProvider(surfaceTexture); preview.setSurfaceProvider(surfaceProvider); return flutterSurfaceTexture.id(); } /** * Creates a {@link Preview.SurfaceProvider} that specifies how to provide a {@link Surface} to a * {@code Preview} that is backed by a Flutter {@link TextureRegistry.SurfaceTextureEntry}. */ @VisibleForTesting public Preview.SurfaceProvider createSurfaceProvider(@NonNull SurfaceTexture surfaceTexture) { return new Preview.SurfaceProvider() { @Override public void onSurfaceRequested(SurfaceRequest request) { surfaceTexture.setDefaultBufferSize( request.getResolution().getWidth(), request.getResolution().getHeight()); Surface flutterSurface = cameraXProxy.createSurface(surfaceTexture); request.provideSurface( flutterSurface, Executors.newSingleThreadExecutor(), (result) -> { // See https://developer.android.com/reference/androidx/camera/core/SurfaceRequest.Result for documentation. // Always attempt a release. flutterSurface.release(); int resultCode = result.getResultCode(); switch (resultCode) { case SurfaceRequest.Result.RESULT_REQUEST_CANCELLED: case SurfaceRequest.Result.RESULT_WILL_NOT_PROVIDE_SURFACE: case SurfaceRequest.Result.RESULT_SURFACE_ALREADY_PROVIDED: case SurfaceRequest.Result.RESULT_SURFACE_USED_SUCCESSFULLY: // Only need to release, do nothing. break; case SurfaceRequest.Result.RESULT_INVALID_SURFACE: // Intentional fall through. default: // Release and send error. SystemServicesFlutterApiImpl systemServicesFlutterApi = cameraXProxy.createSystemServicesFlutterApiImpl(binaryMessenger); systemServicesFlutterApi.sendCameraError( getProvideSurfaceErrorDescription(resultCode), reply -> {}); break; } }); }; }; } /** * Returns an error description for each {@link SurfaceRequest.Result} that represents an error * with providing a surface. */ private String getProvideSurfaceErrorDescription(@Nullable int resultCode) { switch (resultCode) { case SurfaceRequest.Result.RESULT_INVALID_SURFACE: return resultCode + ": Provided surface could not be used by the camera."; default: return resultCode + ": Attempt to provide a surface resulted with unrecognizable code."; } } /** * Releases the Flutter {@link TextureRegistry.SurfaceTextureEntry} if used to provide a surface * for a {@link Preview}. */ @Override public void releaseFlutterSurfaceTexture() { if (flutterSurfaceTexture != null) { flutterSurfaceTexture.release(); } } /** Returns the resolution information for the specified {@link Preview}. */ @Override public GeneratedCameraXLibrary.ResolutionInfo getResolutionInfo(@NonNull Long identifier) { Preview preview = (Preview) Objects.requireNonNull(instanceManager.getInstance(identifier)); Size resolution = preview.getResolutionInfo().getResolution(); GeneratedCameraXLibrary.ResolutionInfo.Builder resolutionInfo = new GeneratedCameraXLibrary.ResolutionInfo.Builder() .setWidth(Long.valueOf(resolution.getWidth())) .setHeight(Long.valueOf(resolution.getHeight())); return resolutionInfo.build(); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderFlutterApiImpl.java ================================================ // Copyright 2013 The Flutter 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.lifecycle.ProcessCameraProvider; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ProcessCameraProviderFlutterApi; public class ProcessCameraProviderFlutterApiImpl extends ProcessCameraProviderFlutterApi { public ProcessCameraProviderFlutterApiImpl( BinaryMessenger binaryMessenger, InstanceManager instanceManager) { super(binaryMessenger); this.instanceManager = instanceManager; } private final InstanceManager instanceManager; void create(ProcessCameraProvider processCameraProvider, Reply reply) { create(instanceManager.addHostCreatedInstance(processCameraProvider), reply); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/ProcessCameraProviderHostApiImpl.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.camerax; import android.content.Context; import 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) { this.binaryMessenger = binaryMessenger; this.instanceManager = instanceManager; 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. * *

If using the camera plugin in an add-to-app context, ensure that a new instance of the * {@code ProcessCameraProvider} is fetched via {@code #getInstance} anytime the context changes. */ public void setContext(Context context) { this.context = 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) { ListenableFuture processCameraProviderFuture = ProcessCameraProvider.getInstance(context); processCameraProviderFuture.addListener( () -> { try { // Camera provider is now guaranteed to be available. ProcessCameraProvider processCameraProvider = processCameraProviderFuture.get(); final ProcessCameraProviderFlutterApiImpl flutterApi = new ProcessCameraProviderFlutterApiImpl(binaryMessenger, instanceManager); if (!instanceManager.containsInstance(processCameraProvider)) { flutterApi.create(processCameraProvider, reply -> {}); } result.success(instanceManager.getIdentifierForStrongReference(processCameraProvider)); } catch (Exception e) { result.error(e); } }, ContextCompat.getMainExecutor(context)); } /** Returns cameras available to the {@code ProcessCameraProvider}. */ @Override public List getAvailableCameraInfos(@NonNull Long identifier) { ProcessCameraProvider processCameraProvider = (ProcessCameraProvider) Objects.requireNonNull(instanceManager.getInstance(identifier)); List availableCameras = processCameraProvider.getAvailableCameraInfos(); List availableCamerasIds = new ArrayList(); final CameraInfoFlutterApiImpl cameraInfoFlutterApi = new CameraInfoFlutterApiImpl(binaryMessenger, instanceManager); for (CameraInfo cameraInfo : availableCameras) { 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(); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesFlutterApiImpl.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.camerax; import androidx.annotation.NonNull; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.SystemServicesFlutterApi; public class SystemServicesFlutterApiImpl extends SystemServicesFlutterApi { public SystemServicesFlutterApiImpl(@NonNull BinaryMessenger binaryMessenger) { super(binaryMessenger); } public void sendDeviceOrientationChangedEvent( @NonNull String orientation, @NonNull Reply reply) { super.onDeviceOrientationChanged(orientation, reply); } public void sendCameraError(@NonNull String errorDescription, @NonNull Reply reply) { super.onCameraError(errorDescription, reply); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/SystemServicesHostApiImpl.java ================================================ // Copyright 2013 The Flutter 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); } 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.sendDeviceOrientationChangedEvent( 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(); } } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraInfoTest.java ================================================ // Copyright 2013 The Flutter 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.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import androidx.camera.core.CameraInfo; import io.flutter.plugin.common.BinaryMessenger; import java.util.Objects; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; public class CameraInfoTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock public CameraInfo mockCameraInfo; @Mock public BinaryMessenger mockBinaryMessenger; InstanceManager testInstanceManager; @Before public void setUp() { testInstanceManager = InstanceManager.open(identifier -> {}); } @After public void tearDown() { testInstanceManager.close(); } @Test public void getSensorRotationDegreesTest() { final CameraInfoHostApiImpl cameraInfoHostApi = new CameraInfoHostApiImpl(testInstanceManager); testInstanceManager.addDartCreatedInstance(mockCameraInfo, 1); when(mockCameraInfo.getSensorRotationDegrees()).thenReturn(90); assertEquals((long) cameraInfoHostApi.getSensorRotationDegrees(1L), 90L); verify(mockCameraInfo).getSensorRotationDegrees(); } @Test public void flutterApiCreateTest() { final CameraInfoFlutterApiImpl spyFlutterApi = spy(new CameraInfoFlutterApiImpl(mockBinaryMessenger, testInstanceManager)); spyFlutterApi.create(mockCameraInfo, reply -> {}); final long identifier = Objects.requireNonNull(testInstanceManager.getIdentifierForStrongReference(mockCameraInfo)); verify(spyFlutterApi).create(eq(identifier), any()); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraPermissionsManagerTest.java ================================================ // Copyright 2013 The Flutter 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."); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraSelectorTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.camerax; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import androidx.camera.core.CameraInfo; import androidx.camera.core.CameraSelector; import io.flutter.plugin.common.BinaryMessenger; import java.util.Arrays; import java.util.List; 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 CameraSelectorTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock public CameraSelector mockCameraSelector; @Mock public BinaryMessenger mockBinaryMessenger; InstanceManager testInstanceManager; @Before public void setUp() { testInstanceManager = InstanceManager.open(identifier -> {}); } @After public void tearDown() { testInstanceManager.close(); } @Test public void createTest() { final CameraSelectorHostApiImpl cameraSelectorHostApi = new CameraSelectorHostApiImpl(mockBinaryMessenger, testInstanceManager); final CameraXProxy mockCameraXProxy = mock(CameraXProxy.class); final CameraSelector.Builder mockCameraSelectorBuilder = mock(CameraSelector.Builder.class); cameraSelectorHostApi.cameraXProxy = mockCameraXProxy; when(mockCameraXProxy.createCameraSelectorBuilder()).thenReturn(mockCameraSelectorBuilder); when(mockCameraSelectorBuilder.requireLensFacing(1)).thenReturn(mockCameraSelectorBuilder); when(mockCameraSelectorBuilder.build()).thenReturn(mockCameraSelector); cameraSelectorHostApi.create(0L, 1L); verify(mockCameraSelectorBuilder).requireLensFacing(CameraSelector.LENS_FACING_BACK); assertEquals(testInstanceManager.getInstance(0L), mockCameraSelector); } @Test public void filterTest() { final CameraSelectorHostApiImpl cameraSelectorHostApi = new CameraSelectorHostApiImpl(mockBinaryMessenger, testInstanceManager); final CameraInfo cameraInfo = mock(CameraInfo.class); final List cameraInfosForFilter = Arrays.asList(cameraInfo); final List cameraInfosIds = Arrays.asList(1L); testInstanceManager.addDartCreatedInstance(mockCameraSelector, 0); testInstanceManager.addDartCreatedInstance(cameraInfo, 1); when(mockCameraSelector.filter(cameraInfosForFilter)).thenReturn(cameraInfosForFilter); assertEquals( cameraSelectorHostApi.filter(0L, cameraInfosIds), Arrays.asList(testInstanceManager.getIdentifierForStrongReference(cameraInfo))); verify(mockCameraSelector).filter(cameraInfosForFilter); } @Test public void flutterApiCreateTest() { final CameraSelectorFlutterApiImpl spyFlutterApi = spy(new CameraSelectorFlutterApiImpl(mockBinaryMessenger, testInstanceManager)); spyFlutterApi.create(mockCameraSelector, 0L, reply -> {}); final long identifier = Objects.requireNonNull( testInstanceManager.getIdentifierForStrongReference(mockCameraSelector)); verify(spyFlutterApi).create(eq(identifier), eq(0L), any()); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java ================================================ // Copyright 2013 The Flutter 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()); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java ================================================ // Copyright 2013 The Flutter 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); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/InstanceManagerTest.java ================================================ // Copyright 2013 The Flutter 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.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import org.junit.Test; public class InstanceManagerTest { @Test public void addDartCreatedInstance() { final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); final Object object = new Object(); instanceManager.addDartCreatedInstance(object, 0); assertEquals(object, instanceManager.getInstance(0)); assertEquals((Long) 0L, instanceManager.getIdentifierForStrongReference(object)); assertTrue(instanceManager.containsInstance(object)); instanceManager.close(); } @Test public void addHostCreatedInstance() { final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); final Object object = new Object(); long identifier = instanceManager.addHostCreatedInstance(object); assertNotNull(instanceManager.getInstance(identifier)); assertEquals(object, instanceManager.getInstance(identifier)); assertTrue(instanceManager.containsInstance(object)); 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 -> {}); Object object = new Object(); instanceManager.addDartCreatedInstance(object, 0); assertEquals(object, instanceManager.remove(0)); // To allow for object to be garbage collected. //noinspection UnusedAssignment object = null; Runtime.getRuntime().gc(); assertNull(instanceManager.getInstance(0)); instanceManager.close(); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/JavaObjectHostApiTest.java ================================================ // Copyright 2013 The Flutter 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.assertNull; import org.junit.Test; public class JavaObjectHostApiTest { @Test public void dispose() { final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); final JavaObjectHostApiImpl hostApi = new JavaObjectHostApiImpl(instanceManager); Object object = new Object(); instanceManager.addDartCreatedInstance(object, 0); // To free object for garbage collection. //noinspection UnusedAssignment object = null; hostApi.dispose(0L); Runtime.getRuntime().gc(); assertNull(instanceManager.getInstance(0)); instanceManager.close(); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java ================================================ // Copyright 2013 The Flutter 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.Mockito.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.graphics.SurfaceTexture; import android.util.Size; import android.view.Surface; import androidx.camera.core.Preview; import androidx.camera.core.SurfaceRequest; import androidx.core.util.Consumer; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.ResolutionInfo; import io.flutter.plugins.camerax.GeneratedCameraXLibrary.SystemServicesFlutterApi.Reply; import io.flutter.view.TextureRegistry; import java.util.concurrent.Executor; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class PreviewTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock public Preview mockPreview; @Mock public BinaryMessenger mockBinaryMessenger; @Mock public TextureRegistry mockTextureRegistry; @Mock public CameraXProxy mockCameraXProxy; InstanceManager testInstanceManager; @Before public void setUp() { testInstanceManager = spy(InstanceManager.open(identifier -> {})); } @After public void tearDown() { testInstanceManager.close(); } @Test public void create_createsPreviewWithCorrectConfiguration() { final PreviewHostApiImpl previewHostApi = new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry); final Preview.Builder mockPreviewBuilder = mock(Preview.Builder.class); final int targetRotation = 90; final int targetResolutionWidth = 10; final int targetResolutionHeight = 50; final Long previewIdentifier = 3L; final GeneratedCameraXLibrary.ResolutionInfo resolutionInfo = new GeneratedCameraXLibrary.ResolutionInfo.Builder() .setWidth(Long.valueOf(targetResolutionWidth)) .setHeight(Long.valueOf(targetResolutionHeight)) .build(); previewHostApi.cameraXProxy = mockCameraXProxy; when(mockCameraXProxy.createPreviewBuilder()).thenReturn(mockPreviewBuilder); when(mockPreviewBuilder.build()).thenReturn(mockPreview); final ArgumentCaptor sizeCaptor = ArgumentCaptor.forClass(Size.class); previewHostApi.create(previewIdentifier, Long.valueOf(targetRotation), resolutionInfo); verify(mockPreviewBuilder).setTargetRotation(targetRotation); verify(mockPreviewBuilder).setTargetResolution(sizeCaptor.capture()); assertEquals(sizeCaptor.getValue().getWidth(), targetResolutionWidth); assertEquals(sizeCaptor.getValue().getHeight(), targetResolutionHeight); verify(mockPreviewBuilder).build(); verify(testInstanceManager).addDartCreatedInstance(mockPreview, previewIdentifier); } @Test public void setSurfaceProviderTest_createsSurfaceProviderAndReturnsTextureEntryId() { final PreviewHostApiImpl previewHostApi = spy(new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry)); final TextureRegistry.SurfaceTextureEntry mockSurfaceTextureEntry = mock(TextureRegistry.SurfaceTextureEntry.class); final SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class); final Long previewIdentifier = 5L; final Long surfaceTextureEntryId = 120L; previewHostApi.cameraXProxy = mockCameraXProxy; testInstanceManager.addDartCreatedInstance(mockPreview, previewIdentifier); when(mockTextureRegistry.createSurfaceTexture()).thenReturn(mockSurfaceTextureEntry); when(mockSurfaceTextureEntry.surfaceTexture()).thenReturn(mockSurfaceTexture); when(mockSurfaceTextureEntry.id()).thenReturn(surfaceTextureEntryId); final ArgumentCaptor surfaceProviderCaptor = ArgumentCaptor.forClass(Preview.SurfaceProvider.class); final ArgumentCaptor surfaceCaptor = ArgumentCaptor.forClass(Surface.class); final ArgumentCaptor consumerCaptor = ArgumentCaptor.forClass(Consumer.class); // Test that surface provider was set and the surface texture ID was returned. assertEquals(previewHostApi.setSurfaceProvider(previewIdentifier), surfaceTextureEntryId); verify(mockPreview).setSurfaceProvider(surfaceProviderCaptor.capture()); verify(previewHostApi).createSurfaceProvider(mockSurfaceTexture); } @Test public void createSurfaceProvider_createsExpectedPreviewSurfaceProvider() { final PreviewHostApiImpl previewHostApi = new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry); final SurfaceTexture mockSurfaceTexture = mock(SurfaceTexture.class); final Surface mockSurface = mock(Surface.class); final SurfaceRequest mockSurfaceRequest = mock(SurfaceRequest.class); final SurfaceRequest.Result mockSurfaceRequestResult = mock(SurfaceRequest.Result.class); final SystemServicesFlutterApiImpl mockSystemServicesFlutterApi = mock(SystemServicesFlutterApiImpl.class); final int resolutionWidth = 200; final int resolutionHeight = 500; previewHostApi.cameraXProxy = mockCameraXProxy; when(mockCameraXProxy.createSurface(mockSurfaceTexture)).thenReturn(mockSurface); when(mockSurfaceRequest.getResolution()) .thenReturn(new Size(resolutionWidth, resolutionHeight)); when(mockCameraXProxy.createSystemServicesFlutterApiImpl(mockBinaryMessenger)) .thenReturn(mockSystemServicesFlutterApi); final ArgumentCaptor surfaceCaptor = ArgumentCaptor.forClass(Surface.class); final ArgumentCaptor consumerCaptor = ArgumentCaptor.forClass(Consumer.class); Preview.SurfaceProvider previewSurfaceProvider = previewHostApi.createSurfaceProvider(mockSurfaceTexture); previewSurfaceProvider.onSurfaceRequested(mockSurfaceRequest); verify(mockSurfaceTexture).setDefaultBufferSize(resolutionWidth, resolutionHeight); verify(mockSurfaceRequest) .provideSurface(surfaceCaptor.capture(), any(Executor.class), consumerCaptor.capture()); // Test that the surface derived from the surface texture entry will be provided to the surface request. assertEquals(surfaceCaptor.getValue(), mockSurface); // Test that the Consumer used to handle surface request result releases Flutter surface texture appropriately // and sends camera errors appropriately. Consumer capturedConsumer = consumerCaptor.getValue(); // Case where Surface should be released. when(mockSurfaceRequestResult.getResultCode()) .thenReturn(SurfaceRequest.Result.RESULT_REQUEST_CANCELLED); capturedConsumer.accept(mockSurfaceRequestResult); verify(mockSurface).release(); reset(mockSurface); when(mockSurfaceRequestResult.getResultCode()) .thenReturn(SurfaceRequest.Result.RESULT_REQUEST_CANCELLED); capturedConsumer.accept(mockSurfaceRequestResult); verify(mockSurface).release(); reset(mockSurface); when(mockSurfaceRequestResult.getResultCode()) .thenReturn(SurfaceRequest.Result.RESULT_WILL_NOT_PROVIDE_SURFACE); capturedConsumer.accept(mockSurfaceRequestResult); verify(mockSurface).release(); reset(mockSurface); when(mockSurfaceRequestResult.getResultCode()) .thenReturn(SurfaceRequest.Result.RESULT_SURFACE_USED_SUCCESSFULLY); capturedConsumer.accept(mockSurfaceRequestResult); verify(mockSurface).release(); reset(mockSurface); // Case where error must be sent. when(mockSurfaceRequestResult.getResultCode()) .thenReturn(SurfaceRequest.Result.RESULT_INVALID_SURFACE); capturedConsumer.accept(mockSurfaceRequestResult); verify(mockSurface).release(); verify(mockSystemServicesFlutterApi).sendCameraError(anyString(), any(Reply.class)); } @Test public void releaseFlutterSurfaceTexture_makesCallToReleaseFlutterSurfaceTexture() { final PreviewHostApiImpl previewHostApi = new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry); final TextureRegistry.SurfaceTextureEntry mockSurfaceTextureEntry = mock(TextureRegistry.SurfaceTextureEntry.class); previewHostApi.flutterSurfaceTexture = mockSurfaceTextureEntry; previewHostApi.releaseFlutterSurfaceTexture(); verify(mockSurfaceTextureEntry).release(); } @Test public void getResolutionInfo_makesCallToRetrievePreviewResolutionInfo() { final PreviewHostApiImpl previewHostApi = new PreviewHostApiImpl(mockBinaryMessenger, testInstanceManager, mockTextureRegistry); final androidx.camera.core.ResolutionInfo mockResolutionInfo = mock(androidx.camera.core.ResolutionInfo.class); final Long previewIdentifier = 23L; final int resolutionWidth = 500; final int resolutionHeight = 200; testInstanceManager.addDartCreatedInstance(mockPreview, previewIdentifier); when(mockPreview.getResolutionInfo()).thenReturn(mockResolutionInfo); when(mockResolutionInfo.getResolution()) .thenReturn(new Size(resolutionWidth, resolutionHeight)); ResolutionInfo resolutionInfo = previewHostApi.getResolutionInfo(previewIdentifier); assertEquals(resolutionInfo.getWidth(), Long.valueOf(resolutionWidth)); assertEquals(resolutionInfo.getHeight(), Long.valueOf(resolutionHeight)); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProcessCameraProviderTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.camerax; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.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; 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.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class ProcessCameraProviderTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock public ProcessCameraProvider processCameraProvider; @Mock public BinaryMessenger mockBinaryMessenger; InstanceManager testInstanceManager; private Context context; @Before public void setUp() { testInstanceManager = InstanceManager.open(identifier -> {}); context = ApplicationProvider.getApplicationContext(); } @After public void tearDown() { testInstanceManager.close(); } @Test public void getInstanceTest() { final ProcessCameraProviderHostApiImpl processCameraProviderHostApi = new ProcessCameraProviderHostApiImpl(mockBinaryMessenger, testInstanceManager, context); final ListenableFuture processCameraProviderFuture = spy(Futures.immediateFuture(processCameraProvider)); final GeneratedCameraXLibrary.Result mockResult = mock(GeneratedCameraXLibrary.Result.class); testInstanceManager.addDartCreatedInstance(processCameraProvider, 0); try (MockedStatic mockedProcessCameraProvider = Mockito.mockStatic(ProcessCameraProvider.class)) { mockedProcessCameraProvider .when(() -> ProcessCameraProvider.getInstance(context)) .thenAnswer( (Answer>) invocation -> processCameraProviderFuture); final ArgumentCaptor runnableCaptor = ArgumentCaptor.forClass(Runnable.class); processCameraProviderHostApi.getInstance(mockResult); verify(processCameraProviderFuture).addListener(runnableCaptor.capture(), any()); runnableCaptor.getValue().run(); verify(mockResult).success(0L); } } @Test public void getAvailableCameraInfosTest() { final ProcessCameraProviderHostApiImpl processCameraProviderHostApi = new ProcessCameraProviderHostApiImpl(mockBinaryMessenger, testInstanceManager, context); final CameraInfo mockCameraInfo = mock(CameraInfo.class); testInstanceManager.addDartCreatedInstance(processCameraProvider, 0); testInstanceManager.addDartCreatedInstance(mockCameraInfo, 1); when(processCameraProvider.getAvailableCameraInfos()).thenReturn(Arrays.asList(mockCameraInfo)); assertEquals(processCameraProviderHostApi.getAvailableCameraInfos(0L), Arrays.asList(1L)); 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 = spy(new ProcessCameraProviderFlutterApiImpl(mockBinaryMessenger, testInstanceManager)); spyFlutterApi.create(processCameraProvider, reply -> {}); final long identifier = Objects.requireNonNull( testInstanceManager.getIdentifierForStrongReference(processCameraProvider)); verify(spyFlutterApi).create(eq(identifier), any()); } } ================================================ FILE: packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/SystemServicesTest.java ================================================ // Copyright 2013 The Flutter 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) .sendDeviceOrientationChangedEvent( eq(DeviceOrientation.PORTRAIT_DOWN.toString()), any(Reply.class)); // Test that the DeviceOrientationManager starts listening for device orientation changes. verify(mockDeviceOrientationManager).start(); } } ================================================ FILE: packages/camera/camera_android_camerax/example/README.md ================================================ # 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. ================================================ FILE: packages/camera/camera_android_camerax/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 33 ndkVersion flutter.ndkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "io.flutter.plugins.cameraxexample" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. minSdkVersion 21 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' api 'androidx.test:core:1.2.0' } ================================================ FILE: packages/camera/camera_android_camerax/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/camera/camera_android_camerax/example/android/app/src/androidTest/java/io/flutter/plugins/cameraxexample/MainActivityTest.java ================================================ // Copyright 2013 The Flutter 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.cameraxexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class MainActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class); } ================================================ FILE: packages/camera/camera_android_camerax/example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: packages/camera/camera_android_camerax/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/camera/camera_android_camerax/example/android/app/src/main/java/io/flutter/plugins/cameraexample/MainActivity.java ================================================ // Copyright 2013 The Flutter 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.cameraxexample; import io.flutter.embedding.android.FlutterActivity; public class MainActivity extends FlutterActivity {} ================================================ FILE: packages/camera/camera_android_camerax/example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: packages/camera/camera_android_camerax/example/android/app/src/main/res/drawable-v21/launch_background.xml ================================================ ================================================ FILE: packages/camera/camera_android_camerax/example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: packages/camera/camera_android_camerax/example/android/app/src/main/res/values-night/styles.xml ================================================ ================================================ FILE: packages/camera/camera_android_camerax/example/android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: packages/camera/camera_android_camerax/example/android/build.gradle ================================================ buildscript { ext.kotlin_version = '1.8.0' repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/camera/camera_android_camerax/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip ================================================ FILE: packages/camera/camera_android_camerax/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true ================================================ FILE: packages/camera/camera_android_camerax/example/android/settings.gradle ================================================ include ':app' def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() assert localPropertiesFile.exists() localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" ================================================ FILE: packages/camera/camera_android_camerax/example/integration_test/integration_test.dart ================================================ // Copyright 2013 The Flutter 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/camera_android_camerax.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); setUpAll(() async { CameraPlatform.instance = AndroidCameraCameraX(); }); testWidgets('availableCameras only supports valid back or front cameras', (WidgetTester tester) async { final List availableCameras = await CameraPlatform.instance.availableCameras(); for (final CameraDescription cameraDescription in availableCameras) { expect( cameraDescription.lensDirection, isNot(CameraLensDirection.external)); expect(cameraDescription.sensorOrientation, anyOf(0, 90, 180, 270)); } }); } ================================================ FILE: packages/camera/camera_android_camerax/example/lib/camera_controller.dart ================================================ // Copyright 2013 The Flutter 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 'dart:collection'; import 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'camera_image.dart'; /// Signature for a callback receiving the a camera image. /// /// This is used by [CameraController.startImageStream]. // TODO(stuartmorgan): Fix this naming the next time there's a breaking change // to this package. // ignore: camel_case_types typedef onLatestImageAvailable = Function(CameraImage image); /// Completes with a list of available cameras. /// /// May throw a [CameraException]. Future> availableCameras() async { return CameraPlatform.instance.availableCameras(); } // TODO(stuartmorgan): Remove this once the package requires 2.10, where the // dart:async `unawaited` accepts a nullable future. void _unawaited(Future? future) {} /// The state of a [CameraController]. class CameraValue { /// Creates a new camera controller state. const CameraValue({ required this.isInitialized, this.errorDescription, this.previewSize, required this.isRecordingVideo, required this.isTakingPicture, required this.isStreamingImages, required bool isRecordingPaused, required this.flashMode, required this.exposureMode, required this.focusMode, required this.exposurePointSupported, required this.focusPointSupported, required this.deviceOrientation, this.lockedCaptureOrientation, this.recordingOrientation, this.isPreviewPaused = false, this.previewPauseOrientation, }) : _isRecordingPaused = isRecordingPaused; /// Creates a new camera controller state for an uninitialized controller. const CameraValue.uninitialized() : this( isInitialized: false, isRecordingVideo: false, isTakingPicture: false, isStreamingImages: false, isRecordingPaused: false, flashMode: FlashMode.auto, exposureMode: ExposureMode.auto, exposurePointSupported: false, focusMode: FocusMode.auto, focusPointSupported: false, deviceOrientation: DeviceOrientation.portraitUp, isPreviewPaused: false, ); /// True after [CameraController.initialize] has completed successfully. final bool isInitialized; /// True when a picture capture request has been sent but as not yet returned. final bool isTakingPicture; /// True when the camera is recording (not the same as previewing). final bool isRecordingVideo; /// True when images from the camera are being streamed. final bool isStreamingImages; final bool _isRecordingPaused; /// True when the preview widget has been paused manually. final bool isPreviewPaused; /// Set to the orientation the preview was paused in, if it is currently paused. final DeviceOrientation? previewPauseOrientation; /// True when camera [isRecordingVideo] and recording is paused. bool get isRecordingPaused => isRecordingVideo && _isRecordingPaused; /// Description of an error state. /// /// This is null while the controller is not in an error state. /// When [hasError] is true this contains the error description. final String? errorDescription; /// The size of the preview in pixels. /// /// Is `null` until [isInitialized] is `true`. final Size? previewSize; /// Convenience getter for `previewSize.width / previewSize.height`. /// /// Can only be called when [initialize] is done. double get aspectRatio => previewSize!.width / previewSize!.height; /// Whether the controller is in an error state. /// /// When true [errorDescription] describes the error. bool get hasError => errorDescription != null; /// The flash mode the camera is currently set to. final FlashMode flashMode; /// The exposure mode the camera is currently set to. final ExposureMode exposureMode; /// The focus mode the camera is currently set to. final FocusMode focusMode; /// Whether setting the exposure point is supported. final bool exposurePointSupported; /// Whether setting the focus point is supported. final bool focusPointSupported; /// The current device UI orientation. final DeviceOrientation deviceOrientation; /// The currently locked capture orientation. final DeviceOrientation? lockedCaptureOrientation; /// Whether the capture orientation is currently locked. bool get isCaptureOrientationLocked => lockedCaptureOrientation != null; /// The orientation of the currently running video recording. final DeviceOrientation? recordingOrientation; /// Creates a modified copy of the object. /// /// Explicitly specified fields get the specified value, all other fields get /// the same value of the current object. CameraValue copyWith({ bool? isInitialized, bool? isRecordingVideo, bool? isTakingPicture, bool? isStreamingImages, String? errorDescription, Size? previewSize, bool? isRecordingPaused, FlashMode? flashMode, ExposureMode? exposureMode, FocusMode? focusMode, bool? exposurePointSupported, bool? focusPointSupported, DeviceOrientation? deviceOrientation, Optional? lockedCaptureOrientation, Optional? recordingOrientation, bool? isPreviewPaused, Optional? previewPauseOrientation, }) { return CameraValue( isInitialized: isInitialized ?? this.isInitialized, errorDescription: errorDescription, previewSize: previewSize ?? this.previewSize, isRecordingVideo: isRecordingVideo ?? this.isRecordingVideo, isTakingPicture: isTakingPicture ?? this.isTakingPicture, isStreamingImages: isStreamingImages ?? this.isStreamingImages, isRecordingPaused: isRecordingPaused ?? _isRecordingPaused, flashMode: flashMode ?? this.flashMode, exposureMode: exposureMode ?? this.exposureMode, focusMode: focusMode ?? this.focusMode, exposurePointSupported: exposurePointSupported ?? this.exposurePointSupported, focusPointSupported: focusPointSupported ?? this.focusPointSupported, deviceOrientation: deviceOrientation ?? this.deviceOrientation, lockedCaptureOrientation: lockedCaptureOrientation == null ? this.lockedCaptureOrientation : lockedCaptureOrientation.orNull, recordingOrientation: recordingOrientation == null ? this.recordingOrientation : recordingOrientation.orNull, isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, previewPauseOrientation: previewPauseOrientation == null ? this.previewPauseOrientation : previewPauseOrientation.orNull, ); } @override String toString() { return '${objectRuntimeType(this, 'CameraValue')}(' 'isRecordingVideo: $isRecordingVideo, ' 'isInitialized: $isInitialized, ' 'errorDescription: $errorDescription, ' 'previewSize: $previewSize, ' 'isStreamingImages: $isStreamingImages, ' 'flashMode: $flashMode, ' 'exposureMode: $exposureMode, ' 'focusMode: $focusMode, ' 'exposurePointSupported: $exposurePointSupported, ' 'focusPointSupported: $focusPointSupported, ' 'deviceOrientation: $deviceOrientation, ' 'lockedCaptureOrientation: $lockedCaptureOrientation, ' 'recordingOrientation: $recordingOrientation, ' 'isPreviewPaused: $isPreviewPaused, ' 'previewPausedOrientation: $previewPauseOrientation)'; } } /// Controls a device camera. /// /// Use [availableCameras] to get a list of available cameras. /// /// Before using a [CameraController] a call to [initialize] must complete. /// /// To show the camera preview on the screen use a [CameraPreview] widget. class CameraController extends ValueNotifier { /// Creates a new camera controller in an uninitialized state. CameraController( this.description, this.resolutionPreset, { this.enableAudio = true, this.imageFormatGroup, }) : super(const CameraValue.uninitialized()); /// The properties of the camera device controlled by this controller. final CameraDescription description; /// The resolution this controller is targeting. /// /// This resolution preset is not guaranteed to be available on the device, /// if unavailable a lower resolution will be used. /// /// See also: [ResolutionPreset]. final ResolutionPreset resolutionPreset; /// Whether to include audio when recording a video. final bool enableAudio; /// The [ImageFormatGroup] describes the output of the raw image format. /// /// When null the imageFormat will fallback to the platforms default. final ImageFormatGroup? imageFormatGroup; /// The id of a camera that hasn't been initialized. @visibleForTesting static const int kUninitializedCameraId = -1; int _cameraId = kUninitializedCameraId; bool _isDisposed = false; StreamSubscription? _imageStreamSubscription; FutureOr? _initCalled; StreamSubscription? _deviceOrientationSubscription; /// Checks whether [CameraController.dispose] has completed successfully. /// /// This is a no-op when asserts are disabled. void debugCheckIsDisposed() { assert(_isDisposed); } /// The camera identifier with which the controller is associated. int get cameraId => _cameraId; /// Initializes the camera on the device. /// /// Throws a [CameraException] if the initialization fails. Future initialize() async { if (_isDisposed) { throw CameraException( 'Disposed CameraController', 'initialize was called on a disposed CameraController', ); } try { final Completer initializeCompleter = Completer(); _deviceOrientationSubscription = CameraPlatform.instance .onDeviceOrientationChanged() .listen((DeviceOrientationChangedEvent event) { value = value.copyWith( deviceOrientation: event.orientation, ); }); _cameraId = await CameraPlatform.instance.createCamera( description, resolutionPreset, enableAudio: enableAudio, ); _unawaited(CameraPlatform.instance .onCameraInitialized(_cameraId) .first .then((CameraInitializedEvent event) { initializeCompleter.complete(event); })); await CameraPlatform.instance.initializeCamera( _cameraId, imageFormatGroup: imageFormatGroup ?? ImageFormatGroup.unknown, ); value = value.copyWith( isInitialized: true, previewSize: await initializeCompleter.future .then((CameraInitializedEvent event) => Size( event.previewWidth, event.previewHeight, )), exposureMode: await initializeCompleter.future .then((CameraInitializedEvent event) => event.exposureMode), focusMode: await initializeCompleter.future .then((CameraInitializedEvent event) => event.focusMode), exposurePointSupported: await initializeCompleter.future.then( (CameraInitializedEvent event) => event.exposurePointSupported), focusPointSupported: await initializeCompleter.future .then((CameraInitializedEvent event) => event.focusPointSupported), ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } _initCalled = true; } /// Prepare the capture session for video recording. /// /// Use of this method is optional, but it may be called for performance /// reasons on iOS. /// /// Preparing audio can cause a minor delay in the CameraPreview view on iOS. /// If video recording is intended, calling this early eliminates this delay /// that would otherwise be experienced when video recording is started. /// This operation is a no-op on Android and Web. /// /// Throws a [CameraException] if the prepare fails. Future prepareForVideoRecording() async { await CameraPlatform.instance.prepareForVideoRecording(); } /// Pauses the current camera preview Future pausePreview() async { if (value.isPreviewPaused) { return; } try { await CameraPlatform.instance.pausePreview(_cameraId); value = value.copyWith( isPreviewPaused: true, previewPauseOrientation: Optional.of( value.lockedCaptureOrientation ?? value.deviceOrientation)); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Resumes the current camera preview Future resumePreview() async { if (!value.isPreviewPaused) { return; } try { await CameraPlatform.instance.resumePreview(_cameraId); value = value.copyWith( isPreviewPaused: false, previewPauseOrientation: const Optional.absent()); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Captures an image and returns the file where it was saved. /// /// Throws a [CameraException] if the capture fails. Future takePicture() async { _throwIfNotInitialized('takePicture'); if (value.isTakingPicture) { throw CameraException( 'Previous capture has not returned yet.', 'takePicture was called before the previous capture returned.', ); } try { value = value.copyWith(isTakingPicture: true); final XFile file = await CameraPlatform.instance.takePicture(_cameraId); value = value.copyWith(isTakingPicture: false); return file; } on PlatformException catch (e) { value = value.copyWith(isTakingPicture: false); throw CameraException(e.code, e.message); } } /// Start streaming images from platform camera. /// /// Settings for capturing images on iOS and Android is set to always use the /// latest image available from the camera and will drop all other images. /// /// When running continuously with [CameraPreview] widget, this function runs /// best with [ResolutionPreset.low]. Running on [ResolutionPreset.high] can /// have significant frame rate drops for [CameraPreview] on lower end /// devices. /// /// Throws a [CameraException] if image streaming or video recording has /// already started. /// /// The `startImageStream` method is only available on Android and iOS (other /// platforms won't be supported in current setup). /// // TODO(bmparr): Add settings for resolution and fps. Future startImageStream(onLatestImageAvailable onAvailable) async { assert(defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS); _throwIfNotInitialized('startImageStream'); if (value.isRecordingVideo) { throw CameraException( 'A video recording is already started.', 'startImageStream was called while a video is being recorded.', ); } if (value.isStreamingImages) { throw CameraException( 'A camera has started streaming images.', 'startImageStream was called while a camera was streaming images.', ); } try { _imageStreamSubscription = CameraPlatform.instance .onStreamedFrameAvailable(_cameraId) .listen((CameraImageData imageData) { onAvailable(CameraImage.fromPlatformInterface(imageData)); }); value = value.copyWith(isStreamingImages: true); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Stop streaming images from platform camera. /// /// Throws a [CameraException] if image streaming was not started or video /// recording was started. /// /// The `stopImageStream` method is only available on Android and iOS (other /// platforms won't be supported in current setup). Future stopImageStream() async { assert(defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS); _throwIfNotInitialized('stopImageStream'); if (!value.isStreamingImages) { throw CameraException( 'No camera is streaming images', 'stopImageStream was called when no camera is streaming images.', ); } try { value = value.copyWith(isStreamingImages: false); await _imageStreamSubscription?.cancel(); _imageStreamSubscription = null; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// 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( {onLatestImageAvailable? onAvailable}) async { _throwIfNotInitialized('startVideoRecording'); if (value.isRecordingVideo) { throw CameraException( 'A video recording is already started.', 'startVideoRecording was called when a recording is already started.', ); } Function(CameraImageData image)? streamCallback; if (onAvailable != null) { streamCallback = (CameraImageData imageData) { onAvailable(CameraImage.fromPlatformInterface(imageData)); }; } try { await CameraPlatform.instance.startVideoCapturing( VideoCaptureOptions(_cameraId, streamCallback: streamCallback)); value = value.copyWith( isRecordingVideo: true, isRecordingPaused: false, recordingOrientation: Optional.of( value.lockedCaptureOrientation ?? value.deviceOrientation), isStreamingImages: onAvailable != null); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Stops the video recording and returns the file where it was saved. /// /// Throws a [CameraException] if the capture failed. Future stopVideoRecording() async { _throwIfNotInitialized('stopVideoRecording'); if (!value.isRecordingVideo) { throw CameraException( 'No video is recording', '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, recordingOrientation: const Optional.absent(), ); return file; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Pause video recording. /// /// This feature is only available on iOS and Android sdk 24+. Future pauseVideoRecording() async { _throwIfNotInitialized('pauseVideoRecording'); if (!value.isRecordingVideo) { throw CameraException( 'No video is recording', 'pauseVideoRecording was called when no video is recording.', ); } try { await CameraPlatform.instance.pauseVideoRecording(_cameraId); value = value.copyWith(isRecordingPaused: true); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Resume video recording after pausing. /// /// This feature is only available on iOS and Android sdk 24+. Future resumeVideoRecording() async { _throwIfNotInitialized('resumeVideoRecording'); if (!value.isRecordingVideo) { throw CameraException( 'No video is recording', 'resumeVideoRecording was called when no video is recording.', ); } try { await CameraPlatform.instance.resumeVideoRecording(_cameraId); value = value.copyWith(isRecordingPaused: false); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Returns a widget showing a live camera preview. Widget buildPreview() { _throwIfNotInitialized('buildPreview'); try { return CameraPlatform.instance.buildPreview(_cameraId); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Gets the maximum supported zoom level for the selected camera. Future getMaxZoomLevel() { _throwIfNotInitialized('getMaxZoomLevel'); try { return CameraPlatform.instance.getMaxZoomLevel(_cameraId); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Gets the minimum supported zoom level for the selected camera. Future getMinZoomLevel() { _throwIfNotInitialized('getMinZoomLevel'); try { return CameraPlatform.instance.getMinZoomLevel(_cameraId); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Set the zoom level for the selected camera. /// /// The supplied [zoom] value should be between 1.0 and the maximum supported /// zoom level returned by the `getMaxZoomLevel`. Throws an `CameraException` /// when an illegal zoom level is suplied. Future setZoomLevel(double zoom) { _throwIfNotInitialized('setZoomLevel'); try { return CameraPlatform.instance.setZoomLevel(_cameraId, zoom); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Sets the flash mode for taking pictures. Future setFlashMode(FlashMode mode) async { try { await CameraPlatform.instance.setFlashMode(_cameraId, mode); value = value.copyWith(flashMode: mode); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Sets the exposure mode for taking pictures. Future setExposureMode(ExposureMode mode) async { try { await CameraPlatform.instance.setExposureMode(_cameraId, mode); value = value.copyWith(exposureMode: mode); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Sets the exposure point for automatically determining the exposure value. /// /// Supplying a `null` value will reset the exposure point to it's default /// value. Future setExposurePoint(Offset? point) async { if (point != null && (point.dx < 0 || point.dx > 1 || point.dy < 0 || point.dy > 1)) { throw ArgumentError( 'The values of point should be anywhere between (0,0) and (1,1).'); } try { await CameraPlatform.instance.setExposurePoint( _cameraId, point == null ? null : Point( point.dx, point.dy, ), ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Gets the minimum supported exposure offset for the selected camera in EV units. Future getMinExposureOffset() async { _throwIfNotInitialized('getMinExposureOffset'); try { return CameraPlatform.instance.getMinExposureOffset(_cameraId); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Gets the maximum supported exposure offset for the selected camera in EV units. Future getMaxExposureOffset() async { _throwIfNotInitialized('getMaxExposureOffset'); try { return CameraPlatform.instance.getMaxExposureOffset(_cameraId); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Gets the supported step size for exposure offset for the selected camera in EV units. /// /// Returns 0 when the camera supports using a free value without stepping. Future getExposureOffsetStepSize() async { _throwIfNotInitialized('getExposureOffsetStepSize'); try { return CameraPlatform.instance.getExposureOffsetStepSize(_cameraId); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Sets the exposure offset for the selected camera. /// /// The supplied [offset] value should be in EV units. 1 EV unit represents a /// doubling in brightness. It should be between the minimum and maximum offsets /// obtained through `getMinExposureOffset` and `getMaxExposureOffset` respectively. /// Throws a `CameraException` when an illegal offset is supplied. /// /// When the supplied [offset] value does not align with the step size obtained /// through `getExposureStepSize`, it will automatically be rounded to the nearest step. /// /// Returns the (rounded) offset value that was set. Future setExposureOffset(double offset) async { _throwIfNotInitialized('setExposureOffset'); // Check if offset is in range final List range = await Future.wait( >[getMinExposureOffset(), getMaxExposureOffset()]); if (offset < range[0] || offset > range[1]) { throw CameraException( 'exposureOffsetOutOfBounds', 'The provided exposure offset was outside the supported range for this device.', ); } // Round to the closest step if needed final double stepSize = await getExposureOffsetStepSize(); if (stepSize > 0) { final double inv = 1.0 / stepSize; double roundedOffset = (offset * inv).roundToDouble() / inv; if (roundedOffset > range[1]) { roundedOffset = (offset * inv).floorToDouble() / inv; } else if (roundedOffset < range[0]) { roundedOffset = (offset * inv).ceilToDouble() / inv; } offset = roundedOffset; } try { return CameraPlatform.instance.setExposureOffset(_cameraId, offset); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Locks the capture orientation. /// /// If [orientation] is omitted, the current device orientation is used. Future lockCaptureOrientation([DeviceOrientation? orientation]) async { try { await CameraPlatform.instance.lockCaptureOrientation( _cameraId, orientation ?? value.deviceOrientation); value = value.copyWith( lockedCaptureOrientation: Optional.of( orientation ?? value.deviceOrientation)); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Sets the focus mode for taking pictures. Future setFocusMode(FocusMode mode) async { try { await CameraPlatform.instance.setFocusMode(_cameraId, mode); value = value.copyWith(focusMode: mode); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Unlocks the capture orientation. Future unlockCaptureOrientation() async { try { await CameraPlatform.instance.unlockCaptureOrientation(_cameraId); value = value.copyWith( lockedCaptureOrientation: const Optional.absent()); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Sets the focus point for automatically determining the focus value. /// /// Supplying a `null` value will reset the focus point to it's default /// value. Future setFocusPoint(Offset? point) async { if (point != null && (point.dx < 0 || point.dx > 1 || point.dy < 0 || point.dy > 1)) { throw ArgumentError( 'The values of point should be anywhere between (0,0) and (1,1).'); } try { await CameraPlatform.instance.setFocusPoint( _cameraId, point == null ? null : Point( point.dx, point.dy, ), ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } /// Releases the resources of this camera. @override Future dispose() async { if (_isDisposed) { return; } _unawaited(_deviceOrientationSubscription?.cancel()); _isDisposed = true; super.dispose(); if (_initCalled != null) { await _initCalled; await CameraPlatform.instance.dispose(_cameraId); } } void _throwIfNotInitialized(String functionName) { if (!value.isInitialized) { throw CameraException( 'Uninitialized CameraController', '$functionName() was called on an uninitialized CameraController.', ); } if (_isDisposed) { throw CameraException( 'Disposed CameraController', '$functionName() was called on a disposed CameraController.', ); } } @override void removeListener(VoidCallback listener) { // Prevent ValueListenableBuilder in CameraPreview widget from causing an // exception to be thrown by attempting to remove its own listener after // the controller has already been disposed. if (!_isDisposed) { super.removeListener(listener); } } } /// 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 }'; } } ================================================ FILE: packages/camera/camera_android_camerax/example/lib/camera_image.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; // TODO(stuartmorgan): Remove all of these classes in a breaking change, and // vend the platform interface versions directly. See // https://github.com/flutter/flutter/issues/104188 /// A single color plane of image data. /// /// The number and meaning of the planes in an image are determined by the /// format of the Image. class Plane { Plane._fromPlatformInterface(CameraImagePlane plane) : bytes = plane.bytes, bytesPerPixel = plane.bytesPerPixel, bytesPerRow = plane.bytesPerRow, height = plane.height, width = plane.width; // Only used by the deprecated codepath that's kept to avoid breaking changes. // Never called by the plugin itself. Plane._fromPlatformData(Map data) : bytes = data['bytes'] as Uint8List, bytesPerPixel = data['bytesPerPixel'] as int?, bytesPerRow = data['bytesPerRow'] as int, height = data['height'] as int?, width = data['width'] as int?; /// Bytes representing this plane. final Uint8List bytes; /// The distance between adjacent pixel samples on Android, in bytes. /// /// Will be `null` on iOS. final int? bytesPerPixel; /// The row stride for this color plane, in bytes. final int bytesPerRow; /// Height of the pixel buffer on iOS. /// /// Will be `null` on Android final int? height; /// Width of the pixel buffer on iOS. /// /// Will be `null` on Android. final int? width; } /// Describes how pixels are represented in an image. class ImageFormat { ImageFormat._fromPlatformInterface(CameraImageFormat format) : group = format.group, raw = format.raw; // Only used by the deprecated codepath that's kept to avoid breaking changes. // Never called by the plugin itself. ImageFormat._fromPlatformData(this.raw) : group = _asImageFormatGroup(raw); /// Describes the format group the raw image format falls into. final ImageFormatGroup group; /// Raw version of the format from the Android or iOS platform. /// /// On Android, this is an `int` from class `android.graphics.ImageFormat`. See /// https://developer.android.com/reference/android/graphics/ImageFormat /// /// On iOS, this is a `FourCharCode` constant from Pixel Format Identifiers. /// See https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers?language=objc final dynamic raw; } // Only used by the deprecated codepath that's kept to avoid breaking changes. // Never called by the plugin itself. ImageFormatGroup _asImageFormatGroup(dynamic rawFormat) { if (defaultTargetPlatform == TargetPlatform.android) { switch (rawFormat) { // android.graphics.ImageFormat.YUV_420_888 case 35: return ImageFormatGroup.yuv420; // android.graphics.ImageFormat.JPEG case 256: return ImageFormatGroup.jpeg; } } if (defaultTargetPlatform == TargetPlatform.iOS) { switch (rawFormat) { // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange case 875704438: return ImageFormatGroup.yuv420; // kCVPixelFormatType_32BGRA case 1111970369: return ImageFormatGroup.bgra8888; } } return ImageFormatGroup.unknown; } /// A single complete image buffer from the platform camera. /// /// This class allows for direct application access to the pixel data of an /// Image through one or more [Uint8List]. Each buffer is encapsulated in a /// [Plane] that describes the layout of the pixel data in that plane. The /// [CameraImage] is not directly usable as a UI resource. /// /// Although not all image formats are planar on iOS, we treat 1-dimensional /// images as single planar images. class CameraImage { /// Creates a [CameraImage] from the platform interface version. CameraImage.fromPlatformInterface(CameraImageData data) : format = ImageFormat._fromPlatformInterface(data.format), height = data.height, width = data.width, planes = List.unmodifiable(data.planes.map( (CameraImagePlane plane) => Plane._fromPlatformInterface(plane))), lensAperture = data.lensAperture, sensorExposureTime = data.sensorExposureTime, sensorSensitivity = data.sensorSensitivity; /// Creates a [CameraImage] from method channel data. @Deprecated('Use fromPlatformInterface instead') CameraImage.fromPlatformData(Map data) : format = ImageFormat._fromPlatformData(data['format']), height = data['height'] as int, width = data['width'] as int, lensAperture = data['lensAperture'] as double?, sensorExposureTime = data['sensorExposureTime'] as int?, sensorSensitivity = data['sensorSensitivity'] as double?, planes = List.unmodifiable((data['planes'] as List) .map((dynamic planeData) => Plane._fromPlatformData(planeData as Map))); /// Format of the image provided. /// /// Determines the number of planes needed to represent the image, and /// the general layout of the pixel data in each [Uint8List]. final ImageFormat format; /// Height of the image in pixels. /// /// For formats where some color channels are subsampled, this is the height /// of the largest-resolution plane. final int height; /// Width of the image in pixels. /// /// For formats where some color channels are subsampled, this is the width /// of the largest-resolution plane. final int width; /// The pixels planes for this image. /// /// The number of planes is determined by the format of the image. final List planes; /// The aperture settings for this image. /// /// Represented as an f-stop value. final double? lensAperture; /// The sensor exposure time for this image in nanoseconds. final int? sensorExposureTime; /// The sensor sensitivity in standard ISO arithmetic units. final double? sensorSensitivity; } ================================================ FILE: packages/camera/camera_android_camerax/example/lib/camera_preview.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'camera_controller.dart'; /// A widget showing a live camera preview. class CameraPreview extends StatelessWidget { /// Creates a preview widget for the given camera controller. const CameraPreview(this.controller, {super.key, this.child}); /// The controller for the camera that the preview is shown for. final CameraController controller; /// A widget to overlay on top of the camera preview final Widget? child; @override Widget build(BuildContext context) { return controller.value.isInitialized ? ValueListenableBuilder( valueListenable: controller, builder: (BuildContext context, Object? value, Widget? child) { return AspectRatio( aspectRatio: _isLandscape() ? controller.value.aspectRatio : (1 / controller.value.aspectRatio), child: Stack( fit: StackFit.expand, children: [ _wrapInRotatedBox(child: controller.buildPreview()), child ?? Container(), ], ), ); }, child: child, ) : Container(); } Widget _wrapInRotatedBox({required Widget child}) { if (kIsWeb || defaultTargetPlatform != TargetPlatform.android) { return child; } return RotatedBox( quarterTurns: _getQuarterTurns(), child: child, ); } bool _isLandscape() { return [ DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight ].contains(_getApplicableOrientation()); } int _getQuarterTurns() { final Map turns = { DeviceOrientation.portraitUp: 0, DeviceOrientation.landscapeRight: 1, DeviceOrientation.portraitDown: 2, DeviceOrientation.landscapeLeft: 3, }; return turns[_getApplicableOrientation()]!; } DeviceOrientation _getApplicableOrientation() { return controller.value.isRecordingVideo ? controller.value.recordingOrientation! : (controller.value.previewPauseOrientation ?? controller.value.lockedCaptureOrientation ?? controller.value.deviceOrientation); } } ================================================ FILE: packages/camera/camera_android_camerax/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:video_player/video_player.dart'; import 'camera_controller.dart'; import 'camera_preview.dart'; /// Camera example home widget. class CameraExampleHome extends StatefulWidget { /// Default Constructor const CameraExampleHome({super.key}); @override State createState() { return _CameraExampleHomeState(); } } /// Returns a suitable camera icon for [direction]. IconData getCameraLensIcon(CameraLensDirection direction) { switch (direction) { case CameraLensDirection.back: return Icons.camera_rear; case CameraLensDirection.front: return Icons.camera_front; case CameraLensDirection.external: return Icons.camera; } // 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) { // ignore: avoid_print print('Error: $code${message == null ? '' : '\nError Message: $message'}'); } class _CameraExampleHomeState extends State with WidgetsBindingObserver, TickerProviderStateMixin { CameraController? controller; XFile? imageFile; XFile? videoFile; VideoPlayerController? videoController; VoidCallback? videoPlayerListener; bool enableAudio = true; double _minAvailableExposureOffset = 0.0; double _maxAvailableExposureOffset = 0.0; double _currentExposureOffset = 0.0; late AnimationController _flashModeControlRowAnimationController; late Animation _flashModeControlRowAnimation; late AnimationController _exposureModeControlRowAnimationController; late Animation _exposureModeControlRowAnimation; late AnimationController _focusModeControlRowAnimationController; late Animation _focusModeControlRowAnimation; double _minAvailableZoom = 1.0; double _maxAvailableZoom = 1.0; double _currentScale = 1.0; double _baseScale = 1.0; // Counting pointers (number of user fingers on screen) int _pointers = 0; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _flashModeControlRowAnimationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _flashModeControlRowAnimation = CurvedAnimation( parent: _flashModeControlRowAnimationController, curve: Curves.easeInCubic, ); _exposureModeControlRowAnimationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _exposureModeControlRowAnimation = CurvedAnimation( parent: _exposureModeControlRowAnimationController, curve: Curves.easeInCubic, ); _focusModeControlRowAnimationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _focusModeControlRowAnimation = CurvedAnimation( parent: _focusModeControlRowAnimationController, curve: Curves.easeInCubic, ); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _flashModeControlRowAnimationController.dispose(); _exposureModeControlRowAnimationController.dispose(); super.dispose(); } // #docregion AppLifecycle @override void didChangeAppLifecycleState(AppLifecycleState state) { final CameraController? cameraController = controller; // App state changed before we got the chance to initialize. if (cameraController == null || !cameraController.value.isInitialized) { return; } if (state == AppLifecycleState.inactive) { cameraController.dispose(); } else if (state == AppLifecycleState.resumed) { onNewCameraSelected(cameraController.description); } } // #enddocregion AppLifecycle @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Camera example'), ), body: Column( children: [ Expanded( child: Container( decoration: BoxDecoration( color: Colors.black, border: Border.all( color: controller != null && controller!.value.isRecordingVideo ? Colors.redAccent : Colors.grey, width: 3.0, ), ), child: Padding( padding: const EdgeInsets.all(1.0), child: Center( child: _cameraPreviewWidget(), ), ), ), ), _captureControlRowWidget(), _modeControlRowWidget(), Padding( padding: const EdgeInsets.all(5.0), child: Row( children: [ _cameraTogglesRowWidget(), _thumbnailWidget(), ], ), ), ], ), ); } /// Display the preview from the camera (or a message if the preview is not available). Widget _cameraPreviewWidget() { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { return const Text( 'Tap a camera', style: TextStyle( color: Colors.white, fontSize: 24.0, fontWeight: FontWeight.w900, ), ); } else { return Listener( onPointerDown: (_) => _pointers++, onPointerUp: (_) => _pointers--, child: CameraPreview( controller!, child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return GestureDetector( behavior: HitTestBehavior.opaque, onScaleStart: _handleScaleStart, onScaleUpdate: _handleScaleUpdate, onTapDown: (TapDownDetails details) => onViewFinderTap(details, constraints), ); }), ), ); } } void _handleScaleStart(ScaleStartDetails details) { _baseScale = _currentScale; } Future _handleScaleUpdate(ScaleUpdateDetails details) async { // When there are not exactly two fingers on screen don't scale if (controller == null || _pointers != 2) { return; } _currentScale = (_baseScale * details.scale) .clamp(_minAvailableZoom, _maxAvailableZoom); await controller!.setZoomLevel(_currentScale); } /// Display the thumbnail of the captured image or video. Widget _thumbnailWidget() { final VideoPlayerController? localVideoController = videoController; return Expanded( child: Align( alignment: Alignment.centerRight, child: Row( mainAxisSize: MainAxisSize.min, children: [ if (localVideoController == null && imageFile == null) Container() else SizedBox( width: 64.0, height: 64.0, child: (localVideoController == null) ? ( // The captured image on the web contains a network-accessible URL // pointing to a location within the browser. It may be displayed // either with Image.network or Image.memory after loading the image // bytes to memory. kIsWeb ? Image.network(imageFile!.path) : Image.file(File(imageFile!.path))) : Container( decoration: BoxDecoration( border: Border.all(color: Colors.pink)), child: Center( child: AspectRatio( aspectRatio: localVideoController.value.size != null ? localVideoController.value.aspectRatio : 1.0, child: VideoPlayer(localVideoController)), ), ), ), ], ), ), ); } /// Display a bar with buttons to change the flash and exposure modes Widget _modeControlRowWidget() { return Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( icon: const Icon(Icons.flash_on), color: Colors.blue, onPressed: () {}, // TODO(camsim99): Add functionality back here. ), // The exposure and focus mode are currently not supported on the web. ...!kIsWeb ? [ IconButton( icon: const Icon(Icons.exposure), color: Colors.blue, onPressed: () {}, // TODO(camsim99): Add functionality back here. ), IconButton( icon: const Icon(Icons.filter_center_focus), color: Colors.blue, onPressed: () {}, // TODO(camsim99): Add functionality back here. ) ] : [], IconButton( icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute), color: Colors.blue, onPressed: () {}, // TODO(camsim99): Add functionality back here. ), IconButton( icon: Icon(controller?.value.isCaptureOrientationLocked ?? false ? Icons.screen_lock_rotation : Icons.screen_rotation), color: Colors.blue, onPressed: () {}, // TODO(camsim99): Add functionality back here. ), ], ), _flashModeControlRowWidget(), _exposureModeControlRowWidget(), _focusModeControlRowWidget(), ], ); } Widget _flashModeControlRowWidget() { return SizeTransition( sizeFactor: _flashModeControlRowAnimation, child: ClipRect( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( icon: const Icon(Icons.flash_off), color: controller?.value.flashMode == FlashMode.off ? Colors.orange : Colors.blue, onPressed: () {}, // TODO(camsim99): Add functionality back here. ), IconButton( icon: const Icon(Icons.flash_auto), color: controller?.value.flashMode == FlashMode.auto ? Colors.orange : Colors.blue, onPressed: () {}, // TODO(camsim99): Add functionality back here. ), IconButton( icon: const Icon(Icons.flash_on), color: controller?.value.flashMode == FlashMode.always ? Colors.orange : Colors.blue, onPressed: () {}, // TODO(camsim99): Add functionality back here. ), IconButton( icon: const Icon(Icons.highlight), color: controller?.value.flashMode == FlashMode.torch ? Colors.orange : Colors.blue, onPressed: () {}, // TODO(camsim99): Add functionality back here. ), ], ), ), ); } Widget _exposureModeControlRowWidget() { final ButtonStyle styleAuto = TextButton.styleFrom( // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: controller?.value.exposureMode == ExposureMode.auto ? Colors.orange : Colors.blue, ); final ButtonStyle styleLocked = TextButton.styleFrom( // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: controller?.value.exposureMode == ExposureMode.locked ? Colors.orange : Colors.blue, ); return SizeTransition( sizeFactor: _exposureModeControlRowAnimation, child: ClipRect( child: Container( color: Colors.grey.shade50, child: Column( children: [ const Center( child: Text('Exposure Mode'), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ TextButton( style: styleAuto, onPressed: () {}, // TODO(camsim99): Add functionality back here. onLongPress: () { if (controller != null) { controller!.setExposurePoint(null); showInSnackBar('Resetting exposure point'); } }, child: const Text('AUTO'), ), TextButton( style: styleLocked, onPressed: () {}, // TODO(camsim99): Add functionality back here. child: const Text('LOCKED'), ), TextButton( style: styleLocked, onPressed: () {}, // TODO(camsim99): Add functionality back here. child: const Text('RESET OFFSET'), ), ], ), const Center( child: Text('Exposure Offset'), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text(_minAvailableExposureOffset.toString()), Slider( value: _currentExposureOffset, min: _minAvailableExposureOffset, max: _maxAvailableExposureOffset, label: _currentExposureOffset.toString(), onChanged: _minAvailableExposureOffset == _maxAvailableExposureOffset ? null : setExposureOffset, ), Text(_maxAvailableExposureOffset.toString()), ], ), ], ), ), ), ); } Widget _focusModeControlRowWidget() { final ButtonStyle styleAuto = TextButton.styleFrom( // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: controller?.value.focusMode == FocusMode.auto ? Colors.orange : Colors.blue, ); final ButtonStyle styleLocked = TextButton.styleFrom( // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: controller?.value.focusMode == FocusMode.locked ? Colors.orange : Colors.blue, ); return SizeTransition( sizeFactor: _focusModeControlRowAnimation, child: ClipRect( child: Container( color: Colors.grey.shade50, child: Column( children: [ const Center( child: Text('Focus Mode'), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ TextButton( style: styleAuto, onPressed: () {}, // TODO(camsim99): Add functionality back here. onLongPress: () { if (controller != null) { controller!.setFocusPoint(null); } showInSnackBar('Resetting focus point'); }, child: const Text('AUTO'), ), TextButton( style: styleLocked, onPressed: () {}, // TODO(camsim99): Add functionality back here. child: const Text('LOCKED'), ), ], ), ], ), ), ), ); } /// Display the control bar with buttons to take pictures and record videos. Widget _captureControlRowWidget() { final CameraController? cameraController = controller; return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( icon: const Icon(Icons.camera_alt), color: Colors.blue, onPressed: () {}, // TODO(camsim99): Add functionality back here. ), IconButton( icon: const Icon(Icons.videocam), color: Colors.blue, onPressed: () {}, // TODO(camsim99): Add functionality back here. ), IconButton( icon: cameraController != null && cameraController.value.isRecordingPaused ? const Icon(Icons.play_arrow) : const Icon(Icons.pause), color: Colors.blue, onPressed: () {}, // TODO(camsim99): Add functionality back here. ), IconButton( icon: const Icon(Icons.stop), color: Colors.red, onPressed: () {}, // TODO(camsim99): Add functionality back here. ), IconButton( icon: const Icon(Icons.pause_presentation), color: cameraController != null && cameraController.value.isPreviewPaused ? Colors.red : Colors.blue, onPressed: cameraController == null ? null : onPausePreviewButtonPressed, ), ], ); } /// Display a row of toggle to select the camera (or a message if no camera is available). Widget _cameraTogglesRowWidget() { final List toggles = []; void onChanged(CameraDescription? description) { if (description == null) { return; } onNewCameraSelected(description); } if (_cameras.isEmpty) { SchedulerBinding.instance.addPostFrameCallback((_) async { showInSnackBar('No camera found.'); }); return const Text('None'); } else { for (final CameraDescription cameraDescription in _cameras) { toggles.add( SizedBox( width: 90.0, child: RadioListTile( title: Icon(getCameraLensIcon(cameraDescription.lensDirection)), groupValue: controller?.description, value: cameraDescription, onChanged: controller != null && controller!.value.isRecordingVideo ? null : onChanged, ), ), ); } } return Row(children: toggles); } String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); void showInSnackBar(String message) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(message))); } void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { if (controller == null) { return; } final CameraController cameraController = controller!; final Offset offset = Offset( details.localPosition.dx / constraints.maxWidth, details.localPosition.dy / constraints.maxHeight, ); cameraController.setExposurePoint(offset); cameraController.setFocusPoint(offset); } Future onNewCameraSelected(CameraDescription cameraDescription) async { final CameraController? oldController = controller; if (oldController != null) { // `controller` needs to be set to null before getting disposed, // to avoid a race condition when we use the controller that is being // disposed. This happens when camera permission dialog shows up, // which triggers `didChangeAppLifecycleState`, which disposes and // re-creates the controller. controller = null; await oldController.dispose(); } final CameraController cameraController = CameraController( cameraDescription, kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, enableAudio: enableAudio, imageFormatGroup: ImageFormatGroup.jpeg, ); controller = cameraController; // If the controller is updated then update the UI. cameraController.addListener(() { if (mounted) { setState(() {}); } if (cameraController.value.hasError) { showInSnackBar( 'Camera error ${cameraController.value.errorDescription}'); } }); try { await cameraController.initialize(); await Future.wait(>[ // The exposure mode is currently not supported on the web. ...!kIsWeb ? >[ cameraController.getMinExposureOffset().then( (double value) => _minAvailableExposureOffset = value), cameraController .getMaxExposureOffset() .then((double value) => _maxAvailableExposureOffset = value) ] : >[], cameraController .getMaxZoomLevel() .then((double value) => _maxAvailableZoom = value), cameraController .getMinZoomLevel() .then((double value) => _minAvailableZoom = value), ]); } on CameraException catch (e) { switch (e.code) { case 'CameraAccessDenied': showInSnackBar('You have denied camera access.'); break; case 'CameraAccessDeniedWithoutPrompt': // iOS only showInSnackBar('Please go to Settings app to enable camera access.'); break; case 'CameraAccessRestricted': // iOS only showInSnackBar('Camera access is restricted.'); break; case 'AudioAccessDenied': showInSnackBar('You have denied audio access.'); break; case 'AudioAccessDeniedWithoutPrompt': // iOS only showInSnackBar('Please go to Settings app to enable audio access.'); break; case 'AudioAccessRestricted': // iOS only showInSnackBar('Audio access is restricted.'); break; default: _showCameraException(e); break; } } if (mounted) { setState(() {}); } } void onTakePictureButtonPressed() { takePicture().then((XFile? file) { if (mounted) { setState(() { imageFile = file; videoController?.dispose(); videoController = null; }); if (file != null) { showInSnackBar('Picture saved to ${file.path}'); } } }); } void onFlashModeButtonPressed() { if (_flashModeControlRowAnimationController.value == 1) { _flashModeControlRowAnimationController.reverse(); } else { _flashModeControlRowAnimationController.forward(); _exposureModeControlRowAnimationController.reverse(); _focusModeControlRowAnimationController.reverse(); } } void onExposureModeButtonPressed() { if (_exposureModeControlRowAnimationController.value == 1) { _exposureModeControlRowAnimationController.reverse(); } else { _exposureModeControlRowAnimationController.forward(); _flashModeControlRowAnimationController.reverse(); _focusModeControlRowAnimationController.reverse(); } } void onFocusModeButtonPressed() { if (_focusModeControlRowAnimationController.value == 1) { _focusModeControlRowAnimationController.reverse(); } else { _focusModeControlRowAnimationController.forward(); _flashModeControlRowAnimationController.reverse(); _exposureModeControlRowAnimationController.reverse(); } } void onAudioModeButtonPressed() { enableAudio = !enableAudio; if (controller != null) { onNewCameraSelected(controller!.description); } } Future onCaptureOrientationLockButtonPressed() async { try { if (controller != null) { final CameraController cameraController = controller!; if (cameraController.value.isCaptureOrientationLocked) { await cameraController.unlockCaptureOrientation(); showInSnackBar('Capture orientation unlocked'); } else { await cameraController.lockCaptureOrientation(); showInSnackBar( 'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}'); } } } on CameraException catch (e) { _showCameraException(e); } } void onSetFlashModeButtonPressed(FlashMode mode) { setFlashMode(mode).then((_) { if (mounted) { setState(() {}); } showInSnackBar('Flash mode set to ${mode.toString().split('.').last}'); }); } void onSetExposureModeButtonPressed(ExposureMode mode) { setExposureMode(mode).then((_) { if (mounted) { setState(() {}); } showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}'); }); } void onSetFocusModeButtonPressed(FocusMode mode) { setFocusMode(mode).then((_) { if (mounted) { setState(() {}); } showInSnackBar('Focus mode set to ${mode.toString().split('.').last}'); }); } void onVideoRecordButtonPressed() { startVideoRecording().then((_) { if (mounted) { setState(() {}); } }); } void onStopButtonPressed() { stopVideoRecording().then((XFile? file) { if (mounted) { setState(() {}); } if (file != null) { showInSnackBar('Video recorded to ${file.path}'); videoFile = file; _startVideoPlayer(); } }); } Future onPausePreviewButtonPressed() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return; } if (cameraController.value.isPreviewPaused) { await cameraController.resumePreview(); } else { await cameraController.pausePreview(); } if (mounted) { setState(() {}); } } void onPauseButtonPressed() { pauseVideoRecording().then((_) { if (mounted) { setState(() {}); } showInSnackBar('Video recording paused'); }); } void onResumeButtonPressed() { resumeVideoRecording().then((_) { if (mounted) { setState(() {}); } showInSnackBar('Video recording resumed'); }); } Future startVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return; } if (cameraController.value.isRecordingVideo) { // A recording is already started, do nothing. return; } try { await cameraController.startVideoRecording(); } on CameraException catch (e) { _showCameraException(e); return; } } Future stopVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { return null; } try { return cameraController.stopVideoRecording(); } on CameraException catch (e) { _showCameraException(e); return null; } } Future pauseVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { return; } try { await cameraController.pauseVideoRecording(); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future resumeVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { return; } try { await cameraController.resumeVideoRecording(); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future setFlashMode(FlashMode mode) async { if (controller == null) { return; } try { await controller!.setFlashMode(mode); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future setExposureMode(ExposureMode mode) async { if (controller == null) { return; } try { await controller!.setExposureMode(mode); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future setExposureOffset(double offset) async { if (controller == null) { return; } setState(() { _currentExposureOffset = offset; }); try { offset = await controller!.setExposureOffset(offset); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future setFocusMode(FocusMode mode) async { if (controller == null) { return; } try { await controller!.setFocusMode(mode); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future _startVideoPlayer() async { if (videoFile == null) { return; } final VideoPlayerController vController = kIsWeb ? VideoPlayerController.network(videoFile!.path) : VideoPlayerController.file(File(videoFile!.path)); videoPlayerListener = () { if (videoController != null && videoController!.value.size != null) { // Refreshing the state to update video player with the correct ratio. if (mounted) { setState(() {}); } videoController!.removeListener(videoPlayerListener!); } }; vController.addListener(videoPlayerListener!); await vController.setLooping(true); await vController.initialize(); await videoController?.dispose(); if (mounted) { setState(() { imageFile = null; videoController = vController; }); } await vController.play(); } Future takePicture() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return null; } if (cameraController.value.isTakingPicture) { // A capture is already pending, do nothing. return null; } try { final XFile file = await cameraController.takePicture(); return file; } on CameraException catch (e) { _showCameraException(e); return null; } } void _showCameraException(CameraException e) { _logError(e.code, e.description); showInSnackBar('Error: ${e.code}\n${e.description}'); } } /// CameraApp is the Main Application. class CameraApp extends StatelessWidget { /// Default Constructor const CameraApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( home: CameraExampleHome(), ); } } List _cameras = []; Future main() async { // Fetch the available cameras before initializing the app. try { WidgetsFlutterBinding.ensureInitialized(); _cameras = await availableCameras(); } on CameraException catch (e) { _logError(e.code, e.description); } runApp(const CameraApp()); } ================================================ FILE: packages/camera/camera_android_camerax/example/pubspec.yaml ================================================ name: camera_android_camerax_example description: Demonstrates how to use the camera_android_camerax plugin. publish_to: 'none' environment: sdk: '>=2.17.0 <3.0.0' flutter: ">=3.0.0" dependencies: camera_android_camerax: # When depending on this package from a real application you should use: # camera_android_camerax: ^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: ../ camera_platform_interface: ^2.2.0 flutter: sdk: flutter video_player: ^2.4.10 dev_dependencies: flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/camera/camera_android_camerax/example/test/widget_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This is a basic Flutter widget test. // // To perform an interaction with a widget in your test, use the WidgetTester // utility in the flutter_test package. For example, you can send tap and scroll // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('Fake test', (WidgetTester tester) async { expect(true, isTrue); }); } ================================================ FILE: packages/camera/camera_android_camerax/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/camera/camera_android_camerax/lib/camera_android_camerax.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'src/android_camera_camerax.dart'; ================================================ FILE: packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart ================================================ // Copyright 2013 The Flutter 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'; import 'package:flutter/widgets.dart'; import 'package:stream_transform/stream_transform.dart'; import 'camera.dart'; import 'camera_info.dart'; import 'camera_selector.dart'; import 'camerax_library.g.dart'; import 'preview.dart'; import 'process_camera_provider.dart'; import 'surface.dart'; import 'system_services.dart'; import 'use_case.dart'; /// The Android implementation of [CameraPlatform] that uses the CameraX library. class AndroidCameraCameraX extends CameraPlatform { /// Registers this class as the default instance of [CameraPlatform]. static void registerWith() { CameraPlatform.instance = AndroidCameraCameraX(); } /// The [ProcessCameraProvider] instance used to access camera functionality. @visibleForTesting ProcessCameraProvider? processCameraProvider; /// The [Camera] instance returned by the [processCameraProvider] when a [UseCase] is /// bound to the lifecycle of the camera it manages. @visibleForTesting Camera? camera; /// The [Preview] instance that can be configured to present a live camera preview. @visibleForTesting Preview? preview; /// Whether or not the [preview] is currently bound to the lifecycle that the /// [processCameraProvider] tracks. @visibleForTesting bool previewIsBound = false; bool _previewIsPaused = false; /// The [CameraSelector] used to configure the [processCameraProvider] to use /// the desired camera. @visibleForTesting CameraSelector? cameraSelector; /// The controller we need to broadcast the different camera events. /// /// It is a `broadcast` because multiple controllers will connect to /// different stream views of this Controller. /// This is only exposed for test purposes. It shouldn't be used by clients of /// the plugin as it may break or change at any time. @visibleForTesting final StreamController cameraEventStreamController = StreamController.broadcast(); /// The stream of camera events. Stream _cameraEvents(int cameraId) => cameraEventStreamController.stream .where((CameraEvent event) => event.cameraId == cameraId); /// Returns list of all available cameras and their descriptions. @override Future> availableCameras() async { final List cameraDescriptions = []; processCameraProvider ??= await ProcessCameraProvider.getInstance(); final List cameraInfos = await processCameraProvider!.getAvailableCameraInfos(); CameraLensDirection? cameraLensDirection; int cameraCount = 0; int? cameraSensorOrientation; String? cameraName; for (final CameraInfo cameraInfo in cameraInfos) { // Determine the lens direction by filtering the CameraInfo // TODO(gmackall): replace this with call to CameraInfo.getLensFacing when changes containing that method are available if ((await createCameraSelector(CameraSelector.lensFacingBack) .filter([cameraInfo])) .isNotEmpty) { cameraLensDirection = CameraLensDirection.back; } else if ((await createCameraSelector(CameraSelector.lensFacingFront) .filter([cameraInfo])) .isNotEmpty) { cameraLensDirection = CameraLensDirection.front; } else { //Skip this CameraInfo as its lens direction is unknown continue; } cameraSensorOrientation = await cameraInfo.getSensorRotationDegrees(); cameraName = 'Camera $cameraCount'; cameraCount++; cameraDescriptions.add(CameraDescription( name: cameraName, lensDirection: cameraLensDirection, sensorOrientation: cameraSensorOrientation)); } return cameraDescriptions; } /// Creates an uninitialized camera instance and returns the camera ID. /// /// In the CameraX library, cameras are accessed by combining [UseCase]s /// to an instance of a [ProcessCameraProvider]. Thus, to create an /// unitialized camera instance, this method retrieves a /// [ProcessCameraProvider] instance. /// /// To return the camera ID, which is equivalent to the ID of the surface texture /// that a camera preview can be drawn to, a [Preview] instance is configured /// and bound to the [ProcessCameraProvider] instance. @override Future createCamera( CameraDescription cameraDescription, ResolutionPreset? resolutionPreset, { bool enableAudio = false, }) async { // Must obtain proper permissions before attempting to access a camera. await requestCameraPermissions(enableAudio); // Save CameraSelector that matches cameraDescription. final int cameraSelectorLensDirection = _getCameraSelectorLensDirection(cameraDescription.lensDirection); final bool cameraIsFrontFacing = cameraSelectorLensDirection == CameraSelector.lensFacingFront; cameraSelector = createCameraSelector(cameraSelectorLensDirection); // Start listening for device orientation changes preceding camera creation. startListeningForDeviceOrientationChange( cameraIsFrontFacing, cameraDescription.sensorOrientation); // Retrieve a ProcessCameraProvider instance. processCameraProvider ??= await ProcessCameraProvider.getInstance(); // Configure Preview instance and bind to ProcessCameraProvider. final int targetRotation = _getTargetRotation(cameraDescription.sensorOrientation); final ResolutionInfo? targetResolution = _getTargetResolutionForPreview(resolutionPreset); preview = createPreview(targetRotation, targetResolution); previewIsBound = false; _previewIsPaused = false; final int flutterSurfaceTextureId = await preview!.setSurfaceProvider(); return flutterSurfaceTextureId; } /// Initializes the camera on the device. /// /// Since initialization of a camera does not directly map as an operation to /// the CameraX library, this method just retrieves information about the /// camera and sends a [CameraInitializedEvent]. /// /// [imageFormatGroup] is used to specify the image formatting used. /// On Android this defaults to ImageFormat.YUV_420_888 and applies only to /// the image stream. @override Future initializeCamera( int cameraId, { ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown, }) async { // TODO(camsim99): Use imageFormatGroup to configure ImageAnalysis use case // for image streaming. // https://github.com/flutter/flutter/issues/120463 // Configure CameraInitializedEvent to send as representation of a // configured camera: // Retrieve preview resolution. assert( preview != null, 'Preview instance not found. Please call the "createCamera" method before calling "initializeCamera"', ); await _bindPreviewToLifecycle(); final ResolutionInfo previewResolutionInfo = await preview!.getResolutionInfo(); _unbindPreviewFromLifecycle(); // Retrieve exposure and focus mode configurations: // TODO(camsim99): Implement support for retrieving exposure mode configuration. // https://github.com/flutter/flutter/issues/120468 const ExposureMode exposureMode = ExposureMode.auto; const bool exposurePointSupported = false; // TODO(camsim99): Implement support for retrieving focus mode configuration. // https://github.com/flutter/flutter/issues/120467 const FocusMode focusMode = FocusMode.auto; const bool focusPointSupported = false; cameraEventStreamController.add(CameraInitializedEvent( cameraId, previewResolutionInfo.width.toDouble(), previewResolutionInfo.height.toDouble(), exposureMode, exposurePointSupported, focusMode, focusPointSupported)); } /// Releases the resources of the accessed camera. /// /// [cameraId] not used. @override Future dispose(int cameraId) async { preview?.releaseFlutterSurfaceTexture(); processCameraProvider?.unbindAll(); } /// The camera has been initialized. @override Stream onCameraInitialized(int cameraId) { return _cameraEvents(cameraId).whereType(); } /// The camera experienced an error. @override Stream onCameraError(int cameraId) { return SystemServices.cameraErrorStreamController.stream .map((String errorDescription) { return CameraErrorEvent(cameraId, errorDescription); }); } /// The ui orientation changed. @override Stream onDeviceOrientationChanged() { return SystemServices.deviceOrientationChangedStreamController.stream; } /// Pause the active preview on the current frame for the selected camera. /// /// [cameraId] not used. @override Future pausePreview(int cameraId) async { _unbindPreviewFromLifecycle(); _previewIsPaused = true; } /// Resume the paused preview for the selected camera. /// /// [cameraId] not used. @override Future resumePreview(int cameraId) async { await _bindPreviewToLifecycle(); _previewIsPaused = false; } /// Returns a widget showing a live camera preview. @override Widget buildPreview(int cameraId) { return FutureBuilder( future: _bindPreviewToLifecycle(), builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { case ConnectionState.none: case ConnectionState.waiting: case ConnectionState.active: // Do nothing while waiting for preview to be bound to lifecyle. return const SizedBox.shrink(); case ConnectionState.done: return Texture(textureId: cameraId); } }); } // Methods for binding UseCases to the lifecycle of the camera controlled // by a ProcessCameraProvider instance: /// Binds [preview] instance to the camera lifecycle controlled by the /// [processCameraProvider]. Future _bindPreviewToLifecycle() async { assert(processCameraProvider != null); assert(cameraSelector != null); if (previewIsBound || _previewIsPaused) { // Only bind if preview is not already bound or intentionally paused. return; } camera = await processCameraProvider! .bindToLifecycle(cameraSelector!, [preview!]); previewIsBound = true; } /// Unbinds [preview] instance to camera lifecycle controlled by the /// [processCameraProvider]. void _unbindPreviewFromLifecycle() { if (preview == null || !previewIsBound) { return; } assert(processCameraProvider != null); processCameraProvider!.unbind([preview!]); previewIsBound = false; } // Methods for mapping Flutter camera constants to CameraX constants: /// Returns [CameraSelector] lens direction that maps to specified /// [CameraLensDirection]. int _getCameraSelectorLensDirection(CameraLensDirection lensDirection) { switch (lensDirection) { case CameraLensDirection.front: return CameraSelector.lensFacingFront; case CameraLensDirection.back: return CameraSelector.lensFacingBack; case CameraLensDirection.external: return CameraSelector.lensFacingExternal; } } /// Returns [Surface] target rotation constant that maps to specified sensor /// orientation. int _getTargetRotation(int sensorOrientation) { switch (sensorOrientation) { case 90: return Surface.ROTATION_90; case 180: return Surface.ROTATION_180; case 270: return Surface.ROTATION_270; case 0: return Surface.ROTATION_0; default: throw ArgumentError( '"$sensorOrientation" is not a valid sensor orientation value'); } } /// Returns [ResolutionInfo] that maps to the specified resolution preset for /// a camera preview. ResolutionInfo? _getTargetResolutionForPreview(ResolutionPreset? resolution) { // TODO(camsim99): Implement resolution configuration. // https://github.com/flutter/flutter/issues/120462 return null; } // Methods for calls that need to be tested: /// Requests camera permissions. @visibleForTesting Future requestCameraPermissions(bool enableAudio) async { await SystemServices.requestCameraPermissions(enableAudio); } /// Subscribes the plugin as a listener to changes in device orientation. @visibleForTesting void startListeningForDeviceOrientationChange( bool cameraIsFrontFacing, int sensorOrientation) { SystemServices.startListeningForDeviceOrientationChange( cameraIsFrontFacing, sensorOrientation); } /// Returns a [CameraSelector] based on the specified camera lens direction. @visibleForTesting CameraSelector createCameraSelector(int cameraSelectorLensDirection) { switch (cameraSelectorLensDirection) { case CameraSelector.lensFacingFront: return CameraSelector.getDefaultFrontCamera(); case CameraSelector.lensFacingBack: return CameraSelector.getDefaultBackCamera(); default: return CameraSelector(lensFacing: cameraSelectorLensDirection); } } /// Returns a [Preview] configured with the specified target rotation and /// resolution. @visibleForTesting Preview createPreview(int targetRotation, ResolutionInfo? targetResolution) { return Preview( targetRotation: targetRotation, targetResolution: targetResolution); } } ================================================ FILE: packages/camera/camera_android_camerax/lib/src/android_camera_camerax_flutter_api_impls.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'camera.dart'; import 'camera_info.dart'; import 'camera_selector.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(); this.cameraInfoFlutterApi = cameraInfoFlutterApi ?? CameraInfoFlutterApiImpl(); this.cameraSelectorFlutterApi = cameraSelectorFlutterApi ?? CameraSelectorFlutterApiImpl(); this.processCameraProviderFlutterApi = processCameraProviderFlutterApi ?? ProcessCameraProviderFlutterApiImpl(); this.cameraFlutterApi = cameraFlutterApi ?? CameraFlutterApiImpl(); this.systemServicesFlutterApi = systemServicesFlutterApi ?? SystemServicesFlutterApiImpl(); } static bool _haveBeenSetUp = false; /// Mutable instance containing all Flutter Apis for Android CameraX Camera. /// /// This should only be changed for testing purposes. static AndroidCameraXCameraFlutterApis instance = AndroidCameraXCameraFlutterApis(); /// Handles callbacks methods for the native Java Object class. late final JavaObjectFlutterApi javaObjectFlutterApi; /// Flutter Api for [CameraInfo]. late final CameraInfoFlutterApiImpl cameraInfoFlutterApi; /// Flutter Api for [CameraSelector]. late final CameraSelectorFlutterApiImpl cameraSelectorFlutterApi; /// Flutter Api for [ProcessCameraProvider]. 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) { JavaObjectFlutterApi.setup(javaObjectFlutterApi); CameraInfoFlutterApi.setup(cameraInfoFlutterApi); CameraSelectorFlutterApi.setup(cameraSelectorFlutterApi); ProcessCameraProviderFlutterApi.setup(processCameraProviderFlutterApi); CameraFlutterApi.setup(cameraFlutterApi); SystemServicesFlutterApi.setup(systemServicesFlutterApi); _haveBeenSetUp = true; } } } ================================================ FILE: packages/camera/camera_android_camerax/lib/src/camera.dart ================================================ // Copyright 2013 The Flutter 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); }, ); } } ================================================ FILE: packages/camera/camera_android_camerax/lib/src/camera_info.dart ================================================ // Copyright 2013 The Flutter 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'; /// Represents the metadata of a camera. /// /// See https://developer.android.com/reference/androidx/camera/core/CameraInfo. class CameraInfo extends JavaObject { /// Constructs a [CameraInfo] that is not automatically attached to a native object. CameraInfo.detached( {BinaryMessenger? binaryMessenger, InstanceManager? instanceManager}) : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager) { _api = CameraInfoHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager); AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); } late final CameraInfoHostApiImpl _api; /// Gets sensor orientation degrees of camera. Future getSensorRotationDegrees() => _api.getSensorRotationDegreesFromInstance(this); } /// Host API implementation of [CameraInfo]. class CameraInfoHostApiImpl extends CameraInfoHostApi { /// Constructs a [CameraInfoHostApiImpl]. CameraInfoHostApiImpl( {super.binaryMessenger, InstanceManager? instanceManager}) { this.instanceManager = instanceManager ?? JavaObject.globalInstanceManager; } /// Maintains instances stored to communicate with native language objects. late final InstanceManager instanceManager; /// Gets sensor orientation degrees of [CameraInfo]. Future getSensorRotationDegreesFromInstance( CameraInfo instance, ) async { final int sensorRotationDegrees = await getSensorRotationDegrees( instanceManager.getIdentifier(instance)!); return sensorRotationDegrees; } } /// Flutter API implementation of [CameraInfo]. class CameraInfoFlutterApiImpl extends CameraInfoFlutterApi { /// Constructs a [CameraInfoFlutterApiImpl]. CameraInfoFlutterApiImpl({ 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( CameraInfo.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager), identifier, onCopy: (CameraInfo original) { return CameraInfo.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager); }, ); } } ================================================ FILE: packages/camera/camera_android_camerax/lib/src/camera_selector.dart ================================================ // Copyright 2013 The Flutter 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 'android_camera_camerax_flutter_api_impls.dart'; import 'camera_info.dart'; import 'camerax_library.g.dart'; import 'instance_manager.dart'; import 'java_object.dart'; /// Selects a camera for use. /// /// See https://developer.android.com/reference/androidx/camera/core/CameraSelector. class CameraSelector extends JavaObject { /// Creates a [CameraSelector]. CameraSelector( {BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, this.lensFacing}) : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager) { _api = CameraSelectorHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager); AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); _api.createFromInstance(this, lensFacing); } /// Creates a detached [CameraSelector]. CameraSelector.detached( {BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, this.lensFacing}) : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager) { _api = CameraSelectorHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager); AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); } late final CameraSelectorHostApiImpl _api; /// ID for front facing lens. /// /// See https://developer.android.com/reference/androidx/camera/core/CameraSelector#LENS_FACING_FRONT(). static const int lensFacingFront = 0; /// ID for back facing lens. /// /// See https://developer.android.com/reference/androidx/camera/core/CameraSelector#LENS_FACING_BACK(). static const int lensFacingBack = 1; /// ID for external lens. /// /// See https://developer.android.com/reference/androidx/camera/core/CameraSelector#LENS_FACING_EXTERNAL(). static const int lensFacingExternal = 2; /// ID for unknown lens. /// /// See https://developer.android.com/reference/androidx/camera/core/CameraSelector#LENS_FACING_UNKNOWN(). static const int lensFacingUnknown = -1; /// Selector for default front facing camera. static CameraSelector getDefaultFrontCamera({ BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, }) { return CameraSelector( binaryMessenger: binaryMessenger, instanceManager: instanceManager, lensFacing: lensFacingFront, ); } /// Selector for default back facing camera. static CameraSelector getDefaultBackCamera({ BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, }) { return CameraSelector( binaryMessenger: binaryMessenger, instanceManager: instanceManager, lensFacing: lensFacingBack, ); } /// Lens direction of this selector. final int? lensFacing; /// Filters available cameras based on provided [CameraInfo]s. Future> filter(List cameraInfos) { return _api.filterFromInstance(this, cameraInfos); } } /// Host API implementation of [CameraSelector]. class CameraSelectorHostApiImpl extends CameraSelectorHostApi { /// Constructs a [CameraSelectorHostApiImpl]. CameraSelectorHostApiImpl( {this.binaryMessenger, InstanceManager? instanceManager}) : super(binaryMessenger: binaryMessenger) { this.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. late final InstanceManager instanceManager; /// Creates a [CameraSelector] with the lens direction provided if specified. void createFromInstance(CameraSelector instance, int? lensFacing) { int? identifier = instanceManager.getIdentifier(instance); identifier ??= instanceManager.addDartCreatedInstance(instance, onCopy: (CameraSelector original) { return CameraSelector.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, lensFacing: original.lensFacing); }); create(identifier, lensFacing); } /// Filters a list of [CameraInfo]s based on the [CameraSelector]. Future> filterFromInstance( CameraSelector instance, List cameraInfos, ) async { int? identifier = instanceManager.getIdentifier(instance); identifier ??= instanceManager.addDartCreatedInstance(instance, onCopy: (CameraSelector original) { return CameraSelector.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, lensFacing: original.lensFacing); }); final List cameraInfoIds = cameraInfos .map((CameraInfo info) => instanceManager.getIdentifier(info)!) .toList(); final List filteredCameraInfoIds = await filter(identifier, cameraInfoIds); if (filteredCameraInfoIds.isEmpty) { return []; } return filteredCameraInfoIds .map((int? id) => instanceManager.getInstanceWithWeakReference(id!)! as CameraInfo) .toList(); } } /// Flutter API implementation of [CameraSelector]. class CameraSelectorFlutterApiImpl implements CameraSelectorFlutterApi { /// Constructs a [CameraSelectorFlutterApiImpl]. CameraSelectorFlutterApiImpl({ 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, int? lensFacing) { instanceManager.addHostCreatedInstance( CameraSelector.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, lensFacing: lensFacing), identifier, onCopy: (CameraSelector original) { return CameraSelector.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, lensFacing: original.lensFacing); }, ); } } ================================================ FILE: packages/camera/camera_android_camerax/lib/src/camerax_library.g.dart ================================================ // Copyright 2013 The Flutter 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.2.9), 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 Uint8List, Int32List, Int64List, Float64List; import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer; import 'package:flutter/services.dart'; class ResolutionInfo { ResolutionInfo({ required this.width, required this.height, }); int width; int height; Object encode() { final Map pigeonMap = {}; pigeonMap['width'] = width; pigeonMap['height'] = height; return pigeonMap; } static ResolutionInfo decode(Object message) { final Map pigeonMap = message as Map; return ResolutionInfo( width: pigeonMap['width']! as int, height: pigeonMap['height']! as int, ); } } 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(); } class JavaObjectHostApi { /// Constructor for [JavaObjectHostApi]. 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. JavaObjectHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = _JavaObjectHostApiCodec(); Future dispose(int arg_identifier) async { 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) { 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 _JavaObjectFlutterApiCodec extends StandardMessageCodec { const _JavaObjectFlutterApiCodec(); } abstract class JavaObjectFlutterApi { static const MessageCodec codec = _JavaObjectFlutterApiCodec(); void dispose(int identifier); static void setup(JavaObjectFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.JavaObjectFlutterApi.dispose', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.JavaObjectFlutterApi.dispose 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.JavaObjectFlutterApi.dispose was null, expected non-null int.'); api.dispose(arg_identifier!); return; }); } } } } class _CameraInfoHostApiCodec extends StandardMessageCodec { const _CameraInfoHostApiCodec(); } class CameraInfoHostApi { /// Constructor for [CameraInfoHostApi]. 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. CameraInfoHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = _CameraInfoHostApiCodec(); Future getSensorRotationDegrees(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.CameraInfoHostApi.getSensorRotationDegrees', 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 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?)!; } } } class _CameraInfoFlutterApiCodec extends StandardMessageCodec { const _CameraInfoFlutterApiCodec(); } abstract class CameraInfoFlutterApi { static const MessageCodec codec = _CameraInfoFlutterApiCodec(); void create(int identifier); static void setup(CameraInfoFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.CameraInfoFlutterApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.CameraInfoFlutterApi.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.CameraInfoFlutterApi.create was null, expected non-null int.'); api.create(arg_identifier!); return; }); } } } } class _CameraSelectorHostApiCodec extends StandardMessageCodec { const _CameraSelectorHostApiCodec(); } class CameraSelectorHostApi { /// Constructor for [CameraSelectorHostApi]. 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. CameraSelectorHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = _CameraSelectorHostApiCodec(); Future create(int arg_identifier, int? arg_lensFacing) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.CameraSelectorHostApi.create', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_identifier, arg_lensFacing]) 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> filter( int arg_identifier, List arg_cameraInfoIds) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.CameraSelectorHostApi.filter', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_identifier, arg_cameraInfoIds]) 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 List?)!.cast(); } } } class _CameraSelectorFlutterApiCodec extends StandardMessageCodec { const _CameraSelectorFlutterApiCodec(); } abstract class CameraSelectorFlutterApi { static const MessageCodec codec = _CameraSelectorFlutterApiCodec(); void create(int identifier, int? lensFacing); static void setup(CameraSelectorFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.CameraSelectorFlutterApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.CameraSelectorFlutterApi.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.CameraSelectorFlutterApi.create was null, expected non-null int.'); final int? arg_lensFacing = (args[1] as int?); api.create(arg_identifier!, arg_lensFacing); return; }); } } } } class _ProcessCameraProviderHostApiCodec extends StandardMessageCodec { const _ProcessCameraProviderHostApiCodec(); } class ProcessCameraProviderHostApi { /// Constructor for [ProcessCameraProviderHostApi]. 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. ProcessCameraProviderHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = _ProcessCameraProviderHostApiCodec(); Future getInstance() async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.ProcessCameraProviderHostApi.getInstance', 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 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> getAvailableCameraInfos(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.ProcessCameraProviderHostApi.getAvailableCameraInfos', 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 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 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 { const _ProcessCameraProviderFlutterApiCodec(); } abstract class ProcessCameraProviderFlutterApi { static const MessageCodec codec = _ProcessCameraProviderFlutterApiCodec(); void create(int identifier); static void setup(ProcessCameraProviderFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.ProcessCameraProviderFlutterApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.ProcessCameraProviderFlutterApi.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.ProcessCameraProviderFlutterApi.create was null, expected non-null int.'); api.create(arg_identifier!); return; }); } } } } 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); void onCameraError(String errorDescription); 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; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.SystemServicesFlutterApi.onCameraError', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.SystemServicesFlutterApi.onCameraError was null.'); final List args = (message as List?)!; final String? arg_errorDescription = (args[0] as String?); assert(arg_errorDescription != null, 'Argument for dev.flutter.pigeon.SystemServicesFlutterApi.onCameraError was null, expected non-null String.'); api.onCameraError(arg_errorDescription!); return; }); } } } } class _PreviewHostApiCodec extends StandardMessageCodec { const _PreviewHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is ResolutionInfo) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is ResolutionInfo) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return ResolutionInfo.decode(readValue(buffer)!); case 129: return ResolutionInfo.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } class PreviewHostApi { /// Constructor for [PreviewHostApi]. 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. PreviewHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = _PreviewHostApiCodec(); Future create(int arg_identifier, int? arg_rotation, ResolutionInfo? arg_targetResolution) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.PreviewHostApi.create', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel .send([arg_identifier, arg_rotation, arg_targetResolution]) 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 setSurfaceProvider(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.PreviewHostApi.setSurfaceProvider', 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 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 releaseFlutterSurfaceTexture() async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.PreviewHostApi.releaseFlutterSurfaceTexture', 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; } } Future getResolutionInfo(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.PreviewHostApi.getResolutionInfo', 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 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 ResolutionInfo?)!; } } } ================================================ FILE: packages/camera/camera_android_camerax/lib/src/instance_manager.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; /// Maintains instances used to communicate with the native objects they /// represent. /// /// Added instances are stored as weak references and their copies are stored /// as strong references to maintain access to their variables and callback /// methods. Both are stored with the same identifier. /// /// When a weak referenced instance becomes inaccessible, /// [onWeakReferenceRemoved] is called with its associated identifier. /// /// If an instance is retrieved and has the possibility to be used, /// (e.g. calling [getInstanceWithWeakReference]) a copy of the strong reference /// is added as a weak reference with the same identifier. This prevents a /// scenario where the weak referenced instance was released and then later /// returned by the host platform. class InstanceManager { /// Constructs an [InstanceManager]. InstanceManager({required void Function(int) onWeakReferenceRemoved}) { this.onWeakReferenceRemoved = (int identifier) { debugPrint('Releasing weak reference with identifier: $identifier'); _weakInstances.remove(identifier); onWeakReferenceRemoved(identifier); }; _finalizer = Finalizer(this.onWeakReferenceRemoved); } // Identifiers are locked to a specific range to avoid collisions with objects // created simultaneously by the host platform. // Host uses identifiers >= 2^16 and Dart is expected to use values n where, // 0 <= n < 2^16. static const int _maxDartCreatedIdentifier = 65536; // Expando is used because it doesn't prevent its keys from becoming // inaccessible. This allows the manager to efficiently retrieve an identifier // of an instance without holding a strong reference to that instance. // // It also doesn't use `==` to search for identifiers, which would lead to an // infinite loop when comparing an object to its copy. (i.e. which was caused // by calling instanceManager.getIdentifier() inside of `==` while this was a // HashMap). final Expando _identifiers = Expando(); final Map> _weakInstances = >{}; final Map _strongInstances = {}; final Map _copyCallbacks = {}; late final Finalizer _finalizer; int _nextIdentifier = 0; /// Called when a weak referenced instance is removed by [removeWeakReference] /// or becomes inaccessible. late final void Function(int) onWeakReferenceRemoved; /// Adds a new instance that was instantiated by Dart. /// /// In other words, Dart wants to add a new instance that will represent /// an object that will be instantiated on the host platform. /// /// Throws assertion error if the instance has already been added. /// /// Returns the randomly generated id of the [instance] added. int addDartCreatedInstance( T instance, { required T Function(T original) onCopy, }) { assert(getIdentifier(instance) == null); final int identifier = _nextUniqueIdentifier(); _addInstanceWithIdentifier(instance, identifier, onCopy: onCopy); return identifier; } /// Removes the instance, if present, and call [onWeakReferenceRemoved] with /// its identifier. /// /// Returns the identifier associated with the removed instance. Otherwise, /// `null` if the instance was not found in this manager. /// /// This does not remove the the strong referenced instance associated with /// [instance]. This can be done with [remove]. int? removeWeakReference(Object instance) { final int? identifier = getIdentifier(instance); if (identifier == null) { return null; } _identifiers[instance] = null; _finalizer.detach(instance); onWeakReferenceRemoved(identifier); return identifier; } /// Removes [identifier] and its associated strongly referenced instance, if /// present, from the manager. /// /// Returns the strong referenced instance associated with [identifier] before /// it was removed. Returns `null` if [identifier] was not associated with /// any strong reference. /// /// This does not remove the the weak referenced instance associtated with /// [identifier]. This can be done with [removeWeakReference]. T? remove(int identifier) { debugPrint('Releasing strong reference with identifier: $identifier'); _copyCallbacks.remove(identifier); return _strongInstances.remove(identifier) as T?; } /// Retrieves the instance associated with identifier. /// /// The value returned is chosen from the following order: /// /// 1. A weakly referenced instance associated with identifier. /// 2. If the only instance associated with identifier is a strongly /// referenced instance, a copy of the instance is added as a weak reference /// with the same identifier. Returning the newly created copy. /// 3. If no instance is associated with identifier, returns null. /// /// This method also expects the host `InstanceManager` to have a strong /// reference to the instance the identifier is associated with. T? getInstanceWithWeakReference(int identifier) { final T? weakInstance = _weakInstances[identifier]?.target as T?; if (weakInstance == null) { final T? strongInstance = _strongInstances[identifier] as T?; if (strongInstance != null) { // 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); _finalizer.attach(copy, identifier, detach: copy); return copy; } return strongInstance; } return weakInstance; } /// Retrieves the identifier associated with instance. int? getIdentifier(Object instance) { return _identifiers[instance]; } /// Adds a new instance that was instantiated by the host platform. /// /// In other words, the host platform wants to add a new instance that /// represents an object on the host platform. Stored with [identifier]. /// /// Throws assertion error if the instance or its identifier has already been /// added. /// /// Returns unique identifier of the [instance] added. void addHostCreatedInstance( T instance, int identifier, { required T Function(T original) onCopy, }) { assert(!containsIdentifier(identifier)); assert(getIdentifier(instance) == null); assert(identifier >= 0); _addInstanceWithIdentifier(instance, identifier, onCopy: onCopy); } void _addInstanceWithIdentifier( T instance, int identifier, { required T Function(T original) onCopy, }) { _identifiers[instance] = identifier; _weakInstances[identifier] = WeakReference(instance); _finalizer.attach(instance, identifier, detach: instance); final Object copy = onCopy(instance); _identifiers[copy] = identifier; _strongInstances[identifier] = copy; _copyCallbacks[identifier] = onCopy; } /// Whether this manager contains the given [identifier]. bool containsIdentifier(int identifier) { return _weakInstances.containsKey(identifier) || _strongInstances.containsKey(identifier); } int _nextUniqueIdentifier() { late int identifier; do { identifier = _nextIdentifier; _nextIdentifier = (_nextIdentifier + 1) % _maxDartCreatedIdentifier; } while (containsIdentifier(identifier)); return identifier; } } ================================================ FILE: packages/camera/camera_android_camerax/lib/src/java_object.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart' show immutable; import 'package:flutter/services.dart'; import 'camerax_library.g.dart'; import 'instance_manager.dart'; /// Root of the Java class hierarchy. /// /// See https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html. @immutable class JavaObject { /// Constructs a [JavaObject] without creating the associated Java object. /// /// This should only be used by subclasses created by this library or to /// create copies. JavaObject.detached({ BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, }) : _api = JavaObjectHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ); /// Global instance of [InstanceManager]. static final InstanceManager globalInstanceManager = InstanceManager( onWeakReferenceRemoved: (int identifier) { JavaObjectHostApiImpl().dispose(identifier); }, ); /// Pigeon Host Api implementation for [JavaObject]. final JavaObjectHostApiImpl _api; /// Release the reference to a native Java instance. static void dispose(JavaObject instance) { instance._api.instanceManager.removeWeakReference(instance); } } /// Handles methods calls to the native Java Object class. class JavaObjectHostApiImpl extends JavaObjectHostApi { /// Constructs a [JavaObjectHostApiImpl]. JavaObjectHostApiImpl({ this.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, 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; /// Maintains instances stored to communicate with native language objects. final InstanceManager instanceManager; } /// Handles callbacks methods for the native Java Object class. class JavaObjectFlutterApiImpl implements JavaObjectFlutterApi { /// Constructs a [JavaObjectFlutterApiImpl]. JavaObjectFlutterApiImpl({InstanceManager? instanceManager}) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with native language objects. final InstanceManager instanceManager; @override void dispose(int identifier) { instanceManager.remove(identifier); } } ================================================ FILE: packages/camera/camera_android_camerax/lib/src/preview.dart ================================================ // Copyright 2013 The Flutter 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 'camerax_library.g.dart'; import 'instance_manager.dart'; import 'java_object.dart'; import 'use_case.dart'; /// Use case that provides a camera preview stream for display. /// /// See https://developer.android.com/reference/androidx/camera/core/Preview. class Preview extends UseCase { /// Creates a [Preview]. Preview( {BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, this.targetRotation, this.targetResolution}) : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager) { _api = PreviewHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager); _api.createFromInstance(this, targetRotation, targetResolution); } /// Constructs a [Preview] that is not automatically attached to a native object. Preview.detached( {BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, this.targetRotation, this.targetResolution}) : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager) { _api = PreviewHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager); } late final PreviewHostApiImpl _api; /// Target rotation of the camera used for the preview stream. final int? targetRotation; /// Target resolution of the camera preview stream. final ResolutionInfo? targetResolution; /// Sets the surface provider for the preview stream. /// /// Returns the ID of the FlutterSurfaceTextureEntry used on the native end /// used to display the preview stream on a [Texture] of the same ID. Future setSurfaceProvider() { return _api.setSurfaceProviderFromInstance(this); } /// Releases Flutter surface texture used to provide a surface for the preview /// stream. void releaseFlutterSurfaceTexture() { _api.releaseFlutterSurfaceTextureFromInstance(); } /// Retrieves the selected resolution information of this [Preview]. Future getResolutionInfo() { return _api.getResolutionInfoFromInstance(this); } } /// Host API implementation of [Preview]. class PreviewHostApiImpl extends PreviewHostApi { /// Constructs a [PreviewHostApiImpl]. PreviewHostApiImpl({this.binaryMessenger, InstanceManager? instanceManager}) { this.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. late final InstanceManager instanceManager; /// Creates a [Preview] with the target rotation provided if specified. void createFromInstance( Preview instance, int? targetRotation, ResolutionInfo? targetResolution) { final int identifier = instanceManager.addDartCreatedInstance(instance, onCopy: (Preview original) { return Preview.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, targetRotation: original.targetRotation); }); create(identifier, targetRotation, targetResolution); } /// Sets the surface provider of the specified [Preview] instance and returns /// the ID corresponding to the surface it will provide. Future setSurfaceProviderFromInstance(Preview instance) async { final int? identifier = instanceManager.getIdentifier(instance); assert(identifier != null, 'No Preview has the identifer of that requested to set the surface provider on.'); final int surfaceTextureEntryId = await setSurfaceProvider(identifier!); return surfaceTextureEntryId; } /// Releases Flutter surface texture used to provide a surface for the preview /// stream if a surface provider was set for a [Preview] instance. void releaseFlutterSurfaceTextureFromInstance() { releaseFlutterSurfaceTexture(); } /// Gets the resolution information of the specified [Preview] instance. Future getResolutionInfoFromInstance(Preview instance) async { final int? identifier = instanceManager.getIdentifier(instance); assert(identifier != null, 'No Preview has the identifer of that requested to get the resolution information for.'); final ResolutionInfo resolutionInfo = await getResolutionInfo(identifier!); return resolutionInfo; } } ================================================ FILE: packages/camera/camera_android_camerax/lib/src/process_camera_provider.dart ================================================ // Copyright 2013 The Flutter 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 'android_camera_camerax_flutter_api_impls.dart'; import 'camera.dart'; import 'camera_info.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. /// /// See https://developer.android.com/reference/androidx/camera/lifecycle/ProcessCameraProvider. class ProcessCameraProvider extends JavaObject { /// Creates a detached [ProcessCameraProvider]. ProcessCameraProvider.detached( {BinaryMessenger? binaryMessenger, InstanceManager? instanceManager}) : super.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager) { _api = ProcessCameraProviderHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager); AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); } late final ProcessCameraProviderHostApiImpl _api; /// Gets an instance of [ProcessCameraProvider]. static Future getInstance( {BinaryMessenger? binaryMessenger, InstanceManager? instanceManager}) { AndroidCameraXCameraFlutterApis.instance.ensureSetUp(); final ProcessCameraProviderHostApiImpl api = ProcessCameraProviderHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager); return api.getInstancefromInstances(); } /// Retrieves the cameras available to the device. 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]. class ProcessCameraProviderHostApiImpl extends ProcessCameraProviderHostApi { /// Creates a [ProcessCameraProviderHostApiImpl]. ProcessCameraProviderHostApiImpl( {this.binaryMessenger, InstanceManager? instanceManager}) : super(binaryMessenger: binaryMessenger) { this.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. late final InstanceManager instanceManager; /// Retrieves an instance of a ProcessCameraProvider from the context of /// the FlutterActivity. Future getInstancefromInstances() async { return instanceManager.getInstanceWithWeakReference(await getInstance())! 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 { 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]. class ProcessCameraProviderFlutterApiImpl implements ProcessCameraProviderFlutterApi { /// Constructs a [ProcessCameraProviderFlutterApiImpl]. ProcessCameraProviderFlutterApiImpl({ 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( ProcessCameraProvider.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager), identifier, onCopy: (ProcessCameraProvider original) { return ProcessCameraProvider.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager); }, ); } } ================================================ FILE: packages/camera/camera_android_camerax/lib/src/surface.dart ================================================ // Copyright 2013 The Flutter 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'; /// Handle onto the raw buffer managed by screen compositor. /// /// See https://developer.android.com/reference/android/view/Surface.html. class Surface extends JavaObject { /// Creates a detached [UseCase]. Surface.detached({super.binaryMessenger, super.instanceManager}) : super.detached(); /// Rotation constant to signify the natural orientation. /// /// See https://developer.android.com/reference/android/view/Surface.html#ROTATION_0. static const int ROTATION_0 = 0; /// Rotation constant to signify a 90 degrees rotation. /// /// See https://developer.android.com/reference/android/view/Surface.html#ROTATION_90. static const int ROTATION_90 = 1; /// Rotation constant to signify a 180 degrees rotation. /// /// See https://developer.android.com/reference/android/view/Surface.html#ROTATION_180. static const int ROTATION_180 = 2; /// Rotation constant to signify a 270 degrees rotation. /// /// See https://developer.android.com/reference/android/view/Surface.html#ROTATION_270. static const int ROTATION_270 = 3; } ================================================ FILE: packages/camera/camera_android_camerax/lib/src/system_services.dart ================================================ // Copyright 2013 The Flutter 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 and other informational streams. 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(); /// Stream that emits the errors caused by camera usage on the native side. static final StreamController cameraErrorStreamController = 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'); } } /// Callback method for any errors caused by camera usage on the Java side. @override void onCameraError(String errorDescription) { SystemServices.cameraErrorStreamController.add(errorDescription); } } ================================================ FILE: packages/camera/camera_android_camerax/lib/src/use_case.dart ================================================ // Copyright 2013 The Flutter 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(); } ================================================ FILE: packages/camera/camera_android_camerax/pigeons/camerax_library.dart ================================================ // Copyright 2013 The Flutter 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/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', 'found in the LICENSE file.', ]), javaOut: 'android/src/main/java/io/flutter/plugins/camerax/GeneratedCameraXLibrary.java', javaOptions: JavaOptions( package: 'io.flutter.plugins.camerax', className: 'GeneratedCameraXLibrary', copyrightHeader: [ 'Copyright 2013 The Flutter Authors. All rights reserved.', 'Use of this source code is governed by a BSD-style license that can be', 'found in the LICENSE file.', ], ), ), ) class ResolutionInfo { ResolutionInfo({ required this.width, required this.height, }); int width; int height; } class CameraPermissionsErrorData { CameraPermissionsErrorData({ required this.errorCode, required this.description, }); String errorCode; String description; } @HostApi(dartHostTestHandler: 'TestJavaObjectHostApi') abstract class JavaObjectHostApi { void dispose(int identifier); } @FlutterApi() abstract class JavaObjectFlutterApi { void dispose(int identifier); } @HostApi(dartHostTestHandler: 'TestCameraInfoHostApi') abstract class CameraInfoHostApi { int getSensorRotationDegrees(int identifier); } @FlutterApi() abstract class CameraInfoFlutterApi { void create(int identifier); } @HostApi(dartHostTestHandler: 'TestCameraSelectorHostApi') abstract class CameraSelectorHostApi { void create(int identifier, int? lensFacing); List filter(int identifier, List cameraInfoIds); } @FlutterApi() abstract class CameraSelectorFlutterApi { void create(int identifier, int? lensFacing); } @HostApi(dartHostTestHandler: 'TestProcessCameraProviderHostApi') abstract class ProcessCameraProviderHostApi { @async 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); void onCameraError(String errorDescription); } @HostApi(dartHostTestHandler: 'TestPreviewHostApi') abstract class PreviewHostApi { void create(int identifier, int? rotation, ResolutionInfo? targetResolution); int setSurfaceProvider(int identifier); void releaseFlutterSurfaceTexture(); ResolutionInfo getResolutionInfo(int identifier); } ================================================ FILE: packages/camera/camera_android_camerax/pubspec.yaml ================================================ name: camera_android_camerax description: Android implementation of the camera plugin using the CameraX library. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_android_camerax issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 publish_to: 'none' environment: sdk: '>=2.17.0 <3.0.0' flutter: ">=3.0.0" flutter: plugin: implements: camera platforms: android: package: io.flutter.plugins.camerax pluginClass: CameraAndroidCameraxPlugin dartPluginClass: AndroidCameraCameraX dependencies: camera_platform_interface: ^2.2.0 flutter: sdk: flutter integration_test: sdk: flutter stream_transform: ^2.1.0 dev_dependencies: async: ^2.5.0 build_runner: ^2.1.4 flutter_test: sdk: flutter mockito: ^5.3.2 pigeon: ^3.2.6 ================================================ FILE: packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart ================================================ // Copyright 2013 The Flutter 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:async/async.dart'; import 'package:camera_android_camerax/camera_android_camerax.dart'; 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/camerax_library.g.dart'; import 'package:camera_android_camerax/src/preview.dart'; import 'package:camera_android_camerax/src/process_camera_provider.dart'; import 'package:camera_android_camerax/src/system_services.dart'; import 'package:camera_android_camerax/src/use_case.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart' show DeviceOrientation; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'android_camera_camerax_test.mocks.dart'; @GenerateNiceMocks(>[ MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), ]) @GenerateMocks([BuildContext]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); test('Should fetch CameraDescription instances for available cameras', () async { // Arrange final MockAndroidCameraCamerax camera = MockAndroidCameraCamerax(); camera.processCameraProvider = MockProcessCameraProvider(); final List returnData = [ { 'name': 'Camera 0', 'lensFacing': 'back', 'sensorOrientation': 0 }, { 'name': 'Camera 1', 'lensFacing': 'front', 'sensorOrientation': 90 } ]; // Create mocks to use final MockCameraInfo mockFrontCameraInfo = MockCameraInfo(); final MockCameraInfo mockBackCameraInfo = MockCameraInfo(); // Mock calls to native platform when(camera.processCameraProvider!.getAvailableCameraInfos()).thenAnswer( (_) async => [mockBackCameraInfo, mockFrontCameraInfo]); when(camera.mockBackCameraSelector .filter([mockFrontCameraInfo])) .thenAnswer((_) async => []); when(camera.mockBackCameraSelector .filter([mockBackCameraInfo])) .thenAnswer((_) async => [mockBackCameraInfo]); when(camera.mockFrontCameraSelector .filter([mockBackCameraInfo])) .thenAnswer((_) async => []); when(camera.mockFrontCameraSelector .filter([mockFrontCameraInfo])) .thenAnswer((_) async => [mockFrontCameraInfo]); when(mockBackCameraInfo.getSensorRotationDegrees()) .thenAnswer((_) async => 0); when(mockFrontCameraInfo.getSensorRotationDegrees()) .thenAnswer((_) async => 90); final List cameraDescriptions = await camera.availableCameras(); expect(cameraDescriptions.length, returnData.length); for (int i = 0; i < returnData.length; i++) { final Map typedData = (returnData[i] as Map).cast(); final CameraDescription cameraDescription = CameraDescription( name: typedData['name']! as String, lensDirection: (typedData['lensFacing']! as String) == 'front' ? CameraLensDirection.front : CameraLensDirection.back, sensorOrientation: typedData['sensorOrientation']! as int, ); expect(cameraDescriptions[i], cameraDescription); } }); test( 'createCamera requests permissions, starts listening for device orientation changes, and returns flutter surface texture ID', () async { final MockAndroidCameraCamerax camera = MockAndroidCameraCamerax(); camera.processCameraProvider = MockProcessCameraProvider(); const CameraLensDirection testLensDirection = CameraLensDirection.back; const int testSensorOrientation = 90; const CameraDescription testCameraDescription = CameraDescription( name: 'cameraName', lensDirection: testLensDirection, sensorOrientation: testSensorOrientation); const ResolutionPreset testResolutionPreset = ResolutionPreset.veryHigh; const bool enableAudio = true; const int testSurfaceTextureId = 6; when(camera.testPreview.setSurfaceProvider()) .thenAnswer((_) async => testSurfaceTextureId); expect( await camera.createCamera(testCameraDescription, testResolutionPreset, enableAudio: enableAudio), equals(testSurfaceTextureId)); // Verify permissions are requested and the camera starts listening for device orientation changes. expect(camera.cameraPermissionsRequested, isTrue); expect(camera.startedListeningForDeviceOrientationChanges, isTrue); // Verify CameraSelector is set with appropriate lens direction. expect(camera.cameraSelector, equals(camera.mockBackCameraSelector)); // Verify the camera's Preview instance is instantiated properly. expect(camera.preview, equals(camera.testPreview)); // Verify the camera's Preview instance has its surface provider set. verify(camera.preview!.setSurfaceProvider()); }); test( 'initializeCamera throws AssertionError when createCamera has not been called before initializedCamera', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); expect(() => camera.initializeCamera(3), throwsAssertionError); }); test('initializeCamera sends expected CameraInitializedEvent', () async { final MockAndroidCameraCamerax camera = MockAndroidCameraCamerax(); camera.processCameraProvider = MockProcessCameraProvider(); const int cameraId = 10; const CameraLensDirection testLensDirection = CameraLensDirection.back; const int testSensorOrientation = 90; const CameraDescription testCameraDescription = CameraDescription( name: 'cameraName', lensDirection: testLensDirection, sensorOrientation: testSensorOrientation); const ResolutionPreset testResolutionPreset = ResolutionPreset.veryHigh; const bool enableAudio = true; const int resolutionWidth = 350; const int resolutionHeight = 750; final Camera mockCamera = MockCamera(); final ResolutionInfo testResolutionInfo = ResolutionInfo(width: resolutionWidth, height: resolutionHeight); // TODO(camsim99): Modify this when camera configuration is supported and // defualt values no longer being used. // https://github.com/flutter/flutter/issues/120468 // https://github.com/flutter/flutter/issues/120467 final CameraInitializedEvent testCameraInitializedEvent = CameraInitializedEvent( cameraId, resolutionWidth.toDouble(), resolutionHeight.toDouble(), ExposureMode.auto, false, FocusMode.auto, false); // Call createCamera. when(camera.testPreview.setSurfaceProvider()) .thenAnswer((_) async => cameraId); await camera.createCamera(testCameraDescription, testResolutionPreset, enableAudio: enableAudio); when(camera.processCameraProvider!.bindToLifecycle( camera.cameraSelector!, [camera.testPreview])) .thenAnswer((_) async => mockCamera); when(camera.testPreview.getResolutionInfo()) .thenAnswer((_) async => testResolutionInfo); // Start listening to camera events stream to verify the proper CameraInitializedEvent is sent. camera.cameraEventStreamController.stream.listen((CameraEvent event) { expect(event, const TypeMatcher()); expect(event, equals(testCameraInitializedEvent)); }); await camera.initializeCamera(cameraId); // Verify preview was bound and unbound to get preview resolution information. verify(camera.processCameraProvider!.bindToLifecycle( camera.cameraSelector!, [camera.testPreview])); verify(camera.processCameraProvider!.unbind([camera.testPreview])); // Check camera instance was received, but preview is no longer bound. expect(camera.camera, equals(mockCamera)); expect(camera.previewIsBound, isFalse); }); test('dispose releases Flutter surface texture and unbinds all use cases', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); camera.preview = MockPreview(); camera.processCameraProvider = MockProcessCameraProvider(); camera.dispose(3); verify(camera.preview!.releaseFlutterSurfaceTexture()); verify(camera.processCameraProvider!.unbindAll()); }); test('onCameraInitialized stream emits CameraInitializedEvents', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); const int cameraId = 16; final Stream eventStream = camera.onCameraInitialized(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); const CameraInitializedEvent testEvent = CameraInitializedEvent( cameraId, 320, 80, ExposureMode.auto, false, FocusMode.auto, false); camera.cameraEventStreamController.add(testEvent); expect(await streamQueue.next, testEvent); await streamQueue.cancel(); }); test('onCameraError stream emits errors caught by system services', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); const int cameraId = 27; const String testErrorDescription = 'Test error description!'; final Stream eventStream = camera.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); SystemServices.cameraErrorStreamController.add(testErrorDescription); expect(await streamQueue.next, equals(const CameraErrorEvent(cameraId, testErrorDescription))); await streamQueue.cancel(); }); test( 'onDeviceOrientationChanged stream emits changes in device oreintation detected by system services', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); final Stream eventStream = camera.onDeviceOrientationChanged(); final StreamQueue streamQueue = StreamQueue(eventStream); const DeviceOrientationChangedEvent testEvent = DeviceOrientationChangedEvent(DeviceOrientation.portraitDown); SystemServices.deviceOrientationChangedStreamController.add(testEvent); expect(await streamQueue.next, testEvent); await streamQueue.cancel(); }); test( 'pausePreview unbinds preview from lifecycle when preview is nonnull and has been bound to lifecycle', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); camera.processCameraProvider = MockProcessCameraProvider(); camera.preview = MockPreview(); camera.previewIsBound = true; await camera.pausePreview(579); verify(camera.processCameraProvider!.unbind([camera.preview!])); expect(camera.previewIsBound, isFalse); }); test( 'pausePreview does not unbind preview from lifecycle when preview has not been bound to lifecycle', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); camera.processCameraProvider = MockProcessCameraProvider(); camera.preview = MockPreview(); await camera.pausePreview(632); verifyNever( camera.processCameraProvider!.unbind([camera.preview!])); }); test('resumePreview does not bind preview to lifecycle if already bound', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); camera.processCameraProvider = MockProcessCameraProvider(); camera.cameraSelector = MockCameraSelector(); camera.preview = MockPreview(); camera.previewIsBound = true; await camera.resumePreview(78); verifyNever(camera.processCameraProvider! .bindToLifecycle(camera.cameraSelector!, [camera.preview!])); }); test('resumePreview binds preview to lifecycle if not already bound', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); camera.processCameraProvider = MockProcessCameraProvider(); camera.cameraSelector = MockCameraSelector(); camera.preview = MockPreview(); await camera.resumePreview(78); verify(camera.processCameraProvider! .bindToLifecycle(camera.cameraSelector!, [camera.preview!])); }); test( 'buildPreview returns a FutureBuilder that does not return a Texture until the preview is bound to the lifecycle', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); const int textureId = 75; camera.processCameraProvider = MockProcessCameraProvider(); camera.cameraSelector = MockCameraSelector(); camera.preview = MockPreview(); final FutureBuilder previewWidget = camera.buildPreview(textureId) as FutureBuilder; expect( previewWidget.builder( MockBuildContext(), const AsyncSnapshot.nothing()), isA()); expect( previewWidget.builder( MockBuildContext(), const AsyncSnapshot.waiting()), isA()); expect( previewWidget.builder(MockBuildContext(), const AsyncSnapshot.withData(ConnectionState.active, null)), isA()); }); test( 'buildPreview returns a FutureBuilder that returns a Texture once the preview is bound to the lifecycle', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); const int textureId = 75; camera.processCameraProvider = MockProcessCameraProvider(); camera.cameraSelector = MockCameraSelector(); camera.preview = MockPreview(); final FutureBuilder previewWidget = camera.buildPreview(textureId) as FutureBuilder; final Texture previewTexture = previewWidget.builder(MockBuildContext(), const AsyncSnapshot.withData(ConnectionState.done, null)) as Texture; expect(previewTexture.textureId, equals(textureId)); }); } /// Mock of [AndroidCameraCameraX] that stubs behavior of some methods for /// testing. class MockAndroidCameraCamerax extends AndroidCameraCameraX { bool cameraPermissionsRequested = false; bool startedListeningForDeviceOrientationChanges = false; final MockPreview testPreview = MockPreview(); final MockCameraSelector mockBackCameraSelector = MockCameraSelector(); final MockCameraSelector mockFrontCameraSelector = MockCameraSelector(); @override Future requestCameraPermissions(bool enableAudio) async { cameraPermissionsRequested = true; } @override void startListeningForDeviceOrientationChange( bool cameraIsFrontFacing, int sensorOrientation) { startedListeningForDeviceOrientationChanges = true; return; } @override CameraSelector createCameraSelector(int cameraSelectorLensDirection) { switch (cameraSelectorLensDirection) { case CameraSelector.lensFacingFront: return mockFrontCameraSelector; case CameraSelector.lensFacingBack: default: return mockBackCameraSelector; } } @override Preview createPreview(int targetRotation, ResolutionInfo? targetResolution) { return testPreview; } } ================================================ FILE: packages/camera/camera_android_camerax/test/android_camera_camerax_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in camera_android_camerax/test/android_camera_camerax_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i8; import 'package:camera_android_camerax/src/camera.dart' as _i3; import 'package:camera_android_camerax/src/camera_info.dart' as _i7; import 'package:camera_android_camerax/src/camera_selector.dart' as _i9; import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i2; import 'package:camera_android_camerax/src/preview.dart' as _i10; import 'package:camera_android_camerax/src/process_camera_provider.dart' as _i11; import 'package:camera_android_camerax/src/use_case.dart' as _i12; import 'package:flutter/foundation.dart' as _i6; import 'package:flutter/services.dart' as _i5; import 'package:flutter/src/widgets/framework.dart' as _i4; import 'package:flutter/src/widgets/notification_listener.dart' as _i13; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class class _FakeResolutionInfo_0 extends _i1.SmartFake implements _i2.ResolutionInfo { _FakeResolutionInfo_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeCamera_1 extends _i1.SmartFake implements _i3.Camera { _FakeCamera_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWidget_2 extends _i1.SmartFake implements _i4.Widget { _FakeWidget_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeInheritedWidget_3 extends _i1.SmartFake implements _i4.InheritedWidget { _FakeInheritedWidget_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeDiagnosticsNode_4 extends _i1.SmartFake implements _i6.DiagnosticsNode { _FakeDiagnosticsNode_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({ _i6.TextTreeConfiguration? parentConfiguration, _i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info, }) => super.toString(); } /// A class which mocks [Camera]. /// /// See the documentation for Mockito's code generation for more information. class MockCamera extends _i1.Mock implements _i3.Camera {} /// A class which mocks [CameraInfo]. /// /// See the documentation for Mockito's code generation for more information. class MockCameraInfo extends _i1.Mock implements _i7.CameraInfo { @override _i8.Future getSensorRotationDegrees() => (super.noSuchMethod( Invocation.method( #getSensorRotationDegrees, [], ), returnValue: _i8.Future.value(0), returnValueForMissingStub: _i8.Future.value(0), ) as _i8.Future); } /// A class which mocks [CameraSelector]. /// /// See the documentation for Mockito's code generation for more information. class MockCameraSelector extends _i1.Mock implements _i9.CameraSelector { @override _i8.Future> filter(List<_i7.CameraInfo>? cameraInfos) => (super.noSuchMethod( Invocation.method( #filter, [cameraInfos], ), returnValue: _i8.Future>.value(<_i7.CameraInfo>[]), returnValueForMissingStub: _i8.Future>.value(<_i7.CameraInfo>[]), ) as _i8.Future>); } /// A class which mocks [Preview]. /// /// See the documentation for Mockito's code generation for more information. class MockPreview extends _i1.Mock implements _i10.Preview { @override _i8.Future setSurfaceProvider() => (super.noSuchMethod( Invocation.method( #setSurfaceProvider, [], ), returnValue: _i8.Future.value(0), returnValueForMissingStub: _i8.Future.value(0), ) as _i8.Future); @override void releaseFlutterSurfaceTexture() => super.noSuchMethod( Invocation.method( #releaseFlutterSurfaceTexture, [], ), returnValueForMissingStub: null, ); @override _i8.Future<_i2.ResolutionInfo> getResolutionInfo() => (super.noSuchMethod( Invocation.method( #getResolutionInfo, [], ), returnValue: _i8.Future<_i2.ResolutionInfo>.value(_FakeResolutionInfo_0( this, Invocation.method( #getResolutionInfo, [], ), )), returnValueForMissingStub: _i8.Future<_i2.ResolutionInfo>.value(_FakeResolutionInfo_0( this, Invocation.method( #getResolutionInfo, [], ), )), ) as _i8.Future<_i2.ResolutionInfo>); } /// A class which mocks [ProcessCameraProvider]. /// /// See the documentation for Mockito's code generation for more information. class MockProcessCameraProvider extends _i1.Mock implements _i11.ProcessCameraProvider { @override _i8.Future> getAvailableCameraInfos() => (super.noSuchMethod( Invocation.method( #getAvailableCameraInfos, [], ), returnValue: _i8.Future>.value(<_i7.CameraInfo>[]), returnValueForMissingStub: _i8.Future>.value(<_i7.CameraInfo>[]), ) as _i8.Future>); @override _i8.Future<_i3.Camera> bindToLifecycle( _i9.CameraSelector? cameraSelector, List<_i12.UseCase>? useCases, ) => (super.noSuchMethod( Invocation.method( #bindToLifecycle, [ cameraSelector, useCases, ], ), returnValue: _i8.Future<_i3.Camera>.value(_FakeCamera_1( this, Invocation.method( #bindToLifecycle, [ cameraSelector, useCases, ], ), )), returnValueForMissingStub: _i8.Future<_i3.Camera>.value(_FakeCamera_1( this, Invocation.method( #bindToLifecycle, [ cameraSelector, useCases, ], ), )), ) as _i8.Future<_i3.Camera>); @override void unbind(List<_i12.UseCase>? useCases) => super.noSuchMethod( Invocation.method( #unbind, [useCases], ), returnValueForMissingStub: null, ); @override void unbindAll() => super.noSuchMethod( Invocation.method( #unbindAll, [], ), returnValueForMissingStub: null, ); } /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. class MockBuildContext extends _i1.Mock implements _i4.BuildContext { MockBuildContext() { _i1.throwOnMissingStub(this); } @override _i4.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), returnValue: _FakeWidget_2( this, Invocation.getter(#widget), ), ) as _i4.Widget); @override bool get mounted => (super.noSuchMethod( Invocation.getter(#mounted), returnValue: false, ) as bool); @override bool get debugDoingBuild => (super.noSuchMethod( Invocation.getter(#debugDoingBuild), returnValue: false, ) as bool); @override _i4.InheritedWidget dependOnInheritedElement( _i4.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), returnValue: _FakeInheritedWidget_3( this, Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), ), ) as _i4.InheritedWidget); @override void visitAncestorElements(bool Function(_i4.Element)? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, [visitor], ), returnValueForMissingStub: null, ); @override void visitChildElements(_i4.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], ), returnValueForMissingStub: null, ); @override void dispatchNotification(_i13.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, [notification], ), returnValueForMissingStub: null, ); @override _i6.DiagnosticsNode describeElement( String? name, { _i6.DiagnosticsTreeStyle? style = _i6.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeElement, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_4( this, Invocation.method( #describeElement, [name], {#style: style}, ), ), ) as _i6.DiagnosticsNode); @override _i6.DiagnosticsNode describeWidget( String? name, { _i6.DiagnosticsTreeStyle? style = _i6.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeWidget, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_4( this, Invocation.method( #describeWidget, [name], {#style: style}, ), ), ) as _i6.DiagnosticsNode); @override List<_i6.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( #describeMissingAncestor, [], {#expectedAncestorType: expectedAncestorType}, ), returnValue: <_i6.DiagnosticsNode>[], ) as List<_i6.DiagnosticsNode>); @override _i6.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), returnValue: _FakeDiagnosticsNode_4( this, Invocation.method( #describeOwnershipChain, [name], ), ), ) as _i6.DiagnosticsNode); } ================================================ FILE: packages/camera/camera_android_camerax/test/camera_info_test.dart ================================================ // Copyright 2013 The Flutter 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_info.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.g.dart'; @GenerateMocks([TestCameraInfoHostApi]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('CameraInfo', () { tearDown(() => TestCameraInfoHostApi.setup(null)); test('getSensorRotationDegreesTest', () async { final MockTestCameraInfoHostApi mockApi = MockTestCameraInfoHostApi(); TestCameraInfoHostApi.setup(mockApi); final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); final CameraInfo cameraInfo = CameraInfo.detached( instanceManager: instanceManager, ); instanceManager.addHostCreatedInstance( cameraInfo, 0, onCopy: (_) => CameraInfo.detached(), ); when(mockApi.getSensorRotationDegrees( instanceManager.getIdentifier(cameraInfo))) .thenReturn(90); expect(await cameraInfo.getSensorRotationDegrees(), equals(90)); verify(mockApi.getSensorRotationDegrees(0)); }); test('flutterApiCreateTest', () { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); final CameraInfoFlutterApi flutterApi = CameraInfoFlutterApiImpl( instanceManager: instanceManager, ); flutterApi.create(0); expect( instanceManager.getInstanceWithWeakReference(0), isA()); }); }); } ================================================ FILE: packages/camera/camera_android_camerax/test/camera_info_test.mocks.dart ================================================ // 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.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 [TestCameraInfoHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestCameraInfoHostApi extends _i1.Mock implements _i2.TestCameraInfoHostApi { MockTestCameraInfoHostApi() { _i1.throwOnMissingStub(this); } @override int getSensorRotationDegrees(int? identifier) => (super.noSuchMethod( Invocation.method( #getSensorRotationDegrees, [identifier], ), returnValue: 0, ) as int); } ================================================ FILE: packages/camera/camera_android_camerax/test/camera_selector_test.dart ================================================ // Copyright 2013 The Flutter 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_info.dart'; import 'package:camera_android_camerax/src/camera_selector.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.g.dart'; @GenerateMocks([TestCameraSelectorHostApi]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('CameraSelector', () { tearDown(() => TestCameraSelectorHostApi.setup(null)); test('detachedCreateTest', () async { final MockTestCameraSelectorHostApi mockApi = MockTestCameraSelectorHostApi(); TestCameraSelectorHostApi.setup(mockApi); final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); CameraSelector.detached( instanceManager: instanceManager, ); verifyNever(mockApi.create(argThat(isA()), null)); }); test('createTestWithoutLensSpecified', () async { final MockTestCameraSelectorHostApi mockApi = MockTestCameraSelectorHostApi(); TestCameraSelectorHostApi.setup(mockApi); final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); CameraSelector( instanceManager: instanceManager, ); verify(mockApi.create(argThat(isA()), null)); }); test('createTestWithLensSpecified', () async { final MockTestCameraSelectorHostApi mockApi = MockTestCameraSelectorHostApi(); TestCameraSelectorHostApi.setup(mockApi); final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); CameraSelector( instanceManager: instanceManager, lensFacing: CameraSelector.lensFacingBack); verify( mockApi.create(argThat(isA()), CameraSelector.lensFacingBack)); }); test('filterTest', () async { final MockTestCameraSelectorHostApi mockApi = MockTestCameraSelectorHostApi(); TestCameraSelectorHostApi.setup(mockApi); final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); final CameraSelector cameraSelector = CameraSelector.detached( instanceManager: instanceManager, ); const int cameraInfoId = 3; final CameraInfo cameraInfo = CameraInfo.detached(instanceManager: instanceManager); instanceManager.addHostCreatedInstance( cameraSelector, 0, onCopy: (_) => CameraSelector.detached(), ); instanceManager.addHostCreatedInstance( cameraInfo, cameraInfoId, onCopy: (_) => CameraInfo.detached(), ); when(mockApi.filter(instanceManager.getIdentifier(cameraSelector), [cameraInfoId])).thenReturn([cameraInfoId]); expect(await cameraSelector.filter([cameraInfo]), equals([cameraInfo])); verify(mockApi.filter(0, [cameraInfoId])); }); test('flutterApiCreateTest', () { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); final CameraSelectorFlutterApi flutterApi = CameraSelectorFlutterApiImpl( instanceManager: instanceManager, ); flutterApi.create(0, CameraSelector.lensFacingBack); expect(instanceManager.getInstanceWithWeakReference(0), isA()); expect( (instanceManager.getInstanceWithWeakReference(0)! as CameraSelector) .lensFacing, equals(CameraSelector.lensFacingBack)); }); }); } ================================================ FILE: packages/camera/camera_android_camerax/test/camera_selector_test.mocks.dart ================================================ // 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.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 [TestCameraSelectorHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestCameraSelectorHostApi extends _i1.Mock implements _i2.TestCameraSelectorHostApi { MockTestCameraSelectorHostApi() { _i1.throwOnMissingStub(this); } @override 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); } ================================================ FILE: packages/camera/camera_android_camerax/test/camera_test.dart ================================================ // Copyright 2013 The Flutter 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()); }); }); } ================================================ FILE: packages/camera/camera_android_camerax/test/instance_manager_test.dart ================================================ // Copyright 2013 The Flutter 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/instance_manager.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('InstanceManager', () { test('addHostCreatedInstance', () { final Object object = Object(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addHostCreatedInstance( object, 0, onCopy: (_) => Object(), ); expect(instanceManager.getIdentifier(object), 0); expect( instanceManager.getInstanceWithWeakReference(0), object, ); }); test('addHostCreatedInstance prevents already used objects and ids', () { final Object object = Object(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addHostCreatedInstance( object, 0, onCopy: (_) => Object(), ); expect( () => instanceManager.addHostCreatedInstance( object, 0, onCopy: (_) => Object(), ), throwsAssertionError, ); expect( () => instanceManager.addHostCreatedInstance( Object(), 0, onCopy: (_) => Object(), ), throwsAssertionError, ); }); test('addDartCreatedInstance', () { final Object object = Object(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addDartCreatedInstance( object, onCopy: (_) => Object(), ); final int? instanceId = instanceManager.getIdentifier(object); expect(instanceId, isNotNull); expect( instanceManager.getInstanceWithWeakReference(instanceId!), object, ); }); test('removeWeakReference', () { final Object object = Object(); int? weakInstanceId; final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (int instanceId) { weakInstanceId = instanceId; }); instanceManager.addHostCreatedInstance( object, 0, onCopy: (_) => Object(), ); expect(instanceManager.removeWeakReference(object), 0); expect( instanceManager.getInstanceWithWeakReference(0), isA(), ); expect(weakInstanceId, 0); }); test('removeWeakReference removes only weak reference', () { final Object object = Object(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addHostCreatedInstance( object, 0, onCopy: (_) => Object(), ); expect(instanceManager.removeWeakReference(object), 0); final Object copy = instanceManager.getInstanceWithWeakReference( 0, )!; expect(identical(object, copy), isFalse); }); test('removeStrongReference', () { final Object object = Object(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addHostCreatedInstance( object, 0, onCopy: (_) => Object(), ); instanceManager.removeWeakReference(object); expect(instanceManager.remove(0), isA()); expect(instanceManager.containsIdentifier(0), isFalse); }); test('removeStrongReference removes only strong reference', () { final Object object = Object(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addHostCreatedInstance( object, 0, onCopy: (_) => Object(), ); expect(instanceManager.remove(0), isA()); expect( instanceManager.getInstanceWithWeakReference(0), object, ); }); test('getInstance can add a new weak reference', () { final Object object = Object(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addHostCreatedInstance( object, 0, onCopy: (_) => Object(), ); instanceManager.removeWeakReference(object); final Object newWeakCopy = instanceManager.getInstanceWithWeakReference( 0, )!; expect(identical(object, newWeakCopy), isFalse); }); }); } ================================================ FILE: packages/camera/camera_android_camerax/test/preview_test.dart ================================================ // Copyright 2013 The Flutter 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'; import 'package:camera_android_camerax/src/instance_manager.dart'; import 'package:camera_android_camerax/src/preview.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'preview_test.mocks.dart'; import 'test_camerax_library.g.dart'; @GenerateMocks([TestPreviewHostApi]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('Preview', () { tearDown(() => TestPreviewHostApi.setup(null)); test('detached create does not call create on the Java side', () async { final MockTestPreviewHostApi mockApi = MockTestPreviewHostApi(); TestPreviewHostApi.setup(mockApi); final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); Preview.detached( instanceManager: instanceManager, targetRotation: 90, targetResolution: ResolutionInfo(width: 50, height: 10), ); verifyNever(mockApi.create(argThat(isA()), argThat(isA()), argThat(isA()))); }); test('create calls create on the Java side', () async { final MockTestPreviewHostApi mockApi = MockTestPreviewHostApi(); TestPreviewHostApi.setup(mockApi); final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); const int targetRotation = 90; const int targetResolutionWidth = 10; const int targetResolutionHeight = 50; Preview( instanceManager: instanceManager, targetRotation: targetRotation, targetResolution: ResolutionInfo( width: targetResolutionWidth, height: targetResolutionHeight), ); final VerificationResult createVerification = verify(mockApi.create( argThat(isA()), argThat(equals(targetRotation)), captureAny)); final ResolutionInfo capturedResolutionInfo = createVerification.captured.single as ResolutionInfo; expect(capturedResolutionInfo.width, equals(targetResolutionWidth)); expect(capturedResolutionInfo.height, equals(targetResolutionHeight)); }); test( 'setSurfaceProvider makes call to set surface provider for preview instance', () async { final MockTestPreviewHostApi mockApi = MockTestPreviewHostApi(); TestPreviewHostApi.setup(mockApi); final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); const int textureId = 8; final Preview preview = Preview.detached( instanceManager: instanceManager, ); instanceManager.addHostCreatedInstance( preview, 0, onCopy: (_) => Preview.detached(), ); when(mockApi.setSurfaceProvider(instanceManager.getIdentifier(preview))) .thenReturn(textureId); expect(await preview.setSurfaceProvider(), equals(textureId)); verify( mockApi.setSurfaceProvider(instanceManager.getIdentifier(preview))); }); test( 'releaseFlutterSurfaceTexture makes call to relase flutter surface texture entry', () async { final MockTestPreviewHostApi mockApi = MockTestPreviewHostApi(); TestPreviewHostApi.setup(mockApi); final Preview preview = Preview.detached(); preview.releaseFlutterSurfaceTexture(); verify(mockApi.releaseFlutterSurfaceTexture()); }); test( 'getResolutionInfo makes call to get resolution information for preview instance', () async { final MockTestPreviewHostApi mockApi = MockTestPreviewHostApi(); TestPreviewHostApi.setup(mockApi); final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); final Preview preview = Preview.detached( instanceManager: instanceManager, ); const int resolutionWidth = 10; const int resolutionHeight = 60; final ResolutionInfo testResolutionInfo = ResolutionInfo(width: resolutionWidth, height: resolutionHeight); instanceManager.addHostCreatedInstance( preview, 0, onCopy: (_) => Preview.detached(), ); when(mockApi.getResolutionInfo(instanceManager.getIdentifier(preview))) .thenReturn(testResolutionInfo); final ResolutionInfo previewResolutionInfo = await preview.getResolutionInfo(); expect(previewResolutionInfo.width, equals(resolutionWidth)); expect(previewResolutionInfo.height, equals(resolutionHeight)); verify(mockApi.getResolutionInfo(instanceManager.getIdentifier(preview))); }); }); } ================================================ FILE: packages/camera/camera_android_camerax/test/preview_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in camera_android_camerax/test/preview_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:camera_android_camerax/src/camerax_library.g.dart' as _i2; import 'package:mockito/mockito.dart' as _i1; import 'test_camerax_library.g.dart' as _i3; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class class _FakeResolutionInfo_0 extends _i1.SmartFake implements _i2.ResolutionInfo { _FakeResolutionInfo_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [TestPreviewHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestPreviewHostApi extends _i1.Mock implements _i3.TestPreviewHostApi { MockTestPreviewHostApi() { _i1.throwOnMissingStub(this); } @override void create( int? identifier, int? rotation, _i2.ResolutionInfo? targetResolution, ) => super.noSuchMethod( Invocation.method( #create, [ identifier, rotation, targetResolution, ], ), returnValueForMissingStub: null, ); @override int setSurfaceProvider(int? identifier) => (super.noSuchMethod( Invocation.method( #setSurfaceProvider, [identifier], ), returnValue: 0, ) as int); @override void releaseFlutterSurfaceTexture() => super.noSuchMethod( Invocation.method( #releaseFlutterSurfaceTexture, [], ), returnValueForMissingStub: null, ); @override _i2.ResolutionInfo getResolutionInfo(int? identifier) => (super.noSuchMethod( Invocation.method( #getResolutionInfo, [identifier], ), returnValue: _FakeResolutionInfo_0( this, Invocation.method( #getResolutionInfo, [identifier], ), ), ) as _i2.ResolutionInfo); } ================================================ FILE: packages/camera/camera_android_camerax/test/process_camera_provider_test.dart ================================================ // Copyright 2013 The Flutter 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/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.g.dart'; @GenerateMocks([TestProcessCameraProviderHostApi]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('ProcessCameraProvider', () { tearDown(() => TestProcessCameraProviderHostApi.setup(null)); test('getInstanceTest', () async { final MockTestProcessCameraProviderHostApi mockApi = MockTestProcessCameraProviderHostApi(); TestProcessCameraProviderHostApi.setup(mockApi); final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); final ProcessCameraProvider processCameraProvider = ProcessCameraProvider.detached( instanceManager: instanceManager, ); instanceManager.addHostCreatedInstance( processCameraProvider, 0, onCopy: (_) => ProcessCameraProvider.detached(), ); when(mockApi.getInstance()).thenAnswer((_) async => 0); expect( await ProcessCameraProvider.getInstance( instanceManager: instanceManager), equals(processCameraProvider)); verify(mockApi.getInstance()); }); test('getAvailableCameraInfosTest', () async { final MockTestProcessCameraProviderHostApi mockApi = MockTestProcessCameraProviderHostApi(); TestProcessCameraProviderHostApi.setup(mockApi); final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); final ProcessCameraProvider processCameraProvider = ProcessCameraProvider.detached( instanceManager: instanceManager, ); instanceManager.addHostCreatedInstance( processCameraProvider, 0, onCopy: (_) => ProcessCameraProvider.detached(), ); final CameraInfo fakeAvailableCameraInfo = CameraInfo.detached(instanceManager: instanceManager); instanceManager.addHostCreatedInstance( fakeAvailableCameraInfo, 1, onCopy: (_) => CameraInfo.detached(), ); when(mockApi.getAvailableCameraInfos(0)).thenReturn([1]); expect(await processCameraProvider.getAvailableCameraInfos(), equals([fakeAvailableCameraInfo])); 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: (_) {}, ); final ProcessCameraProviderFlutterApiImpl flutterApi = ProcessCameraProviderFlutterApiImpl( instanceManager: instanceManager, ); flutterApi.create(0); expect(instanceManager.getInstanceWithWeakReference(0), isA()); }); }); } ================================================ FILE: packages/camera/camera_android_camerax/test/process_camera_provider_test.mocks.dart ================================================ // 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. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i3; import 'package:mockito/mockito.dart' as _i1; import 'test_camerax_library.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: 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 [TestProcessCameraProviderHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestProcessCameraProviderHostApi extends _i1.Mock implements _i2.TestProcessCameraProviderHostApi { MockTestProcessCameraProviderHostApi() { _i1.throwOnMissingStub(this); } @override _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); @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, ); } ================================================ FILE: packages/camera/camera_android_camerax/test/system_services_test.dart ================================================ // Copyright 2013 The Flutter 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'))); }); test('onCameraError adds new error to stream', () { const String testErrorDescription = 'Test error description!'; SystemServices.cameraErrorStreamController.stream .listen((String errorDescription) { expect(errorDescription, equals(testErrorDescription)); }); SystemServicesFlutterApiImpl().onCameraError(testErrorDescription); }); }); } ================================================ FILE: packages/camera/camera_android_camerax/test/system_services_test.mocks.dart ================================================ // 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, ); } ================================================ FILE: packages/camera/camera_android_camerax/test/test_camerax_library.g.dart ================================================ // Copyright 2013 The Flutter 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.2.9), 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 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 'package:camera_android_camerax/src/camerax_library.g.dart'; class _TestJavaObjectHostApiCodec extends StandardMessageCodec { const _TestJavaObjectHostApiCodec(); } abstract class TestJavaObjectHostApi { static const MessageCodec codec = _TestJavaObjectHostApiCodec(); void dispose(int identifier); static void setup(TestJavaObjectHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.JavaObjectHostApi.dispose', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.JavaObjectHostApi.dispose 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.JavaObjectHostApi.dispose was null, expected non-null int.'); api.dispose(arg_identifier!); return {}; }); } } } } class _TestCameraInfoHostApiCodec extends StandardMessageCodec { const _TestCameraInfoHostApiCodec(); } abstract class TestCameraInfoHostApi { static const MessageCodec codec = _TestCameraInfoHostApiCodec(); int getSensorRotationDegrees(int identifier); static void setup(TestCameraInfoHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.CameraInfoHostApi.getSensorRotationDegrees', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.CameraInfoHostApi.getSensorRotationDegrees 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.CameraInfoHostApi.getSensorRotationDegrees was null, expected non-null int.'); final int output = api.getSensorRotationDegrees(arg_identifier!); return {'result': output}; }); } } } } class _TestCameraSelectorHostApiCodec extends StandardMessageCodec { const _TestCameraSelectorHostApiCodec(); } abstract class TestCameraSelectorHostApi { static const MessageCodec codec = _TestCameraSelectorHostApiCodec(); void create(int identifier, int? lensFacing); List filter(int identifier, List cameraInfoIds); static void setup(TestCameraSelectorHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.CameraSelectorHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.CameraSelectorHostApi.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.CameraSelectorHostApi.create was null, expected non-null int.'); final int? arg_lensFacing = (args[1] as int?); api.create(arg_identifier!, arg_lensFacing); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.CameraSelectorHostApi.filter', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.CameraSelectorHostApi.filter 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.CameraSelectorHostApi.filter was null, expected non-null int.'); final List? arg_cameraInfoIds = (args[1] as List?)?.cast(); assert(arg_cameraInfoIds != null, 'Argument for dev.flutter.pigeon.CameraSelectorHostApi.filter was null, expected non-null List.'); final List output = api.filter(arg_identifier!, arg_cameraInfoIds!); return {'result': output}; }); } } } } class _TestProcessCameraProviderHostApiCodec extends StandardMessageCodec { const _TestProcessCameraProviderHostApiCodec(); } abstract class TestProcessCameraProviderHostApi { static const MessageCodec codec = _TestProcessCameraProviderHostApiCodec(); 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}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.ProcessCameraProviderHostApi.getInstance', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { // ignore message final int output = await api.getInstance(); return {'result': output}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.ProcessCameraProviderHostApi.getAvailableCameraInfos', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.ProcessCameraProviderHostApi.getAvailableCameraInfos 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.getAvailableCameraInfos was null, expected non-null int.'); final List output = api.getAvailableCameraInfos(arg_identifier!); return {'result': output}; }); } } { 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 {}; }); } } } } class _TestPreviewHostApiCodec extends StandardMessageCodec { const _TestPreviewHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is ResolutionInfo) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is ResolutionInfo) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return ResolutionInfo.decode(readValue(buffer)!); case 129: return ResolutionInfo.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } abstract class TestPreviewHostApi { static const MessageCodec codec = _TestPreviewHostApiCodec(); void create(int identifier, int? rotation, ResolutionInfo? targetResolution); int setSurfaceProvider(int identifier); void releaseFlutterSurfaceTexture(); ResolutionInfo getResolutionInfo(int identifier); static void setup(TestPreviewHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.PreviewHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.PreviewHostApi.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.PreviewHostApi.create was null, expected non-null int.'); final int? arg_rotation = (args[1] as int?); final ResolutionInfo? arg_targetResolution = (args[2] as ResolutionInfo?); api.create(arg_identifier!, arg_rotation, arg_targetResolution); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.PreviewHostApi.setSurfaceProvider', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.PreviewHostApi.setSurfaceProvider 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.PreviewHostApi.setSurfaceProvider was null, expected non-null int.'); final int output = api.setSurfaceProvider(arg_identifier!); return {'result': output}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.PreviewHostApi.releaseFlutterSurfaceTexture', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { // ignore message api.releaseFlutterSurfaceTexture(); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.PreviewHostApi.getResolutionInfo', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.PreviewHostApi.getResolutionInfo 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.PreviewHostApi.getResolutionInfo was null, expected non-null int.'); final ResolutionInfo output = api.getResolutionInfo(arg_identifier!); return {'result': output}; }); } } } } ================================================ FILE: packages/camera/camera_avfoundation/AUTHORS ================================================ # 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> ================================================ FILE: packages/camera/camera_avfoundation/CHANGELOG.md ================================================ ## 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. ## 0.9.9 * Implements option to also stream when recording a video. ## 0.9.8+6 * Updates code for `no_leading_underscores_for_local_identifiers` lint. * Updates minimum Flutter version to 2.10. ## 0.9.8+5 * Fixes a regression introduced in 0.9.8+4 where the stream handler is not set. ## 0.9.8+4 * Fixes a crash due to sending orientation change events when the engine is torn down. ## 0.9.8+3 * Fixes avoid_redundant_argument_values lint warnings and minor typos. * Ignores missing return warnings in preparation for [upcoming analysis changes](https://github.com/flutter/flutter/issues/105750). ## 0.9.8+2 * Fixes exception in registerWith caused by the switch to an in-package method channel. ## 0.9.8+1 * Ignores deprecation warnings for upcoming styleFrom button API changes. ## 0.9.8 * Switches to internal method channel implementation. ## 0.9.7+1 * Splits from `camera` as a federated implementation. ================================================ FILE: packages/camera/camera_avfoundation/LICENSE ================================================ 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. ================================================ FILE: packages/camera/camera_avfoundation/README.md ================================================ # camera\_avfoundation The iOS implementation of [`camera`][1]. ## Usage This package is [endorsed][2], which means you can simply use `camera` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/camera [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/camera/camera_avfoundation/example/integration_test/camera_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; import 'dart:ui'; import 'package:camera_avfoundation/camera_avfoundation.dart'; import 'package:camera_example/camera_controller.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/painting.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:path_provider/path_provider.dart'; import 'package:video_player/video_player.dart'; void main() { late Directory testDir; IntegrationTestWidgetsFlutterBinding.ensureInitialized(); setUpAll(() async { CameraPlatform.instance = AVFoundationCamera(); final Directory extDir = await getTemporaryDirectory(); testDir = await Directory('${extDir.path}/test').create(recursive: true); }); tearDownAll(() async { await testDir.delete(recursive: true); }); final Map presetExpectedSizes = { ResolutionPreset.low: const Size(288, 352), ResolutionPreset.medium: const Size(480, 640), ResolutionPreset.high: const Size(720, 1280), ResolutionPreset.veryHigh: const Size(1080, 1920), ResolutionPreset.ultraHigh: const Size(2160, 3840), // Don't bother checking for max here since it could be anything. }; /// Verify that [actual] has dimensions that are at least as large as /// [expectedSize]. Allows for a mismatch in portrait vs landscape. Returns /// whether the dimensions exactly match. bool assertExpectedDimensions(Size expectedSize, Size actual) { expect(actual.shortestSide, lessThanOrEqualTo(expectedSize.shortestSide)); expect(actual.longestSide, lessThanOrEqualTo(expectedSize.longestSide)); return actual.shortestSide == expectedSize.shortestSide && actual.longestSide == expectedSize.longestSide; } // This tests that the capture is no bigger than the preset, since we have // automatic code to fall back to smaller sizes when we need to. Returns // whether the image is exactly the desired resolution. Future testCaptureImageResolution( CameraController controller, ResolutionPreset preset) async { final Size expectedSize = presetExpectedSizes[preset]!; // Take Picture final XFile file = await controller.takePicture(); // Load picture final File fileImage = File(file.path); final Image image = await decodeImageFromList(fileImage.readAsBytesSync()); // Verify image dimensions are as expected expect(image, isNotNull); return assertExpectedDimensions( expectedSize, Size(image.height.toDouble(), image.width.toDouble())); } testWidgets('Capture specific image resolutions', (WidgetTester tester) async { final List cameras = await CameraPlatform.instance.availableCameras(); if (cameras.isEmpty) { return; } for (final CameraDescription cameraDescription in cameras) { bool previousPresetExactlySupported = true; for (final MapEntry preset in presetExpectedSizes.entries) { final CameraController controller = CameraController(cameraDescription, preset.key); await controller.initialize(); final bool presetExactlySupported = await testCaptureImageResolution(controller, preset.key); assert(!(!previousPresetExactlySupported && presetExactlySupported), 'The camera took higher resolution pictures at a lower resolution.'); previousPresetExactlySupported = presetExactlySupported; await controller.dispose(); } } }); // This tests that the capture is no bigger than the preset, since we have // automatic code to fall back to smaller sizes when we need to. Returns // whether the image is exactly the desired resolution. Future testCaptureVideoResolution( CameraController controller, ResolutionPreset preset) async { final Size expectedSize = presetExpectedSizes[preset]!; // Take Video await controller.startVideoRecording(); sleep(const Duration(milliseconds: 300)); final XFile file = await controller.stopVideoRecording(); // Load video metadata final File videoFile = File(file.path); final VideoPlayerController videoController = VideoPlayerController.file(videoFile); await videoController.initialize(); final Size video = videoController.value.size; // Verify image dimensions are as expected expect(video, isNotNull); return assertExpectedDimensions( expectedSize, Size(video.height, video.width)); } testWidgets('Capture specific video resolutions', (WidgetTester tester) async { final List cameras = await CameraPlatform.instance.availableCameras(); if (cameras.isEmpty) { return; } for (final CameraDescription cameraDescription in cameras) { bool previousPresetExactlySupported = true; for (final MapEntry preset in presetExpectedSizes.entries) { final CameraController controller = CameraController(cameraDescription, preset.key); await controller.initialize(); await controller.prepareForVideoRecording(); final bool presetExactlySupported = await testCaptureVideoResolution(controller, preset.key); assert(!(!previousPresetExactlySupported && presetExactlySupported), 'The camera took higher resolution pictures at a lower resolution.'); previousPresetExactlySupported = presetExactlySupported; await controller.dispose(); } } }); testWidgets('Pause and resume video recording', (WidgetTester tester) async { final List cameras = await CameraPlatform.instance.availableCameras(); if (cameras.isEmpty) { return; } final CameraController controller = CameraController( cameras[0], ResolutionPreset.low, enableAudio: false, ); await controller.initialize(); await controller.prepareForVideoRecording(); int startPause; int timePaused = 0; await controller.startVideoRecording(); final int recordingStart = DateTime.now().millisecondsSinceEpoch; sleep(const Duration(milliseconds: 500)); await controller.pauseVideoRecording(); startPause = DateTime.now().millisecondsSinceEpoch; sleep(const Duration(milliseconds: 500)); await controller.resumeVideoRecording(); timePaused += DateTime.now().millisecondsSinceEpoch - startPause; sleep(const Duration(milliseconds: 500)); await controller.pauseVideoRecording(); startPause = DateTime.now().millisecondsSinceEpoch; sleep(const Duration(milliseconds: 500)); await controller.resumeVideoRecording(); timePaused += DateTime.now().millisecondsSinceEpoch - startPause; sleep(const Duration(milliseconds: 500)); final XFile file = await controller.stopVideoRecording(); final int recordingTime = DateTime.now().millisecondsSinceEpoch - recordingStart; final File videoFile = File(file.path); final VideoPlayerController videoController = VideoPlayerController.file( videoFile, ); await videoController.initialize(); final int duration = videoController.value.duration.inMilliseconds; await videoController.dispose(); expect(duration, lessThan(recordingTime - timePaused)); }); /// Start streaming with specifying the ImageFormatGroup. Future startStreaming(List cameras, ImageFormatGroup? imageFormatGroup) async { final CameraController controller = CameraController( cameras.first, ResolutionPreset.low, enableAudio: false, imageFormatGroup: imageFormatGroup, ); await controller.initialize(); final Completer completer = Completer(); await controller.startImageStream((CameraImageData image) { if (!completer.isCompleted) { Future(() async { await controller.stopImageStream(); await controller.dispose(); }).then((Object? value) { completer.complete(image); }); } }); return completer.future; } testWidgets( 'image streaming with imageFormatGroup', (WidgetTester tester) async { final List cameras = await CameraPlatform.instance.availableCameras(); if (cameras.isEmpty) { return; } CameraImageData image = await startStreaming(cameras, null); expect(image, isNotNull); expect(image.format.group, ImageFormatGroup.bgra8888); expect(image.planes.length, 1); image = await startStreaming(cameras, ImageFormatGroup.yuv420); expect(image, isNotNull); expect(image.format.group, ImageFormatGroup.yuv420); expect(image.planes.length, 2); image = await startStreaming(cameras, ImageFormatGroup.bgra8888); expect(image, isNotNull); expect(image.format.group, ImageFormatGroup.bgra8888); expect(image.planes.length, 1); }, ); testWidgets('Recording with video streaming', (WidgetTester tester) async { final List cameras = await CameraPlatform.instance.availableCameras(); if (cameras.isEmpty) { return; } final CameraController controller = CameraController( cameras[0], ResolutionPreset.low, enableAudio: false, ); await controller.initialize(); await controller.prepareForVideoRecording(); final Completer completer = Completer(); await controller.startVideoRecording( streamCallback: (CameraImageData image) { if (!completer.isCompleted) { completer.complete(image); } }); sleep(const Duration(milliseconds: 500)); await controller.stopVideoRecording(); await controller.dispose(); expect(await completer.future, isNotNull); }); } ================================================ FILE: packages/camera/camera_avfoundation/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 UIRequiredDeviceCapabilities arm64 MinimumOSVersion 9.0 ================================================ FILE: packages/camera/camera_avfoundation/example/ios/Flutter/Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/camera/camera_avfoundation/example/ios/Flutter/Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/camera/camera_avfoundation/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do platform :ios, '9.0' inherit! :search_paths # Pods for testing pod 'OCMock', '~> 3.8.1' end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/camera/camera_avfoundation/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "idiom" : "ios-marketing", "size" : "1024x1024", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/camera/camera_avfoundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "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" } } ================================================ FILE: packages/camera/camera_avfoundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: packages/camera/camera_avfoundation/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/camera/camera_avfoundation/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/camera/camera_avfoundation/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName camera_example CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSApplicationCategoryType LSRequiresIPhoneOS NSCameraUsageDescription Can I use the camera please? Only for demo purpose of the app NSMicrophoneUsageDescription Only for demo purpose of the app UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: packages/camera/camera_avfoundation/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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 { // The setup logic in `AppDelegate::didFinishLaunchingWithOptions:` eventually sends camera // operations on the background queue, which would run concurrently with the test cases during // unit tests, making the debugging process confusing. This setup is actually not necessary for // the unit tests, so it is better to skip the AppDelegate when running unit tests. BOOL isTesting = NSClassFromString(@"XCTestCase") != nil; return UIApplicationMain(argc, argv, nil, isTesting ? nil : NSStringFromClass([AppDelegate class])); } } ================================================ FILE: packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 033B94BE269C40A200B4DF97 /* CameraMethodChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 033B94BD269C40A200B4DF97 /* CameraMethodChannelTests.m */; }; 03BB766B2665316900CE5A93 /* CameraFocusTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 03BB766A2665316900CE5A93 /* CameraFocusTests.m */; }; 03F6F8B226CBB4670024B8D3 /* ThreadSafeFlutterResultTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 03F6F8B126CBB4670024B8D3 /* ThreadSafeFlutterResultTests.m */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 236906D1621AE863A5B2E770 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 89D82918721FABF772705DB0 /* libPods-Runner.a */; }; 25C3919135C3D981E6F800D0 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1944D8072499F3B5E7653D44 /* libPods-RunnerTests.a */; }; 334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 03BB767226653ABE00CE5A93 /* CameraOrientationTests.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 43ED1537282570DE00EB00DE /* AvailableCamerasTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 43ED1536282570DE00EB00DE /* AvailableCamerasTest.m */; }; 788A065A27B0E02900533D74 /* StreamingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 788A065927B0E02900533D74 /* StreamingTest.m */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; E01EE4A82799F3A5008C1950 /* QueueUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E01EE4A72799F3A5008C1950 /* QueueUtilsTests.m */; }; E032F250279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E032F24F279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m */; }; E04F108627A87CA600573D0C /* FLTSavePhotoDelegateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */; }; E071CF7227B3061B006EF3BA /* FLTCamPhotoCaptureTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */; }; E071CF7427B31DE4006EF3BA /* FLTCamSampleBufferTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */; }; E0B0D2BB27DFF2AF00E71E4B /* CameraPermissionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0B0D2BA27DFF2AF00E71E4B /* CameraPermissionTests.m */; }; E0C6E2002770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FD2770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m */; }; E0C6E2012770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */; }; E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */; }; E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */; }; E0F95E3D27A32AB900699390 /* CameraPropertiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0F95E3C27A32AB900699390 /* CameraPropertiesTests.m */; }; E487C86026D686A10034AC92 /* CameraPreviewPauseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */; }; F6EE622F2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m in Sources */ = {isa = PBXBuildFile; fileRef = F6EE622E2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 03BB766D2665316900CE5A93 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 033B94BD269C40A200B4DF97 /* CameraMethodChannelTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraMethodChannelTests.m; sourceTree = ""; }; 03BB76682665316900CE5A93 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 03BB766A2665316900CE5A93 /* CameraFocusTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraFocusTests.m; sourceTree = ""; }; 03BB766C2665316900CE5A93 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 03BB767226653ABE00CE5A93 /* CameraOrientationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CameraOrientationTests.m; sourceTree = ""; }; 03F6F8B126CBB4670024B8D3 /* ThreadSafeFlutterResultTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeFlutterResultTests.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 = ""; }; 14AE82C910C2A12F2ECB2094 /* 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 = ""; }; 1944D8072499F3B5E7653D44 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 43ED1536282570DE00EB00DE /* AvailableCamerasTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AvailableCamerasTest.m; sourceTree = ""; }; 59848A7CA98C1FADF8840207 /* 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 = ""; }; 788A065927B0E02900533D74 /* StreamingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StreamingTest.m; 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 = ""; }; 89D82918721FABF772705DB0 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; 9C5CC6CAD53AD388B2694F3A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; A24F9E418BA48BCC7409B117 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; E01EE4A72799F3A5008C1950 /* QueueUtilsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QueueUtilsTests.m; sourceTree = ""; }; E032F24F279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CameraCaptureSessionQueueRaceConditionTests.m; sourceTree = ""; }; E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTSavePhotoDelegateTests.m; sourceTree = ""; }; E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTCamPhotoCaptureTests.m; sourceTree = ""; }; E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTCamSampleBufferTests.m; sourceTree = ""; }; E0B0D2BA27DFF2AF00E71E4B /* CameraPermissionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraPermissionTests.m; sourceTree = ""; }; E0C6E1FD2770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeMethodChannelTests.m; sourceTree = ""; }; E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeTextureRegistryTests.m; sourceTree = ""; }; E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeEventChannelTests.m; sourceTree = ""; }; E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CameraTestUtils.h; sourceTree = ""; }; E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraTestUtils.m; sourceTree = ""; }; E0F95E3C27A32AB900699390 /* CameraPropertiesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraPropertiesTests.m; sourceTree = ""; }; E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraPreviewPauseTests.m; sourceTree = ""; }; F63F9EED27143B19002479BF /* MockFLTThreadSafeFlutterResult.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockFLTThreadSafeFlutterResult.h; sourceTree = ""; }; F6EE622E2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockFLTThreadSafeFlutterResult.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 03BB76652665316900CE5A93 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 25C3919135C3D981E6F800D0 /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 236906D1621AE863A5B2E770 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 03BB76692665316900CE5A93 /* RunnerTests */ = { isa = PBXGroup; children = ( 03BB766A2665316900CE5A93 /* CameraFocusTests.m */, 03BB767226653ABE00CE5A93 /* CameraOrientationTests.m */, 03BB766C2665316900CE5A93 /* Info.plist */, 033B94BD269C40A200B4DF97 /* CameraMethodChannelTests.m */, 03F6F8B126CBB4670024B8D3 /* ThreadSafeFlutterResultTests.m */, E0C6E1FF2770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m */, E0C6E1FD2770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m */, E0C6E1FE2770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m */, E04F108527A87CA600573D0C /* FLTSavePhotoDelegateTests.m */, E071CF7127B3061B006EF3BA /* FLTCamPhotoCaptureTests.m */, E071CF7327B31DE4006EF3BA /* FLTCamSampleBufferTests.m */, E0B0D2BA27DFF2AF00E71E4B /* CameraPermissionTests.m */, E01EE4A72799F3A5008C1950 /* QueueUtilsTests.m */, E0CDBAC027CD9729002561D9 /* CameraTestUtils.h */, E0CDBAC127CD9729002561D9 /* CameraTestUtils.m */, E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */, F6EE622E2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m */, F63F9EED27143B19002479BF /* MockFLTThreadSafeFlutterResult.h */, E032F24F279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m */, E0F95E3C27A32AB900699390 /* CameraPropertiesTests.m */, 788A065927B0E02900533D74 /* StreamingTest.m */, 43ED1536282570DE00EB00DE /* AvailableCamerasTest.m */, ); path = RunnerTests; sourceTree = ""; }; 3242FD2B467C15C62200632F /* Frameworks */ = { isa = PBXGroup; children = ( 89D82918721FABF772705DB0 /* libPods-Runner.a */, 1944D8072499F3B5E7653D44 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 03BB76692665316900CE5A93 /* RunnerTests */, 97C146EF1CF9000F007C117D /* Products */, FD386F00E98D73419C929072 /* Pods */, 3242FD2B467C15C62200632F /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, 03BB76682665316900CE5A93 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; FD386F00E98D73419C929072 /* Pods */ = { isa = PBXGroup; children = ( 59848A7CA98C1FADF8840207 /* Pods-Runner.debug.xcconfig */, 14AE82C910C2A12F2ECB2094 /* Pods-Runner.release.xcconfig */, 9C5CC6CAD53AD388B2694F3A /* Pods-RunnerTests.debug.xcconfig */, A24F9E418BA48BCC7409B117 /* Pods-RunnerTests.release.xcconfig */, ); path = Pods; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 03BB76672665316900CE5A93 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = 03BB76712665316900CE5A93 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( 422786A96136AA9087A2041B /* [CP] Check Pods Manifest.lock */, 03BB76642665316900CE5A93 /* Sources */, 03BB76652665316900CE5A93 /* Frameworks */, 03BB76662665316900CE5A93 /* Resources */, ); buildRules = ( ); dependencies = ( 03BB766E2665316900CE5A93 /* PBXTargetDependency */, ); name = RunnerTests; productName = camera_exampleTests; productReference = 03BB76682665316900CE5A93 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 9872F2A25E8A171A111468CD /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 03BB76672665316900CE5A93 = { CreatedOnToolsVersion = 12.5; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, 03BB76672665316900CE5A93 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 03BB76662665316900CE5A93 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 422786A96136AA9087A2041B /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 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"; }; 9872F2A25E8A171A111468CD /* [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", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 03BB76642665316900CE5A93 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 03F6F8B226CBB4670024B8D3 /* ThreadSafeFlutterResultTests.m in Sources */, 033B94BE269C40A200B4DF97 /* CameraMethodChannelTests.m in Sources */, E071CF7227B3061B006EF3BA /* FLTCamPhotoCaptureTests.m in Sources */, E0F95E3D27A32AB900699390 /* CameraPropertiesTests.m in Sources */, 03BB766B2665316900CE5A93 /* CameraFocusTests.m in Sources */, E487C86026D686A10034AC92 /* CameraPreviewPauseTests.m in Sources */, E071CF7427B31DE4006EF3BA /* FLTCamSampleBufferTests.m in Sources */, E04F108627A87CA600573D0C /* FLTSavePhotoDelegateTests.m in Sources */, 43ED1537282570DE00EB00DE /* AvailableCamerasTest.m in Sources */, F6EE622F2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m in Sources */, E0CDBAC227CD9729002561D9 /* CameraTestUtils.m in Sources */, 334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */, E032F250279F5E94009E9028 /* CameraCaptureSessionQueueRaceConditionTests.m in Sources */, 788A065A27B0E02900533D74 /* StreamingTest.m in Sources */, E0C6E2022770F01A00EA6AA3 /* ThreadSafeEventChannelTests.m in Sources */, E0C6E2012770F01A00EA6AA3 /* ThreadSafeTextureRegistryTests.m in Sources */, E0B0D2BB27DFF2AF00E71E4B /* CameraPermissionTests.m in Sources */, E0C6E2002770F01A00EA6AA3 /* ThreadSafeMethodChannelTests.m in Sources */, E01EE4A82799F3A5008C1950 /* QueueUtilsTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 03BB766E2665316900CE5A93 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = 03BB766D2665316900CE5A93 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 03BB766F2665316900CE5A93 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9C5CC6CAD53AD388B2694F3A /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = RunnerTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "dev.flutter.plugins.cameraExample.camera-exampleTests"; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Debug; }; 03BB76702665316900CE5A93 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = A24F9E418BA48BCC7409B117 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = RunnerTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "dev.flutter.plugins.cameraExample.camera-exampleTests"; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; }; 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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 = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = ""; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.cameraExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = ""; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.cameraExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 03BB76712665316900CE5A93 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 03BB766F2665316900CE5A93 /* Debug */, 03BB76702665316900CE5A93 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/camera/camera_avfoundation/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/camera/camera_avfoundation/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/AvailableCamerasTest.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import camera_avfoundation.Test; @import XCTest; @import AVFoundation; #import #import "MockFLTThreadSafeFlutterResult.h" @interface AvailableCamerasTest : XCTestCase @end @implementation AvailableCamerasTest - (void)testAvailableCamerasShouldReturnAllCamerasOnMultiCameraIPhone { CameraPlugin *camera = [[CameraPlugin alloc] initWithRegistry:nil messenger:nil]; XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Result finished"]; // iPhone 13 Cameras: AVCaptureDevice *wideAngleCamera = OCMClassMock([AVCaptureDevice class]); OCMStub([wideAngleCamera uniqueID]).andReturn(@"0"); OCMStub([wideAngleCamera position]).andReturn(AVCaptureDevicePositionBack); AVCaptureDevice *frontFacingCamera = OCMClassMock([AVCaptureDevice class]); OCMStub([frontFacingCamera uniqueID]).andReturn(@"1"); OCMStub([frontFacingCamera position]).andReturn(AVCaptureDevicePositionFront); AVCaptureDevice *ultraWideCamera = OCMClassMock([AVCaptureDevice class]); OCMStub([ultraWideCamera uniqueID]).andReturn(@"2"); OCMStub([ultraWideCamera position]).andReturn(AVCaptureDevicePositionBack); AVCaptureDevice *telephotoCamera = OCMClassMock([AVCaptureDevice class]); OCMStub([telephotoCamera uniqueID]).andReturn(@"3"); OCMStub([telephotoCamera position]).andReturn(AVCaptureDevicePositionBack); NSMutableArray *requiredTypes = [@[ AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeBuiltInTelephotoCamera ] mutableCopy]; if (@available(iOS 13.0, *)) { [requiredTypes addObject:AVCaptureDeviceTypeBuiltInUltraWideCamera]; } id discoverySessionMock = OCMClassMock([AVCaptureDeviceDiscoverySession class]); OCMStub([discoverySessionMock discoverySessionWithDeviceTypes:requiredTypes mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified]) .andReturn(discoverySessionMock); NSMutableArray *cameras = [NSMutableArray array]; [cameras addObjectsFromArray:@[ wideAngleCamera, frontFacingCamera, telephotoCamera ]]; if (@available(iOS 13.0, *)) { [cameras addObject:ultraWideCamera]; } OCMStub([discoverySessionMock devices]).andReturn([NSArray arrayWithArray:cameras]); MockFLTThreadSafeFlutterResult *resultObject = [[MockFLTThreadSafeFlutterResult alloc] initWithExpectation:expectation]; // Set up method call FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"availableCameras" arguments:nil]; [camera handleMethodCallAsync:call result:resultObject]; // Verify the result NSDictionary *dictionaryResult = (NSDictionary *)resultObject.receivedResult; if (@available(iOS 13.0, *)) { XCTAssertTrue([dictionaryResult count] == 4); } else { XCTAssertTrue([dictionaryResult count] == 3); } } - (void)testAvailableCamerasShouldReturnOneCameraOnSingleCameraIPhone { CameraPlugin *camera = [[CameraPlugin alloc] initWithRegistry:nil messenger:nil]; XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Result finished"]; // iPhone 8 Cameras: AVCaptureDevice *wideAngleCamera = OCMClassMock([AVCaptureDevice class]); OCMStub([wideAngleCamera uniqueID]).andReturn(@"0"); OCMStub([wideAngleCamera position]).andReturn(AVCaptureDevicePositionBack); AVCaptureDevice *frontFacingCamera = OCMClassMock([AVCaptureDevice class]); OCMStub([frontFacingCamera uniqueID]).andReturn(@"1"); OCMStub([frontFacingCamera position]).andReturn(AVCaptureDevicePositionFront); NSMutableArray *requiredTypes = [@[ AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeBuiltInTelephotoCamera ] mutableCopy]; if (@available(iOS 13.0, *)) { [requiredTypes addObject:AVCaptureDeviceTypeBuiltInUltraWideCamera]; } id discoverySessionMock = OCMClassMock([AVCaptureDeviceDiscoverySession class]); OCMStub([discoverySessionMock discoverySessionWithDeviceTypes:requiredTypes mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified]) .andReturn(discoverySessionMock); NSMutableArray *cameras = [NSMutableArray array]; [cameras addObjectsFromArray:@[ wideAngleCamera, frontFacingCamera ]]; OCMStub([discoverySessionMock devices]).andReturn([NSArray arrayWithArray:cameras]); MockFLTThreadSafeFlutterResult *resultObject = [[MockFLTThreadSafeFlutterResult alloc] initWithExpectation:expectation]; // Set up method call FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"availableCameras" arguments:nil]; [camera handleMethodCallAsync:call result:resultObject]; // Verify the result NSDictionary *dictionaryResult = (NSDictionary *)resultObject.receivedResult; XCTAssertTrue([dictionaryResult count] == 2); } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraCaptureSessionQueueRaceConditionTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import camera_avfoundation.Test; @import XCTest; @interface CameraCaptureSessionQueueRaceConditionTests : XCTestCase @end @implementation CameraCaptureSessionQueueRaceConditionTests - (void)testFixForCaptureSessionQueueNullPointerCrashDueToRaceCondition { CameraPlugin *camera = [[CameraPlugin alloc] initWithRegistry:nil messenger:nil]; XCTestExpectation *disposeExpectation = [self expectationWithDescription:@"dispose's result block must be called"]; XCTestExpectation *createExpectation = [self expectationWithDescription:@"create's result block must be called"]; FlutterMethodCall *disposeCall = [FlutterMethodCall methodCallWithMethodName:@"dispose" arguments:nil]; FlutterMethodCall *createCall = [FlutterMethodCall methodCallWithMethodName:@"create" arguments:@{@"resolutionPreset" : @"medium", @"enableAudio" : @(1)}]; // Mimic a dispose call followed by a create call, which can be triggered by slightly dragging the // home bar, causing the app to be inactive, and immediately regain active. [camera handleMethodCall:disposeCall result:^(id _Nullable result) { [disposeExpectation fulfill]; }]; [camera createCameraOnSessionQueueWithCreateMethodCall:createCall result:[[FLTThreadSafeFlutterResult alloc] initWithResult:^(id _Nullable result) { [createExpectation fulfill]; }]]; [self waitForExpectationsWithTimeout:1 handler:nil]; // `captureSessionQueue` must not be nil after `create` call. Otherwise a nil // `captureSessionQueue` passed into `AVCaptureVideoDataOutput::setSampleBufferDelegate:queue:` // API will cause a crash. XCTAssertNotNil(camera.captureSessionQueue, @"captureSessionQueue must not be nil after create method. "); } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraExposureTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import XCTest; @import AVFoundation; #import @interface FLTCam : NSObject - (void)setExposurePointWithResult:(FlutterResult)result x:(double)x y:(double)y; @end @interface CameraExposureTests : XCTestCase @property(readonly, nonatomic) FLTCam *camera; @property(readonly, nonatomic) id mockDevice; @property(readonly, nonatomic) id mockUIDevice; @end @implementation CameraExposureTests - (void)setUp { _camera = [[FLTCam alloc] init]; _mockDevice = OCMClassMock([AVCaptureDevice class]); _mockUIDevice = OCMPartialMock([UIDevice currentDevice]); } - (void)tearDown { [_mockDevice stopMocking]; [_mockUIDevice stopMocking]; } - (void)testSetExpsourePointWithResult_SetsExposurePointOfInterest { // UI is currently in landscape left orientation OCMStub([(UIDevice *)_mockUIDevice orientation]).andReturn(UIDeviceOrientationLandscapeLeft); // Exposure point of interest is supported OCMStub([_mockDevice isExposurePointOfInterestSupported]).andReturn(true); // Set mock device as the current capture device [_camera setValue:_mockDevice forKey:@"captureDevice"]; // Run test [_camera setExposurePointWithResult:^void(id _Nullable result) { } x:1 y:1]; // Verify the focus point of interest has been set OCMVerify([_mockDevice setExposurePointOfInterest:CGPointMake(1, 1)]); } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraFocusTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import camera_avfoundation.Test; @import XCTest; @import AVFoundation; #import @interface CameraFocusTests : XCTestCase @property(readonly, nonatomic) FLTCam *camera; @property(readonly, nonatomic) id mockDevice; @property(readonly, nonatomic) id mockUIDevice; @end @implementation CameraFocusTests - (void)setUp { _camera = [[FLTCam alloc] init]; _mockDevice = OCMClassMock([AVCaptureDevice class]); _mockUIDevice = OCMPartialMock([UIDevice currentDevice]); } - (void)tearDown { [_mockDevice stopMocking]; [_mockUIDevice stopMocking]; } - (void)testAutoFocusWithContinuousModeSupported_ShouldSetContinuousAutoFocus { // AVCaptureFocusModeContinuousAutoFocus is supported OCMStub([_mockDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]).andReturn(true); // AVCaptureFocusModeContinuousAutoFocus is supported OCMStub([_mockDevice isFocusModeSupported:AVCaptureFocusModeAutoFocus]).andReturn(true); // Don't expect setFocusMode:AVCaptureFocusModeAutoFocus [[_mockDevice reject] setFocusMode:AVCaptureFocusModeAutoFocus]; // Run test [_camera applyFocusMode:FLTFocusModeAuto onDevice:_mockDevice]; // Expect setFocusMode:AVCaptureFocusModeContinuousAutoFocus OCMVerify([_mockDevice setFocusMode:AVCaptureFocusModeContinuousAutoFocus]); } - (void)testAutoFocusWithContinuousModeNotSupported_ShouldSetAutoFocus { // AVCaptureFocusModeContinuousAutoFocus is not supported OCMStub([_mockDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) .andReturn(false); // AVCaptureFocusModeContinuousAutoFocus is supported OCMStub([_mockDevice isFocusModeSupported:AVCaptureFocusModeAutoFocus]).andReturn(true); // Don't expect setFocusMode:AVCaptureFocusModeContinuousAutoFocus [[_mockDevice reject] setFocusMode:AVCaptureFocusModeContinuousAutoFocus]; // Run test [_camera applyFocusMode:FLTFocusModeAuto onDevice:_mockDevice]; // Expect setFocusMode:AVCaptureFocusModeAutoFocus OCMVerify([_mockDevice setFocusMode:AVCaptureFocusModeAutoFocus]); } - (void)testAutoFocusWithNoModeSupported_ShouldSetNothing { // AVCaptureFocusModeContinuousAutoFocus is not supported OCMStub([_mockDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) .andReturn(false); // AVCaptureFocusModeContinuousAutoFocus is not supported OCMStub([_mockDevice isFocusModeSupported:AVCaptureFocusModeAutoFocus]).andReturn(false); // Don't expect any setFocus [[_mockDevice reject] setFocusMode:AVCaptureFocusModeContinuousAutoFocus]; [[_mockDevice reject] setFocusMode:AVCaptureFocusModeAutoFocus]; // Run test [_camera applyFocusMode:FLTFocusModeAuto onDevice:_mockDevice]; } - (void)testLockedFocusWithModeSupported_ShouldSetModeAutoFocus { // AVCaptureFocusModeContinuousAutoFocus is supported OCMStub([_mockDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]).andReturn(true); // AVCaptureFocusModeContinuousAutoFocus is supported OCMStub([_mockDevice isFocusModeSupported:AVCaptureFocusModeAutoFocus]).andReturn(true); // Don't expect any setFocus [[_mockDevice reject] setFocusMode:AVCaptureFocusModeContinuousAutoFocus]; // Run test [_camera applyFocusMode:FLTFocusModeLocked onDevice:_mockDevice]; // Expect setFocusMode:AVCaptureFocusModeAutoFocus OCMVerify([_mockDevice setFocusMode:AVCaptureFocusModeAutoFocus]); } - (void)testLockedFocusWithModeNotSupported_ShouldSetNothing { // AVCaptureFocusModeContinuousAutoFocus is supported OCMStub([_mockDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]).andReturn(true); // AVCaptureFocusModeContinuousAutoFocus is not supported OCMStub([_mockDevice isFocusModeSupported:AVCaptureFocusModeAutoFocus]).andReturn(false); // Don't expect any setFocus [[_mockDevice reject] setFocusMode:AVCaptureFocusModeContinuousAutoFocus]; [[_mockDevice reject] setFocusMode:AVCaptureFocusModeAutoFocus]; // Run test [_camera applyFocusMode:FLTFocusModeLocked onDevice:_mockDevice]; } - (void)testSetFocusPointWithResult_SetsFocusPointOfInterest { // UI is currently in landscape left orientation OCMStub([(UIDevice *)_mockUIDevice orientation]).andReturn(UIDeviceOrientationLandscapeLeft); // Focus point of interest is supported OCMStub([_mockDevice isFocusPointOfInterestSupported]).andReturn(true); // Set mock device as the current capture device [_camera setValue:_mockDevice forKey:@"captureDevice"]; // Run test [_camera setFocusPointWithResult:[[FLTThreadSafeFlutterResult alloc] initWithResult:^(id _Nullable result){ }] x:1 y:1]; // Verify the focus point of interest has been set OCMVerify([_mockDevice setFocusPointOfInterest:CGPointMake(1, 1)]); } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import camera_avfoundation.Test; @import XCTest; @import AVFoundation; #import #import "MockFLTThreadSafeFlutterResult.h" @interface CameraMethodChannelTests : XCTestCase @end @implementation CameraMethodChannelTests - (void)testCreate_ShouldCallResultOnMainThread { CameraPlugin *camera = [[CameraPlugin alloc] initWithRegistry:nil messenger:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"Result finished"]; // Set up mocks for initWithCameraName method id avCaptureDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]); OCMStub([avCaptureDeviceInputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg anyObjectRef]]) .andReturn([AVCaptureInput alloc]); id avCaptureSessionMock = OCMClassMock([AVCaptureSession class]); OCMStub([avCaptureSessionMock alloc]).andReturn(avCaptureSessionMock); OCMStub([avCaptureSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); MockFLTThreadSafeFlutterResult *resultObject = [[MockFLTThreadSafeFlutterResult alloc] initWithExpectation:expectation]; // Set up method call FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"create" arguments:@{@"resolutionPreset" : @"medium", @"enableAudio" : @(1)}]; [camera createCameraOnSessionQueueWithCreateMethodCall:call result:resultObject]; [self waitForExpectationsWithTimeout:1 handler:nil]; // Verify the result NSDictionary *dictionaryResult = (NSDictionary *)resultObject.receivedResult; XCTAssertNotNil(dictionaryResult); XCTAssert([[dictionaryResult allKeys] containsObject:@"cameraId"]); } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import camera_avfoundation.Test; @import XCTest; @import Flutter; #import @interface CameraOrientationTests : XCTestCase @end @implementation CameraOrientationTests - (void)testOrientationNotifications { id mockMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); CameraPlugin *cameraPlugin = [[CameraPlugin alloc] initWithRegistry:nil messenger:mockMessenger]; [mockMessenger setExpectationOrderMatters:YES]; [self rotate:UIDeviceOrientationPortraitUpsideDown expectedChannelOrientation:@"portraitDown" cameraPlugin:cameraPlugin messenger:mockMessenger]; [self rotate:UIDeviceOrientationPortrait expectedChannelOrientation:@"portraitUp" cameraPlugin:cameraPlugin messenger:mockMessenger]; [self rotate:UIDeviceOrientationLandscapeRight expectedChannelOrientation:@"landscapeLeft" cameraPlugin:cameraPlugin messenger:mockMessenger]; [self rotate:UIDeviceOrientationLandscapeLeft expectedChannelOrientation:@"landscapeRight" cameraPlugin:cameraPlugin messenger:mockMessenger]; OCMReject([mockMessenger sendOnChannel:[OCMArg any] message:[OCMArg any]]); // No notification when flat. [cameraPlugin orientationChanged:[self createMockNotificationForOrientation:UIDeviceOrientationFaceUp]]; // No notification when facedown. [cameraPlugin orientationChanged:[self createMockNotificationForOrientation:UIDeviceOrientationFaceDown]]; OCMVerifyAll(mockMessenger); } - (void)testOrientationUpdateMustBeOnCaptureSessionQueue { XCTestExpectation *queueExpectation = [self expectationWithDescription:@"Orientation update must happen on the capture session queue"]; CameraPlugin *camera = [[CameraPlugin alloc] initWithRegistry:nil messenger:nil]; const char *captureSessionQueueSpecific = "capture_session_queue"; dispatch_queue_set_specific(camera.captureSessionQueue, captureSessionQueueSpecific, (void *)captureSessionQueueSpecific, NULL); FLTCam *mockCam = OCMClassMock([FLTCam class]); camera.camera = mockCam; OCMStub([mockCam setDeviceOrientation:UIDeviceOrientationLandscapeLeft]) .andDo(^(NSInvocation *invocation) { if (dispatch_get_specific(captureSessionQueueSpecific)) { [queueExpectation fulfill]; } }); [camera orientationChanged: [self createMockNotificationForOrientation:UIDeviceOrientationLandscapeLeft]]; [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)rotate:(UIDeviceOrientation)deviceOrientation expectedChannelOrientation:(NSString *)channelOrientation cameraPlugin:(CameraPlugin *)cameraPlugin messenger:(NSObject *)messenger { XCTestExpectation *orientationExpectation = [self expectationWithDescription:channelOrientation]; OCMExpect([messenger sendOnChannel:[OCMArg any] message:[OCMArg checkWithBlock:^BOOL(NSData *data) { NSObject *codec = [FlutterStandardMethodCodec sharedInstance]; FlutterMethodCall *methodCall = [codec decodeMethodCall:data]; [orientationExpectation fulfill]; return [methodCall.method isEqualToString:@"orientation_changed"] && [methodCall.arguments isEqualToDictionary:@{@"orientation" : channelOrientation}]; }]]); [cameraPlugin orientationChanged:[self createMockNotificationForOrientation:deviceOrientation]]; [self waitForExpectationsWithTimeout:30.0 handler:nil]; } - (void)testOrientationChanged_noRetainCycle { dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL); FLTCam *mockCam = OCMClassMock([FLTCam class]); FLTThreadSafeMethodChannel *mockChannel = OCMClassMock([FLTThreadSafeMethodChannel class]); __weak CameraPlugin *weakCamera; @autoreleasepool { CameraPlugin *camera = [[CameraPlugin alloc] initWithRegistry:nil messenger:nil]; weakCamera = camera; camera.captureSessionQueue = captureSessionQueue; camera.camera = mockCam; camera.deviceEventMethodChannel = mockChannel; [camera orientationChanged: [self createMockNotificationForOrientation:UIDeviceOrientationLandscapeLeft]]; } // Sanity check XCTAssertNil(weakCamera, @"Camera must have been deallocated."); // Must check in captureSessionQueue since orientationChanged dispatches to this queue. XCTestExpectation *expectation = [self expectationWithDescription:@"Dispatched to capture session queue"]; dispatch_async(captureSessionQueue, ^{ OCMVerify(never(), [mockCam setDeviceOrientation:UIDeviceOrientationLandscapeLeft]); OCMVerify(never(), [mockChannel invokeMethod:@"orientation_changed" arguments:OCMOCK_ANY]); [expectation fulfill]; }); [self waitForExpectationsWithTimeout:1 handler:nil]; } - (NSNotification *)createMockNotificationForOrientation:(UIDeviceOrientation)deviceOrientation { UIDevice *mockDevice = OCMClassMock([UIDevice class]); OCMStub([mockDevice orientation]).andReturn(deviceOrientation); return [NSNotification notificationWithName:@"orientation_test" object:mockDevice]; } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import camera_avfoundation.Test; @import AVFoundation; @import XCTest; #import #import "CameraTestUtils.h" @interface CameraPermissionTests : XCTestCase @end @implementation CameraPermissionTests #pragma mark - camera permissions - (void)testRequestCameraPermission_completeWithoutErrorIfPrevoiuslyAuthorized { XCTestExpectation *expectation = [self expectationWithDescription: @"Must copmlete without error if camera access was previously authorized."]; id mockDevice = OCMClassMock([AVCaptureDevice class]); OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeVideo]) .andReturn(AVAuthorizationStatusAuthorized); FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) { if (error == nil) { [expectation fulfill]; } }); [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testRequestCameraPermission_completeWithErrorIfPreviouslyDenied { XCTestExpectation *expectation = [self expectationWithDescription: @"Must complete with error if camera access was previously denied."]; FlutterError *expectedError = [FlutterError errorWithCode:@"CameraAccessDeniedWithoutPrompt" message:@"User has previously denied the camera access request. Go to " @"Settings to enable camera access." details:nil]; id mockDevice = OCMClassMock([AVCaptureDevice class]); OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeVideo]) .andReturn(AVAuthorizationStatusDenied); FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) { if ([error isEqual:expectedError]) { [expectation fulfill]; } }); [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testRequestCameraPermission_completeWithErrorIfRestricted { XCTestExpectation *expectation = [self expectationWithDescription:@"Must complete with error if camera access is restricted."]; FlutterError *expectedError = [FlutterError errorWithCode:@"CameraAccessRestricted" message:@"Camera access is restricted. " details:nil]; id mockDevice = OCMClassMock([AVCaptureDevice class]); OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeVideo]) .andReturn(AVAuthorizationStatusRestricted); FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) { if ([error isEqual:expectedError]) { [expectation fulfill]; } }); [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testRequestCameraPermission_completeWithoutErrorIfUserGrantAccess { XCTestExpectation *grantedExpectation = [self expectationWithDescription:@"Must complete without error if user choose to grant access"]; id mockDevice = OCMClassMock([AVCaptureDevice class]); OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeVideo]) .andReturn(AVAuthorizationStatusNotDetermined); // Mimic user choosing "allow" in permission dialog. OCMStub([mockDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:[OCMArg checkWithBlock:^BOOL(void (^block)(BOOL)) { block(YES); return YES; }]]); FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) { if (error == nil) { [grantedExpectation fulfill]; } }); [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testRequestCameraPermission_completeWithErrorIfUserDenyAccess { XCTestExpectation *expectation = [self expectationWithDescription:@"Must complete with error if user choose to deny access"]; FlutterError *expectedError = [FlutterError errorWithCode:@"CameraAccessDenied" message:@"User denied the camera access request." details:nil]; id mockDevice = OCMClassMock([AVCaptureDevice class]); OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeVideo]) .andReturn(AVAuthorizationStatusNotDetermined); // Mimic user choosing "deny" in permission dialog. OCMStub([mockDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:[OCMArg checkWithBlock:^BOOL(void (^block)(BOOL)) { block(NO); return YES; }]]); FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) { if ([error isEqual:expectedError]) { [expectation fulfill]; } }); [self waitForExpectationsWithTimeout:1 handler:nil]; } #pragma mark - audio permissions - (void)testRequestAudioPermission_completeWithoutErrorIfPrevoiuslyAuthorized { XCTestExpectation *expectation = [self expectationWithDescription: @"Must copmlete without error if audio access was previously authorized."]; id mockDevice = OCMClassMock([AVCaptureDevice class]); OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio]) .andReturn(AVAuthorizationStatusAuthorized); FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) { if (error == nil) { [expectation fulfill]; } }); [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testRequestAudioPermission_completeWithErrorIfPreviouslyDenied { XCTestExpectation *expectation = [self expectationWithDescription: @"Must complete with error if audio access was previously denied."]; FlutterError *expectedError = [FlutterError errorWithCode:@"AudioAccessDeniedWithoutPrompt" message:@"User has previously denied the audio access request. Go to " @"Settings to enable audio access." details:nil]; id mockDevice = OCMClassMock([AVCaptureDevice class]); OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio]) .andReturn(AVAuthorizationStatusDenied); FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) { if ([error isEqual:expectedError]) { [expectation fulfill]; } }); [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testRequestAudioPermission_completeWithErrorIfRestricted { XCTestExpectation *expectation = [self expectationWithDescription:@"Must complete with error if audio access is restricted."]; FlutterError *expectedError = [FlutterError errorWithCode:@"AudioAccessRestricted" message:@"Audio access is restricted. " details:nil]; id mockDevice = OCMClassMock([AVCaptureDevice class]); OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio]) .andReturn(AVAuthorizationStatusRestricted); FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) { if ([error isEqual:expectedError]) { [expectation fulfill]; } }); [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testRequestAudioPermission_completeWithoutErrorIfUserGrantAccess { XCTestExpectation *grantedExpectation = [self expectationWithDescription:@"Must complete without error if user choose to grant access"]; id mockDevice = OCMClassMock([AVCaptureDevice class]); OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio]) .andReturn(AVAuthorizationStatusNotDetermined); // Mimic user choosing "allow" in permission dialog. OCMStub([mockDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:[OCMArg checkWithBlock:^BOOL(void (^block)(BOOL)) { block(YES); return YES; }]]); FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) { if (error == nil) { [grantedExpectation fulfill]; } }); [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testRequestAudioPermission_completeWithErrorIfUserDenyAccess { XCTestExpectation *expectation = [self expectationWithDescription:@"Must complete with error if user choose to deny access"]; FlutterError *expectedError = [FlutterError errorWithCode:@"AudioAccessDenied" message:@"User denied the audio access request." details:nil]; id mockDevice = OCMClassMock([AVCaptureDevice class]); OCMStub([mockDevice authorizationStatusForMediaType:AVMediaTypeAudio]) .andReturn(AVAuthorizationStatusNotDetermined); // Mimic user choosing "deny" in permission dialog. OCMStub([mockDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:[OCMArg checkWithBlock:^BOOL(void (^block)(BOOL)) { block(NO); return YES; }]]); FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) { if ([error isEqual:expectedError]) { [expectation fulfill]; } }); [self waitForExpectationsWithTimeout:1 handler:nil]; } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPreviewPauseTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import camera_avfoundation.Test; @import XCTest; @import AVFoundation; #import #import "MockFLTThreadSafeFlutterResult.h" @interface CameraPreviewPauseTests : XCTestCase @end @implementation CameraPreviewPauseTests - (void)testPausePreviewWithResult_shouldPausePreview { FLTCam *camera = [[FLTCam alloc] init]; MockFLTThreadSafeFlutterResult *resultObject = [[MockFLTThreadSafeFlutterResult alloc] init]; [camera pausePreviewWithResult:resultObject]; XCTAssertTrue(camera.isPreviewPaused); } - (void)testResumePreviewWithResult_shouldResumePreview { FLTCam *camera = [[FLTCam alloc] init]; MockFLTThreadSafeFlutterResult *resultObject = [[MockFLTThreadSafeFlutterResult alloc] init]; [camera resumePreviewWithResult:resultObject]; XCTAssertFalse(camera.isPreviewPaused); } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraPropertiesTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation.Test; @import AVFoundation; @import XCTest; @interface CameraPropertiesTests : XCTestCase @end @implementation CameraPropertiesTests #pragma mark - flash mode tests - (void)testFLTGetFLTFlashModeForString { XCTAssertEqual(FLTFlashModeOff, FLTGetFLTFlashModeForString(@"off")); XCTAssertEqual(FLTFlashModeAuto, FLTGetFLTFlashModeForString(@"auto")); XCTAssertEqual(FLTFlashModeAlways, FLTGetFLTFlashModeForString(@"always")); XCTAssertEqual(FLTFlashModeTorch, FLTGetFLTFlashModeForString(@"torch")); XCTAssertThrows(FLTGetFLTFlashModeForString(@"unkwown")); } - (void)testFLTGetAVCaptureFlashModeForFLTFlashMode { XCTAssertEqual(AVCaptureFlashModeOff, FLTGetAVCaptureFlashModeForFLTFlashMode(FLTFlashModeOff)); XCTAssertEqual(AVCaptureFlashModeAuto, FLTGetAVCaptureFlashModeForFLTFlashMode(FLTFlashModeAuto)); XCTAssertEqual(AVCaptureFlashModeOn, FLTGetAVCaptureFlashModeForFLTFlashMode(FLTFlashModeAlways)); XCTAssertEqual(-1, FLTGetAVCaptureFlashModeForFLTFlashMode(FLTFlashModeTorch)); } #pragma mark - exposure mode tests - (void)testFLTGetStringForFLTExposureMode { XCTAssertEqualObjects(@"auto", FLTGetStringForFLTExposureMode(FLTExposureModeAuto)); XCTAssertEqualObjects(@"locked", FLTGetStringForFLTExposureMode(FLTExposureModeLocked)); XCTAssertThrows(FLTGetStringForFLTExposureMode(-1)); } - (void)testFLTGetFLTExposureModeForString { XCTAssertEqual(FLTExposureModeAuto, FLTGetFLTExposureModeForString(@"auto")); XCTAssertEqual(FLTExposureModeLocked, FLTGetFLTExposureModeForString(@"locked")); XCTAssertThrows(FLTGetFLTExposureModeForString(@"unknown")); } #pragma mark - focus mode tests - (void)testFLTGetStringForFLTFocusMode { XCTAssertEqualObjects(@"auto", FLTGetStringForFLTFocusMode(FLTFocusModeAuto)); XCTAssertEqualObjects(@"locked", FLTGetStringForFLTFocusMode(FLTFocusModeLocked)); XCTAssertThrows(FLTGetStringForFLTFocusMode(-1)); } - (void)testFLTGetFLTFocusModeForString { XCTAssertEqual(FLTFocusModeAuto, FLTGetFLTFocusModeForString(@"auto")); XCTAssertEqual(FLTFocusModeLocked, FLTGetFLTFocusModeForString(@"locked")); XCTAssertThrows(FLTGetFLTFocusModeForString(@"unknown")); } #pragma mark - resolution preset tests - (void)testFLTGetFLTResolutionPresetForString { XCTAssertEqual(FLTResolutionPresetVeryLow, FLTGetFLTResolutionPresetForString(@"veryLow")); XCTAssertEqual(FLTResolutionPresetLow, FLTGetFLTResolutionPresetForString(@"low")); XCTAssertEqual(FLTResolutionPresetMedium, FLTGetFLTResolutionPresetForString(@"medium")); XCTAssertEqual(FLTResolutionPresetHigh, FLTGetFLTResolutionPresetForString(@"high")); XCTAssertEqual(FLTResolutionPresetVeryHigh, FLTGetFLTResolutionPresetForString(@"veryHigh")); XCTAssertEqual(FLTResolutionPresetUltraHigh, FLTGetFLTResolutionPresetForString(@"ultraHigh")); XCTAssertEqual(FLTResolutionPresetMax, FLTGetFLTResolutionPresetForString(@"max")); XCTAssertThrows(FLTGetFLTFlashModeForString(@"unknown")); } #pragma mark - video format tests - (void)testFLTGetVideoFormatFromString { XCTAssertEqual(kCVPixelFormatType_32BGRA, FLTGetVideoFormatFromString(@"bgra8888")); XCTAssertEqual(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, FLTGetVideoFormatFromString(@"yuv420")); XCTAssertEqual(kCVPixelFormatType_32BGRA, FLTGetVideoFormatFromString(@"unknown")); } #pragma mark - device orientation tests - (void)testFLTGetUIDeviceOrientationForString { XCTAssertEqual(UIDeviceOrientationPortraitUpsideDown, FLTGetUIDeviceOrientationForString(@"portraitDown")); XCTAssertEqual(UIDeviceOrientationLandscapeRight, FLTGetUIDeviceOrientationForString(@"landscapeLeft")); XCTAssertEqual(UIDeviceOrientationLandscapeLeft, FLTGetUIDeviceOrientationForString(@"landscapeRight")); XCTAssertEqual(UIDeviceOrientationPortrait, FLTGetUIDeviceOrientationForString(@"portraitUp")); XCTAssertThrows(FLTGetUIDeviceOrientationForString(@"unknown")); } - (void)testFLTGetStringForUIDeviceOrientation { XCTAssertEqualObjects(@"portraitDown", FLTGetStringForUIDeviceOrientation(UIDeviceOrientationPortraitUpsideDown)); XCTAssertEqualObjects(@"landscapeLeft", FLTGetStringForUIDeviceOrientation(UIDeviceOrientationLandscapeRight)); XCTAssertEqualObjects(@"landscapeRight", FLTGetStringForUIDeviceOrientation(UIDeviceOrientationLandscapeLeft)); XCTAssertEqualObjects(@"portraitUp", FLTGetStringForUIDeviceOrientation(UIDeviceOrientationPortrait)); XCTAssertEqualObjects(@"portraitUp", FLTGetStringForUIDeviceOrientation(-1)); } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; NS_ASSUME_NONNULL_BEGIN /// Creates an `FLTCam` that runs its capture session operations on a given queue. /// @param captureSessionQueue the capture session queue /// @return an FLTCam object. extern FLTCam *FLTCreateCamWithCaptureSessionQueue(dispatch_queue_t captureSessionQueue); /// Creates a test sample buffer. /// @return a test sample buffer. extern CMSampleBufferRef FLTCreateTestSampleBuffer(void); NS_ASSUME_NONNULL_END ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m ================================================ // Copyright 2013 The Flutter 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 "CameraTestUtils.h" #import @import AVFoundation; FLTCam *FLTCreateCamWithCaptureSessionQueue(dispatch_queue_t captureSessionQueue) { id inputMock = OCMClassMock([AVCaptureDeviceInput class]); OCMStub([inputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg setTo:nil]]) .andReturn(inputMock); id sessionMock = OCMClassMock([AVCaptureSession class]); OCMStub([sessionMock addInputWithNoConnections:[OCMArg any]]); // no-op OCMStub([sessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES); return [[FLTCam alloc] initWithCameraName:@"camera" resolutionPreset:@"medium" enableAudio:true orientation:UIDeviceOrientationPortrait captureSession:sessionMock captureSessionQueue:captureSessionQueue error:nil]; } CMSampleBufferRef FLTCreateTestSampleBuffer(void) { CVPixelBufferRef pixelBuffer; CVPixelBufferCreate(kCFAllocatorDefault, 100, 100, kCVPixelFormatType_32BGRA, NULL, &pixelBuffer); CMFormatDescriptionRef formatDescription; CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, &formatDescription); CMSampleTimingInfo timingInfo = {CMTimeMake(1, 44100), kCMTimeZero, kCMTimeInvalid}; CMSampleBufferRef sampleBuffer; CMSampleBufferCreateReadyWithImageBuffer(kCFAllocatorDefault, pixelBuffer, formatDescription, &timingInfo, &sampleBuffer); CFRelease(pixelBuffer); CFRelease(formatDescription); return sampleBuffer; } ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraUtilTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import XCTest; @import AVFoundation; #import @interface FLTCam : NSObject - (CGPoint)getCGPointForCoordsWithOrientation:(UIDeviceOrientation)orientation x:(double)x y:(double)y; @end @interface CameraUtilTests : XCTestCase @property(readonly, nonatomic) FLTCam *camera; @end @implementation CameraUtilTests - (void)setUp { _camera = [[FLTCam alloc] init]; } - (void)testGetCGPointForCoordsWithOrientation_ShouldRotateCoords { CGPoint point; point = [_camera getCGPointForCoordsWithOrientation:UIDeviceOrientationLandscapeLeft x:1 y:1]; XCTAssertTrue(CGPointEqualToPoint(point, CGPointMake(1, 1)), @"Resulting coordinates are invalid."); point = [_camera getCGPointForCoordsWithOrientation:UIDeviceOrientationPortrait x:0 y:1]; XCTAssertTrue(CGPointEqualToPoint(point, CGPointMake(1, 1)), @"Resulting coordinates are invalid."); point = [_camera getCGPointForCoordsWithOrientation:UIDeviceOrientationLandscapeRight x:0 y:0]; XCTAssertTrue(CGPointEqualToPoint(point, CGPointMake(1, 1)), @"Resulting coordinates are invalid."); point = [_camera getCGPointForCoordsWithOrientation:UIDeviceOrientationPortraitUpsideDown x:1 y:0]; XCTAssertTrue(CGPointEqualToPoint(point, CGPointMake(1, 1)), @"Resulting coordinates are invalid."); } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamPhotoCaptureTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import camera_avfoundation.Test; @import AVFoundation; @import XCTest; #import #import "CameraTestUtils.h" /// Includes test cases related to photo capture operations for FLTCam class. @interface FLTCamPhotoCaptureTests : XCTestCase @end @implementation FLTCamPhotoCaptureTests - (void)testCaptureToFile_mustReportErrorToResultIfSavePhotoDelegateCompletionsWithError { XCTestExpectation *errorExpectation = [self expectationWithDescription: @"Must send error to result if save photo delegate completes with error."]; dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL); dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific, (void *)FLTCaptureSessionQueueSpecific, NULL); FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue); AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings]; id mockSettings = OCMClassMock([AVCapturePhotoSettings class]); OCMStub([mockSettings photoSettings]).andReturn(settings); NSError *error = [NSError errorWithDomain:@"test" code:0 userInfo:nil]; id mockResult = OCMClassMock([FLTThreadSafeFlutterResult class]); OCMStub([mockResult sendError:error]).andDo(^(NSInvocation *invocation) { [errorExpectation fulfill]; }); id mockOutput = OCMClassMock([AVCapturePhotoOutput class]); OCMStub([mockOutput capturePhotoWithSettings:OCMOCK_ANY delegate:OCMOCK_ANY]) .andDo(^(NSInvocation *invocation) { FLTSavePhotoDelegate *delegate = cam.inProgressSavePhotoDelegates[@(settings.uniqueID)]; // Completion runs on IO queue. dispatch_queue_t ioQueue = dispatch_queue_create("io_queue", NULL); dispatch_async(ioQueue, ^{ delegate.completionHandler(nil, error); }); }); cam.capturePhotoOutput = mockOutput; // `FLTCam::captureToFile` runs on capture session queue. dispatch_async(captureSessionQueue, ^{ [cam captureToFile:mockResult]; }); [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testCaptureToFile_mustReportPathToResultIfSavePhotoDelegateCompletionsWithPath { XCTestExpectation *pathExpectation = [self expectationWithDescription: @"Must send file path to result if save photo delegate completes with file path."]; dispatch_queue_t captureSessionQueue = dispatch_queue_create("capture_session_queue", NULL); dispatch_queue_set_specific(captureSessionQueue, FLTCaptureSessionQueueSpecific, (void *)FLTCaptureSessionQueueSpecific, NULL); FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue); AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings]; id mockSettings = OCMClassMock([AVCapturePhotoSettings class]); OCMStub([mockSettings photoSettings]).andReturn(settings); NSString *filePath = @"test"; id mockResult = OCMClassMock([FLTThreadSafeFlutterResult class]); OCMStub([mockResult sendSuccessWithData:filePath]).andDo(^(NSInvocation *invocation) { [pathExpectation fulfill]; }); id mockOutput = OCMClassMock([AVCapturePhotoOutput class]); OCMStub([mockOutput capturePhotoWithSettings:OCMOCK_ANY delegate:OCMOCK_ANY]) .andDo(^(NSInvocation *invocation) { FLTSavePhotoDelegate *delegate = cam.inProgressSavePhotoDelegates[@(settings.uniqueID)]; // Completion runs on IO queue. dispatch_queue_t ioQueue = dispatch_queue_create("io_queue", NULL); dispatch_async(ioQueue, ^{ delegate.completionHandler(filePath, nil); }); }); cam.capturePhotoOutput = mockOutput; // `FLTCam::captureToFile` runs on capture session queue. dispatch_async(captureSessionQueue, ^{ [cam captureToFile:mockResult]; }); [self waitForExpectationsWithTimeout:1 handler:nil]; } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTCamSampleBufferTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import camera_avfoundation.Test; @import AVFoundation; @import XCTest; #import #import "CameraTestUtils.h" /// Includes test cases related to sample buffer handling for FLTCam class. @interface FLTCamSampleBufferTests : XCTestCase @end @implementation FLTCamSampleBufferTests - (void)testSampleBufferCallbackQueueMustBeCaptureSessionQueue { dispatch_queue_t captureSessionQueue = dispatch_queue_create("testing", NULL); FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue); XCTAssertEqual(captureSessionQueue, cam.captureVideoOutput.sampleBufferCallbackQueue, @"Sample buffer callback queue must be the capture session queue."); } - (void)testCopyPixelBuffer { FLTCam *cam = FLTCreateCamWithCaptureSessionQueue(dispatch_queue_create("test", NULL)); CMSampleBufferRef capturedSampleBuffer = FLTCreateTestSampleBuffer(); CVPixelBufferRef capturedPixelBuffer = CMSampleBufferGetImageBuffer(capturedSampleBuffer); // Mimic sample buffer callback when captured a new video sample [cam captureOutput:cam.captureVideoOutput didOutputSampleBuffer:capturedSampleBuffer fromConnection:OCMClassMock([AVCaptureConnection class])]; CVPixelBufferRef deliveriedPixelBuffer = [cam copyPixelBuffer]; XCTAssertEqual(deliveriedPixelBuffer, capturedPixelBuffer, @"FLTCam must deliver the latest captured pixel buffer to copyPixelBuffer API."); CFRelease(capturedSampleBuffer); CFRelease(deliveriedPixelBuffer); } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/FLTSavePhotoDelegateTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import camera_avfoundation.Test; @import AVFoundation; @import XCTest; #import @interface FLTSavePhotoDelegateTests : XCTestCase @end @implementation FLTSavePhotoDelegateTests - (void)testHandlePhotoCaptureResult_mustCompleteWithErrorIfFailedToCapture { XCTestExpectation *completionExpectation = [self expectationWithDescription:@"Must complete with error if failed to capture photo."]; NSError *captureError = [NSError errorWithDomain:@"test" code:0 userInfo:nil]; dispatch_queue_t ioQueue = dispatch_queue_create("test", NULL); FLTSavePhotoDelegate *delegate = [[FLTSavePhotoDelegate alloc] initWithPath:@"test" ioQueue:ioQueue completionHandler:^(NSString *_Nullable path, NSError *_Nullable error) { XCTAssertEqualObjects(captureError, error); XCTAssertNil(path); [completionExpectation fulfill]; }]; [delegate handlePhotoCaptureResultWithError:captureError photoDataProvider:^NSData * { return nil; }]; [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testHandlePhotoCaptureResult_mustCompleteWithErrorIfFailedToWrite { XCTestExpectation *completionExpectation = [self expectationWithDescription:@"Must complete with error if failed to write file."]; dispatch_queue_t ioQueue = dispatch_queue_create("test", NULL); NSError *ioError = [NSError errorWithDomain:@"IOError" code:0 userInfo:@{NSLocalizedDescriptionKey : @"Localized IO Error"}]; FLTSavePhotoDelegate *delegate = [[FLTSavePhotoDelegate alloc] initWithPath:@"test" ioQueue:ioQueue completionHandler:^(NSString *_Nullable path, NSError *_Nullable error) { XCTAssertEqualObjects(ioError, error); XCTAssertNil(path); [completionExpectation fulfill]; }]; // Do not use OCMClassMock for NSData because some XCTest APIs uses NSData (e.g. // `XCTRunnerIDESession::logDebugMessage:`) on a private queue. id mockData = OCMPartialMock([NSData data]); OCMStub([mockData writeToFile:OCMOCK_ANY options:NSDataWritingAtomic error:[OCMArg setTo:ioError]]) .andReturn(NO); [delegate handlePhotoCaptureResultWithError:nil photoDataProvider:^NSData * { return mockData; }]; [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testHandlePhotoCaptureResult_mustCompleteWithFilePathIfSuccessToWrite { XCTestExpectation *completionExpectation = [self expectationWithDescription:@"Must complete with file path if success to write file."]; dispatch_queue_t ioQueue = dispatch_queue_create("test", NULL); NSString *filePath = @"test"; FLTSavePhotoDelegate *delegate = [[FLTSavePhotoDelegate alloc] initWithPath:filePath ioQueue:ioQueue completionHandler:^(NSString *_Nullable path, NSError *_Nullable error) { XCTAssertNil(error); XCTAssertEqualObjects(filePath, path); [completionExpectation fulfill]; }]; // Do not use OCMClassMock for NSData because some XCTest APIs uses NSData (e.g. // `XCTRunnerIDESession::logDebugMessage:`) on a private queue. id mockData = OCMPartialMock([NSData data]); OCMStub([mockData writeToFile:filePath options:NSDataWritingAtomic error:[OCMArg setTo:nil]]) .andReturn(YES); [delegate handlePhotoCaptureResultWithError:nil photoDataProvider:^NSData * { return mockData; }]; [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testHandlePhotoCaptureResult_bothProvideDataAndSaveFileMustRunOnIOQueue { XCTestExpectation *dataProviderQueueExpectation = [self expectationWithDescription:@"Data provider must run on io queue."]; XCTestExpectation *writeFileQueueExpectation = [self expectationWithDescription:@"File writing must run on io queue"]; XCTestExpectation *completionExpectation = [self expectationWithDescription:@"Must complete with file path if success to write file."]; dispatch_queue_t ioQueue = dispatch_queue_create("test", NULL); const char *ioQueueSpecific = "io_queue_specific"; dispatch_queue_set_specific(ioQueue, ioQueueSpecific, (void *)ioQueueSpecific, NULL); // Do not use OCMClassMock for NSData because some XCTest APIs uses NSData (e.g. // `XCTRunnerIDESession::logDebugMessage:`) on a private queue. id mockData = OCMPartialMock([NSData data]); OCMStub([mockData writeToFile:OCMOCK_ANY options:NSDataWritingAtomic error:[OCMArg setTo:nil]]) .andDo(^(NSInvocation *invocation) { if (dispatch_get_specific(ioQueueSpecific)) { [writeFileQueueExpectation fulfill]; } }) .andReturn(YES); NSString *filePath = @"test"; FLTSavePhotoDelegate *delegate = [[FLTSavePhotoDelegate alloc] initWithPath:filePath ioQueue:ioQueue completionHandler:^(NSString *_Nullable path, NSError *_Nullable error) { [completionExpectation fulfill]; }]; [delegate handlePhotoCaptureResultWithError:nil photoDataProvider:^NSData * { if (dispatch_get_specific(ioQueueSpecific)) { [dataProviderQueueExpectation fulfill]; } return mockData; }]; [self waitForExpectationsWithTimeout:1 handler:nil]; } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/Info.plist ================================================ 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 ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/MockFLTThreadSafeFlutterResult.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef MockFLTThreadSafeFlutterResult_h #define MockFLTThreadSafeFlutterResult_h /** * Extends FLTThreadSafeFlutterResult to give tests the ability to wait on the result and * read the received result. */ @interface MockFLTThreadSafeFlutterResult : FLTThreadSafeFlutterResult @property(readonly, nonatomic, nonnull) XCTestExpectation *expectation; @property(nonatomic, nullable) id receivedResult; /** * Initializes the MockFLTThreadSafeFlutterResult with an expectation. * * The expectation is fullfilled when a result is called allowing tests to await the result in an * asynchronous manner. */ - (nonnull instancetype)initWithExpectation:(nonnull XCTestExpectation *)expectation; @end #endif /* MockFLTThreadSafeFlutterResult_h */ ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/MockFLTThreadSafeFlutterResult.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import XCTest; #import "MockFLTThreadSafeFlutterResult.h" @implementation MockFLTThreadSafeFlutterResult - (instancetype)initWithExpectation:(XCTestExpectation *)expectation { self = [super init]; _expectation = expectation; return self; } - (void)sendSuccessWithData:(id)data { self.receivedResult = data; [self.expectation fulfill]; } - (void)sendSuccess { self.receivedResult = nil; [self.expectation fulfill]; } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/QueueUtilsTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import XCTest; @interface QueueUtilsTests : XCTestCase @end @implementation QueueUtilsTests - (void)testShouldStayOnMainQueueIfCalledFromMainQueue { XCTestExpectation *expectation = [self expectationWithDescription:@"Block must be run on the main queue."]; FLTEnsureToRunOnMainQueue(^{ if (NSThread.isMainThread) { [expectation fulfill]; } }); [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testShouldDispatchToMainQueueIfCalledFromBackgroundQueue { XCTestExpectation *expectation = [self expectationWithDescription:@"Block must be run on the main queue."]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ FLTEnsureToRunOnMainQueue(^{ if (NSThread.isMainThread) { [expectation fulfill]; } }); }); [self waitForExpectationsWithTimeout:1 handler:nil]; } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTest.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import camera_avfoundation.Test; @import XCTest; @import AVFoundation; #import #import "CameraTestUtils.h" @interface StreamingTests : XCTestCase @property(readonly, nonatomic) FLTCam *camera; @property(readonly, nonatomic) CMSampleBufferRef sampleBuffer; @end @implementation StreamingTests - (void)setUp { dispatch_queue_t captureSessionQueue = dispatch_queue_create("testing", NULL); _camera = FLTCreateCamWithCaptureSessionQueue(captureSessionQueue); _sampleBuffer = FLTCreateTestSampleBuffer(); } - (void)tearDown { CFRelease(_sampleBuffer); } - (void)testExceedMaxStreamingPendingFramesCount { XCTestExpectation *streamingExpectation = [self expectationWithDescription:@"Must not call handler over maxStreamingPendingFramesCount"]; id handlerMock = OCMClassMock([FLTImageStreamHandler class]); OCMStub([handlerMock eventSink]).andReturn(^(id event) { [streamingExpectation fulfill]; }); id messenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); [_camera startImageStreamWithMessenger:messenger imageStreamHandler:handlerMock]; XCTKVOExpectation *expectation = [[XCTKVOExpectation alloc] initWithKeyPath:@"isStreamingImages" object:_camera expectedValue:@YES]; XCTWaiterResult result = [XCTWaiter waitForExpectations:@[ expectation ] timeout:1]; XCTAssertEqual(result, XCTWaiterResultCompleted); streamingExpectation.expectedFulfillmentCount = 4; for (int i = 0; i < 10; i++) { [_camera captureOutput:nil didOutputSampleBuffer:self.sampleBuffer fromConnection:nil]; } [self waitForExpectationsWithTimeout:1.0 handler:nil]; } - (void)testReceivedImageStreamData { XCTestExpectation *streamingExpectation = [self expectationWithDescription: @"Must be able to call the handler again when receivedImageStreamData is called"]; id handlerMock = OCMClassMock([FLTImageStreamHandler class]); OCMStub([handlerMock eventSink]).andReturn(^(id event) { [streamingExpectation fulfill]; }); id messenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); [_camera startImageStreamWithMessenger:messenger imageStreamHandler:handlerMock]; XCTKVOExpectation *expectation = [[XCTKVOExpectation alloc] initWithKeyPath:@"isStreamingImages" object:_camera expectedValue:@YES]; XCTWaiterResult result = [XCTWaiter waitForExpectations:@[ expectation ] timeout:1]; XCTAssertEqual(result, XCTWaiterResultCompleted); streamingExpectation.expectedFulfillmentCount = 5; for (int i = 0; i < 10; i++) { [_camera captureOutput:nil didOutputSampleBuffer:self.sampleBuffer fromConnection:nil]; } [_camera receivedImageStreamData]; [_camera captureOutput:nil didOutputSampleBuffer:self.sampleBuffer fromConnection:nil]; [self waitForExpectationsWithTimeout:1.0 handler:nil]; } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import XCTest; #import @interface ThreadSafeEventChannelTests : XCTestCase @end @implementation ThreadSafeEventChannelTests - (void)testSetStreamHandler_shouldStayOnMainThreadIfCalledFromMainThread { FlutterEventChannel *mockEventChannel = OCMClassMock([FlutterEventChannel class]); FLTThreadSafeEventChannel *threadSafeEventChannel = [[FLTThreadSafeEventChannel alloc] initWithEventChannel:mockEventChannel]; XCTestExpectation *mainThreadExpectation = [self expectationWithDescription:@"setStreamHandler must be called on the main thread"]; XCTestExpectation *mainThreadCompletionExpectation = [self expectationWithDescription: @"setStreamHandler's completion block must be called on the main thread"]; OCMStub([mockEventChannel setStreamHandler:[OCMArg any]]).andDo(^(NSInvocation *invocation) { if (NSThread.isMainThread) { [mainThreadExpectation fulfill]; } }); [threadSafeEventChannel setStreamHandler:nil completion:^{ if (NSThread.isMainThread) { [mainThreadCompletionExpectation fulfill]; } }]; [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testSetStreamHandler_shouldDispatchToMainThreadIfCalledFromBackgroundThread { FlutterEventChannel *mockEventChannel = OCMClassMock([FlutterEventChannel class]); FLTThreadSafeEventChannel *threadSafeEventChannel = [[FLTThreadSafeEventChannel alloc] initWithEventChannel:mockEventChannel]; XCTestExpectation *mainThreadExpectation = [self expectationWithDescription:@"setStreamHandler must be called on the main thread"]; XCTestExpectation *mainThreadCompletionExpectation = [self expectationWithDescription: @"setStreamHandler's completion block must be called on the main thread"]; OCMStub([mockEventChannel setStreamHandler:[OCMArg any]]).andDo(^(NSInvocation *invocation) { if (NSThread.isMainThread) { [mainThreadExpectation fulfill]; } }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [threadSafeEventChannel setStreamHandler:nil completion:^{ if (NSThread.isMainThread) { [mainThreadCompletionExpectation fulfill]; } }]; }); [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testEventChannel_shouldBeKeptAliveWhenDispatchingBackToMainThread { XCTestExpectation *expectation = [self expectationWithDescription:@"Completion should be called."]; dispatch_async(dispatch_queue_create("test", NULL), ^{ FLTThreadSafeEventChannel *channel = [[FLTThreadSafeEventChannel alloc] initWithEventChannel:OCMClassMock([FlutterEventChannel class])]; [channel setStreamHandler:OCMOCK_ANY completion:^{ [expectation fulfill]; }]; }); [self waitForExpectationsWithTimeout:1 handler:nil]; } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeFlutterResultTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import XCTest; @interface ThreadSafeFlutterResultTests : XCTestCase @end @implementation ThreadSafeFlutterResultTests - (void)testAsyncSendSuccess_ShouldCallResultOnMainThread { XCTestExpectation *expectation = [self expectationWithDescription:@"Result finished"]; FLTThreadSafeFlutterResult *threadSafeFlutterResult = [[FLTThreadSafeFlutterResult alloc] initWithResult:^(id _Nullable result) { XCTAssert(NSThread.isMainThread); [expectation fulfill]; }]; dispatch_queue_t dispatchQueue = dispatch_queue_create("test dispatchqueue", NULL); dispatch_async(dispatchQueue, ^{ [threadSafeFlutterResult sendSuccess]; }); [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testSyncSendSuccess_ShouldCallResultOnMainThread { XCTestExpectation *expectation = [self expectationWithDescription:@"Result finished"]; FLTThreadSafeFlutterResult *threadSafeFlutterResult = [[FLTThreadSafeFlutterResult alloc] initWithResult:^(id _Nullable result) { XCTAssert(NSThread.isMainThread); [expectation fulfill]; }]; [threadSafeFlutterResult sendSuccess]; [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testSendNotImplemented_ShouldSendNotImplementedToFlutterResult { XCTestExpectation *expectation = [self expectationWithDescription:@"Result finished"]; FLTThreadSafeFlutterResult *threadSafeFlutterResult = [[FLTThreadSafeFlutterResult alloc] initWithResult:^(id _Nullable result) { XCTAssert([result isKindOfClass:FlutterMethodNotImplemented.class]); [expectation fulfill]; }]; dispatch_queue_t dispatchQueue = dispatch_queue_create("test dispatchqueue", NULL); dispatch_async(dispatchQueue, ^{ [threadSafeFlutterResult sendNotImplemented]; }); [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testSendErrorDetails_ShouldSendErrorToFlutterResult { NSString *errorCode = @"errorCode"; NSString *errorMessage = @"message"; NSString *errorDetails = @"error details"; XCTestExpectation *expectation = [self expectationWithDescription:@"Result finished"]; FLTThreadSafeFlutterResult *threadSafeFlutterResult = [[FLTThreadSafeFlutterResult alloc] initWithResult:^(id _Nullable result) { XCTAssert([result isKindOfClass:FlutterError.class]); FlutterError *error = (FlutterError *)result; XCTAssertEqualObjects(error.code, errorCode); XCTAssertEqualObjects(error.message, errorMessage); XCTAssertEqualObjects(error.details, errorDetails); [expectation fulfill]; }]; dispatch_queue_t dispatchQueue = dispatch_queue_create("test dispatchqueue", NULL); dispatch_async(dispatchQueue, ^{ [threadSafeFlutterResult sendErrorWithCode:errorCode message:errorMessage details:errorDetails]; }); [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testSendNSError_ShouldSendErrorToFlutterResult { NSError *originalError = [[NSError alloc] initWithDomain:NSURLErrorDomain code:404 userInfo:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"Result finished"]; FLTThreadSafeFlutterResult *threadSafeFlutterResult = [[FLTThreadSafeFlutterResult alloc] initWithResult:^(id _Nullable result) { XCTAssert([result isKindOfClass:FlutterError.class]); FlutterError *error = (FlutterError *)result; NSString *constructedErrorCode = [NSString stringWithFormat:@"Error %d", (int)originalError.code]; XCTAssertEqualObjects(error.code, constructedErrorCode); [expectation fulfill]; }]; dispatch_queue_t dispatchQueue = dispatch_queue_create("test dispatchqueue", NULL); dispatch_async(dispatchQueue, ^{ [threadSafeFlutterResult sendError:originalError]; }); [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testSendResult_ShouldSendResultToFlutterResult { NSString *resultData = @"resultData"; XCTestExpectation *expectation = [self expectationWithDescription:@"Result finished"]; FLTThreadSafeFlutterResult *threadSafeFlutterResult = [[FLTThreadSafeFlutterResult alloc] initWithResult:^(id _Nullable result) { XCTAssertEqualObjects(result, resultData); [expectation fulfill]; }]; dispatch_queue_t dispatchQueue = dispatch_queue_create("test dispatchqueue", NULL); dispatch_async(dispatchQueue, ^{ [threadSafeFlutterResult sendSuccessWithData:resultData]; }); [self waitForExpectationsWithTimeout:1 handler:nil]; } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeMethodChannelTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import XCTest; #import @interface ThreadSafeMethodChannelTests : XCTestCase @end @implementation ThreadSafeMethodChannelTests - (void)testInvokeMethod_shouldStayOnMainThreadIfCalledFromMainThread { FlutterMethodChannel *mockMethodChannel = OCMClassMock([FlutterMethodChannel class]); FLTThreadSafeMethodChannel *threadSafeMethodChannel = [[FLTThreadSafeMethodChannel alloc] initWithMethodChannel:mockMethodChannel]; XCTestExpectation *mainThreadExpectation = [self expectationWithDescription:@"invokeMethod must be called on the main thread"]; OCMStub([mockMethodChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]) .andDo(^(NSInvocation *invocation) { if (NSThread.isMainThread) { [mainThreadExpectation fulfill]; } }); [threadSafeMethodChannel invokeMethod:@"foo" arguments:nil]; [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testInvokeMethod__shouldDispatchToMainThreadIfCalledFromBackgroundThread { FlutterMethodChannel *mockMethodChannel = OCMClassMock([FlutterMethodChannel class]); FLTThreadSafeMethodChannel *threadSafeMethodChannel = [[FLTThreadSafeMethodChannel alloc] initWithMethodChannel:mockMethodChannel]; XCTestExpectation *mainThreadExpectation = [self expectationWithDescription:@"invokeMethod must be called on the main thread"]; OCMStub([mockMethodChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]) .andDo(^(NSInvocation *invocation) { if (NSThread.isMainThread) { [mainThreadExpectation fulfill]; } }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [threadSafeMethodChannel invokeMethod:@"foo" arguments:nil]; }); [self waitForExpectationsWithTimeout:1 handler:nil]; } @end ================================================ FILE: packages/camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeTextureRegistryTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import camera_avfoundation; @import XCTest; #import @interface ThreadSafeTextureRegistryTests : XCTestCase @end @implementation ThreadSafeTextureRegistryTests - (void)testShouldStayOnMainThreadIfCalledFromMainThread { NSObject *mockTextureRegistry = OCMProtocolMock(@protocol(FlutterTextureRegistry)); FLTThreadSafeTextureRegistry *threadSafeTextureRegistry = [[FLTThreadSafeTextureRegistry alloc] initWithTextureRegistry:mockTextureRegistry]; XCTestExpectation *registerTextureExpectation = [self expectationWithDescription:@"registerTexture must be called on the main thread"]; XCTestExpectation *unregisterTextureExpectation = [self expectationWithDescription:@"unregisterTexture must be called on the main thread"]; XCTestExpectation *textureFrameAvailableExpectation = [self expectationWithDescription:@"textureFrameAvailable must be called on the main thread"]; XCTestExpectation *registerTextureCompletionExpectation = [self expectationWithDescription: @"registerTexture's completion block must be called on the main thread"]; OCMStub([mockTextureRegistry registerTexture:[OCMArg any]]).andDo(^(NSInvocation *invocation) { if (NSThread.isMainThread) { [registerTextureExpectation fulfill]; } }); OCMStub([mockTextureRegistry unregisterTexture:0]).andDo(^(NSInvocation *invocation) { if (NSThread.isMainThread) { [unregisterTextureExpectation fulfill]; } }); OCMStub([mockTextureRegistry textureFrameAvailable:0]).andDo(^(NSInvocation *invocation) { if (NSThread.isMainThread) { [textureFrameAvailableExpectation fulfill]; } }); NSObject *anyTexture = OCMProtocolMock(@protocol(FlutterTexture)); [threadSafeTextureRegistry registerTexture:anyTexture completion:^(int64_t textureId) { if (NSThread.isMainThread) { [registerTextureCompletionExpectation fulfill]; } }]; [threadSafeTextureRegistry textureFrameAvailable:0]; [threadSafeTextureRegistry unregisterTexture:0]; [self waitForExpectationsWithTimeout:1 handler:nil]; } - (void)testShouldDispatchToMainThreadIfCalledFromBackgroundThread { NSObject *mockTextureRegistry = OCMProtocolMock(@protocol(FlutterTextureRegistry)); FLTThreadSafeTextureRegistry *threadSafeTextureRegistry = [[FLTThreadSafeTextureRegistry alloc] initWithTextureRegistry:mockTextureRegistry]; XCTestExpectation *registerTextureExpectation = [self expectationWithDescription:@"registerTexture must be called on the main thread"]; XCTestExpectation *unregisterTextureExpectation = [self expectationWithDescription:@"unregisterTexture must be called on the main thread"]; XCTestExpectation *textureFrameAvailableExpectation = [self expectationWithDescription:@"textureFrameAvailable must be called on the main thread"]; XCTestExpectation *registerTextureCompletionExpectation = [self expectationWithDescription: @"registerTexture's completion block must be called on the main thread"]; OCMStub([mockTextureRegistry registerTexture:[OCMArg any]]).andDo(^(NSInvocation *invocation) { if (NSThread.isMainThread) { [registerTextureExpectation fulfill]; } }); OCMStub([mockTextureRegistry unregisterTexture:0]).andDo(^(NSInvocation *invocation) { if (NSThread.isMainThread) { [unregisterTextureExpectation fulfill]; } }); OCMStub([mockTextureRegistry textureFrameAvailable:0]).andDo(^(NSInvocation *invocation) { if (NSThread.isMainThread) { [textureFrameAvailableExpectation fulfill]; } }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSObject *anyTexture = OCMProtocolMock(@protocol(FlutterTexture)); [threadSafeTextureRegistry registerTexture:anyTexture completion:^(int64_t textureId) { if (NSThread.isMainThread) { [registerTextureCompletionExpectation fulfill]; } }]; [threadSafeTextureRegistry textureFrameAvailable:0]; [threadSafeTextureRegistry unregisterTexture:0]; }); [self waitForExpectationsWithTimeout:1 handler:nil]; } @end ================================================ FILE: packages/camera/camera_avfoundation/example/lib/camera_controller.dart ================================================ // Copyright 2013 The Flutter 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 'dart:collection'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; /// The state of a [CameraController]. class CameraValue { /// Creates a new camera controller state. const CameraValue({ required this.isInitialized, this.previewSize, required this.isRecordingVideo, required this.isTakingPicture, required this.isStreamingImages, required this.isRecordingPaused, required this.flashMode, required this.exposureMode, required this.focusMode, required this.deviceOrientation, this.lockedCaptureOrientation, this.recordingOrientation, this.isPreviewPaused = false, this.previewPauseOrientation, }); /// Creates a new camera controller state for an uninitialized controller. const CameraValue.uninitialized() : this( isInitialized: false, isRecordingVideo: false, isTakingPicture: false, isStreamingImages: false, isRecordingPaused: false, flashMode: FlashMode.auto, exposureMode: ExposureMode.auto, focusMode: FocusMode.auto, deviceOrientation: DeviceOrientation.portraitUp, isPreviewPaused: false, ); /// True after [CameraController.initialize] has completed successfully. final bool isInitialized; /// True when a picture capture request has been sent but as not yet returned. final bool isTakingPicture; /// True when the camera is recording (not the same as previewing). final bool isRecordingVideo; /// True when images from the camera are being streamed. final bool isStreamingImages; /// True when video recording is paused. final bool isRecordingPaused; /// True when the preview widget has been paused manually. final bool isPreviewPaused; /// Set to the orientation the preview was paused in, if it is currently paused. final DeviceOrientation? previewPauseOrientation; /// The size of the preview in pixels. /// /// Is `null` until [isInitialized] is `true`. final Size? previewSize; /// The flash mode the camera is currently set to. final FlashMode flashMode; /// The exposure mode the camera is currently set to. final ExposureMode exposureMode; /// The focus mode the camera is currently set to. final FocusMode focusMode; /// The current device UI orientation. final DeviceOrientation deviceOrientation; /// The currently locked capture orientation. final DeviceOrientation? lockedCaptureOrientation; /// Whether the capture orientation is currently locked. bool get isCaptureOrientationLocked => lockedCaptureOrientation != null; /// The orientation of the currently running video recording. final DeviceOrientation? recordingOrientation; /// Creates a modified copy of the object. /// /// Explicitly specified fields get the specified value, all other fields get /// the same value of the current object. CameraValue copyWith({ bool? isInitialized, bool? isRecordingVideo, bool? isTakingPicture, bool? isStreamingImages, Size? previewSize, bool? isRecordingPaused, FlashMode? flashMode, ExposureMode? exposureMode, FocusMode? focusMode, bool? exposurePointSupported, bool? focusPointSupported, DeviceOrientation? deviceOrientation, Optional? lockedCaptureOrientation, Optional? recordingOrientation, bool? isPreviewPaused, Optional? previewPauseOrientation, }) { return CameraValue( isInitialized: isInitialized ?? this.isInitialized, previewSize: previewSize ?? this.previewSize, isRecordingVideo: isRecordingVideo ?? this.isRecordingVideo, isTakingPicture: isTakingPicture ?? this.isTakingPicture, isStreamingImages: isStreamingImages ?? this.isStreamingImages, isRecordingPaused: isRecordingPaused ?? this.isRecordingPaused, flashMode: flashMode ?? this.flashMode, exposureMode: exposureMode ?? this.exposureMode, focusMode: focusMode ?? this.focusMode, deviceOrientation: deviceOrientation ?? this.deviceOrientation, lockedCaptureOrientation: lockedCaptureOrientation == null ? this.lockedCaptureOrientation : lockedCaptureOrientation.orNull, recordingOrientation: recordingOrientation == null ? this.recordingOrientation : recordingOrientation.orNull, isPreviewPaused: isPreviewPaused ?? this.isPreviewPaused, previewPauseOrientation: previewPauseOrientation == null ? this.previewPauseOrientation : previewPauseOrientation.orNull, ); } @override String toString() { return '${objectRuntimeType(this, 'CameraValue')}(' 'isRecordingVideo: $isRecordingVideo, ' 'isInitialized: $isInitialized, ' 'previewSize: $previewSize, ' 'isStreamingImages: $isStreamingImages, ' 'flashMode: $flashMode, ' 'exposureMode: $exposureMode, ' 'focusMode: $focusMode, ' 'deviceOrientation: $deviceOrientation, ' 'lockedCaptureOrientation: $lockedCaptureOrientation, ' 'recordingOrientation: $recordingOrientation, ' 'isPreviewPaused: $isPreviewPaused, ' 'previewPausedOrientation: $previewPauseOrientation)'; } } /// Controls a device camera. /// /// This is a stripped-down version of the app-facing controller to serve as a /// utility for the example and integration tests. It wraps only the calls that /// have state associated with them, to consolidate tracking of camera state /// outside of the overall example code. class CameraController extends ValueNotifier { /// Creates a new camera controller in an uninitialized state. CameraController( this.description, this.resolutionPreset, { this.enableAudio = true, this.imageFormatGroup, }) : super(const CameraValue.uninitialized()); /// The properties of the camera device controlled by this controller. final CameraDescription description; /// The resolution this controller is targeting. /// /// This resolution preset is not guaranteed to be available on the device, /// if unavailable a lower resolution will be used. /// /// See also: [ResolutionPreset]. final ResolutionPreset resolutionPreset; /// Whether to include audio when recording a video. final bool enableAudio; /// The [ImageFormatGroup] describes the output of the raw image format. /// /// When null the imageFormat will fallback to the platforms default. final ImageFormatGroup? imageFormatGroup; late int _cameraId; bool _isDisposed = false; StreamSubscription? _imageStreamSubscription; FutureOr? _initCalled; StreamSubscription? _deviceOrientationSubscription; /// The camera identifier with which the controller is associated. int get cameraId => _cameraId; /// Initializes the camera on the device. Future initialize() async { final Completer initializeCompleter = Completer(); _deviceOrientationSubscription = CameraPlatform.instance .onDeviceOrientationChanged() .listen((DeviceOrientationChangedEvent event) { value = value.copyWith( deviceOrientation: event.orientation, ); }); _cameraId = await CameraPlatform.instance.createCamera( description, resolutionPreset, enableAudio: enableAudio, ); CameraPlatform.instance .onCameraInitialized(_cameraId) .first .then((CameraInitializedEvent event) { initializeCompleter.complete(event); }); await CameraPlatform.instance.initializeCamera( _cameraId, imageFormatGroup: imageFormatGroup ?? ImageFormatGroup.unknown, ); value = value.copyWith( isInitialized: true, previewSize: await initializeCompleter.future .then((CameraInitializedEvent event) => Size( event.previewWidth, event.previewHeight, )), exposureMode: await initializeCompleter.future .then((CameraInitializedEvent event) => event.exposureMode), focusMode: await initializeCompleter.future .then((CameraInitializedEvent event) => event.focusMode), exposurePointSupported: await initializeCompleter.future .then((CameraInitializedEvent event) => event.exposurePointSupported), focusPointSupported: await initializeCompleter.future .then((CameraInitializedEvent event) => event.focusPointSupported), ); _initCalled = true; } /// Prepare the capture session for video recording. Future prepareForVideoRecording() async { await CameraPlatform.instance.prepareForVideoRecording(); } /// Pauses the current camera preview Future pausePreview() async { await CameraPlatform.instance.pausePreview(_cameraId); value = value.copyWith( isPreviewPaused: true, 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, previewPauseOrientation: const Optional.absent()); } /// Captures an image and returns the file where it was saved. /// /// Throws a [CameraException] if the capture fails. Future takePicture() async { value = value.copyWith(isTakingPicture: true); final XFile file = await CameraPlatform.instance.takePicture(_cameraId); value = value.copyWith(isTakingPicture: false); return file; } /// Start streaming images from platform camera. Future startImageStream( Function(CameraImageData image) onAvailable) async { _imageStreamSubscription = CameraPlatform.instance .onStreamedFrameAvailable(_cameraId) .listen((CameraImageData imageData) { onAvailable(imageData); }); value = value.copyWith(isStreamingImages: true); } /// Stop streaming images from platform camera. Future stopImageStream() async { value = value.copyWith(isStreamingImages: false); await _imageStreamSubscription?.cancel(); _imageStreamSubscription = null; } /// Start a video recording. /// /// The video is returned as a [XFile] after calling [stopVideoRecording]. /// Throws a [CameraException] if the capture fails. Future startVideoRecording( {Function(CameraImageData image)? streamCallback}) async { await CameraPlatform.instance.startVideoCapturing( VideoCaptureOptions(_cameraId, streamCallback: streamCallback)); value = value.copyWith( isRecordingVideo: true, isRecordingPaused: false, isStreamingImages: streamCallback != null, recordingOrientation: Optional.of( value.lockedCaptureOrientation ?? value.deviceOrientation)); } /// Stops the video recording and returns the file where it was saved. /// /// Throws a [CameraException] if the capture failed. Future stopVideoRecording() async { if (value.isStreamingImages) { await stopImageStream(); } final XFile file = await CameraPlatform.instance.stopVideoRecording(_cameraId); value = value.copyWith( isRecordingVideo: false, recordingOrientation: const Optional.absent(), ); return file; } /// Pause video recording. /// /// This feature is only available on iOS and Android sdk 24+. Future pauseVideoRecording() async { await CameraPlatform.instance.pauseVideoRecording(_cameraId); value = value.copyWith(isRecordingPaused: true); } /// Resume video recording after pausing. /// /// This feature is only available on iOS and Android sdk 24+. Future resumeVideoRecording() async { await CameraPlatform.instance.resumeVideoRecording(_cameraId); value = value.copyWith(isRecordingPaused: false); } /// Returns a widget showing a live camera preview. Widget buildPreview() { return CameraPlatform.instance.buildPreview(_cameraId); } /// Sets the flash mode for taking pictures. Future setFlashMode(FlashMode mode) async { await CameraPlatform.instance.setFlashMode(_cameraId, mode); value = value.copyWith(flashMode: mode); } /// Sets the exposure mode for taking pictures. Future setExposureMode(ExposureMode mode) async { await CameraPlatform.instance.setExposureMode(_cameraId, mode); value = value.copyWith(exposureMode: mode); } /// Sets the exposure offset for the selected camera. Future setExposureOffset(double offset) async { // Check if offset is in range final List range = await Future.wait(>[ CameraPlatform.instance.getMinExposureOffset(_cameraId), CameraPlatform.instance.getMaxExposureOffset(_cameraId) ]); // Round to the closest step if needed final double stepSize = await CameraPlatform.instance.getExposureOffsetStepSize(_cameraId); if (stepSize > 0) { final double inv = 1.0 / stepSize; double roundedOffset = (offset * inv).roundToDouble() / inv; if (roundedOffset > range[1]) { roundedOffset = (offset * inv).floorToDouble() / inv; } else if (roundedOffset < range[0]) { roundedOffset = (offset * inv).ceilToDouble() / inv; } offset = roundedOffset; } return CameraPlatform.instance.setExposureOffset(_cameraId, offset); } /// Locks the capture orientation. /// /// If [orientation] is omitted, the current device orientation is used. Future lockCaptureOrientation() async { await CameraPlatform.instance .lockCaptureOrientation(_cameraId, 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. Future setFocusMode(FocusMode mode) async { await CameraPlatform.instance.setFocusMode(_cameraId, mode); value = value.copyWith(focusMode: mode); } /// Releases the resources of this camera. @override Future dispose() async { if (_isDisposed) { return; } _deviceOrientationSubscription?.cancel(); _isDisposed = true; super.dispose(); if (_initCalled != null) { await _initCalled; await CameraPlatform.instance.dispose(_cameraId); } } @override void removeListener(VoidCallback listener) { // Prevent ValueListenableBuilder in CameraPreview widget from causing an // exception to be thrown by attempting to remove its own listener after // the controller has already been disposed. if (!_isDisposed) { super.removeListener(listener); } } } /// 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 }'; } } ================================================ FILE: packages/camera/camera_avfoundation/example/lib/camera_preview.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'camera_controller.dart'; /// A widget showing a live camera preview. class CameraPreview extends StatelessWidget { /// Creates a preview widget for the given camera controller. const CameraPreview(this.controller, {Key? key, this.child}) : super(key: key); /// The controller for the camera that the preview is shown for. final CameraController controller; /// A widget to overlay on top of the camera preview final Widget? child; @override Widget build(BuildContext context) { return controller.value.isInitialized ? ValueListenableBuilder( valueListenable: controller, builder: (BuildContext context, Object? value, Widget? child) { final double cameraAspectRatio = controller.value.previewSize!.width / controller.value.previewSize!.height; return AspectRatio( aspectRatio: _isLandscape() ? cameraAspectRatio : (1 / cameraAspectRatio), child: Stack( fit: StackFit.expand, children: [ _wrapInRotatedBox(child: controller.buildPreview()), child ?? Container(), ], ), ); }, child: child, ) : Container(); } Widget _wrapInRotatedBox({required Widget child}) { if (kIsWeb || defaultTargetPlatform != TargetPlatform.android) { return child; } return RotatedBox( quarterTurns: _getQuarterTurns(), child: child, ); } bool _isLandscape() { return [ DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight ].contains(_getApplicableOrientation()); } int _getQuarterTurns() { final Map turns = { DeviceOrientation.portraitUp: 0, DeviceOrientation.landscapeRight: 1, DeviceOrientation.portraitDown: 2, DeviceOrientation.landscapeLeft: 3, }; return turns[_getApplicableOrientation()]!; } DeviceOrientation _getApplicableOrientation() { return controller.value.isRecordingVideo ? controller.value.recordingOrientation! : (controller.value.previewPauseOrientation ?? controller.value.lockedCaptureOrientation ?? controller.value.deviceOrientation); } } ================================================ FILE: packages/camera/camera_avfoundation/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; import 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:video_player/video_player.dart'; import 'camera_controller.dart'; import 'camera_preview.dart'; /// Camera example home widget. class CameraExampleHome extends StatefulWidget { /// Default Constructor const CameraExampleHome({Key? key}) : super(key: key); @override State createState() { return _CameraExampleHomeState(); } } /// Returns a suitable camera icon for [direction]. IconData getCameraLensIcon(CameraLensDirection direction) { switch (direction) { case CameraLensDirection.back: return Icons.camera_rear; case CameraLensDirection.front: return Icons.camera_front; case CameraLensDirection.external: return Icons.camera; } // 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) { // ignore: avoid_print print('Error: $code${message == null ? '' : '\nError Message: $message'}'); } class _CameraExampleHomeState extends State with WidgetsBindingObserver, TickerProviderStateMixin { CameraController? controller; XFile? imageFile; XFile? videoFile; VideoPlayerController? videoController; VoidCallback? videoPlayerListener; bool enableAudio = true; double _minAvailableExposureOffset = 0.0; double _maxAvailableExposureOffset = 0.0; double _currentExposureOffset = 0.0; late AnimationController _flashModeControlRowAnimationController; late Animation _flashModeControlRowAnimation; late AnimationController _exposureModeControlRowAnimationController; late Animation _exposureModeControlRowAnimation; late AnimationController _focusModeControlRowAnimationController; late Animation _focusModeControlRowAnimation; double _minAvailableZoom = 1.0; double _maxAvailableZoom = 1.0; double _currentScale = 1.0; double _baseScale = 1.0; // Counting pointers (number of user fingers on screen) int _pointers = 0; @override void initState() { super.initState(); _ambiguate(WidgetsBinding.instance)?.addObserver(this); _flashModeControlRowAnimationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _flashModeControlRowAnimation = CurvedAnimation( parent: _flashModeControlRowAnimationController, curve: Curves.easeInCubic, ); _exposureModeControlRowAnimationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _exposureModeControlRowAnimation = CurvedAnimation( parent: _exposureModeControlRowAnimationController, curve: Curves.easeInCubic, ); _focusModeControlRowAnimationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); _focusModeControlRowAnimation = CurvedAnimation( parent: _focusModeControlRowAnimationController, curve: Curves.easeInCubic, ); } @override void dispose() { _ambiguate(WidgetsBinding.instance)?.removeObserver(this); _flashModeControlRowAnimationController.dispose(); _exposureModeControlRowAnimationController.dispose(); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { final CameraController? cameraController = controller; // App state changed before we got the chance to initialize. if (cameraController == null || !cameraController.value.isInitialized) { return; } if (state == AppLifecycleState.inactive) { cameraController.dispose(); } else if (state == AppLifecycleState.resumed) { onNewCameraSelected(cameraController.description); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Camera example'), ), body: Column( children: [ Expanded( child: Container( decoration: BoxDecoration( color: Colors.black, border: Border.all( color: controller != null && controller!.value.isRecordingVideo ? Colors.redAccent : Colors.grey, width: 3.0, ), ), child: Padding( padding: const EdgeInsets.all(1.0), child: Center( child: _cameraPreviewWidget(), ), ), ), ), _captureControlRowWidget(), _modeControlRowWidget(), Padding( padding: const EdgeInsets.all(5.0), child: Row( children: [ _cameraTogglesRowWidget(), _thumbnailWidget(), ], ), ), ], ), ); } /// Display the preview from the camera (or a message if the preview is not available). Widget _cameraPreviewWidget() { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { return const Text( 'Tap a camera', style: TextStyle( color: Colors.white, fontSize: 24.0, fontWeight: FontWeight.w900, ), ); } else { return Listener( onPointerDown: (_) => _pointers++, onPointerUp: (_) => _pointers--, child: CameraPreview( controller!, child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return GestureDetector( behavior: HitTestBehavior.opaque, onScaleStart: _handleScaleStart, onScaleUpdate: _handleScaleUpdate, onTapDown: (TapDownDetails details) => onViewFinderTap(details, constraints), ); }), ), ); } } void _handleScaleStart(ScaleStartDetails details) { _baseScale = _currentScale; } Future _handleScaleUpdate(ScaleUpdateDetails details) async { // When there are not exactly two fingers on screen don't scale if (controller == null || _pointers != 2) { return; } _currentScale = (_baseScale * details.scale) .clamp(_minAvailableZoom, _maxAvailableZoom); await CameraPlatform.instance .setZoomLevel(controller!.cameraId, _currentScale); } /// Display the thumbnail of the captured image or video. Widget _thumbnailWidget() { final VideoPlayerController? localVideoController = videoController; return Expanded( child: Align( alignment: Alignment.centerRight, child: Row( mainAxisSize: MainAxisSize.min, children: [ if (localVideoController == null && imageFile == null) Container() else SizedBox( width: 64.0, height: 64.0, child: (localVideoController == null) ? ( // The captured image on the web contains a network-accessible URL // pointing to a location within the browser. It may be displayed // either with Image.network or Image.memory after loading the image // bytes to memory. kIsWeb ? Image.network(imageFile!.path) : Image.file(File(imageFile!.path))) : Container( decoration: BoxDecoration( border: Border.all(color: Colors.pink)), child: Center( child: AspectRatio( aspectRatio: localVideoController.value.size != null ? localVideoController.value.aspectRatio : 1.0, child: VideoPlayer(localVideoController)), ), ), ), ], ), ), ); } /// Display a bar with buttons to change the flash and exposure modes Widget _modeControlRowWidget() { return Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( icon: const Icon(Icons.flash_on), color: Colors.blue, onPressed: controller != null ? onFlashModeButtonPressed : null, ), // The exposure and focus mode are currently not supported on the web. ...!kIsWeb ? [ IconButton( icon: const Icon(Icons.exposure), color: Colors.blue, onPressed: controller != null ? onExposureModeButtonPressed : null, ), IconButton( icon: const Icon(Icons.filter_center_focus), color: Colors.blue, onPressed: controller != null ? onFocusModeButtonPressed : null, ) ] : [], IconButton( icon: Icon(enableAudio ? Icons.volume_up : Icons.volume_mute), color: Colors.blue, onPressed: controller != null ? onAudioModeButtonPressed : null, ), IconButton( icon: Icon(controller?.value.isCaptureOrientationLocked ?? false ? Icons.screen_lock_rotation : Icons.screen_rotation), color: Colors.blue, onPressed: controller != null ? onCaptureOrientationLockButtonPressed : null, ), ], ), _flashModeControlRowWidget(), _exposureModeControlRowWidget(), _focusModeControlRowWidget(), ], ); } Widget _flashModeControlRowWidget() { return SizeTransition( sizeFactor: _flashModeControlRowAnimation, child: ClipRect( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( icon: const Icon(Icons.flash_off), color: controller?.value.flashMode == FlashMode.off ? Colors.orange : Colors.blue, onPressed: controller != null ? () => onSetFlashModeButtonPressed(FlashMode.off) : null, ), IconButton( icon: const Icon(Icons.flash_auto), color: controller?.value.flashMode == FlashMode.auto ? Colors.orange : Colors.blue, onPressed: controller != null ? () => onSetFlashModeButtonPressed(FlashMode.auto) : null, ), IconButton( icon: const Icon(Icons.flash_on), color: controller?.value.flashMode == FlashMode.always ? Colors.orange : Colors.blue, onPressed: controller != null ? () => onSetFlashModeButtonPressed(FlashMode.always) : null, ), IconButton( icon: const Icon(Icons.highlight), color: controller?.value.flashMode == FlashMode.torch ? Colors.orange : Colors.blue, onPressed: controller != null ? () => onSetFlashModeButtonPressed(FlashMode.torch) : null, ), ], ), ), ); } Widget _exposureModeControlRowWidget() { final ButtonStyle styleAuto = TextButton.styleFrom( // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: controller?.value.exposureMode == ExposureMode.auto ? Colors.orange : Colors.blue, ); final ButtonStyle styleLocked = TextButton.styleFrom( // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: controller?.value.exposureMode == ExposureMode.locked ? Colors.orange : Colors.blue, ); return SizeTransition( sizeFactor: _exposureModeControlRowAnimation, child: ClipRect( child: Container( color: Colors.grey.shade50, child: Column( children: [ const Center( child: Text('Exposure Mode'), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ TextButton( style: styleAuto, onPressed: controller != null ? () => onSetExposureModeButtonPressed(ExposureMode.auto) : null, onLongPress: () { if (controller != null) { CameraPlatform.instance .setExposurePoint(controller!.cameraId, null); showInSnackBar('Resetting exposure point'); } }, child: const Text('AUTO'), ), TextButton( style: styleLocked, onPressed: controller != null ? () => onSetExposureModeButtonPressed(ExposureMode.locked) : null, child: const Text('LOCKED'), ), TextButton( style: styleLocked, onPressed: controller != null ? () => controller!.setExposureOffset(0.0) : null, child: const Text('RESET OFFSET'), ), ], ), const Center( child: Text('Exposure Offset'), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text(_minAvailableExposureOffset.toString()), Slider( value: _currentExposureOffset, min: _minAvailableExposureOffset, max: _maxAvailableExposureOffset, label: _currentExposureOffset.toString(), onChanged: _minAvailableExposureOffset == _maxAvailableExposureOffset ? null : setExposureOffset, ), Text(_maxAvailableExposureOffset.toString()), ], ), ], ), ), ), ); } Widget _focusModeControlRowWidget() { final ButtonStyle styleAuto = TextButton.styleFrom( // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: controller?.value.focusMode == FocusMode.auto ? Colors.orange : Colors.blue, ); final ButtonStyle styleLocked = TextButton.styleFrom( // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: controller?.value.focusMode == FocusMode.locked ? Colors.orange : Colors.blue, ); return SizeTransition( sizeFactor: _focusModeControlRowAnimation, child: ClipRect( child: Container( color: Colors.grey.shade50, child: Column( children: [ const Center( child: Text('Focus Mode'), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ TextButton( style: styleAuto, onPressed: controller != null ? () => onSetFocusModeButtonPressed(FocusMode.auto) : null, onLongPress: () { if (controller != null) { CameraPlatform.instance .setFocusPoint(controller!.cameraId, null); } showInSnackBar('Resetting focus point'); }, child: const Text('AUTO'), ), TextButton( style: styleLocked, onPressed: controller != null ? () => onSetFocusModeButtonPressed(FocusMode.locked) : null, child: const Text('LOCKED'), ), ], ), ], ), ), ), ); } /// Display the control bar with buttons to take pictures and record videos. Widget _captureControlRowWidget() { final CameraController? cameraController = controller; return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ IconButton( icon: const Icon(Icons.camera_alt), color: Colors.blue, onPressed: cameraController != null && cameraController.value.isInitialized && !cameraController.value.isRecordingVideo ? onTakePictureButtonPressed : null, ), IconButton( icon: const Icon(Icons.videocam), color: Colors.blue, onPressed: cameraController != null && cameraController.value.isInitialized && !cameraController.value.isRecordingVideo ? onVideoRecordButtonPressed : null, ), IconButton( icon: cameraController != null && (!cameraController.value.isRecordingVideo || cameraController.value.isRecordingPaused) ? const Icon(Icons.play_arrow) : const Icon(Icons.pause), color: Colors.blue, onPressed: cameraController != null && cameraController.value.isInitialized && cameraController.value.isRecordingVideo ? (cameraController.value.isRecordingPaused) ? onResumeButtonPressed : onPauseButtonPressed : null, ), IconButton( icon: const Icon(Icons.stop), color: Colors.red, onPressed: cameraController != null && cameraController.value.isInitialized && cameraController.value.isRecordingVideo ? onStopButtonPressed : null, ), IconButton( icon: const Icon(Icons.pause_presentation), color: cameraController != null && cameraController.value.isPreviewPaused ? Colors.red : Colors.blue, onPressed: cameraController == null ? null : onPausePreviewButtonPressed, ), ], ); } /// Display a row of toggle to select the camera (or a message if no camera is available). Widget _cameraTogglesRowWidget() { final List toggles = []; void onChanged(CameraDescription? description) { if (description == null) { return; } onNewCameraSelected(description); } if (_cameras.isEmpty) { _ambiguate(SchedulerBinding.instance)?.addPostFrameCallback((_) async { showInSnackBar('No camera found.'); }); return const Text('None'); } else { for (final CameraDescription cameraDescription in _cameras) { toggles.add( SizedBox( width: 90.0, child: RadioListTile( title: Icon(getCameraLensIcon(cameraDescription.lensDirection)), groupValue: controller?.description, value: cameraDescription, onChanged: controller != null && controller!.value.isRecordingVideo ? null : onChanged, ), ), ); } } return Row(children: toggles); } String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); void showInSnackBar(String message) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(message))); } void onViewFinderTap(TapDownDetails details, BoxConstraints constraints) { if (controller == null) { return; } final CameraController cameraController = controller!; final Point point = Point( details.localPosition.dx / constraints.maxWidth, details.localPosition.dy / constraints.maxHeight, ); CameraPlatform.instance.setExposurePoint(cameraController.cameraId, point); CameraPlatform.instance.setFocusPoint(cameraController.cameraId, point); } Future onNewCameraSelected(CameraDescription cameraDescription) async { final CameraController? oldController = controller; if (oldController != null) { // `controller` needs to be set to null before getting disposed, // to avoid a race condition when we use the controller that is being // disposed. This happens when camera permission dialog shows up, // which triggers `didChangeAppLifecycleState`, which disposes and // re-creates the controller. controller = null; await oldController.dispose(); } final CameraController cameraController = CameraController( cameraDescription, kIsWeb ? ResolutionPreset.max : ResolutionPreset.medium, enableAudio: enableAudio, imageFormatGroup: ImageFormatGroup.jpeg, ); controller = cameraController; // If the controller is updated then update the UI. cameraController.addListener(() { if (mounted) { setState(() {}); } }); try { await cameraController.initialize(); await Future.wait(>[ // The exposure mode is currently not supported on the web. ...!kIsWeb ? >[ CameraPlatform.instance .getMinExposureOffset(cameraController.cameraId) .then( (double value) => _minAvailableExposureOffset = value), CameraPlatform.instance .getMaxExposureOffset(cameraController.cameraId) .then((double value) => _maxAvailableExposureOffset = value) ] : >[], CameraPlatform.instance .getMaxZoomLevel(cameraController.cameraId) .then((double value) => _maxAvailableZoom = value), CameraPlatform.instance .getMinZoomLevel(cameraController.cameraId) .then((double value) => _minAvailableZoom = value), ]); } on CameraException catch (e) { switch (e.code) { case 'CameraAccessDenied': showInSnackBar('You have denied camera access.'); break; case 'CameraAccessDeniedWithoutPrompt': // iOS only showInSnackBar('Please go to Settings app to enable camera access.'); break; case 'CameraAccessRestricted': // iOS only showInSnackBar('Camera access is restricted.'); break; case 'AudioAccessDenied': showInSnackBar('You have denied audio access.'); break; case 'AudioAccessDeniedWithoutPrompt': // iOS only showInSnackBar('Please go to Settings app to enable audio access.'); break; case 'AudioAccessRestricted': // iOS only showInSnackBar('Audio access is restricted.'); break; case 'cameraPermission': // Android & web only showInSnackBar('Unknown permission error.'); break; default: _showCameraException(e); break; } } if (mounted) { setState(() {}); } } void onTakePictureButtonPressed() { takePicture().then((XFile? file) { if (mounted) { setState(() { imageFile = file; videoController?.dispose(); videoController = null; }); if (file != null) { showInSnackBar('Picture saved to ${file.path}'); } } }); } void onFlashModeButtonPressed() { if (_flashModeControlRowAnimationController.value == 1) { _flashModeControlRowAnimationController.reverse(); } else { _flashModeControlRowAnimationController.forward(); _exposureModeControlRowAnimationController.reverse(); _focusModeControlRowAnimationController.reverse(); } } void onExposureModeButtonPressed() { if (_exposureModeControlRowAnimationController.value == 1) { _exposureModeControlRowAnimationController.reverse(); } else { _exposureModeControlRowAnimationController.forward(); _flashModeControlRowAnimationController.reverse(); _focusModeControlRowAnimationController.reverse(); } } void onFocusModeButtonPressed() { if (_focusModeControlRowAnimationController.value == 1) { _focusModeControlRowAnimationController.reverse(); } else { _focusModeControlRowAnimationController.forward(); _flashModeControlRowAnimationController.reverse(); _exposureModeControlRowAnimationController.reverse(); } } void onAudioModeButtonPressed() { enableAudio = !enableAudio; if (controller != null) { onNewCameraSelected(controller!.description); } } Future onCaptureOrientationLockButtonPressed() async { try { if (controller != null) { final CameraController cameraController = controller!; if (cameraController.value.isCaptureOrientationLocked) { await cameraController.unlockCaptureOrientation(); showInSnackBar('Capture orientation unlocked'); } else { await cameraController.lockCaptureOrientation(); showInSnackBar( 'Capture orientation locked to ${cameraController.value.lockedCaptureOrientation.toString().split('.').last}'); } } } on CameraException catch (e) { _showCameraException(e); } } void onSetFlashModeButtonPressed(FlashMode mode) { setFlashMode(mode).then((_) { if (mounted) { setState(() {}); } showInSnackBar('Flash mode set to ${mode.toString().split('.').last}'); }); } void onSetExposureModeButtonPressed(ExposureMode mode) { setExposureMode(mode).then((_) { if (mounted) { setState(() {}); } showInSnackBar('Exposure mode set to ${mode.toString().split('.').last}'); }); } void onSetFocusModeButtonPressed(FocusMode mode) { setFocusMode(mode).then((_) { if (mounted) { setState(() {}); } showInSnackBar('Focus mode set to ${mode.toString().split('.').last}'); }); } void onVideoRecordButtonPressed() { startVideoRecording().then((_) { if (mounted) { setState(() {}); } }); } void onStopButtonPressed() { stopVideoRecording().then((XFile? file) { if (mounted) { setState(() {}); } if (file != null) { showInSnackBar('Video recorded to ${file.path}'); videoFile = file; _startVideoPlayer(); } }); } Future onPausePreviewButtonPressed() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return; } if (cameraController.value.isPreviewPaused) { await cameraController.resumePreview(); } else { await cameraController.pausePreview(); } if (mounted) { setState(() {}); } } void onPauseButtonPressed() { pauseVideoRecording().then((_) { if (mounted) { setState(() {}); } showInSnackBar('Video recording paused'); }); } void onResumeButtonPressed() { resumeVideoRecording().then((_) { if (mounted) { setState(() {}); } showInSnackBar('Video recording resumed'); }); } Future startVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return; } if (cameraController.value.isRecordingVideo) { // A recording is already started, do nothing. return; } try { await cameraController.startVideoRecording(); } on CameraException catch (e) { _showCameraException(e); return; } } Future stopVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { return null; } try { return cameraController.stopVideoRecording(); } on CameraException catch (e) { _showCameraException(e); return null; } } Future pauseVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { return; } try { await cameraController.pauseVideoRecording(); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future resumeVideoRecording() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isRecordingVideo) { return; } try { await cameraController.resumeVideoRecording(); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future setFlashMode(FlashMode mode) async { if (controller == null) { return; } try { await controller!.setFlashMode(mode); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future setExposureMode(ExposureMode mode) async { if (controller == null) { return; } try { await controller!.setExposureMode(mode); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future setExposureOffset(double offset) async { if (controller == null) { return; } setState(() { _currentExposureOffset = offset; }); try { offset = await controller!.setExposureOffset(offset); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future setFocusMode(FocusMode mode) async { if (controller == null) { return; } try { await controller!.setFocusMode(mode); } on CameraException catch (e) { _showCameraException(e); rethrow; } } Future _startVideoPlayer() async { if (videoFile == null) { return; } final VideoPlayerController vController = kIsWeb ? VideoPlayerController.network(videoFile!.path) : VideoPlayerController.file(File(videoFile!.path)); videoPlayerListener = () { if (videoController != null && videoController!.value.size != null) { // Refreshing the state to update video player with the correct ratio. if (mounted) { setState(() {}); } videoController!.removeListener(videoPlayerListener!); } }; vController.addListener(videoPlayerListener!); await vController.setLooping(true); await vController.initialize(); await videoController?.dispose(); if (mounted) { setState(() { imageFile = null; videoController = vController; }); } await vController.play(); } Future takePicture() async { final CameraController? cameraController = controller; if (cameraController == null || !cameraController.value.isInitialized) { showInSnackBar('Error: select a camera first.'); return null; } if (cameraController.value.isTakingPicture) { // A capture is already pending, do nothing. return null; } try { final XFile file = await cameraController.takePicture(); return file; } on CameraException catch (e) { _showCameraException(e); return null; } } void _showCameraException(CameraException e) { _logError(e.code, e.description); showInSnackBar('Error: ${e.code}\n${e.description}'); } } /// CameraApp is the Main Application. class CameraApp extends StatelessWidget { /// Default Constructor const CameraApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const MaterialApp( home: CameraExampleHome(), ); } } List _cameras = []; Future main() async { // Fetch the available cameras before initializing the app. try { WidgetsFlutterBinding.ensureInitialized(); _cameras = await CameraPlatform.instance.availableCameras(); } on CameraException catch (e) { _logError(e.code, e.description); } runApp(const CameraApp()); } /// 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; ================================================ FILE: packages/camera/camera_avfoundation/example/pubspec.yaml ================================================ name: camera_example description: Demonstrates how to use the camera plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: camera_avfoundation: # When depending on this package from a real application you should use: # camera_avfoundation: ^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: ../ camera_platform_interface: ^2.2.0 flutter: sdk: flutter path_provider: ^2.0.0 quiver: ^3.0.0 video_player: ^2.1.4 dev_dependencies: build_runner: ^2.1.10 flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/camera/camera_avfoundation/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/camera/camera_avfoundation/ios/Assets/.gitkeep ================================================ ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/CameraPermissionUtils.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import Foundation; #import typedef void (^FLTCameraPermissionRequestCompletionHandler)(FlutterError *); /// Requests camera access permission. /// /// If it is the first time requesting camera access, a permission dialog will show up on the /// screen. Otherwise AVFoundation simply returns the user's previous choice, and in this case the /// user will have to update the choice in Settings app. /// /// @param handler if access permission is (or was previously) granted, completion handler will be /// called without error; Otherwise completion handler will be called with error. Handler can be /// called on an arbitrary dispatch queue. extern void FLTRequestCameraPermissionWithCompletionHandler( FLTCameraPermissionRequestCompletionHandler handler); /// Requests audio access permission. /// /// If it is the first time requesting audio access, a permission dialog will show up on the /// screen. Otherwise AVFoundation simply returns the user's previous choice, and in this case the /// user will have to update the choice in Settings app. /// /// @param handler if access permission is (or was previously) granted, completion handler will be /// called without error; Otherwise completion handler will be called with error. Handler can be /// called on an arbitrary dispatch queue. extern void FLTRequestAudioPermissionWithCompletionHandler( FLTCameraPermissionRequestCompletionHandler handler); ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/CameraPermissionUtils.m ================================================ // Copyright 2013 The Flutter 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 AVFoundation; #import "CameraPermissionUtils.h" void FLTRequestPermission(BOOL forAudio, FLTCameraPermissionRequestCompletionHandler handler) { AVMediaType mediaType; if (forAudio) { mediaType = AVMediaTypeAudio; } else { mediaType = AVMediaTypeVideo; } switch ([AVCaptureDevice authorizationStatusForMediaType:mediaType]) { case AVAuthorizationStatusAuthorized: handler(nil); break; case AVAuthorizationStatusDenied: { FlutterError *flutterError; if (forAudio) { flutterError = [FlutterError errorWithCode:@"AudioAccessDeniedWithoutPrompt" message:@"User has previously denied the audio access request. " @"Go to Settings to enable audio access." details:nil]; } else { flutterError = [FlutterError errorWithCode:@"CameraAccessDeniedWithoutPrompt" message:@"User has previously denied the camera access request. " @"Go to Settings to enable camera access." details:nil]; } handler(flutterError); break; } case AVAuthorizationStatusRestricted: { FlutterError *flutterError; if (forAudio) { flutterError = [FlutterError errorWithCode:@"AudioAccessRestricted" message:@"Audio access is restricted. " details:nil]; } else { flutterError = [FlutterError errorWithCode:@"CameraAccessRestricted" message:@"Camera access is restricted. " details:nil]; } handler(flutterError); break; } case AVAuthorizationStatusNotDetermined: { [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) { // handler can be invoked on an arbitrary dispatch queue. if (granted) { handler(nil); } else { FlutterError *flutterError; if (forAudio) { flutterError = [FlutterError errorWithCode:@"AudioAccessDenied" message:@"User denied the audio access request." details:nil]; } else { flutterError = [FlutterError errorWithCode:@"CameraAccessDenied" message:@"User denied the camera access request." details:nil]; } handler(flutterError); } }]; break; } } } void FLTRequestCameraPermissionWithCompletionHandler( FLTCameraPermissionRequestCompletionHandler handler) { FLTRequestPermission(/*forAudio*/ NO, handler); } void FLTRequestAudioPermissionWithCompletionHandler( FLTCameraPermissionRequestCompletionHandler handler) { FLTRequestPermission(/*forAudio*/ YES, handler); } ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.h ================================================ // Copyright 2013 The Flutter 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 CameraPlugin : NSObject @end ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.m ================================================ // Copyright 2013 The Flutter 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 "CameraPlugin.h" #import "CameraPlugin_Test.h" @import AVFoundation; #import "CameraPermissionUtils.h" #import "CameraProperties.h" #import "FLTCam.h" #import "FLTThreadSafeEventChannel.h" #import "FLTThreadSafeFlutterResult.h" #import "FLTThreadSafeMethodChannel.h" #import "FLTThreadSafeTextureRegistry.h" #import "QueueUtils.h" @interface CameraPlugin () @property(readonly, nonatomic) FLTThreadSafeTextureRegistry *registry; @property(readonly, nonatomic) NSObject *messenger; @end @implementation CameraPlugin + (void)registerWithRegistrar:(NSObject *)registrar { FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/camera_avfoundation" binaryMessenger:[registrar messenger]]; CameraPlugin *instance = [[CameraPlugin alloc] initWithRegistry:[registrar textures] messenger:[registrar messenger]]; [registrar addMethodCallDelegate:instance channel:channel]; } - (instancetype)initWithRegistry:(NSObject *)registry messenger:(NSObject *)messenger { self = [super init]; NSAssert(self, @"super init cannot be nil"); _registry = [[FLTThreadSafeTextureRegistry alloc] initWithTextureRegistry:registry]; _messenger = messenger; _captureSessionQueue = dispatch_queue_create("io.flutter.camera.captureSessionQueue", NULL); dispatch_queue_set_specific(_captureSessionQueue, FLTCaptureSessionQueueSpecific, (void *)FLTCaptureSessionQueueSpecific, NULL); [self initDeviceEventMethodChannel]; [self startOrientationListener]; return self; } - (void)initDeviceEventMethodChannel { FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/camera_avfoundation/fromPlatform" binaryMessenger:_messenger]; _deviceEventMethodChannel = [[FLTThreadSafeMethodChannel alloc] initWithMethodChannel:methodChannel]; } - (void)detachFromEngineForRegistrar:(NSObject *)registrar { [UIDevice.currentDevice endGeneratingDeviceOrientationNotifications]; } - (void)startOrientationListener { [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:[UIDevice currentDevice]]; } - (void)orientationChanged:(NSNotification *)note { UIDevice *device = note.object; UIDeviceOrientation orientation = device.orientation; if (orientation == UIDeviceOrientationFaceUp || orientation == UIDeviceOrientationFaceDown) { // Do not change when oriented flat. return; } __weak typeof(self) weakSelf = self; dispatch_async(self.captureSessionQueue, ^{ // `FLTCam::setDeviceOrientation` must be called on capture session queue. [weakSelf.camera setDeviceOrientation:orientation]; // `CameraPlugin::sendDeviceOrientation` can be called on any queue. [weakSelf sendDeviceOrientation:orientation]; }); } - (void)sendDeviceOrientation:(UIDeviceOrientation)orientation { [_deviceEventMethodChannel invokeMethod:@"orientation_changed" arguments:@{@"orientation" : FLTGetStringForUIDeviceOrientation(orientation)}]; } - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { // Invoke the plugin on another dispatch queue to avoid blocking the UI. __weak typeof(self) weakSelf = self; dispatch_async(self.captureSessionQueue, ^{ FLTThreadSafeFlutterResult *threadSafeResult = [[FLTThreadSafeFlutterResult alloc] initWithResult:result]; [weakSelf handleMethodCallAsync:call result:threadSafeResult]; }); } - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FLTThreadSafeFlutterResult *)result { if ([@"availableCameras" isEqualToString:call.method]) { if (@available(iOS 10.0, *)) { NSMutableArray *discoveryDevices = [@[ AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeBuiltInTelephotoCamera ] mutableCopy]; if (@available(iOS 13.0, *)) { [discoveryDevices addObject:AVCaptureDeviceTypeBuiltInUltraWideCamera]; } AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:discoveryDevices mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified]; NSArray *devices = discoverySession.devices; NSMutableArray *> *reply = [[NSMutableArray alloc] initWithCapacity:devices.count]; for (AVCaptureDevice *device in devices) { NSString *lensFacing; switch ([device position]) { case AVCaptureDevicePositionBack: lensFacing = @"back"; break; case AVCaptureDevicePositionFront: lensFacing = @"front"; break; case AVCaptureDevicePositionUnspecified: lensFacing = @"external"; break; } [reply addObject:@{ @"name" : [device uniqueID], @"lensFacing" : lensFacing, @"sensorOrientation" : @90, }]; } [result sendSuccessWithData:reply]; } else { [result sendNotImplemented]; } } else if ([@"create" isEqualToString:call.method]) { [self handleCreateMethodCall:call result:result]; } else if ([@"startImageStream" isEqualToString:call.method]) { [_camera startImageStreamWithMessenger:_messenger]; [result sendSuccess]; } else if ([@"stopImageStream" isEqualToString:call.method]) { [_camera stopImageStream]; [result sendSuccess]; } else if ([@"receivedImageStreamData" isEqualToString:call.method]) { [_camera receivedImageStreamData]; [result sendSuccess]; } else { NSDictionary *argsMap = call.arguments; NSUInteger cameraId = ((NSNumber *)argsMap[@"cameraId"]).unsignedIntegerValue; if ([@"initialize" isEqualToString:call.method]) { NSString *videoFormatValue = ((NSString *)argsMap[@"imageFormatGroup"]); [_camera setVideoFormat:FLTGetVideoFormatFromString(videoFormatValue)]; __weak CameraPlugin *weakSelf = self; _camera.onFrameAvailable = ^{ if (![weakSelf.camera isPreviewPaused]) { [weakSelf.registry textureFrameAvailable:cameraId]; } }; FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName: [NSString stringWithFormat:@"plugins.flutter.io/camera_avfoundation/camera%lu", (unsigned long)cameraId] binaryMessenger:_messenger]; FLTThreadSafeMethodChannel *threadSafeMethodChannel = [[FLTThreadSafeMethodChannel alloc] initWithMethodChannel:methodChannel]; _camera.methodChannel = threadSafeMethodChannel; [threadSafeMethodChannel invokeMethod:@"initialized" arguments:@{ @"previewWidth" : @(_camera.previewSize.width), @"previewHeight" : @(_camera.previewSize.height), @"exposureMode" : FLTGetStringForFLTExposureMode([_camera exposureMode]), @"focusMode" : FLTGetStringForFLTFocusMode([_camera focusMode]), @"exposurePointSupported" : @([_camera.captureDevice isExposurePointOfInterestSupported]), @"focusPointSupported" : @([_camera.captureDevice isFocusPointOfInterestSupported]), }]; [self sendDeviceOrientation:[UIDevice currentDevice].orientation]; [_camera start]; [result sendSuccess]; } else if ([@"takePicture" isEqualToString:call.method]) { if (@available(iOS 10.0, *)) { [_camera captureToFile:result]; } else { [result sendNotImplemented]; } } else if ([@"dispose" isEqualToString:call.method]) { [_registry unregisterTexture:cameraId]; [_camera close]; [result sendSuccess]; } else if ([@"prepareForVideoRecording" isEqualToString:call.method]) { [self.camera setUpCaptureSessionForAudio]; [result sendSuccess]; } else if ([@"startVideoRecording" isEqualToString:call.method]) { BOOL enableStream = [call.arguments[@"enableStream"] boolValue]; if (enableStream) { [_camera startVideoRecordingWithResult:result messengerForStreaming:_messenger]; } else { [_camera startVideoRecordingWithResult:result]; } } else if ([@"stopVideoRecording" isEqualToString:call.method]) { [_camera stopVideoRecordingWithResult:result]; } else if ([@"pauseVideoRecording" isEqualToString:call.method]) { [_camera pauseVideoRecordingWithResult:result]; } else if ([@"resumeVideoRecording" isEqualToString:call.method]) { [_camera resumeVideoRecordingWithResult:result]; } else if ([@"getMaxZoomLevel" isEqualToString:call.method]) { [_camera getMaxZoomLevelWithResult:result]; } else if ([@"getMinZoomLevel" isEqualToString:call.method]) { [_camera getMinZoomLevelWithResult:result]; } else if ([@"setZoomLevel" isEqualToString:call.method]) { CGFloat zoom = ((NSNumber *)argsMap[@"zoom"]).floatValue; [_camera setZoomLevel:zoom Result:result]; } else if ([@"setFlashMode" isEqualToString:call.method]) { [_camera setFlashModeWithResult:result mode:call.arguments[@"mode"]]; } else if ([@"setExposureMode" isEqualToString:call.method]) { [_camera setExposureModeWithResult:result mode:call.arguments[@"mode"]]; } else if ([@"setExposurePoint" isEqualToString:call.method]) { BOOL reset = ((NSNumber *)call.arguments[@"reset"]).boolValue; double x = 0.5; double y = 0.5; if (!reset) { x = ((NSNumber *)call.arguments[@"x"]).doubleValue; y = ((NSNumber *)call.arguments[@"y"]).doubleValue; } [_camera setExposurePointWithResult:result x:x y:y]; } else if ([@"getMinExposureOffset" isEqualToString:call.method]) { [result sendSuccessWithData:@(_camera.captureDevice.minExposureTargetBias)]; } else if ([@"getMaxExposureOffset" isEqualToString:call.method]) { [result sendSuccessWithData:@(_camera.captureDevice.maxExposureTargetBias)]; } else if ([@"getExposureOffsetStepSize" isEqualToString:call.method]) { [result sendSuccessWithData:@(0.0)]; } else if ([@"setExposureOffset" isEqualToString:call.method]) { [_camera setExposureOffsetWithResult:result offset:((NSNumber *)call.arguments[@"offset"]).doubleValue]; } else if ([@"lockCaptureOrientation" isEqualToString:call.method]) { [_camera lockCaptureOrientationWithResult:result orientation:call.arguments[@"orientation"]]; } else if ([@"unlockCaptureOrientation" isEqualToString:call.method]) { [_camera unlockCaptureOrientationWithResult:result]; } else if ([@"setFocusMode" isEqualToString:call.method]) { [_camera setFocusModeWithResult:result mode:call.arguments[@"mode"]]; } else if ([@"setFocusPoint" isEqualToString:call.method]) { BOOL reset = ((NSNumber *)call.arguments[@"reset"]).boolValue; double x = 0.5; double y = 0.5; if (!reset) { x = ((NSNumber *)call.arguments[@"x"]).doubleValue; y = ((NSNumber *)call.arguments[@"y"]).doubleValue; } [_camera setFocusPointWithResult:result x:x y:y]; } else if ([@"pausePreview" isEqualToString:call.method]) { [_camera pausePreviewWithResult:result]; } else if ([@"resumePreview" isEqualToString:call.method]) { [_camera resumePreviewWithResult:result]; } else { [result sendNotImplemented]; } } } - (void)handleCreateMethodCall:(FlutterMethodCall *)call result:(FLTThreadSafeFlutterResult *)result { // Create FLTCam only if granted camera access (and audio access if audio is enabled) __weak typeof(self) weakSelf = self; FLTRequestCameraPermissionWithCompletionHandler(^(FlutterError *error) { typeof(self) strongSelf = weakSelf; if (!strongSelf) return; if (error) { [result sendFlutterError:error]; } else { // Request audio permission on `create` call with `enableAudio` argument instead of the // `prepareForVideoRecording` call. This is because `prepareForVideoRecording` call is // optional, and used as a workaround to fix a missing frame issue on iOS. BOOL audioEnabled = [call.arguments[@"enableAudio"] boolValue]; if (audioEnabled) { // Setup audio capture session only if granted audio access. FLTRequestAudioPermissionWithCompletionHandler(^(FlutterError *error) { // cannot use the outter `strongSelf` typeof(self) strongSelf = weakSelf; if (!strongSelf) return; if (error) { [result sendFlutterError:error]; } else { [strongSelf createCameraOnSessionQueueWithCreateMethodCall:call result:result]; } }); } else { [strongSelf createCameraOnSessionQueueWithCreateMethodCall:call result:result]; } } }); } - (void)createCameraOnSessionQueueWithCreateMethodCall:(FlutterMethodCall *)createMethodCall result:(FLTThreadSafeFlutterResult *)result { __weak typeof(self) weakSelf = self; dispatch_async(self.captureSessionQueue, ^{ typeof(self) strongSelf = weakSelf; if (!strongSelf) return; NSString *cameraName = createMethodCall.arguments[@"cameraName"]; NSString *resolutionPreset = createMethodCall.arguments[@"resolutionPreset"]; NSNumber *enableAudio = createMethodCall.arguments[@"enableAudio"]; NSError *error; FLTCam *cam = [[FLTCam alloc] initWithCameraName:cameraName resolutionPreset:resolutionPreset enableAudio:[enableAudio boolValue] orientation:[[UIDevice currentDevice] orientation] captureSessionQueue:strongSelf.captureSessionQueue error:&error]; if (error) { [result sendError:error]; } else { if (strongSelf.camera) { [strongSelf.camera close]; } strongSelf.camera = cam; [strongSelf.registry registerTexture:cam completion:^(int64_t textureId) { [result sendSuccessWithData:@{ @"cameraId" : @(textureId), }]; }]; } }); } @end ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/CameraPlugin.modulemap ================================================ framework module camera_avfoundation { umbrella header "camera_avfoundation-umbrella.h" export * module * { export * } explicit module Test { header "CameraPlugin_Test.h" header "CameraPermissionUtils.h" header "CameraProperties.h" header "FLTCam.h" header "FLTCam_Test.h" header "FLTSavePhotoDelegate_Test.h" header "FLTThreadSafeEventChannel.h" header "FLTThreadSafeFlutterResult.h" header "FLTThreadSafeMethodChannel.h" header "FLTThreadSafeTextureRegistry.h" header "QueueUtils.h" } } ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/CameraPlugin_Test.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This header is available in the Test module. Import via "@import camera_avfoundation.Test;" #import "CameraPlugin.h" #import "FLTCam.h" #import "FLTThreadSafeFlutterResult.h" /// APIs exposed for unit testing. @interface CameraPlugin () /// All FLTCam's state access and capture session related operations should be on run on this queue. @property(nonatomic, strong) dispatch_queue_t captureSessionQueue; /// An internal camera object that manages camera's state and performs camera operations. @property(nonatomic, strong) FLTCam *camera; /// A thread safe wrapper of the method channel used to send device events such as orientation /// changes. @property(nonatomic, strong) FLTThreadSafeMethodChannel *deviceEventMethodChannel; /// Inject @p FlutterTextureRegistry and @p FlutterBinaryMessenger for unit testing. - (instancetype)initWithRegistry:(NSObject *)registry messenger:(NSObject *)messenger NS_DESIGNATED_INITIALIZER; /// Hide the default public constructor. - (instancetype)init NS_UNAVAILABLE; /// Handles `FlutterMethodCall`s and ensures result is send on the main dispatch queue. /// /// @param call The method call command object. /// @param result A wrapper around the `FlutterResult` callback which ensures the callback is called /// on the main dispatch queue. - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FLTThreadSafeFlutterResult *)result; /// Called by the @c NSNotificationManager each time the device's orientation is changed. /// /// @param notification @c NSNotification instance containing a reference to the `UIDevice` object /// that triggered the orientation change. - (void)orientationChanged:(NSNotification *)notification; /// Creates FLTCam on session queue and reports the creation result. /// @param createMethodCall the create method call /// @param result a thread safe flutter result wrapper object to report creation result. - (void)createCameraOnSessionQueueWithCreateMethodCall:(FlutterMethodCall *)createMethodCall result:(FLTThreadSafeFlutterResult *)result; @end ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/CameraProperties.h ================================================ // Copyright 2013 The Flutter 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 AVFoundation; @import Foundation; NS_ASSUME_NONNULL_BEGIN #pragma mark - flash mode /** * Represents camera's flash mode. Mirrors `FlashMode` enum in flash_mode.dart. */ typedef NS_ENUM(NSInteger, FLTFlashMode) { FLTFlashModeOff, FLTFlashModeAuto, FLTFlashModeAlways, FLTFlashModeTorch, }; /** * Gets FLTFlashMode from its string representation. * @param mode a string representation of the FLTFlashMode. */ extern FLTFlashMode FLTGetFLTFlashModeForString(NSString *mode); /** * Gets AVCaptureFlashMode from FLTFlashMode. * @param mode flash mode. */ extern AVCaptureFlashMode FLTGetAVCaptureFlashModeForFLTFlashMode(FLTFlashMode mode); #pragma mark - exposure mode /** * Represents camera's exposure mode. Mirrors ExposureMode in camera.dart. */ typedef NS_ENUM(NSInteger, FLTExposureMode) { FLTExposureModeAuto, FLTExposureModeLocked, }; /** * Gets a string representation of exposure mode. * @param mode exposure mode */ extern NSString *FLTGetStringForFLTExposureMode(FLTExposureMode mode); /** * Gets FLTExposureMode from its string representation. * @param mode a string representation of the FLTExposureMode. */ extern FLTExposureMode FLTGetFLTExposureModeForString(NSString *mode); #pragma mark - focus mode /** * Represents camera's focus mode. Mirrors FocusMode in camera.dart. */ typedef NS_ENUM(NSInteger, FLTFocusMode) { FLTFocusModeAuto, FLTFocusModeLocked, }; /** * Gets a string representation from FLTFocusMode. * @param mode focus mode */ extern NSString *FLTGetStringForFLTFocusMode(FLTFocusMode mode); /** * Gets FLTFocusMode from its string representation. * @param mode a string representation of focus mode. */ extern FLTFocusMode FLTGetFLTFocusModeForString(NSString *mode); #pragma mark - device orientation /** * Gets UIDeviceOrientation from its string representation. */ extern UIDeviceOrientation FLTGetUIDeviceOrientationForString(NSString *orientation); /** * Gets a string representation of UIDeviceOrientation. */ extern NSString *FLTGetStringForUIDeviceOrientation(UIDeviceOrientation orientation); #pragma mark - resolution preset /** * Represents camera's resolution present. Mirrors ResolutionPreset in camera.dart. */ typedef NS_ENUM(NSInteger, FLTResolutionPreset) { FLTResolutionPresetVeryLow, FLTResolutionPresetLow, FLTResolutionPresetMedium, FLTResolutionPresetHigh, FLTResolutionPresetVeryHigh, FLTResolutionPresetUltraHigh, FLTResolutionPresetMax, }; /** * Gets FLTResolutionPreset from its string representation. * @param preset a string representation of FLTResolutionPreset. */ extern FLTResolutionPreset FLTGetFLTResolutionPresetForString(NSString *preset); #pragma mark - video format /** * Gets VideoFormat from its string representation. */ extern OSType FLTGetVideoFormatFromString(NSString *videoFormatString); NS_ASSUME_NONNULL_END ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/CameraProperties.m ================================================ // Copyright 2013 The Flutter 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 "CameraProperties.h" #pragma mark - flash mode FLTFlashMode FLTGetFLTFlashModeForString(NSString *mode) { if ([mode isEqualToString:@"off"]) { return FLTFlashModeOff; } else if ([mode isEqualToString:@"auto"]) { return FLTFlashModeAuto; } else if ([mode isEqualToString:@"always"]) { return FLTFlashModeAlways; } else if ([mode isEqualToString:@"torch"]) { return FLTFlashModeTorch; } else { NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSURLErrorUnknown userInfo:@{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Unknown flash mode %@", mode] }]; @throw error; } } AVCaptureFlashMode FLTGetAVCaptureFlashModeForFLTFlashMode(FLTFlashMode mode) { switch (mode) { case FLTFlashModeOff: return AVCaptureFlashModeOff; case FLTFlashModeAuto: return AVCaptureFlashModeAuto; case FLTFlashModeAlways: return AVCaptureFlashModeOn; case FLTFlashModeTorch: default: return -1; } } #pragma mark - exposure mode NSString *FLTGetStringForFLTExposureMode(FLTExposureMode mode) { switch (mode) { case FLTExposureModeAuto: return @"auto"; case FLTExposureModeLocked: return @"locked"; } NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSURLErrorUnknown userInfo:@{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Unknown string for exposure mode"] }]; @throw error; } FLTExposureMode FLTGetFLTExposureModeForString(NSString *mode) { if ([mode isEqualToString:@"auto"]) { return FLTExposureModeAuto; } else if ([mode isEqualToString:@"locked"]) { return FLTExposureModeLocked; } else { NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSURLErrorUnknown userInfo:@{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Unknown exposure mode %@", mode] }]; @throw error; } } #pragma mark - focus mode NSString *FLTGetStringForFLTFocusMode(FLTFocusMode mode) { switch (mode) { case FLTFocusModeAuto: return @"auto"; case FLTFocusModeLocked: return @"locked"; } NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSURLErrorUnknown userInfo:@{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Unknown string for focus mode"] }]; @throw error; } FLTFocusMode FLTGetFLTFocusModeForString(NSString *mode) { if ([mode isEqualToString:@"auto"]) { return FLTFocusModeAuto; } else if ([mode isEqualToString:@"locked"]) { return FLTFocusModeLocked; } else { NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSURLErrorUnknown userInfo:@{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Unknown focus mode %@", mode] }]; @throw error; } } #pragma mark - device orientation UIDeviceOrientation FLTGetUIDeviceOrientationForString(NSString *orientation) { if ([orientation isEqualToString:@"portraitDown"]) { return UIDeviceOrientationPortraitUpsideDown; } else if ([orientation isEqualToString:@"landscapeLeft"]) { return UIDeviceOrientationLandscapeRight; } else if ([orientation isEqualToString:@"landscapeRight"]) { return UIDeviceOrientationLandscapeLeft; } else if ([orientation isEqualToString:@"portraitUp"]) { return UIDeviceOrientationPortrait; } else { NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSURLErrorUnknown userInfo:@{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Unknown device orientation %@", orientation] }]; @throw error; } } NSString *FLTGetStringForUIDeviceOrientation(UIDeviceOrientation orientation) { switch (orientation) { case UIDeviceOrientationPortraitUpsideDown: return @"portraitDown"; case UIDeviceOrientationLandscapeRight: return @"landscapeLeft"; case UIDeviceOrientationLandscapeLeft: return @"landscapeRight"; case UIDeviceOrientationPortrait: default: return @"portraitUp"; }; } #pragma mark - resolution preset FLTResolutionPreset FLTGetFLTResolutionPresetForString(NSString *preset) { if ([preset isEqualToString:@"veryLow"]) { return FLTResolutionPresetVeryLow; } else if ([preset isEqualToString:@"low"]) { return FLTResolutionPresetLow; } else if ([preset isEqualToString:@"medium"]) { return FLTResolutionPresetMedium; } else if ([preset isEqualToString:@"high"]) { return FLTResolutionPresetHigh; } else if ([preset isEqualToString:@"veryHigh"]) { return FLTResolutionPresetVeryHigh; } else if ([preset isEqualToString:@"ultraHigh"]) { return FLTResolutionPresetUltraHigh; } else if ([preset isEqualToString:@"max"]) { return FLTResolutionPresetMax; } else { NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSURLErrorUnknown userInfo:@{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Unknown resolution preset %@", preset] }]; @throw error; } } #pragma mark - video format OSType FLTGetVideoFormatFromString(NSString *videoFormatString) { if ([videoFormatString isEqualToString:@"bgra8888"]) { return kCVPixelFormatType_32BGRA; } else if ([videoFormatString isEqualToString:@"yuv420"]) { return kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange; } else { NSLog(@"The selected imageFormatGroup is not supported by iOS. Defaulting to brga8888"); return kCVPixelFormatType_32BGRA; } } ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/FLTCam.h ================================================ // Copyright 2013 The Flutter 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 AVFoundation; @import Foundation; @import Flutter; #import "CameraProperties.h" #import "FLTThreadSafeEventChannel.h" #import "FLTThreadSafeFlutterResult.h" #import "FLTThreadSafeMethodChannel.h" #import "FLTThreadSafeTextureRegistry.h" NS_ASSUME_NONNULL_BEGIN /** * A class that manages camera's state and performs camera operations. */ @interface FLTCam : NSObject @property(readonly, nonatomic) AVCaptureDevice *captureDevice; @property(readonly, nonatomic) CGSize previewSize; @property(assign, nonatomic) BOOL isPreviewPaused; @property(nonatomic, copy) void (^onFrameAvailable)(void); @property(nonatomic) FLTThreadSafeMethodChannel *methodChannel; @property(assign, nonatomic) FLTResolutionPreset resolutionPreset; @property(assign, nonatomic) FLTExposureMode exposureMode; @property(assign, nonatomic) FLTFocusMode focusMode; @property(assign, nonatomic) FLTFlashMode flashMode; // Format used for video and image streaming. @property(assign, nonatomic) FourCharCode videoFormat; /// Initializes an `FLTCam` instance. /// @param cameraName a name used to uniquely identify the camera. /// @param resolutionPreset the resolution preset /// @param enableAudio YES if audio should be enabled for video capturing; NO otherwise. /// @param orientation the orientation of camera /// @param captureSessionQueue the queue on which camera's capture session operations happen. /// @param error report to the caller if any error happened creating the camera. - (instancetype)initWithCameraName:(NSString *)cameraName resolutionPreset:(NSString *)resolutionPreset enableAudio:(BOOL)enableAudio orientation:(UIDeviceOrientation)orientation captureSessionQueue:(dispatch_queue_t)captureSessionQueue error:(NSError **)error; - (void)start; - (void)stop; - (void)setDeviceOrientation:(UIDeviceOrientation)orientation; - (void)captureToFile:(FLTThreadSafeFlutterResult *)result API_AVAILABLE(ios(10)); - (void)close; - (void)startVideoRecordingWithResult:(FLTThreadSafeFlutterResult *)result; /** * Starts recording a video with an optional streaming messenger. * If the messenger is non-null then it will be called for each * captured frame, allowing streaming concurrently with recording. * * @param messenger Nullable messenger for capturing each frame. */ - (void)startVideoRecordingWithResult:(FLTThreadSafeFlutterResult *)result messengerForStreaming:(nullable NSObject *)messenger; - (void)stopVideoRecordingWithResult:(FLTThreadSafeFlutterResult *)result; - (void)pauseVideoRecordingWithResult:(FLTThreadSafeFlutterResult *)result; - (void)resumeVideoRecordingWithResult:(FLTThreadSafeFlutterResult *)result; - (void)lockCaptureOrientationWithResult:(FLTThreadSafeFlutterResult *)result orientation:(NSString *)orientationStr; - (void)unlockCaptureOrientationWithResult:(FLTThreadSafeFlutterResult *)result; - (void)setFlashModeWithResult:(FLTThreadSafeFlutterResult *)result mode:(NSString *)modeStr; - (void)setExposureModeWithResult:(FLTThreadSafeFlutterResult *)result mode:(NSString *)modeStr; - (void)setFocusModeWithResult:(FLTThreadSafeFlutterResult *)result mode:(NSString *)modeStr; - (void)applyFocusMode; /** * Acknowledges the receipt of one image stream frame. * * This should be called each time a frame is received. Failing to call it may * cause later frames to be dropped instead of streamed. */ - (void)receivedImageStreamData; /** * Applies FocusMode on the AVCaptureDevice. * * If the @c focusMode is set to FocusModeAuto the AVCaptureDevice is configured to use * AVCaptureFocusModeContinuousModeAutoFocus when supported, otherwise it is set to * AVCaptureFocusModeAutoFocus. If neither AVCaptureFocusModeContinuousModeAutoFocus nor * AVCaptureFocusModeAutoFocus are supported focus mode will not be set. * If @c focusMode is set to FocusModeLocked the AVCaptureDevice is configured to use * AVCaptureFocusModeAutoFocus. If AVCaptureFocusModeAutoFocus is not supported focus mode will not * be set. * * @param focusMode The focus mode that should be applied to the @captureDevice instance. * @param captureDevice The AVCaptureDevice to which the @focusMode will be applied. */ - (void)applyFocusMode:(FLTFocusMode)focusMode onDevice:(AVCaptureDevice *)captureDevice; - (void)pausePreviewWithResult:(FLTThreadSafeFlutterResult *)result; - (void)resumePreviewWithResult:(FLTThreadSafeFlutterResult *)result; - (void)setExposurePointWithResult:(FLTThreadSafeFlutterResult *)result x:(double)x y:(double)y; - (void)setFocusPointWithResult:(FLTThreadSafeFlutterResult *)result x:(double)x y:(double)y; - (void)setExposureOffsetWithResult:(FLTThreadSafeFlutterResult *)result offset:(double)offset; - (void)startImageStreamWithMessenger:(NSObject *)messenger; - (void)stopImageStream; - (void)getMaxZoomLevelWithResult:(FLTThreadSafeFlutterResult *)result; - (void)getMinZoomLevelWithResult:(FLTThreadSafeFlutterResult *)result; - (void)setZoomLevel:(CGFloat)zoom Result:(FLTThreadSafeFlutterResult *)result; - (void)setUpCaptureSessionForAudio; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/FLTCam.m ================================================ // Copyright 2013 The Flutter 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 "FLTCam.h" #import "FLTCam_Test.h" #import "FLTSavePhotoDelegate.h" #import "QueueUtils.h" @import CoreMotion; #import @implementation FLTImageStreamHandler - (instancetype)initWithCaptureSessionQueue:(dispatch_queue_t)captureSessionQueue { self = [super init]; NSAssert(self, @"super init cannot be nil"); _captureSessionQueue = captureSessionQueue; return self; } - (FlutterError *_Nullable)onCancelWithArguments:(id _Nullable)arguments { __weak typeof(self) weakSelf = self; dispatch_async(self.captureSessionQueue, ^{ weakSelf.eventSink = nil; }); return nil; } - (FlutterError *_Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { __weak typeof(self) weakSelf = self; dispatch_async(self.captureSessionQueue, ^{ weakSelf.eventSink = events; }); return nil; } @end @interface FLTCam () @property(readonly, nonatomic) int64_t textureId; @property BOOL enableAudio; @property(nonatomic) FLTImageStreamHandler *imageStreamHandler; @property(readonly, nonatomic) AVCaptureSession *captureSession; @property(readonly, nonatomic) AVCaptureInput *captureVideoInput; /// Tracks the latest pixel buffer sent from AVFoundation's sample buffer delegate callback. /// Used to deliver the latest pixel buffer to the flutter engine via the `copyPixelBuffer` API. @property(readwrite, nonatomic) CVPixelBufferRef latestPixelBuffer; @property(readonly, nonatomic) CGSize captureSize; @property(strong, nonatomic) AVAssetWriter *videoWriter; @property(strong, nonatomic) AVAssetWriterInput *videoWriterInput; @property(strong, nonatomic) AVAssetWriterInput *audioWriterInput; @property(strong, nonatomic) AVAssetWriterInputPixelBufferAdaptor *assetWriterPixelBufferAdaptor; @property(strong, nonatomic) AVCaptureVideoDataOutput *videoOutput; @property(strong, nonatomic) AVCaptureAudioDataOutput *audioOutput; @property(strong, nonatomic) NSString *videoRecordingPath; @property(assign, nonatomic) BOOL isRecording; @property(assign, nonatomic) BOOL isRecordingPaused; @property(assign, nonatomic) BOOL videoIsDisconnected; @property(assign, nonatomic) BOOL audioIsDisconnected; @property(assign, nonatomic) BOOL isAudioSetup; /// Number of frames currently pending processing. @property(assign, nonatomic) int streamingPendingFramesCount; /// Maximum number of frames pending processing. @property(assign, nonatomic) int maxStreamingPendingFramesCount; @property(assign, nonatomic) UIDeviceOrientation lockedCaptureOrientation; @property(assign, nonatomic) CMTime lastVideoSampleTime; @property(assign, nonatomic) CMTime lastAudioSampleTime; @property(assign, nonatomic) CMTime videoTimeOffset; @property(assign, nonatomic) CMTime audioTimeOffset; @property(nonatomic) CMMotionManager *motionManager; @property AVAssetWriterInputPixelBufferAdaptor *videoAdaptor; /// All FLTCam's state access and capture session related operations should be on run on this queue. @property(strong, nonatomic) dispatch_queue_t captureSessionQueue; /// The queue on which `latestPixelBuffer` property is accessed. /// To avoid unnecessary contention, do not access `latestPixelBuffer` on the `captureSessionQueue`. @property(strong, nonatomic) dispatch_queue_t pixelBufferSynchronizationQueue; /// The queue on which captured photos (not videos) are written to disk. /// Videos are written to disk by `videoAdaptor` on an internal queue managed by AVFoundation. @property(strong, nonatomic) dispatch_queue_t photoIOQueue; @property(assign, nonatomic) UIDeviceOrientation deviceOrientation; @end @implementation FLTCam NSString *const errorMethod = @"error"; - (instancetype)initWithCameraName:(NSString *)cameraName resolutionPreset:(NSString *)resolutionPreset enableAudio:(BOOL)enableAudio orientation:(UIDeviceOrientation)orientation captureSessionQueue:(dispatch_queue_t)captureSessionQueue error:(NSError **)error { return [self initWithCameraName:cameraName resolutionPreset:resolutionPreset enableAudio:enableAudio orientation:orientation captureSession:[[AVCaptureSession alloc] init] captureSessionQueue:captureSessionQueue error:error]; } - (instancetype)initWithCameraName:(NSString *)cameraName resolutionPreset:(NSString *)resolutionPreset enableAudio:(BOOL)enableAudio orientation:(UIDeviceOrientation)orientation captureSession:(AVCaptureSession *)captureSession captureSessionQueue:(dispatch_queue_t)captureSessionQueue error:(NSError **)error { self = [super init]; NSAssert(self, @"super init cannot be nil"); @try { _resolutionPreset = FLTGetFLTResolutionPresetForString(resolutionPreset); } @catch (NSError *e) { *error = e; } _enableAudio = enableAudio; _captureSessionQueue = captureSessionQueue; _pixelBufferSynchronizationQueue = dispatch_queue_create("io.flutter.camera.pixelBufferSynchronizationQueue", NULL); _photoIOQueue = dispatch_queue_create("io.flutter.camera.photoIOQueue", NULL); _captureSession = captureSession; _captureDevice = [AVCaptureDevice deviceWithUniqueID:cameraName]; _flashMode = _captureDevice.hasFlash ? FLTFlashModeAuto : FLTFlashModeOff; _exposureMode = FLTExposureModeAuto; _focusMode = FLTFocusModeAuto; _lockedCaptureOrientation = UIDeviceOrientationUnknown; _deviceOrientation = orientation; _videoFormat = kCVPixelFormatType_32BGRA; _inProgressSavePhotoDelegates = [NSMutableDictionary dictionary]; // To limit memory consumption, limit the number of frames pending processing. // After some testing, 4 was determined to be the best maximum value. // https://github.com/flutter/plugins/pull/4520#discussion_r766335637 _maxStreamingPendingFramesCount = 4; NSError *localError = nil; _captureVideoInput = [AVCaptureDeviceInput deviceInputWithDevice:_captureDevice error:&localError]; if (localError) { *error = localError; return nil; } _captureVideoOutput = [AVCaptureVideoDataOutput new]; _captureVideoOutput.videoSettings = @{(NSString *)kCVPixelBufferPixelFormatTypeKey : @(_videoFormat)}; [_captureVideoOutput setAlwaysDiscardsLateVideoFrames:YES]; [_captureVideoOutput setSampleBufferDelegate:self queue:captureSessionQueue]; AVCaptureConnection *connection = [AVCaptureConnection connectionWithInputPorts:_captureVideoInput.ports output:_captureVideoOutput]; if ([_captureDevice position] == AVCaptureDevicePositionFront) { connection.videoMirrored = YES; } [_captureSession addInputWithNoConnections:_captureVideoInput]; [_captureSession addOutputWithNoConnections:_captureVideoOutput]; [_captureSession addConnection:connection]; if (@available(iOS 10.0, *)) { _capturePhotoOutput = [AVCapturePhotoOutput new]; [_capturePhotoOutput setHighResolutionCaptureEnabled:YES]; [_captureSession addOutput:_capturePhotoOutput]; } _motionManager = [[CMMotionManager alloc] init]; [_motionManager startAccelerometerUpdates]; [self setCaptureSessionPreset:_resolutionPreset]; [self updateOrientation]; return self; } - (void)start { [_captureSession startRunning]; } - (void)stop { [_captureSession stopRunning]; } - (void)setVideoFormat:(OSType)videoFormat { _videoFormat = videoFormat; _captureVideoOutput.videoSettings = @{(NSString *)kCVPixelBufferPixelFormatTypeKey : @(videoFormat)}; } - (void)setDeviceOrientation:(UIDeviceOrientation)orientation { if (_deviceOrientation == orientation) { return; } _deviceOrientation = orientation; [self updateOrientation]; } - (void)updateOrientation { if (_isRecording) { return; } UIDeviceOrientation orientation = (_lockedCaptureOrientation != UIDeviceOrientationUnknown) ? _lockedCaptureOrientation : _deviceOrientation; [self updateOrientation:orientation forCaptureOutput:_capturePhotoOutput]; [self updateOrientation:orientation forCaptureOutput:_captureVideoOutput]; } - (void)updateOrientation:(UIDeviceOrientation)orientation forCaptureOutput:(AVCaptureOutput *)captureOutput { if (!captureOutput) { return; } AVCaptureConnection *connection = [captureOutput connectionWithMediaType:AVMediaTypeVideo]; if (connection && connection.isVideoOrientationSupported) { connection.videoOrientation = [self getVideoOrientationForDeviceOrientation:orientation]; } } - (void)captureToFile:(FLTThreadSafeFlutterResult *)result API_AVAILABLE(ios(10)) { AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings]; if (_resolutionPreset == FLTResolutionPresetMax) { [settings setHighResolutionPhotoEnabled:YES]; } AVCaptureFlashMode avFlashMode = FLTGetAVCaptureFlashModeForFLTFlashMode(_flashMode); if (avFlashMode != -1) { [settings setFlashMode:avFlashMode]; } NSError *error; NSString *path = [self getTemporaryFilePathWithExtension:@"jpg" subfolder:@"pictures" prefix:@"CAP_" error:error]; if (error) { [result sendError:error]; return; } __weak typeof(self) weakSelf = self; FLTSavePhotoDelegate *savePhotoDelegate = [[FLTSavePhotoDelegate alloc] initWithPath:path ioQueue:self.photoIOQueue completionHandler:^(NSString *_Nullable path, NSError *_Nullable error) { typeof(self) strongSelf = weakSelf; if (!strongSelf) return; dispatch_async(strongSelf.captureSessionQueue, ^{ // cannot use the outter `strongSelf` typeof(self) strongSelf = weakSelf; if (!strongSelf) return; [strongSelf.inProgressSavePhotoDelegates removeObjectForKey:@(settings.uniqueID)]; }); if (error) { [result sendError:error]; } else { NSAssert(path, @"Path must not be nil if no error."); [result sendSuccessWithData:path]; } }]; NSAssert(dispatch_get_specific(FLTCaptureSessionQueueSpecific), @"save photo delegate references must be updated on the capture session queue"); self.inProgressSavePhotoDelegates[@(settings.uniqueID)] = savePhotoDelegate; [self.capturePhotoOutput capturePhotoWithSettings:settings delegate:savePhotoDelegate]; } - (AVCaptureVideoOrientation)getVideoOrientationForDeviceOrientation: (UIDeviceOrientation)deviceOrientation { if (deviceOrientation == UIDeviceOrientationPortrait) { return AVCaptureVideoOrientationPortrait; } else if (deviceOrientation == UIDeviceOrientationLandscapeLeft) { // Note: device orientation is flipped compared to video orientation. When UIDeviceOrientation // is landscape left the video orientation should be landscape right. return AVCaptureVideoOrientationLandscapeRight; } else if (deviceOrientation == UIDeviceOrientationLandscapeRight) { // Note: device orientation is flipped compared to video orientation. When UIDeviceOrientation // is landscape right the video orientation should be landscape left. return AVCaptureVideoOrientationLandscapeLeft; } else if (deviceOrientation == UIDeviceOrientationPortraitUpsideDown) { return AVCaptureVideoOrientationPortraitUpsideDown; } else { return AVCaptureVideoOrientationPortrait; } } - (NSString *)getTemporaryFilePathWithExtension:(NSString *)extension subfolder:(NSString *)subfolder prefix:(NSString *)prefix error:(NSError *)error { NSString *docDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; NSString *fileDir = [[docDir stringByAppendingPathComponent:@"camera"] stringByAppendingPathComponent:subfolder]; NSString *fileName = [prefix stringByAppendingString:[[NSUUID UUID] UUIDString]]; NSString *file = [[fileDir stringByAppendingPathComponent:fileName] stringByAppendingPathExtension:extension]; NSFileManager *fm = [NSFileManager defaultManager]; if (![fm fileExistsAtPath:fileDir]) { [[NSFileManager defaultManager] createDirectoryAtPath:fileDir withIntermediateDirectories:true attributes:nil error:&error]; if (error) { return nil; } } return file; } - (void)setCaptureSessionPreset:(FLTResolutionPreset)resolutionPreset { switch (resolutionPreset) { case FLTResolutionPresetMax: case FLTResolutionPresetUltraHigh: if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset3840x2160]) { _captureSession.sessionPreset = AVCaptureSessionPreset3840x2160; _previewSize = CGSizeMake(3840, 2160); break; } if ([_captureSession canSetSessionPreset:AVCaptureSessionPresetHigh]) { _captureSession.sessionPreset = AVCaptureSessionPresetHigh; _previewSize = CGSizeMake(_captureDevice.activeFormat.highResolutionStillImageDimensions.width, _captureDevice.activeFormat.highResolutionStillImageDimensions.height); break; } case FLTResolutionPresetVeryHigh: if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1920x1080]) { _captureSession.sessionPreset = AVCaptureSessionPreset1920x1080; _previewSize = CGSizeMake(1920, 1080); break; } case FLTResolutionPresetHigh: if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) { _captureSession.sessionPreset = AVCaptureSessionPreset1280x720; _previewSize = CGSizeMake(1280, 720); break; } case FLTResolutionPresetMedium: if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset640x480]) { _captureSession.sessionPreset = AVCaptureSessionPreset640x480; _previewSize = CGSizeMake(640, 480); break; } case FLTResolutionPresetLow: if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset352x288]) { _captureSession.sessionPreset = AVCaptureSessionPreset352x288; _previewSize = CGSizeMake(352, 288); break; } default: if ([_captureSession canSetSessionPreset:AVCaptureSessionPresetLow]) { _captureSession.sessionPreset = AVCaptureSessionPresetLow; _previewSize = CGSizeMake(352, 288); } else { NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSURLErrorUnknown userInfo:@{ NSLocalizedDescriptionKey : @"No capture session available for current capture session." }]; @throw error; } } } - (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { if (output == _captureVideoOutput) { CVPixelBufferRef newBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); CFRetain(newBuffer); __block CVPixelBufferRef previousPixelBuffer = nil; // Use `dispatch_sync` to avoid unnecessary context switch under common non-contest scenarios; // Under rare contest scenarios, it will not block for too long since the critical section is // quite lightweight. dispatch_sync(self.pixelBufferSynchronizationQueue, ^{ // No need weak self because it's dispatch_sync. previousPixelBuffer = self.latestPixelBuffer; self.latestPixelBuffer = newBuffer; }); if (previousPixelBuffer) { CFRelease(previousPixelBuffer); } if (_onFrameAvailable) { _onFrameAvailable(); } } if (!CMSampleBufferDataIsReady(sampleBuffer)) { [_methodChannel invokeMethod:errorMethod arguments:@"sample buffer is not ready. Skipping sample"]; return; } if (_isStreamingImages) { FlutterEventSink eventSink = _imageStreamHandler.eventSink; if (eventSink && (self.streamingPendingFramesCount < self.maxStreamingPendingFramesCount)) { self.streamingPendingFramesCount++; CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); // Must lock base address before accessing the pixel data CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); size_t imageWidth = CVPixelBufferGetWidth(pixelBuffer); size_t imageHeight = CVPixelBufferGetHeight(pixelBuffer); NSMutableArray *planes = [NSMutableArray array]; const Boolean isPlanar = CVPixelBufferIsPlanar(pixelBuffer); size_t planeCount; if (isPlanar) { planeCount = CVPixelBufferGetPlaneCount(pixelBuffer); } else { planeCount = 1; } for (int i = 0; i < planeCount; i++) { void *planeAddress; size_t bytesPerRow; size_t height; size_t width; if (isPlanar) { planeAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, i); bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, i); height = CVPixelBufferGetHeightOfPlane(pixelBuffer, i); width = CVPixelBufferGetWidthOfPlane(pixelBuffer, i); } else { planeAddress = CVPixelBufferGetBaseAddress(pixelBuffer); bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer); height = CVPixelBufferGetHeight(pixelBuffer); width = CVPixelBufferGetWidth(pixelBuffer); } NSNumber *length = @(bytesPerRow * height); NSData *bytes = [NSData dataWithBytes:planeAddress length:length.unsignedIntegerValue]; NSMutableDictionary *planeBuffer = [NSMutableDictionary dictionary]; planeBuffer[@"bytesPerRow"] = @(bytesPerRow); planeBuffer[@"width"] = @(width); planeBuffer[@"height"] = @(height); planeBuffer[@"bytes"] = [FlutterStandardTypedData typedDataWithBytes:bytes]; [planes addObject:planeBuffer]; } // Lock the base address before accessing pixel data, and unlock it afterwards. // Done accessing the `pixelBuffer` at this point. CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); NSMutableDictionary *imageBuffer = [NSMutableDictionary dictionary]; imageBuffer[@"width"] = [NSNumber numberWithUnsignedLong:imageWidth]; imageBuffer[@"height"] = [NSNumber numberWithUnsignedLong:imageHeight]; imageBuffer[@"format"] = @(_videoFormat); imageBuffer[@"planes"] = planes; imageBuffer[@"lensAperture"] = [NSNumber numberWithFloat:[_captureDevice lensAperture]]; Float64 exposureDuration = CMTimeGetSeconds([_captureDevice exposureDuration]); Float64 nsExposureDuration = 1000000000 * exposureDuration; imageBuffer[@"sensorExposureTime"] = [NSNumber numberWithInt:nsExposureDuration]; imageBuffer[@"sensorSensitivity"] = [NSNumber numberWithFloat:[_captureDevice ISO]]; dispatch_async(dispatch_get_main_queue(), ^{ eventSink(imageBuffer); }); } } if (_isRecording && !_isRecordingPaused) { if (_videoWriter.status == AVAssetWriterStatusFailed) { [_methodChannel invokeMethod:errorMethod arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; return; } CFRetain(sampleBuffer); CMTime currentSampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); if (_videoWriter.status != AVAssetWriterStatusWriting) { [_videoWriter startWriting]; [_videoWriter startSessionAtSourceTime:currentSampleTime]; } if (output == _captureVideoOutput) { if (_videoIsDisconnected) { _videoIsDisconnected = NO; if (_videoTimeOffset.value == 0) { _videoTimeOffset = CMTimeSubtract(currentSampleTime, _lastVideoSampleTime); } else { CMTime offset = CMTimeSubtract(currentSampleTime, _lastVideoSampleTime); _videoTimeOffset = CMTimeAdd(_videoTimeOffset, offset); } return; } _lastVideoSampleTime = currentSampleTime; CVPixelBufferRef nextBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); CMTime nextSampleTime = CMTimeSubtract(_lastVideoSampleTime, _videoTimeOffset); [_videoAdaptor appendPixelBuffer:nextBuffer withPresentationTime:nextSampleTime]; } else { CMTime dur = CMSampleBufferGetDuration(sampleBuffer); if (dur.value > 0) { currentSampleTime = CMTimeAdd(currentSampleTime, dur); } if (_audioIsDisconnected) { _audioIsDisconnected = NO; if (_audioTimeOffset.value == 0) { _audioTimeOffset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); } else { CMTime offset = CMTimeSubtract(currentSampleTime, _lastAudioSampleTime); _audioTimeOffset = CMTimeAdd(_audioTimeOffset, offset); } return; } _lastAudioSampleTime = currentSampleTime; if (_audioTimeOffset.value != 0) { CFRelease(sampleBuffer); sampleBuffer = [self adjustTime:sampleBuffer by:_audioTimeOffset]; } [self newAudioSample:sampleBuffer]; } CFRelease(sampleBuffer); } } - (CMSampleBufferRef)adjustTime:(CMSampleBufferRef)sample by:(CMTime)offset CF_RETURNS_RETAINED { CMItemCount count; CMSampleBufferGetSampleTimingInfoArray(sample, 0, nil, &count); CMSampleTimingInfo *pInfo = malloc(sizeof(CMSampleTimingInfo) * count); CMSampleBufferGetSampleTimingInfoArray(sample, count, pInfo, &count); for (CMItemCount i = 0; i < count; i++) { pInfo[i].decodeTimeStamp = CMTimeSubtract(pInfo[i].decodeTimeStamp, offset); pInfo[i].presentationTimeStamp = CMTimeSubtract(pInfo[i].presentationTimeStamp, offset); } CMSampleBufferRef sout; CMSampleBufferCreateCopyWithNewTiming(nil, sample, count, pInfo, &sout); free(pInfo); return sout; } - (void)newVideoSample:(CMSampleBufferRef)sampleBuffer { if (_videoWriter.status != AVAssetWriterStatusWriting) { if (_videoWriter.status == AVAssetWriterStatusFailed) { [_methodChannel invokeMethod:errorMethod arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; } return; } if (_videoWriterInput.readyForMoreMediaData) { if (![_videoWriterInput appendSampleBuffer:sampleBuffer]) { [_methodChannel invokeMethod:errorMethod arguments:[NSString stringWithFormat:@"%@", @"Unable to write to video input"]]; } } } - (void)newAudioSample:(CMSampleBufferRef)sampleBuffer { if (_videoWriter.status != AVAssetWriterStatusWriting) { if (_videoWriter.status == AVAssetWriterStatusFailed) { [_methodChannel invokeMethod:errorMethod arguments:[NSString stringWithFormat:@"%@", _videoWriter.error]]; } return; } if (_audioWriterInput.readyForMoreMediaData) { if (![_audioWriterInput appendSampleBuffer:sampleBuffer]) { [_methodChannel invokeMethod:errorMethod arguments:[NSString stringWithFormat:@"%@", @"Unable to write to audio input"]]; } } } - (void)close { [_captureSession stopRunning]; for (AVCaptureInput *input in [_captureSession inputs]) { [_captureSession removeInput:input]; } for (AVCaptureOutput *output in [_captureSession outputs]) { [_captureSession removeOutput:output]; } } - (void)dealloc { if (_latestPixelBuffer) { CFRelease(_latestPixelBuffer); } [_motionManager stopAccelerometerUpdates]; } - (CVPixelBufferRef)copyPixelBuffer { __block CVPixelBufferRef pixelBuffer = nil; // Use `dispatch_sync` because `copyPixelBuffer` API requires synchronous return. dispatch_sync(self.pixelBufferSynchronizationQueue, ^{ // No need weak self because it's dispatch_sync. pixelBuffer = self.latestPixelBuffer; self.latestPixelBuffer = nil; }); return pixelBuffer; } - (void)startVideoRecordingWithResult:(FLTThreadSafeFlutterResult *)result { [self startVideoRecordingWithResult:result messengerForStreaming:nil]; } - (void)startVideoRecordingWithResult:(FLTThreadSafeFlutterResult *)result messengerForStreaming:(nullable NSObject *)messenger { if (!_isRecording) { if (messenger != nil) { [self startImageStreamWithMessenger:messenger]; } NSError *error; _videoRecordingPath = [self getTemporaryFilePathWithExtension:@"mp4" subfolder:@"videos" prefix:@"REC_" error:error]; if (error) { [result sendError:error]; return; } if (![self setupWriterForPath:_videoRecordingPath]) { [result sendErrorWithCode:@"IOError" message:@"Setup Writer Failed" details:nil]; return; } _isRecording = YES; _isRecordingPaused = NO; _videoTimeOffset = CMTimeMake(0, 1); _audioTimeOffset = CMTimeMake(0, 1); _videoIsDisconnected = NO; _audioIsDisconnected = NO; [result sendSuccess]; } else { [result sendErrorWithCode:@"Error" message:@"Video is already recording" details:nil]; } } - (void)stopVideoRecordingWithResult:(FLTThreadSafeFlutterResult *)result { if (_isRecording) { _isRecording = NO; if (_videoWriter.status != AVAssetWriterStatusUnknown) { [_videoWriter finishWritingWithCompletionHandler:^{ if (self->_videoWriter.status == AVAssetWriterStatusCompleted) { [self updateOrientation]; [result sendSuccessWithData:self->_videoRecordingPath]; self->_videoRecordingPath = nil; } else { [result sendErrorWithCode:@"IOError" message:@"AVAssetWriter could not finish writing!" details:nil]; } }]; } } else { NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSURLErrorResourceUnavailable userInfo:@{NSLocalizedDescriptionKey : @"Video is not recording!"}]; [result sendError:error]; } } - (void)pauseVideoRecordingWithResult:(FLTThreadSafeFlutterResult *)result { _isRecordingPaused = YES; _videoIsDisconnected = YES; _audioIsDisconnected = YES; [result sendSuccess]; } - (void)resumeVideoRecordingWithResult:(FLTThreadSafeFlutterResult *)result { _isRecordingPaused = NO; [result sendSuccess]; } - (void)lockCaptureOrientationWithResult:(FLTThreadSafeFlutterResult *)result orientation:(NSString *)orientationStr { UIDeviceOrientation orientation; @try { orientation = FLTGetUIDeviceOrientationForString(orientationStr); } @catch (NSError *e) { [result sendError:e]; return; } if (_lockedCaptureOrientation != orientation) { _lockedCaptureOrientation = orientation; [self updateOrientation]; } [result sendSuccess]; } - (void)unlockCaptureOrientationWithResult:(FLTThreadSafeFlutterResult *)result { _lockedCaptureOrientation = UIDeviceOrientationUnknown; [self updateOrientation]; [result sendSuccess]; } - (void)setFlashModeWithResult:(FLTThreadSafeFlutterResult *)result mode:(NSString *)modeStr { FLTFlashMode mode; @try { mode = FLTGetFLTFlashModeForString(modeStr); } @catch (NSError *e) { [result sendError:e]; return; } if (mode == FLTFlashModeTorch) { if (!_captureDevice.hasTorch) { [result sendErrorWithCode:@"setFlashModeFailed" message:@"Device does not support torch mode" details:nil]; return; } if (!_captureDevice.isTorchAvailable) { [result sendErrorWithCode:@"setFlashModeFailed" message:@"Torch mode is currently not available" details:nil]; return; } if (_captureDevice.torchMode != AVCaptureTorchModeOn) { [_captureDevice lockForConfiguration:nil]; [_captureDevice setTorchMode:AVCaptureTorchModeOn]; [_captureDevice unlockForConfiguration]; } } else { if (!_captureDevice.hasFlash) { [result sendErrorWithCode:@"setFlashModeFailed" message:@"Device does not have flash capabilities" details:nil]; return; } AVCaptureFlashMode avFlashMode = FLTGetAVCaptureFlashModeForFLTFlashMode(mode); if (![_capturePhotoOutput.supportedFlashModes containsObject:[NSNumber numberWithInt:((int)avFlashMode)]]) { [result sendErrorWithCode:@"setFlashModeFailed" message:@"Device does not support this specific flash mode" details:nil]; return; } if (_captureDevice.torchMode != AVCaptureTorchModeOff) { [_captureDevice lockForConfiguration:nil]; [_captureDevice setTorchMode:AVCaptureTorchModeOff]; [_captureDevice unlockForConfiguration]; } } _flashMode = mode; [result sendSuccess]; } - (void)setExposureModeWithResult:(FLTThreadSafeFlutterResult *)result mode:(NSString *)modeStr { FLTExposureMode mode; @try { mode = FLTGetFLTExposureModeForString(modeStr); } @catch (NSError *e) { [result sendError:e]; return; } _exposureMode = mode; [self applyExposureMode]; [result sendSuccess]; } - (void)applyExposureMode { [_captureDevice lockForConfiguration:nil]; switch (_exposureMode) { case FLTExposureModeLocked: [_captureDevice setExposureMode:AVCaptureExposureModeAutoExpose]; break; case FLTExposureModeAuto: if ([_captureDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) { [_captureDevice setExposureMode:AVCaptureExposureModeContinuousAutoExposure]; } else { [_captureDevice setExposureMode:AVCaptureExposureModeAutoExpose]; } break; } [_captureDevice unlockForConfiguration]; } - (void)setFocusModeWithResult:(FLTThreadSafeFlutterResult *)result mode:(NSString *)modeStr { FLTFocusMode mode; @try { mode = FLTGetFLTFocusModeForString(modeStr); } @catch (NSError *e) { [result sendError:e]; return; } _focusMode = mode; [self applyFocusMode]; [result sendSuccess]; } - (void)applyFocusMode { [self applyFocusMode:_focusMode onDevice:_captureDevice]; } - (void)applyFocusMode:(FLTFocusMode)focusMode onDevice:(AVCaptureDevice *)captureDevice { [captureDevice lockForConfiguration:nil]; switch (focusMode) { case FLTFocusModeLocked: if ([captureDevice isFocusModeSupported:AVCaptureFocusModeAutoFocus]) { [captureDevice setFocusMode:AVCaptureFocusModeAutoFocus]; } break; case FLTFocusModeAuto: if ([captureDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) { [captureDevice setFocusMode:AVCaptureFocusModeContinuousAutoFocus]; } else if ([captureDevice isFocusModeSupported:AVCaptureFocusModeAutoFocus]) { [captureDevice setFocusMode:AVCaptureFocusModeAutoFocus]; } break; } [captureDevice unlockForConfiguration]; } - (void)pausePreviewWithResult:(FLTThreadSafeFlutterResult *)result { _isPreviewPaused = true; [result sendSuccess]; } - (void)resumePreviewWithResult:(FLTThreadSafeFlutterResult *)result { _isPreviewPaused = false; [result sendSuccess]; } - (CGPoint)getCGPointForCoordsWithOrientation:(UIDeviceOrientation)orientation x:(double)x y:(double)y { double oldX = x, oldY = y; switch (orientation) { case UIDeviceOrientationPortrait: // 90 ccw y = 1 - oldX; x = oldY; break; case UIDeviceOrientationPortraitUpsideDown: // 90 cw x = 1 - oldY; y = oldX; break; case UIDeviceOrientationLandscapeRight: // 180 x = 1 - x; y = 1 - y; break; case UIDeviceOrientationLandscapeLeft: default: // No rotation required break; } return CGPointMake(x, y); } - (void)setExposurePointWithResult:(FLTThreadSafeFlutterResult *)result x:(double)x y:(double)y { if (!_captureDevice.isExposurePointOfInterestSupported) { [result sendErrorWithCode:@"setExposurePointFailed" message:@"Device does not have exposure point capabilities" details:nil]; return; } UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation]; [_captureDevice lockForConfiguration:nil]; [_captureDevice setExposurePointOfInterest:[self getCGPointForCoordsWithOrientation:orientation x:x y:y]]; [_captureDevice unlockForConfiguration]; // Retrigger auto exposure [self applyExposureMode]; [result sendSuccess]; } - (void)setFocusPointWithResult:(FLTThreadSafeFlutterResult *)result x:(double)x y:(double)y { if (!_captureDevice.isFocusPointOfInterestSupported) { [result sendErrorWithCode:@"setFocusPointFailed" message:@"Device does not have focus point capabilities" details:nil]; return; } UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation]; [_captureDevice lockForConfiguration:nil]; [_captureDevice setFocusPointOfInterest:[self getCGPointForCoordsWithOrientation:orientation x:x y:y]]; [_captureDevice unlockForConfiguration]; // Retrigger auto focus [self applyFocusMode]; [result sendSuccess]; } - (void)setExposureOffsetWithResult:(FLTThreadSafeFlutterResult *)result offset:(double)offset { [_captureDevice lockForConfiguration:nil]; [_captureDevice setExposureTargetBias:offset completionHandler:nil]; [_captureDevice unlockForConfiguration]; [result sendSuccessWithData:@(offset)]; } - (void)startImageStreamWithMessenger:(NSObject *)messenger { [self startImageStreamWithMessenger:messenger imageStreamHandler:[[FLTImageStreamHandler alloc] initWithCaptureSessionQueue:_captureSessionQueue]]; } - (void)startImageStreamWithMessenger:(NSObject *)messenger imageStreamHandler:(FLTImageStreamHandler *)imageStreamHandler { if (!_isStreamingImages) { FlutterEventChannel *eventChannel = [FlutterEventChannel eventChannelWithName:@"plugins.flutter.io/camera_avfoundation/imageStream" binaryMessenger:messenger]; FLTThreadSafeEventChannel *threadSafeEventChannel = [[FLTThreadSafeEventChannel alloc] initWithEventChannel:eventChannel]; _imageStreamHandler = imageStreamHandler; __weak typeof(self) weakSelf = self; [threadSafeEventChannel setStreamHandler:_imageStreamHandler completion:^{ typeof(self) strongSelf = weakSelf; if (!strongSelf) return; dispatch_async(strongSelf.captureSessionQueue, ^{ // cannot use the outter strongSelf typeof(self) strongSelf = weakSelf; if (!strongSelf) return; strongSelf.isStreamingImages = YES; strongSelf.streamingPendingFramesCount = 0; }); }]; } else { [_methodChannel invokeMethod:errorMethod arguments:@"Images from camera are already streaming!"]; } } - (void)stopImageStream { if (_isStreamingImages) { _isStreamingImages = NO; _imageStreamHandler = nil; } else { [_methodChannel invokeMethod:errorMethod arguments:@"Images from camera are not streaming!"]; } } - (void)receivedImageStreamData { self.streamingPendingFramesCount--; } - (void)getMaxZoomLevelWithResult:(FLTThreadSafeFlutterResult *)result { CGFloat maxZoomFactor = [self getMaxAvailableZoomFactor]; [result sendSuccessWithData:[NSNumber numberWithFloat:maxZoomFactor]]; } - (void)getMinZoomLevelWithResult:(FLTThreadSafeFlutterResult *)result { CGFloat minZoomFactor = [self getMinAvailableZoomFactor]; [result sendSuccessWithData:[NSNumber numberWithFloat:minZoomFactor]]; } - (void)setZoomLevel:(CGFloat)zoom Result:(FLTThreadSafeFlutterResult *)result { CGFloat maxAvailableZoomFactor = [self getMaxAvailableZoomFactor]; CGFloat minAvailableZoomFactor = [self getMinAvailableZoomFactor]; if (maxAvailableZoomFactor < zoom || minAvailableZoomFactor > zoom) { NSString *errorMessage = [NSString stringWithFormat:@"Zoom level out of bounds (zoom level should be between %f and %f).", minAvailableZoomFactor, maxAvailableZoomFactor]; [result sendErrorWithCode:@"ZOOM_ERROR" message:errorMessage details:nil]; return; } NSError *error = nil; if (![_captureDevice lockForConfiguration:&error]) { [result sendError:error]; return; } _captureDevice.videoZoomFactor = zoom; [_captureDevice unlockForConfiguration]; [result sendSuccess]; } - (CGFloat)getMinAvailableZoomFactor { if (@available(iOS 11.0, *)) { return _captureDevice.minAvailableVideoZoomFactor; } else { return 1.0; } } - (CGFloat)getMaxAvailableZoomFactor { if (@available(iOS 11.0, *)) { return _captureDevice.maxAvailableVideoZoomFactor; } else { return _captureDevice.activeFormat.videoMaxZoomFactor; } } - (BOOL)setupWriterForPath:(NSString *)path { NSError *error = nil; NSURL *outputURL; if (path != nil) { outputURL = [NSURL fileURLWithPath:path]; } else { return NO; } if (_enableAudio && !_isAudioSetup) { [self setUpCaptureSessionForAudio]; } _videoWriter = [[AVAssetWriter alloc] initWithURL:outputURL fileType:AVFileTypeMPEG4 error:&error]; NSParameterAssert(_videoWriter); if (error) { [_methodChannel invokeMethod:errorMethod arguments:error.description]; return NO; } NSDictionary *videoSettings = [_captureVideoOutput recommendedVideoSettingsForAssetWriterWithOutputFileType:AVFileTypeMPEG4]; _videoWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings]; _videoAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:_videoWriterInput sourcePixelBufferAttributes:@{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(_videoFormat) }]; NSParameterAssert(_videoWriterInput); _videoWriterInput.expectsMediaDataInRealTime = YES; // Add the audio input if (_enableAudio) { AudioChannelLayout acl; bzero(&acl, sizeof(acl)); acl.mChannelLayoutTag = kAudioChannelLayoutTag_Mono; NSDictionary *audioOutputSettings = nil; // Both type of audio inputs causes output video file to be corrupted. audioOutputSettings = @{ AVFormatIDKey : [NSNumber numberWithInt:kAudioFormatMPEG4AAC], AVSampleRateKey : [NSNumber numberWithFloat:44100.0], AVNumberOfChannelsKey : [NSNumber numberWithInt:1], AVChannelLayoutKey : [NSData dataWithBytes:&acl length:sizeof(acl)], }; _audioWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:audioOutputSettings]; _audioWriterInput.expectsMediaDataInRealTime = YES; [_videoWriter addInput:_audioWriterInput]; [_audioOutput setSampleBufferDelegate:self queue:_captureSessionQueue]; } if (_flashMode == FLTFlashModeTorch) { [self.captureDevice lockForConfiguration:nil]; [self.captureDevice setTorchMode:AVCaptureTorchModeOn]; [self.captureDevice unlockForConfiguration]; } [_videoWriter addInput:_videoWriterInput]; [_captureVideoOutput setSampleBufferDelegate:self queue:_captureSessionQueue]; return YES; } - (void)setUpCaptureSessionForAudio { NSError *error = nil; // Create a device input with the device and add it to the session. // Setup the audio input. AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error]; if (error) { [_methodChannel invokeMethod:errorMethod arguments:error.description]; } // Setup the audio output. _audioOutput = [[AVCaptureAudioDataOutput alloc] init]; if ([_captureSession canAddInput:audioInput]) { [_captureSession addInput:audioInput]; if ([_captureSession canAddOutput:_audioOutput]) { [_captureSession addOutput:_audioOutput]; _isAudioSetup = YES; } else { [_methodChannel invokeMethod:errorMethod arguments:@"Unable to add Audio input/output to session capture"]; _isAudioSetup = NO; } } } @end ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/FLTCam_Test.h ================================================ // Copyright 2013 The Flutter 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 "FLTCam.h" #import "FLTSavePhotoDelegate.h" @interface FLTImageStreamHandler : NSObject /// The queue on which `eventSink` property should be accessed. @property(nonatomic, strong) dispatch_queue_t captureSessionQueue; /// The event sink to stream camera events to Dart. /// /// The property should only be accessed on `captureSessionQueue`. /// The block itself should be invoked on the main queue. @property FlutterEventSink eventSink; @end // APIs exposed for unit testing. @interface FLTCam () /// The output for video capturing. @property(readonly, nonatomic) AVCaptureVideoDataOutput *captureVideoOutput; /// The output for photo capturing. Exposed setter for unit tests. @property(strong, nonatomic) AVCapturePhotoOutput *capturePhotoOutput API_AVAILABLE(ios(10)); /// True when images from the camera are being streamed. @property(assign, nonatomic) BOOL isStreamingImages; /// A dictionary to retain all in-progress FLTSavePhotoDelegates. The key of the dictionary is the /// AVCapturePhotoSettings's uniqueID for each photo capture operation, and the value is the /// FLTSavePhotoDelegate that handles the result of each photo capture operation. Note that photo /// capture operations may overlap, so FLTCam has to keep track of multiple delegates in progress, /// instead of just a single delegate reference. @property(readonly, nonatomic) NSMutableDictionary *inProgressSavePhotoDelegates; /// Delegate callback when receiving a new video or audio sample. /// Exposed for unit tests. - (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection; /// Initializes a camera instance. /// Allows for injecting dependencies that are usually internal. - (instancetype)initWithCameraName:(NSString *)cameraName resolutionPreset:(NSString *)resolutionPreset enableAudio:(BOOL)enableAudio orientation:(UIDeviceOrientation)orientation captureSession:(AVCaptureSession *)captureSession captureSessionQueue:(dispatch_queue_t)captureSessionQueue error:(NSError **)error; /// Start streaming images. - (void)startImageStreamWithMessenger:(NSObject *)messenger imageStreamHandler:(FLTImageStreamHandler *)imageStreamHandler; @end ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/FLTSavePhotoDelegate.h ================================================ // Copyright 2013 The Flutter 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 AVFoundation; @import Foundation; #import "FLTThreadSafeFlutterResult.h" NS_ASSUME_NONNULL_BEGIN /// The completion handler block for save photo operations. /// Can be called from either main queue or IO queue. /// If success, `error` will be present and `path` will be nil. Otherewise, `error` will be nil and /// `path` will be present. /// @param path the path for successfully saved photo file. /// @param error photo capture error or IO error. typedef void (^FLTSavePhotoDelegateCompletionHandler)(NSString *_Nullable path, NSError *_Nullable error); /** Delegate object that handles photo capture results. */ @interface FLTSavePhotoDelegate : NSObject /** * Initialize a photo capture delegate. * @param path the path for captured photo file. * @param ioQueue the queue on which captured photos are written to disk. * @param completionHandler The completion handler block for save photo operations. Can * be called from either main queue or IO queue. */ - (instancetype)initWithPath:(NSString *)path ioQueue:(dispatch_queue_t)ioQueue completionHandler:(FLTSavePhotoDelegateCompletionHandler)completionHandler; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/FLTSavePhotoDelegate.m ================================================ // Copyright 2013 The Flutter 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 "FLTSavePhotoDelegate.h" #import "FLTSavePhotoDelegate_Test.h" @interface FLTSavePhotoDelegate () /// The file path for the captured photo. @property(readonly, nonatomic) NSString *path; /// The queue on which captured photos are written to disk. @property(readonly, nonatomic) dispatch_queue_t ioQueue; @end @implementation FLTSavePhotoDelegate - (instancetype)initWithPath:(NSString *)path ioQueue:(dispatch_queue_t)ioQueue completionHandler:(FLTSavePhotoDelegateCompletionHandler)completionHandler { self = [super init]; NSAssert(self, @"super init cannot be nil"); _path = path; _ioQueue = ioQueue; _completionHandler = completionHandler; return self; } - (void)handlePhotoCaptureResultWithError:(NSError *)error photoDataProvider:(NSData * (^)(void))photoDataProvider { if (error) { self.completionHandler(nil, error); return; } __weak typeof(self) weakSelf = self; dispatch_async(self.ioQueue, ^{ typeof(self) strongSelf = weakSelf; if (!strongSelf) return; NSData *data = photoDataProvider(); NSError *ioError; if ([data writeToFile:strongSelf.path options:NSDataWritingAtomic error:&ioError]) { strongSelf.completionHandler(self.path, nil); } else { strongSelf.completionHandler(nil, ioError); } }); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhotoSampleBuffer:(CMSampleBufferRef)photoSampleBuffer previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings bracketSettings:(AVCaptureBracketedStillImageSettings *)bracketSettings error:(NSError *)error API_AVAILABLE(ios(10)) { [self handlePhotoCaptureResultWithError:error photoDataProvider:^NSData * { return [AVCapturePhotoOutput JPEGPhotoDataRepresentationForJPEGSampleBuffer:photoSampleBuffer previewPhotoSampleBuffer: previewPhotoSampleBuffer]; }]; } #pragma clang diagnostic pop - (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhoto:(AVCapturePhoto *)photo error:(NSError *)error API_AVAILABLE(ios(11.0)) { [self handlePhotoCaptureResultWithError:error photoDataProvider:^NSData * { return [photo fileDataRepresentation]; }]; } @end ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/FLTSavePhotoDelegate_Test.h ================================================ // Copyright 2013 The Flutter 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 "FLTSavePhotoDelegate.h" /** API exposed for unit tests. */ @interface FLTSavePhotoDelegate () /// The completion handler block for capture and save photo operations. /// Can be called from either main queue or IO queue. /// Exposed for unit tests to manually trigger the completion. @property(readonly, nonatomic) FLTSavePhotoDelegateCompletionHandler completionHandler; /// Handler to write captured photo data into a file. /// @param error the capture error. /// @param photoDataProvider a closure that provides photo data. - (void)handlePhotoCaptureResultWithError:(NSError *)error photoDataProvider:(NSData * (^)(void))photoDataProvider; @end ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeEventChannel.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import NS_ASSUME_NONNULL_BEGIN /** * A thread safe wrapper for FlutterEventChannel that can be called from any thread, by dispatching * its underlying engine calls to the main thread. */ @interface FLTThreadSafeEventChannel : NSObject /** * Creates a FLTThreadSafeEventChannel by wrapping a FlutterEventChannel object. * @param channel The FlutterEventChannel object to be wrapped. */ - (instancetype)initWithEventChannel:(FlutterEventChannel *)channel; /* * Registers a handler on the main thread for stream setup requests from the Flutter side. # The completion block runs on the main thread. */ - (void)setStreamHandler:(nullable NSObject *)handler completion:(void (^)(void))completion; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeEventChannel.m ================================================ // Copyright 2013 The Flutter 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 "FLTThreadSafeEventChannel.h" #import "QueueUtils.h" @interface FLTThreadSafeEventChannel () @property(nonatomic, strong) FlutterEventChannel *channel; @end @implementation FLTThreadSafeEventChannel - (instancetype)initWithEventChannel:(FlutterEventChannel *)channel { self = [super init]; if (self) { _channel = channel; } return self; } - (void)setStreamHandler:(NSObject *)handler completion:(void (^)(void))completion { // WARNING: Should not use weak self, because FLTThreadSafeEventChannel is a local variable // (retained within call stack, but not in the heap). FLTEnsureToRunOnMainQueue may trigger a // context switch (when calling from background thread), in which case using weak self will always // result in a nil self. Alternative to using strong self, we can also create a local strong // variable to be captured by this block. FLTEnsureToRunOnMainQueue(^{ [self.channel setStreamHandler:handler]; completion(); }); } @end ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeFlutterResult.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import NS_ASSUME_NONNULL_BEGIN /** * A thread safe wrapper for FlutterResult that can be called from any thread, by dispatching its * underlying engine calls to the main thread. */ @interface FLTThreadSafeFlutterResult : NSObject /** * Gets the original FlutterResult object wrapped by this FLTThreadSafeFlutterResult instance. */ @property(readonly, nonatomic) FlutterResult flutterResult; /** * Initializes with a FlutterResult object. * @param result The FlutterResult object that the result will be given to. */ - (instancetype)initWithResult:(FlutterResult)result; /** * Sends a successful result on the main thread without any data. */ - (void)sendSuccess; /** * Sends a successful result on the main thread with data. * @param data Result data that is send to the Flutter Dart side. */ - (void)sendSuccessWithData:(id)data; /** * Sends an NSError as result on the main thread. * @param error Error that will be send as FlutterError. */ - (void)sendError:(NSError *)error; /** * Sends a FlutterError as result on the main thread. * @param flutterError FlutterError that will be sent to the Flutter Dart side. */ - (void)sendFlutterError:(FlutterError *)flutterError; /** * Sends a FlutterError as result on the main thread. */ - (void)sendErrorWithCode:(NSString *)code message:(nullable NSString *)message details:(nullable id)details; /** * Sends FlutterMethodNotImplemented as result on the main thread. */ - (void)sendNotImplemented; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeFlutterResult.m ================================================ // Copyright 2013 The Flutter 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 "FLTThreadSafeFlutterResult.h" #import #import "QueueUtils.h" @implementation FLTThreadSafeFlutterResult { } - (id)initWithResult:(FlutterResult)result { self = [super init]; if (!self) { return nil; } _flutterResult = result; return self; } - (void)sendSuccess { [self send:nil]; } - (void)sendSuccessWithData:(id)data { [self send:data]; } - (void)sendError:(NSError *)error { [self sendErrorWithCode:[NSString stringWithFormat:@"Error %d", (int)error.code] message:error.localizedDescription details:error.domain]; } - (void)sendErrorWithCode:(NSString *)code message:(NSString *_Nullable)message details:(id _Nullable)details { FlutterError *flutterError = [FlutterError errorWithCode:code message:message details:details]; [self send:flutterError]; } - (void)sendFlutterError:(FlutterError *)flutterError { [self send:flutterError]; } - (void)sendNotImplemented { [self send:FlutterMethodNotImplemented]; } /** * Sends result to flutterResult on the main thread. */ - (void)send:(id _Nullable)result { FLTEnsureToRunOnMainQueue(^{ // WARNING: Should not use weak self, because `FlutterResult`s are passed as arguments // (retained within call stack, but not in the heap). FLTEnsureToRunOnMainQueue may trigger a // context switch (when calling from background thread), in which case using weak self will // always result in a nil self. Alternative to using strong self, we can also create a local // strong variable to be captured by this block. self.flutterResult(result); }); } @end ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeMethodChannel.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import NS_ASSUME_NONNULL_BEGIN /** * A thread safe wrapper for FlutterMethodChannel that can be called from any thread, by dispatching * its underlying engine calls to the main thread. */ @interface FLTThreadSafeMethodChannel : NSObject /** * Creates a FLTThreadSafeMethodChannel by wrapping a FlutterMethodChannel object. * @param channel The FlutterMethodChannel object to be wrapped. */ - (instancetype)initWithMethodChannel:(FlutterMethodChannel *)channel; /** * Invokes the specified flutter method on the main thread with the specified arguments. */ - (void)invokeMethod:(NSString *)method arguments:(nullable id)arguments; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeMethodChannel.m ================================================ // Copyright 2013 The Flutter 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 "FLTThreadSafeMethodChannel.h" #import "QueueUtils.h" @interface FLTThreadSafeMethodChannel () @property(nonatomic, strong) FlutterMethodChannel *channel; @end @implementation FLTThreadSafeMethodChannel - (instancetype)initWithMethodChannel:(FlutterMethodChannel *)channel { self = [super init]; if (self) { _channel = channel; } return self; } - (void)invokeMethod:(NSString *)method arguments:(id)arguments { __weak typeof(self) weakSelf = self; FLTEnsureToRunOnMainQueue(^{ [weakSelf.channel invokeMethod:method arguments:arguments]; }); } @end ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeTextureRegistry.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import NS_ASSUME_NONNULL_BEGIN /** * A thread safe wrapper for FlutterTextureRegistry that can be called from any thread, by * dispatching its underlying engine calls to the main thread. */ @interface FLTThreadSafeTextureRegistry : NSObject /** * Creates a FLTThreadSafeTextureRegistry by wrapping an object conforming to * FlutterTextureRegistry. * @param registry The FlutterTextureRegistry object to be wrapped. */ - (instancetype)initWithTextureRegistry:(NSObject *)registry; /** * Registers a `FlutterTexture` on the main thread for usage in Flutter and returns an id that can * be used to reference that texture when calling into Flutter with channels. * * On success the completion block completes with the pointer to the registered texture, else with * 0. The completion block runs on the main thread. */ - (void)registerTexture:(NSObject *)texture completion:(void (^)(int64_t))completion; /** * Notifies the Flutter engine on the main thread that the given texture has been updated. */ - (void)textureFrameAvailable:(int64_t)textureId; /** * Notifies the Flutter engine on the main thread to unregister a `FlutterTexture` that has been * previously registered with `registerTexture:`. * @param textureId The result that was previously returned from `registerTexture:`. */ - (void)unregisterTexture:(int64_t)textureId; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/FLTThreadSafeTextureRegistry.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "FLTThreadSafeTextureRegistry.h" #import "QueueUtils.h" @interface FLTThreadSafeTextureRegistry () @property(nonatomic, strong) NSObject *registry; @end @implementation FLTThreadSafeTextureRegistry - (instancetype)initWithTextureRegistry:(NSObject *)registry { self = [super init]; if (self) { _registry = registry; } return self; } - (void)registerTexture:(NSObject *)texture completion:(void (^)(int64_t))completion { __weak typeof(self) weakSelf = self; FLTEnsureToRunOnMainQueue(^{ typeof(self) strongSelf = weakSelf; if (!strongSelf) return; completion([strongSelf.registry registerTexture:texture]); }); } - (void)textureFrameAvailable:(int64_t)textureId { __weak typeof(self) weakSelf = self; FLTEnsureToRunOnMainQueue(^{ [weakSelf.registry textureFrameAvailable:textureId]; }); } - (void)unregisterTexture:(int64_t)textureId { __weak typeof(self) weakSelf = self; FLTEnsureToRunOnMainQueue(^{ [weakSelf.registry unregisterTexture:textureId]; }); } @end ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/QueueUtils.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import NS_ASSUME_NONNULL_BEGIN /// Queue-specific context data to be associated with the capture session queue. extern const char* FLTCaptureSessionQueueSpecific; /// Ensures the given block to be run on the main queue. /// If caller site is already on the main queue, the block will be run /// synchronously. Otherwise, the block will be dispatched asynchronously to the /// main queue. /// @param block the block to be run on the main queue. extern void FLTEnsureToRunOnMainQueue(dispatch_block_t block); NS_ASSUME_NONNULL_END ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/QueueUtils.m ================================================ // Copyright 2013 The Flutter 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 "QueueUtils.h" const char *FLTCaptureSessionQueueSpecific = "capture_session_queue"; void FLTEnsureToRunOnMainQueue(dispatch_block_t block) { if (!NSThread.isMainThread) { dispatch_async(dispatch_get_main_queue(), block); } else { block(); } } ================================================ FILE: packages/camera/camera_avfoundation/ios/Classes/camera_avfoundation-umbrella.h ================================================ // Copyright 2013 The Flutter 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 FOUNDATION_EXPORT double cameraVersionNumber; FOUNDATION_EXPORT const unsigned char cameraVersionString[]; ================================================ FILE: packages/camera/camera_avfoundation/ios/camera_avfoundation.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| s.name = 'camera_avfoundation' s.version = '0.0.1' s.summary = 'Flutter Camera' s.description = <<-DESC A Flutter plugin to use the camera from your Flutter app. 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/camera_avfoundation' } s.documentation_url = 'https://pub.dev/packages/camera_avfoundation' s.source_files = 'Classes/**/*.{h,m}' s.public_header_files = 'Classes/**/*.h' s.module_map = 'Classes/CameraPlugin.modulemap' s.dependency 'Flutter' s.platform = :ios, '9.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } end ================================================ FILE: packages/camera/camera_avfoundation/lib/camera_avfoundation.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'src/avfoundation_camera.dart'; ================================================ FILE: packages/camera/camera_avfoundation/lib/src/avfoundation_camera.dart ================================================ // Copyright 2013 The Flutter 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 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:stream_transform/stream_transform.dart'; import 'type_conversion.dart'; import 'utils.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera_avfoundation'); /// An iOS implementation of [CameraPlatform] based on AVFoundation. class AVFoundationCamera extends CameraPlatform { /// Registers this class as the default instance of [CameraPlatform]. static void registerWith() { CameraPlatform.instance = AVFoundationCamera(); } final Map _channels = {}; /// The name of the channel that device events from the platform side are /// sent on. @visibleForTesting static const String deviceEventChannelName = 'plugins.flutter.io/camera_avfoundation/fromPlatform'; /// The controller we need to broadcast the different events coming /// from handleMethodCall, specific to camera events. /// /// It is a `broadcast` because multiple controllers will connect to /// different stream views of this Controller. /// This is only exposed for test purposes. It shouldn't be used by clients of /// the plugin as it may break or change at any time. @visibleForTesting final StreamController cameraEventStreamController = StreamController.broadcast(); /// The controller we need to broadcast the different events coming /// from handleMethodCall, specific to general device events. /// /// It is a `broadcast` because multiple controllers will connect to /// different stream views of this Controller. late final StreamController _deviceEventStreamController = _createDeviceEventStreamController(); StreamController _createDeviceEventStreamController() { // Set up the method handler lazily. const MethodChannel channel = MethodChannel(deviceEventChannelName); channel.setMethodCallHandler(_handleDeviceMethodCall); return StreamController.broadcast(); } // The stream to receive frames from the native code. StreamSubscription? _platformImageStreamSubscription; // The stream for vending frames to platform interface clients. StreamController? _frameStreamController; Stream _cameraEvents(int cameraId) => cameraEventStreamController.stream .where((CameraEvent event) => event.cameraId == cameraId); @override Future> availableCameras() async { try { final List>? cameras = await _channel .invokeListMethod>('availableCameras'); if (cameras == null) { return []; } return cameras.map((Map camera) { return CameraDescription( name: camera['name']! as String, lensDirection: parseCameraLensDirection(camera['lensFacing']! as String), sensorOrientation: camera['sensorOrientation']! as int, ); }).toList(); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } @override Future createCamera( CameraDescription cameraDescription, ResolutionPreset? resolutionPreset, { bool enableAudio = false, }) async { try { final Map? reply = await _channel .invokeMapMethod('create', { 'cameraName': cameraDescription.name, 'resolutionPreset': resolutionPreset != null ? _serializeResolutionPreset(resolutionPreset) : null, 'enableAudio': enableAudio, }); return reply!['cameraId']! as int; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } @override Future initializeCamera( int cameraId, { ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown, }) { _channels.putIfAbsent(cameraId, () { final MethodChannel channel = MethodChannel( 'plugins.flutter.io/camera_avfoundation/camera$cameraId'); channel.setMethodCallHandler( (MethodCall call) => handleCameraMethodCall(call, cameraId)); return channel; }); final Completer completer = Completer(); onCameraInitialized(cameraId).first.then((CameraInitializedEvent value) { completer.complete(); }); _channel.invokeMapMethod( 'initialize', { 'cameraId': cameraId, 'imageFormatGroup': imageFormatGroup.name(), }, ).catchError( // TODO(srawlins): This should return a value of the future's type. This // will fail upcoming analysis checks with // https://github.com/flutter/flutter/issues/105750. // ignore: body_might_complete_normally_catch_error (Object error, StackTrace stackTrace) { if (error is! PlatformException) { // ignore: only_throw_errors throw error; } completer.completeError( CameraException(error.code, error.message), stackTrace, ); }, ); return completer.future; } @override Future dispose(int cameraId) async { if (_channels.containsKey(cameraId)) { final MethodChannel? cameraChannel = _channels[cameraId]; cameraChannel?.setMethodCallHandler(null); _channels.remove(cameraId); } await _channel.invokeMethod( 'dispose', {'cameraId': cameraId}, ); } @override Stream onCameraInitialized(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onCameraResolutionChanged(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onCameraClosing(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onCameraError(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onVideoRecordedEvent(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onDeviceOrientationChanged() { return _deviceEventStreamController.stream .whereType(); } @override Future lockCaptureOrientation( int cameraId, DeviceOrientation orientation, ) async { await _channel.invokeMethod( 'lockCaptureOrientation', { 'cameraId': cameraId, 'orientation': serializeDeviceOrientation(orientation) }, ); } @override Future unlockCaptureOrientation(int cameraId) async { await _channel.invokeMethod( 'unlockCaptureOrientation', {'cameraId': cameraId}, ); } @override Future takePicture(int cameraId) async { final String? path = await _channel.invokeMethod( 'takePicture', {'cameraId': cameraId}, ); if (path == null) { throw CameraException( 'INVALID_PATH', 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', ); } return XFile(path); } @override Future prepareForVideoRecording() => _channel.invokeMethod('prepareForVideoRecording'); @override Future startVideoRecording(int cameraId, {Duration? maxVideoDuration}) async { return startVideoCapturing( VideoCaptureOptions(cameraId, maxDuration: maxVideoDuration)); } @override Future startVideoCapturing(VideoCaptureOptions options) async { await _channel.invokeMethod( 'startVideoRecording', { 'cameraId': options.cameraId, 'maxVideoDuration': options.maxDuration?.inMilliseconds, 'enableStream': options.streamCallback != null, }, ); if (options.streamCallback != null) { _frameStreamController = _createStreamController(); _frameStreamController!.stream.listen(options.streamCallback); _startStreamListener(); } } @override Future stopVideoRecording(int cameraId) async { final String? path = await _channel.invokeMethod( 'stopVideoRecording', {'cameraId': cameraId}, ); if (path == null) { throw CameraException( 'INVALID_PATH', 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', ); } return XFile(path); } @override Future pauseVideoRecording(int cameraId) => _channel.invokeMethod( 'pauseVideoRecording', {'cameraId': cameraId}, ); @override Future resumeVideoRecording(int cameraId) => _channel.invokeMethod( 'resumeVideoRecording', {'cameraId': cameraId}, ); @override Stream onStreamedFrameAvailable(int cameraId, {CameraImageStreamOptions? options}) { _frameStreamController = _createStreamController(onListen: _onFrameStreamListen); return _frameStreamController!.stream; } StreamController _createStreamController( {Function()? onListen}) { return StreamController( onListen: onListen ?? () {}, onPause: _onFrameStreamPauseResume, onResume: _onFrameStreamPauseResume, onCancel: _onFrameStreamCancel, ); } void _onFrameStreamListen() { _startPlatformStream(); } Future _startPlatformStream() async { await _channel.invokeMethod('startImageStream'); _startStreamListener(); } void _startStreamListener() { const EventChannel cameraEventChannel = EventChannel('plugins.flutter.io/camera_avfoundation/imageStream'); _platformImageStreamSubscription = cameraEventChannel.receiveBroadcastStream().listen((dynamic imageData) { try { _channel.invokeMethod('receivedImageStreamData'); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } _frameStreamController! .add(cameraImageFromPlatformData(imageData as Map)); }); } FutureOr _onFrameStreamCancel() async { await _channel.invokeMethod('stopImageStream'); await _platformImageStreamSubscription?.cancel(); _platformImageStreamSubscription = null; _frameStreamController = null; } void _onFrameStreamPauseResume() { throw CameraException('InvalidCall', 'Pause and resume are not supported for onStreamedFrameAvailable'); } @override Future setFlashMode(int cameraId, FlashMode mode) => _channel.invokeMethod( 'setFlashMode', { 'cameraId': cameraId, 'mode': _serializeFlashMode(mode), }, ); @override Future setExposureMode(int cameraId, ExposureMode mode) => _channel.invokeMethod( 'setExposureMode', { 'cameraId': cameraId, 'mode': serializeExposureMode(mode), }, ); @override Future setExposurePoint(int cameraId, Point? point) { assert(point == null || point.x >= 0 && point.x <= 1); assert(point == null || point.y >= 0 && point.y <= 1); return _channel.invokeMethod( 'setExposurePoint', { 'cameraId': cameraId, 'reset': point == null, 'x': point?.x, 'y': point?.y, }, ); } @override Future getMinExposureOffset(int cameraId) async { final double? minExposureOffset = await _channel.invokeMethod( 'getMinExposureOffset', {'cameraId': cameraId}, ); return minExposureOffset!; } @override Future getMaxExposureOffset(int cameraId) async { final double? maxExposureOffset = await _channel.invokeMethod( 'getMaxExposureOffset', {'cameraId': cameraId}, ); return maxExposureOffset!; } @override Future getExposureOffsetStepSize(int cameraId) async { final double? stepSize = await _channel.invokeMethod( 'getExposureOffsetStepSize', {'cameraId': cameraId}, ); return stepSize!; } @override Future setExposureOffset(int cameraId, double offset) async { final double? appliedOffset = await _channel.invokeMethod( 'setExposureOffset', { 'cameraId': cameraId, 'offset': offset, }, ); return appliedOffset!; } @override Future setFocusMode(int cameraId, FocusMode mode) => _channel.invokeMethod( 'setFocusMode', { 'cameraId': cameraId, 'mode': serializeFocusMode(mode), }, ); @override Future setFocusPoint(int cameraId, Point? point) { assert(point == null || point.x >= 0 && point.x <= 1); assert(point == null || point.y >= 0 && point.y <= 1); return _channel.invokeMethod( 'setFocusPoint', { 'cameraId': cameraId, 'reset': point == null, 'x': point?.x, 'y': point?.y, }, ); } @override Future getMaxZoomLevel(int cameraId) async { final double? maxZoomLevel = await _channel.invokeMethod( 'getMaxZoomLevel', {'cameraId': cameraId}, ); return maxZoomLevel!; } @override Future getMinZoomLevel(int cameraId) async { final double? minZoomLevel = await _channel.invokeMethod( 'getMinZoomLevel', {'cameraId': cameraId}, ); return minZoomLevel!; } @override Future setZoomLevel(int cameraId, double zoom) async { try { await _channel.invokeMethod( 'setZoomLevel', { 'cameraId': cameraId, 'zoom': zoom, }, ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } @override Future pausePreview(int cameraId) async { await _channel.invokeMethod( 'pausePreview', {'cameraId': cameraId}, ); } @override Future resumePreview(int cameraId) async { await _channel.invokeMethod( 'resumePreview', {'cameraId': cameraId}, ); } @override Widget buildPreview(int cameraId) { return Texture(textureId: cameraId); } /// Returns the flash mode as a String. String _serializeFlashMode(FlashMode flashMode) { switch (flashMode) { case FlashMode.off: return 'off'; case FlashMode.auto: return 'auto'; case FlashMode.always: return 'always'; case FlashMode.torch: return 'torch'; } // The enum comes from a different package, which could get a new value at // any time, so provide a fallback that ensures this won't break when used // 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. String _serializeResolutionPreset(ResolutionPreset resolutionPreset) { switch (resolutionPreset) { case ResolutionPreset.max: return 'max'; case ResolutionPreset.ultraHigh: return 'ultraHigh'; case ResolutionPreset.veryHigh: return 'veryHigh'; case ResolutionPreset.high: return 'high'; case ResolutionPreset.medium: return 'medium'; case ResolutionPreset.low: return 'low'; } // The enum comes from a different package, which could get a new value at // any time, so provide a fallback that ensures this won't break when used // 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(arguments['orientation']! as String))); break; default: throw MissingPluginException(); } } /// Converts messages received from the native platform into camera events. /// /// This is only exposed for test purposes. It shouldn't be used by clients of /// the plugin as it may break or change at any time. @visibleForTesting Future handleCameraMethodCall(MethodCall call, int cameraId) async { switch (call.method) { case 'initialized': final Map arguments = _getArgumentDictionary(call); cameraEventStreamController.add(CameraInitializedEvent( cameraId, 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, arguments['captureWidth']! as double, arguments['captureHeight']! as double, )); break; case 'camera_closing': cameraEventStreamController.add(CameraClosingEvent( cameraId, )); break; case 'video_recorded': final Map arguments = _getArgumentDictionary(call); cameraEventStreamController.add(VideoRecordedEvent( cameraId, 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, 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(); } } ================================================ FILE: packages/camera/camera_avfoundation/lib/src/type_conversion.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:camera_platform_interface/camera_platform_interface.dart'; /// Converts method channel call [data] for `receivedImageStreamData` to a /// [CameraImageData]. CameraImageData cameraImageFromPlatformData(Map data) { return CameraImageData( format: _cameraImageFormatFromPlatformData(data['format']), height: data['height'] as int, width: data['width'] as int, lensAperture: data['lensAperture'] as double?, sensorExposureTime: data['sensorExposureTime'] as int?, sensorSensitivity: data['sensorSensitivity'] as double?, planes: List.unmodifiable( (data['planes'] as List).map( (dynamic planeData) => _cameraImagePlaneFromPlatformData( planeData as Map)))); } CameraImageFormat _cameraImageFormatFromPlatformData(dynamic data) { return CameraImageFormat(_imageFormatGroupFromPlatformData(data), raw: data); } ImageFormatGroup _imageFormatGroupFromPlatformData(dynamic data) { switch (data) { case 875704438: // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange return ImageFormatGroup.yuv420; case 1111970369: // kCVPixelFormatType_32BGRA return ImageFormatGroup.bgra8888; } return ImageFormatGroup.unknown; } CameraImagePlane _cameraImagePlaneFromPlatformData(Map data) { return CameraImagePlane( bytes: data['bytes'] as Uint8List, bytesPerPixel: data['bytesPerPixel'] as int?, bytesPerRow: data['bytesPerRow'] as int, height: data['height'] as int?, width: data['width'] as int?); } ================================================ FILE: packages/camera/camera_avfoundation/lib/src/utils.dart ================================================ // Copyright 2013 The Flutter 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_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart'; /// Parses a string into a corresponding CameraLensDirection. CameraLensDirection parseCameraLensDirection(String string) { switch (string) { case 'front': return CameraLensDirection.front; case 'back': return CameraLensDirection.back; case 'external': return CameraLensDirection.external; } throw ArgumentError('Unknown CameraLensDirection value'); } /// Returns the device orientation as a String. String serializeDeviceOrientation(DeviceOrientation orientation) { switch (orientation) { case DeviceOrientation.portraitUp: return 'portraitUp'; case DeviceOrientation.portraitDown: return 'portraitDown'; case DeviceOrientation.landscapeRight: return 'landscapeRight'; case DeviceOrientation.landscapeLeft: return 'landscapeLeft'; } // The enum comes from a different package, which could get a new value at // any time, so provide a fallback that ensures this won't break when used // 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. DeviceOrientation deserializeDeviceOrientation(String str) { switch (str) { case 'portraitUp': return DeviceOrientation.portraitUp; case 'portraitDown': return DeviceOrientation.portraitDown; case 'landscapeRight': return DeviceOrientation.landscapeRight; case 'landscapeLeft': return DeviceOrientation.landscapeLeft; default: throw ArgumentError('"$str" is not a valid DeviceOrientation value'); } } ================================================ FILE: packages/camera/camera_avfoundation/pubspec.yaml ================================================ 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.11 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: camera platforms: ios: pluginClass: CameraPlugin dartPluginClass: AVFoundationCamera dependencies: camera_platform_interface: ^2.3.1 flutter: sdk: flutter stream_transform: ^2.0.0 dev_dependencies: async: ^2.5.0 flutter_driver: sdk: flutter flutter_test: sdk: flutter ================================================ FILE: packages/camera/camera_avfoundation/test/avfoundation_camera_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:math'; import 'package:async/async.dart'; import 'package:camera_avfoundation/src/avfoundation_camera.dart'; import 'package:camera_avfoundation/src/utils.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'method_channel_mock.dart'; const String _channelName = 'plugins.flutter.io/camera_avfoundation'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); test('registers instance', () async { AVFoundationCamera.registerWith(); expect(CameraPlatform.instance, isA()); }); test('registration does not set message handlers', () async { AVFoundationCamera.registerWith(); // Setting up a handler requires bindings to be initialized, and since // 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 _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .handlePlatformMessage( AVFoundationCamera.deviceEventChannelName, const StandardMethodCodec().encodeMethodCall(const MethodCall( 'orientation_changed', {'orientation': 'portraitDown'})), (ByteData? data) {}); expect(response, null); }); group('Creation, Initialization & Disposal Tests', () { test('Should send creation data and receive back a camera id', () async { // Arrange final MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: _channelName, methods: { 'create': { 'cameraId': 1, 'imageFormatGroup': 'unknown', } }); final AVFoundationCamera camera = AVFoundationCamera(); // Act final int cameraId = await camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0), ResolutionPreset.high, ); // Assert expect(cameraMockChannel.log, [ isMethodCall( 'create', arguments: { 'cameraName': 'Test', 'resolutionPreset': 'high', 'enableAudio': false }, ), ]); expect(cameraId, 1); }); test('Should throw CameraException when create throws a PlatformException', () { // Arrange MethodChannelMock(channelName: _channelName, methods: { 'create': PlatformException( code: 'TESTING_ERROR_CODE', message: 'Mock error message used during testing.', ) }); final AVFoundationCamera camera = AVFoundationCamera(); // Act expect( () => camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ), throwsA( isA() .having( (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') .having((CameraException e) => e.description, 'description', 'Mock error message used during testing.'), ), ); }); test('Should throw CameraException when create throws a PlatformException', () { // Arrange MethodChannelMock(channelName: _channelName, methods: { 'create': PlatformException( code: 'TESTING_ERROR_CODE', message: 'Mock error message used during testing.', ) }); final AVFoundationCamera camera = AVFoundationCamera(); // Act expect( () => camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ), throwsA( isA() .having( (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') .having((CameraException e) => e.description, 'description', 'Mock error message used during testing.'), ), ); }); test( 'Should throw CameraException when initialize throws a PlatformException', () { // Arrange MethodChannelMock( channelName: _channelName, methods: { 'initialize': PlatformException( code: 'TESTING_ERROR_CODE', message: 'Mock error message used during testing.', ) }, ); final AVFoundationCamera camera = AVFoundationCamera(); // Act expect( () => camera.initializeCamera(0), throwsA( isA() .having( (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') .having( (CameraException e) => e.description, 'description', 'Mock error message used during testing.', ), ), ); }, ); test('Should send initialization data', () async { // Arrange final MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: _channelName, methods: { 'create': { 'cameraId': 1, 'imageFormatGroup': 'unknown', }, 'initialize': null }); final AVFoundationCamera camera = AVFoundationCamera(); final int cameraId = await camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ); // Act final Future initializeFuture = camera.initializeCamera(cameraId); camera.cameraEventStreamController.add(CameraInitializedEvent( cameraId, 1920, 1080, ExposureMode.auto, true, FocusMode.auto, true, )); await initializeFuture; // Assert expect(cameraId, 1); expect(cameraMockChannel.log, [ anything, isMethodCall( 'initialize', arguments: { 'cameraId': 1, 'imageFormatGroup': 'unknown', }, ), ]); }); test('Should send a disposal call on dispose', () async { // Arrange final MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: _channelName, methods: { 'create': {'cameraId': 1}, 'initialize': null, 'dispose': {'cameraId': 1} }); final AVFoundationCamera camera = AVFoundationCamera(); final int cameraId = await camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ); final Future initializeFuture = camera.initializeCamera(cameraId); camera.cameraEventStreamController.add(CameraInitializedEvent( cameraId, 1920, 1080, ExposureMode.auto, true, FocusMode.auto, true, )); await initializeFuture; // Act await camera.dispose(cameraId); // Assert expect(cameraId, 1); expect(cameraMockChannel.log, [ anything, anything, isMethodCall( 'dispose', arguments: {'cameraId': 1}, ), ]); }); }); group('Event Tests', () { late AVFoundationCamera camera; late int cameraId; setUp(() async { MethodChannelMock( channelName: _channelName, methods: { 'create': {'cameraId': 1}, 'initialize': null }, ); camera = AVFoundationCamera(); cameraId = await camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ); final Future initializeFuture = camera.initializeCamera(cameraId); camera.cameraEventStreamController.add(CameraInitializedEvent( cameraId, 1920, 1080, ExposureMode.auto, true, FocusMode.auto, true, )); await initializeFuture; }); test('Should receive initialized event', () async { // Act final Stream eventStream = camera.onCameraInitialized(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); // Emit test events final CameraInitializedEvent event = CameraInitializedEvent( cameraId, 3840, 2160, ExposureMode.auto, true, FocusMode.auto, true, ); await camera.handleCameraMethodCall( MethodCall('initialized', event.toJson()), cameraId); // Assert expect(await streamQueue.next, event); // Clean up await streamQueue.cancel(); }); test('Should receive resolution changes', () async { // Act final Stream resolutionStream = camera.onCameraResolutionChanged(cameraId); final StreamQueue streamQueue = StreamQueue(resolutionStream); // Emit test events final CameraResolutionChangedEvent fhdEvent = CameraResolutionChangedEvent(cameraId, 1920, 1080); final CameraResolutionChangedEvent uhdEvent = CameraResolutionChangedEvent(cameraId, 3840, 2160); await camera.handleCameraMethodCall( MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); // Assert expect(await streamQueue.next, fhdEvent); expect(await streamQueue.next, uhdEvent); expect(await streamQueue.next, fhdEvent); expect(await streamQueue.next, uhdEvent); // Clean up await streamQueue.cancel(); }); test('Should receive camera closing events', () async { // Act final Stream eventStream = camera.onCameraClosing(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); // Emit test events final CameraClosingEvent event = CameraClosingEvent(cameraId); await camera.handleCameraMethodCall( MethodCall('camera_closing', event.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('camera_closing', event.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('camera_closing', event.toJson()), cameraId); // Assert expect(await streamQueue.next, event); expect(await streamQueue.next, event); expect(await streamQueue.next, event); // Clean up await streamQueue.cancel(); }); test('Should receive camera error events', () async { // Act final Stream errorStream = camera.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(errorStream); // Emit test events final CameraErrorEvent event = CameraErrorEvent(cameraId, 'Error Description'); await camera.handleCameraMethodCall( MethodCall('error', event.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('error', event.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('error', event.toJson()), cameraId); // Assert expect(await streamQueue.next, event); expect(await streamQueue.next, event); expect(await streamQueue.next, event); // Clean up await streamQueue.cancel(); }); test('Should receive device orientation change events', () async { // Act final Stream eventStream = camera.onDeviceOrientationChanged(); final StreamQueue streamQueue = StreamQueue(eventStream); // Emit test events const DeviceOrientationChangedEvent event = DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); for (int i = 0; i < 3; i++) { await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .handlePlatformMessage( AVFoundationCamera.deviceEventChannelName, const StandardMethodCodec().encodeMethodCall( MethodCall('orientation_changed', event.toJson())), null); } // Assert expect(await streamQueue.next, event); expect(await streamQueue.next, event); expect(await streamQueue.next, event); // Clean up await streamQueue.cancel(); }); }); group('Function Tests', () { late AVFoundationCamera camera; late int cameraId; setUp(() async { MethodChannelMock( channelName: _channelName, methods: { 'create': {'cameraId': 1}, 'initialize': null }, ); camera = AVFoundationCamera(); cameraId = await camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ); final Future initializeFuture = camera.initializeCamera(cameraId); camera.cameraEventStreamController.add( CameraInitializedEvent( cameraId, 1920, 1080, ExposureMode.auto, true, FocusMode.auto, true, ), ); await initializeFuture; }); 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', 'lensFacing': 'front', 'sensorOrientation': 1 }, { 'name': 'Test 2', 'lensFacing': 'back', 'sensorOrientation': 2 } ]; final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'availableCameras': returnData}, ); // Act final List cameras = await camera.availableCameras(); // Assert expect(channel.log, [ isMethodCall('availableCameras', arguments: null), ]); 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: typedData['name']! as String, lensDirection: parseCameraLensDirection(typedData['lensFacing']! as String), sensorOrientation: typedData['sensorOrientation']! as int, ); expect(cameras[i], cameraDescription); } }); test( 'Should throw CameraException when availableCameras throws a PlatformException', () { // Arrange MethodChannelMock(channelName: _channelName, methods: { 'availableCameras': PlatformException( code: 'TESTING_ERROR_CODE', message: 'Mock error message used during testing.', ) }); // Act expect( camera.availableCameras, throwsA( isA() .having( (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') .having((CameraException e) => e.description, 'description', 'Mock error message used during testing.'), ), ); }); test('Should take a picture and return an XFile instance', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'takePicture': '/test/path.jpg'}); // Act final XFile file = await camera.takePicture(cameraId); // Assert expect(channel.log, [ isMethodCall('takePicture', arguments: { 'cameraId': cameraId, }), ]); expect(file.path, '/test/path.jpg'); }); test('Should prepare for video recording', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'prepareForVideoRecording': null}, ); // Act await camera.prepareForVideoRecording(); // Assert expect(channel.log, [ isMethodCall('prepareForVideoRecording', arguments: null), ]); }); test('Should start recording a video', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'startVideoRecording': null}, ); // Act await camera.startVideoRecording(cameraId); // Assert expect(channel.log, [ isMethodCall('startVideoRecording', arguments: { 'cameraId': cameraId, 'maxVideoDuration': null, 'enableStream': false, }), ]); }); test('Should pass maxVideoDuration when starting recording a video', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'startVideoRecording': null}, ); // Act await camera.startVideoRecording( cameraId, maxVideoDuration: const Duration(seconds: 10), ); // Assert expect(channel.log, [ isMethodCall('startVideoRecording', arguments: { 'cameraId': cameraId, 'maxVideoDuration': 10000, 'enableStream': false, }), ]); }); test( 'Should pass enableStream if callback is passed when starting recording a video', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'startVideoRecording': null}, ); // Act await camera.startVideoCapturing(VideoCaptureOptions(cameraId, streamCallback: (CameraImageData imageData) {})); // Assert expect(channel.log, [ isMethodCall('startVideoRecording', arguments: { 'cameraId': cameraId, 'maxVideoDuration': null, 'enableStream': true, }), ]); }); test('Should stop a video recording and return the file', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'stopVideoRecording': '/test/path.mp4'}, ); // Act final XFile file = await camera.stopVideoRecording(cameraId); // Assert expect(channel.log, [ isMethodCall('stopVideoRecording', arguments: { 'cameraId': cameraId, }), ]); expect(file.path, '/test/path.mp4'); }); test('Should pause a video recording', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'pauseVideoRecording': null}, ); // Act await camera.pauseVideoRecording(cameraId); // Assert expect(channel.log, [ isMethodCall('pauseVideoRecording', arguments: { 'cameraId': cameraId, }), ]); }); test('Should resume a video recording', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'resumeVideoRecording': null}, ); // Act await camera.resumeVideoRecording(cameraId); // Assert expect(channel.log, [ isMethodCall('resumeVideoRecording', arguments: { 'cameraId': cameraId, }), ]); }); test('Should set the flash mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'setFlashMode': null}, ); // Act await camera.setFlashMode(cameraId, FlashMode.torch); await camera.setFlashMode(cameraId, FlashMode.always); await camera.setFlashMode(cameraId, FlashMode.auto); await camera.setFlashMode(cameraId, FlashMode.off); // Assert expect(channel.log, [ isMethodCall('setFlashMode', arguments: { 'cameraId': cameraId, 'mode': 'torch' }), isMethodCall('setFlashMode', arguments: { 'cameraId': cameraId, 'mode': 'always' }), isMethodCall('setFlashMode', arguments: {'cameraId': cameraId, 'mode': 'auto'}), isMethodCall('setFlashMode', arguments: {'cameraId': cameraId, 'mode': 'off'}), ]); }); test('Should set the exposure mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'setExposureMode': null}, ); // Act await camera.setExposureMode(cameraId, ExposureMode.auto); await camera.setExposureMode(cameraId, ExposureMode.locked); // Assert expect(channel.log, [ isMethodCall('setExposureMode', arguments: {'cameraId': cameraId, 'mode': 'auto'}), isMethodCall('setExposureMode', arguments: { 'cameraId': cameraId, 'mode': 'locked' }), ]); }); test('Should set the exposure point', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'setExposurePoint': null}, ); // Act await camera.setExposurePoint(cameraId, const Point(0.5, 0.5)); await camera.setExposurePoint(cameraId, null); // Assert expect(channel.log, [ isMethodCall('setExposurePoint', arguments: { 'cameraId': cameraId, 'x': 0.5, 'y': 0.5, 'reset': false }), isMethodCall('setExposurePoint', arguments: { 'cameraId': cameraId, 'x': null, 'y': null, 'reset': true }), ]); }); test('Should get the min exposure offset', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'getMinExposureOffset': 2.0}, ); // Act final double minExposureOffset = await camera.getMinExposureOffset(cameraId); // Assert expect(minExposureOffset, 2.0); expect(channel.log, [ isMethodCall('getMinExposureOffset', arguments: { 'cameraId': cameraId, }), ]); }); test('Should get the max exposure offset', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'getMaxExposureOffset': 2.0}, ); // Act final double maxExposureOffset = await camera.getMaxExposureOffset(cameraId); // Assert expect(maxExposureOffset, 2.0); expect(channel.log, [ isMethodCall('getMaxExposureOffset', arguments: { 'cameraId': cameraId, }), ]); }); test('Should get the exposure offset step size', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'getExposureOffsetStepSize': 0.25}, ); // Act final double stepSize = await camera.getExposureOffsetStepSize(cameraId); // Assert expect(stepSize, 0.25); expect(channel.log, [ isMethodCall('getExposureOffsetStepSize', arguments: { 'cameraId': cameraId, }), ]); }); test('Should set the exposure offset', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'setExposureOffset': 0.6}, ); // Act final double actualOffset = await camera.setExposureOffset(cameraId, 0.5); // Assert expect(actualOffset, 0.6); expect(channel.log, [ isMethodCall('setExposureOffset', arguments: { 'cameraId': cameraId, 'offset': 0.5, }), ]); }); test('Should set the focus mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'setFocusMode': null}, ); // Act await camera.setFocusMode(cameraId, FocusMode.auto); await camera.setFocusMode(cameraId, FocusMode.locked); // Assert expect(channel.log, [ isMethodCall('setFocusMode', arguments: {'cameraId': cameraId, 'mode': 'auto'}), isMethodCall('setFocusMode', arguments: { 'cameraId': cameraId, 'mode': 'locked' }), ]); }); test('Should set the exposure point', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'setFocusPoint': null}, ); // Act await camera.setFocusPoint(cameraId, const Point(0.5, 0.5)); await camera.setFocusPoint(cameraId, null); // Assert expect(channel.log, [ isMethodCall('setFocusPoint', arguments: { 'cameraId': cameraId, 'x': 0.5, 'y': 0.5, 'reset': false }), isMethodCall('setFocusPoint', arguments: { 'cameraId': cameraId, 'x': null, 'y': null, 'reset': true }), ]); }); test('Should build a texture widget as preview widget', () async { // Act final Widget widget = camera.buildPreview(cameraId); // Act expect(widget is Texture, isTrue); expect((widget as Texture).textureId, cameraId); }); test('Should throw MissingPluginException when handling unknown method', () { final AVFoundationCamera camera = AVFoundationCamera(); expect( () => camera.handleCameraMethodCall( const MethodCall('unknown_method'), 1), throwsA(isA())); }); test('Should get the max zoom level', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'getMaxZoomLevel': 10.0}, ); // Act final double maxZoomLevel = await camera.getMaxZoomLevel(cameraId); // Assert expect(maxZoomLevel, 10.0); expect(channel.log, [ isMethodCall('getMaxZoomLevel', arguments: { 'cameraId': cameraId, }), ]); }); test('Should get the min zoom level', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'getMinZoomLevel': 1.0}, ); // Act final double maxZoomLevel = await camera.getMinZoomLevel(cameraId); // Assert expect(maxZoomLevel, 1.0); expect(channel.log, [ isMethodCall('getMinZoomLevel', arguments: { 'cameraId': cameraId, }), ]); }); test('Should set the zoom level', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'setZoomLevel': null}, ); // Act await camera.setZoomLevel(cameraId, 2.0); // Assert expect(channel.log, [ isMethodCall('setZoomLevel', arguments: {'cameraId': cameraId, 'zoom': 2.0}), ]); }); test('Should throw CameraException when illegal zoom level is supplied', () async { // Arrange MethodChannelMock( channelName: _channelName, methods: { 'setZoomLevel': PlatformException( code: 'ZOOM_ERROR', message: 'Illegal zoom error', ) }, ); // Act & assert expect( () => camera.setZoomLevel(cameraId, -1.0), throwsA(isA() .having((CameraException e) => e.code, 'code', 'ZOOM_ERROR') .having((CameraException e) => e.description, 'description', 'Illegal zoom error'))); }); test('Should lock the capture orientation', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'lockCaptureOrientation': null}, ); // Act await camera.lockCaptureOrientation( cameraId, DeviceOrientation.portraitUp); // Assert expect(channel.log, [ isMethodCall('lockCaptureOrientation', arguments: { 'cameraId': cameraId, 'orientation': 'portraitUp' }), ]); }); test('Should unlock the capture orientation', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'unlockCaptureOrientation': null}, ); // Act await camera.unlockCaptureOrientation(cameraId); // Assert expect(channel.log, [ isMethodCall('unlockCaptureOrientation', arguments: {'cameraId': cameraId}), ]); }); test('Should pause the camera preview', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'pausePreview': null}, ); // Act await camera.pausePreview(cameraId); // Assert expect(channel.log, [ isMethodCall('pausePreview', arguments: {'cameraId': cameraId}), ]); }); test('Should resume the camera preview', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: {'resumePreview': null}, ); // Act await camera.resumePreview(cameraId); // Assert expect(channel.log, [ isMethodCall('resumePreview', arguments: {'cameraId': cameraId}), ]); }); test('Should start streaming', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: { 'startImageStream': null, 'stopImageStream': null, }, ); // Act final StreamSubscription subscription = camera .onStreamedFrameAvailable(cameraId) .listen((CameraImageData imageData) {}); // Assert expect(channel.log, [ isMethodCall('startImageStream', arguments: null), ]); subscription.cancel(); }); test('Should stop streaming', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: _channelName, methods: { 'startImageStream': null, 'stopImageStream': null, }, ); // Act final StreamSubscription subscription = camera .onStreamedFrameAvailable(cameraId) .listen((CameraImageData imageData) {}); subscription.cancel(); // Assert expect(channel.log, [ isMethodCall('startImageStream', arguments: null), isMethodCall('stopImageStream', arguments: null), ]); }); }); } /// 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; ================================================ FILE: packages/camera/camera_avfoundation/test/method_channel_mock.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; class MethodChannelMock { MethodChannelMock({ required String channelName, this.delay, required this.methods, }) : methodChannel = MethodChannel(channelName) { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(methodChannel, _handler); } final Duration? delay; final MethodChannel methodChannel; final Map methods; final List log = []; Future _handler(MethodCall methodCall) async { log.add(methodCall); if (!methods.containsKey(methodCall.method)) { throw MissingPluginException('No implementation found for method ' '${methodCall.method} on channel ${methodChannel.name}'); } return Future.delayed(delay ?? Duration.zero, () { final dynamic result = methods[methodCall.method]; if (result is Exception) { throw result; } return Future.value(result); }); } } /// 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; ================================================ FILE: packages/camera/camera_avfoundation/test/type_conversion_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:camera_avfoundation/src/type_conversion.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('CameraImageData can be created', () { final CameraImageData cameraImage = cameraImageFromPlatformData({ 'format': 1, 'height': 1, 'width': 4, 'lensAperture': 1.8, 'sensorExposureTime': 9991324, 'sensorSensitivity': 92.0, 'planes': [ { 'bytes': Uint8List.fromList([1, 2, 3, 4]), 'bytesPerPixel': 1, 'bytesPerRow': 4, 'height': 1, 'width': 4 } ] }); expect(cameraImage.height, 1); expect(cameraImage.width, 4); expect(cameraImage.format.group, ImageFormatGroup.unknown); expect(cameraImage.planes.length, 1); }); test('CameraImageData has ImageFormatGroup.yuv420', () { final CameraImageData cameraImage = cameraImageFromPlatformData({ 'format': 875704438, 'height': 1, 'width': 4, 'lensAperture': 1.8, 'sensorExposureTime': 9991324, 'sensorSensitivity': 92.0, 'planes': [ { 'bytes': Uint8List.fromList([1, 2, 3, 4]), 'bytesPerPixel': 1, 'bytesPerRow': 4, 'height': 1, 'width': 4 } ] }); expect(cameraImage.format.group, ImageFormatGroup.yuv420); }); } ================================================ FILE: packages/camera/camera_avfoundation/test/utils_test.dart ================================================ // Copyright 2013 The Flutter 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_avfoundation/src/utils.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('Utility methods', () { test( 'Should return CameraLensDirection when valid value is supplied when parsing camera lens direction', () { expect( parseCameraLensDirection('back'), CameraLensDirection.back, ); expect( parseCameraLensDirection('front'), CameraLensDirection.front, ); expect( parseCameraLensDirection('external'), CameraLensDirection.external, ); }); test( 'Should throw ArgumentException when invalid value is supplied when parsing camera lens direction', () { expect( () => parseCameraLensDirection('test'), throwsA(isArgumentError), ); }); test('serializeDeviceOrientation() should serialize correctly', () { expect(serializeDeviceOrientation(DeviceOrientation.portraitUp), 'portraitUp'); expect(serializeDeviceOrientation(DeviceOrientation.portraitDown), 'portraitDown'); expect(serializeDeviceOrientation(DeviceOrientation.landscapeRight), 'landscapeRight'); expect(serializeDeviceOrientation(DeviceOrientation.landscapeLeft), 'landscapeLeft'); }); test('deserializeDeviceOrientation() should deserialize correctly', () { expect(deserializeDeviceOrientation('portraitUp'), DeviceOrientation.portraitUp); expect(deserializeDeviceOrientation('portraitDown'), DeviceOrientation.portraitDown); expect(deserializeDeviceOrientation('landscapeRight'), DeviceOrientation.landscapeRight); expect(deserializeDeviceOrientation('landscapeLeft'), DeviceOrientation.landscapeLeft); }); }); } ================================================ FILE: packages/camera/camera_platform_interface/AUTHORS ================================================ # 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> ================================================ FILE: packages/camera/camera_platform_interface/CHANGELOG.md ================================================ ## 2.4.0 * Allows camera to be switched while video recording. * 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. ## 2.3.1 * Exports VideoCaptureOptions to allow dependencies to implement concurrent stream and record. ## 2.3.0 * Adds new capture method for a camera to allow concurrent streaming and recording. ## 2.2.2 * Updates code for `no_leading_underscores_for_local_identifiers` lint. ## 2.2.1 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. * Fixes avoid_redundant_argument_values lint warnings and minor typos. * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/104231). * Ignores missing return warnings in preparation for [upcoming analysis changes](https://github.com/flutter/flutter/issues/105750). ## 2.2.0 * Adds image streaming to the platform interface. * Removes unnecessary imports. ## 2.1.6 * Adopts `Object.hash`. * Removes obsolete dependency on `pedantic`. ## 2.1.5 * Fixes asynchronous exceptions handling of the `initializeCamera` method. ## 2.1.4 * Removes dependency on `meta`. ## 2.1.3 * Update to use the `verify` method introduced in platform_plugin_interface 2.1.0. ## 2.1.2 * Adopts new analysis options and fixes all violations. ## 2.1.1 * Add web-relevant docs to platform interface code. ## 2.1.0 * Introduces interface methods for pausing and resuming the camera preview. ## 2.0.1 * Update platform_plugin_interface version requirement. ## 2.0.0 - Stable null safety release. ## 1.6.0 - Added VideoRecordedEvent to support ending a video recording in the native implementation. ## 1.5.0 - Introduces interface methods for locking and unlocking the capture orientation. - Introduces interface method for listening to the device orientation. ## 1.4.0 - Added interface methods to support auto focus. ## 1.3.0 - Introduces an option to set the image format when initializing. ## 1.2.0 - Added interface to support automatic exposure. ## 1.1.0 - Added an optional `maxVideoDuration` parameter to the `startVideoRecording` method, which allows implementations to limit the duration of a video recording. ## 1.0.4 - Added the torch option to the FlashMode enum, which when implemented indicates the flash light should be turned on continuously. ## 1.0.3 - Update Flutter SDK constraint. ## 1.0.2 - Added interface methods to support zoom features. ## 1.0.1 - Added interface methods for setting flash mode. ## 1.0.0 - Initial open-source release ================================================ FILE: packages/camera/camera_platform_interface/LICENSE ================================================ 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. ================================================ FILE: packages/camera/camera_platform_interface/README.md ================================================ # camera_platform_interface A common platform interface for the [`camera`][1] plugin. This interface allows platform-specific implementations of the `camera` plugin, as well as the plugin itself, to ensure they are supporting the same interface. # Usage To implement a new platform-specific implementation of `camera`, extend [`CameraPlatform`][2] with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `CameraPlatform` by calling `CameraPlatform.instance = MyPlatformCamera()`. # Note on breaking changes Strongly prefer non-breaking changes (such as adding a method to the interface) over breaking changes for this package. See https://flutter.dev/go/platform-interface-breaking-changes for a discussion on why a less-clean interface is preferable to a breaking change. [1]: ../camera [2]: lib/camera_platform_interface.dart ================================================ FILE: packages/camera/camera_platform_interface/lib/camera_platform_interface.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Expose XFile export 'package:cross_file/cross_file.dart'; export 'src/events/camera_event.dart'; export 'src/events/device_event.dart'; export 'src/platform_interface/camera_platform.dart'; export 'src/types/types.dart'; ================================================ FILE: packages/camera/camera_platform_interface/lib/src/events/camera_event.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart' show immutable; import '../../camera_platform_interface.dart'; /// Generic Event coming from the native side of Camera, /// related to a specific camera module. /// /// All [CameraEvent]s contain the `cameraId` that originated the event. This /// should never be `null`. /// /// This class is used as a base class for all the events that might be /// triggered from a Camera, but it is never used directly as an event type. /// /// Do NOT instantiate new events like `CameraEvent(cameraId)` directly, /// use a specific class instead: /// /// Do `class NewEvent extend CameraEvent` when creating your own events. /// See below for examples: `CameraClosingEvent`, `CameraErrorEvent`... /// These events are more semantic and more pleasant to use than raw generics. /// They can be (and in fact, are) filtered by the `instanceof`-operator. @immutable abstract class CameraEvent { /// Build a Camera Event, that relates a `cameraId`. /// /// The `cameraId` is the ID of the camera that triggered the event. const CameraEvent(this.cameraId) : assert(cameraId != null); /// The ID of the Camera this event is associated to. final int cameraId; @override bool operator ==(Object other) => identical(this, other) || other is CameraEvent && runtimeType == other.runtimeType && cameraId == other.cameraId; @override int get hashCode => cameraId.hashCode; } /// An event fired when the camera has finished initializing. class CameraInitializedEvent extends CameraEvent { /// Build a CameraInitialized event triggered from the camera represented by /// `cameraId`. /// /// The `previewWidth` represents the width of the generated preview in pixels. /// The `previewHeight` represents the height of the generated preview in pixels. const CameraInitializedEvent( int cameraId, this.previewWidth, this.previewHeight, this.exposureMode, this.exposurePointSupported, this.focusMode, this.focusPointSupported, ) : super(cameraId); /// Converts the supplied [Map] to an instance of the [CameraInitializedEvent] /// class. CameraInitializedEvent.fromJson(Map json) : previewWidth = json['previewWidth']! as double, previewHeight = json['previewHeight']! as double, exposureMode = deserializeExposureMode(json['exposureMode']! as String), exposurePointSupported = (json['exposurePointSupported'] as bool?) ?? false, focusMode = deserializeFocusMode(json['focusMode']! as String), focusPointSupported = (json['focusPointSupported'] as bool?) ?? false, super(json['cameraId']! as int); /// The width of the preview in pixels. final double previewWidth; /// The height of the preview in pixels. final double previewHeight; /// The default exposure mode final ExposureMode exposureMode; /// The default focus mode final FocusMode focusMode; /// Whether setting exposure points is supported. final bool exposurePointSupported; /// Whether setting focus points is supported. final bool focusPointSupported; /// Converts the [CameraInitializedEvent] instance into a [Map] instance that /// can be serialized to JSON. Map toJson() => { 'cameraId': cameraId, 'previewWidth': previewWidth, 'previewHeight': previewHeight, 'exposureMode': serializeExposureMode(exposureMode), 'exposurePointSupported': exposurePointSupported, 'focusMode': serializeFocusMode(focusMode), 'focusPointSupported': focusPointSupported, }; @override bool operator ==(Object other) => identical(this, other) || super == other && other is CameraInitializedEvent && runtimeType == other.runtimeType && previewWidth == other.previewWidth && previewHeight == other.previewHeight && exposureMode == other.exposureMode && exposurePointSupported == other.exposurePointSupported && focusMode == other.focusMode && focusPointSupported == other.focusPointSupported; @override int get hashCode => Object.hash( super.hashCode, previewWidth, previewHeight, exposureMode, exposurePointSupported, focusMode, focusPointSupported, ); } /// An event fired when the resolution preset of the camera has changed. class CameraResolutionChangedEvent extends CameraEvent { /// Build a CameraResolutionChanged event triggered from the camera /// represented by `cameraId`. /// /// The `captureWidth` represents the width of the resulting image in pixels. /// The `captureHeight` represents the height of the resulting image in pixels. const CameraResolutionChangedEvent( int cameraId, this.captureWidth, this.captureHeight, ) : super(cameraId); /// Converts the supplied [Map] to an instance of the /// [CameraResolutionChangedEvent] class. CameraResolutionChangedEvent.fromJson(Map json) : captureWidth = json['captureWidth']! as double, captureHeight = json['captureHeight']! as double, super(json['cameraId']! as int); /// The capture width in pixels. final double captureWidth; /// The capture height in pixels. final double captureHeight; /// Converts the [CameraResolutionChangedEvent] instance into a [Map] instance /// that can be serialized to JSON. Map toJson() => { 'cameraId': cameraId, 'captureWidth': captureWidth, 'captureHeight': captureHeight, }; @override bool operator ==(Object other) => identical(this, other) || other is CameraResolutionChangedEvent && super == other && runtimeType == other.runtimeType && captureWidth == other.captureWidth && captureHeight == other.captureHeight; @override int get hashCode => Object.hash(super.hashCode, captureWidth, captureHeight); } /// An event fired when the camera is going to close. class CameraClosingEvent extends CameraEvent { /// Build a CameraClosing event triggered from the camera represented by /// `cameraId`. const CameraClosingEvent(int cameraId) : super(cameraId); /// Converts the supplied [Map] to an instance of the [CameraClosingEvent] /// class. CameraClosingEvent.fromJson(Map json) : super(json['cameraId']! as int); /// Converts the [CameraClosingEvent] instance into a [Map] instance that can /// be serialized to JSON. Map toJson() => { 'cameraId': cameraId, }; @override bool operator ==(Object other) => identical(this, other) || super == other && other is CameraClosingEvent && runtimeType == other.runtimeType; @override // This is here even though it just calls super to make it less likely that // operator== would be changed without changing `hashCode`. // ignore: unnecessary_overrides int get hashCode => super.hashCode; } /// An event fired when an error occured while operating the camera. class CameraErrorEvent extends CameraEvent { /// Build a CameraError event triggered from the camera represented by /// `cameraId`. /// /// The `description` represents the error occured on the camera. const CameraErrorEvent(int cameraId, this.description) : super(cameraId); /// Converts the supplied [Map] to an instance of the [CameraErrorEvent] /// class. CameraErrorEvent.fromJson(Map json) : description = json['description']! as String, super(json['cameraId']! as int); /// Description of the error. final String description; /// Converts the [CameraErrorEvent] instance into a [Map] instance that can be /// serialized to JSON. Map toJson() => { 'cameraId': cameraId, 'description': description, }; @override bool operator ==(Object other) => identical(this, other) || super == other && other is CameraErrorEvent && runtimeType == other.runtimeType && description == other.description; @override int get hashCode => Object.hash(super.hashCode, description); } /// An event fired when a video has finished recording. class VideoRecordedEvent extends CameraEvent { /// Build a VideoRecordedEvent triggered from the camera with the `cameraId`. /// /// The `file` represents the file of the video. /// The `maxVideoDuration` shows if a maxVideoDuration shows if a maximum /// video duration was set. const VideoRecordedEvent(int cameraId, this.file, this.maxVideoDuration) : super(cameraId); /// Converts the supplied [Map] to an instance of the [VideoRecordedEvent] /// class. VideoRecordedEvent.fromJson(Map json) : file = XFile(json['path']! as String), maxVideoDuration = json['maxVideoDuration'] != null ? Duration(milliseconds: json['maxVideoDuration'] as int) : null, super(json['cameraId']! as int); /// XFile of the recorded video. final XFile file; /// Maximum duration of the recorded video. final Duration? maxVideoDuration; /// Converts the [VideoRecordedEvent] instance into a [Map] instance that can be /// serialized to JSON. Map toJson() => { 'cameraId': cameraId, 'path': file.path, 'maxVideoDuration': maxVideoDuration?.inMilliseconds }; @override bool operator ==(Object other) => identical(this, other) || super == other && other is VideoRecordedEvent && runtimeType == other.runtimeType && maxVideoDuration == other.maxVideoDuration; @override int get hashCode => Object.hash(super.hashCode, file, maxVideoDuration); } ================================================ FILE: packages/camera/camera_platform_interface/lib/src/events/device_event.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart' show immutable; import 'package:flutter/services.dart'; import '../utils/utils.dart'; /// Generic Event coming from the native side of Camera, /// not related to a specific camera module. /// /// This class is used as a base class for all the events that might be /// triggered from a device, but it is never used directly as an event type. /// /// Do NOT instantiate new events like `DeviceEvent()` directly, /// use a specific class instead: /// /// Do `class NewEvent extend DeviceEvent` when creating your own events. /// See below for examples: `DeviceOrientationChangedEvent`... /// These events are more semantic and more pleasant to use than raw generics. /// They can be (and in fact, are) filtered by the `instanceof`-operator. @immutable abstract class DeviceEvent { /// Creates a new device event. const DeviceEvent(); } /// The [DeviceOrientationChangedEvent] is fired every time the orientation of the device UI changes. class DeviceOrientationChangedEvent extends DeviceEvent { /// Build a new orientation changed event. const DeviceOrientationChangedEvent(this.orientation); /// Converts the supplied [Map] to an instance of the [DeviceOrientationChangedEvent] /// class. DeviceOrientationChangedEvent.fromJson(Map json) : orientation = deserializeDeviceOrientation(json['orientation']! as String); /// The new orientation of the device final DeviceOrientation orientation; /// Converts the [DeviceOrientationChangedEvent] instance into a [Map] instance that /// can be serialized to JSON. Map toJson() => { 'orientation': serializeDeviceOrientation(orientation), }; @override bool operator ==(Object other) => identical(this, other) || other is DeviceOrientationChangedEvent && runtimeType == other.runtimeType && orientation == other.orientation; @override int get hashCode => orientation.hashCode; } ================================================ FILE: packages/camera/camera_platform_interface/lib/src/method_channel/method_channel_camera.dart ================================================ // Copyright 2013 The Flutter 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 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:stream_transform/stream_transform.dart'; import '../../camera_platform_interface.dart'; import '../utils/utils.dart'; import 'type_conversion.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/camera'); /// An implementation of [CameraPlatform] that uses method channels. class MethodChannelCamera extends CameraPlatform { /// Construct a new method channel camera instance. MethodChannelCamera() { const MethodChannel channel = MethodChannel('flutter.io/cameraPlugin/device'); channel.setMethodCallHandler( (MethodCall call) => handleDeviceMethodCall(call)); } final Map _channels = {}; /// The controller we need to broadcast the different events coming /// from handleMethodCall, specific to camera events. /// /// It is a `broadcast` because multiple controllers will connect to /// different stream views of this Controller. /// This is only exposed for test purposes. It shouldn't be used by clients of /// the plugin as it may break or change at any time. @visibleForTesting final StreamController cameraEventStreamController = StreamController.broadcast(); /// The controller we need to broadcast the different events coming /// from handleMethodCall, specific to general device events. /// /// It is a `broadcast` because multiple controllers will connect to /// different stream views of this Controller. /// This is only exposed for test purposes. It shouldn't be used by clients of /// the plugin as it may break or change at any time. @visibleForTesting final StreamController deviceEventStreamController = StreamController.broadcast(); // The stream to receive frames from the native code. StreamSubscription? _platformImageStreamSubscription; // The stream for vending frames to platform interface clients. StreamController? _frameStreamController; Stream _cameraEvents(int cameraId) => cameraEventStreamController.stream .where((CameraEvent event) => event.cameraId == cameraId); @override Future> availableCameras() async { try { final List>? cameras = await _channel .invokeListMethod>('availableCameras'); if (cameras == null) { return []; } return cameras.map((Map camera) { return CameraDescription( name: camera['name']! as String, lensDirection: parseCameraLensDirection(camera['lensFacing']! as String), sensorOrientation: camera['sensorOrientation']! as int, ); }).toList(); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } @override Future createCamera( CameraDescription cameraDescription, ResolutionPreset? resolutionPreset, { bool enableAudio = false, }) async { try { final Map? reply = await _channel .invokeMapMethod('create', { 'cameraName': cameraDescription.name, 'resolutionPreset': resolutionPreset != null ? _serializeResolutionPreset(resolutionPreset) : null, 'enableAudio': enableAudio, }); return reply!['cameraId']! as int; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } @override Future initializeCamera( int cameraId, { ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown, }) { _channels.putIfAbsent(cameraId, () { final MethodChannel channel = MethodChannel('flutter.io/cameraPlugin/camera$cameraId'); channel.setMethodCallHandler( (MethodCall call) => handleCameraMethodCall(call, cameraId)); return channel; }); final Completer completer = Completer(); onCameraInitialized(cameraId).first.then((CameraInitializedEvent value) { completer.complete(); }); _channel.invokeMapMethod( 'initialize', { 'cameraId': cameraId, 'imageFormatGroup': imageFormatGroup.name(), }, ).catchError( // TODO(srawlins): This should return a value of the future's type. This // will fail upcoming analysis checks with // https://github.com/flutter/flutter/issues/105750. // ignore: body_might_complete_normally_catch_error (Object error, StackTrace stackTrace) { if (error is! PlatformException) { // ignore: only_throw_errors throw error; } completer.completeError( CameraException(error.code, error.message), stackTrace, ); }, ); return completer.future; } @override Future dispose(int cameraId) async { if (_channels.containsKey(cameraId)) { final MethodChannel? cameraChannel = _channels[cameraId]; cameraChannel?.setMethodCallHandler(null); _channels.remove(cameraId); } await _channel.invokeMethod( 'dispose', {'cameraId': cameraId}, ); } @override Stream onCameraInitialized(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onCameraResolutionChanged(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onCameraClosing(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onCameraError(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onVideoRecordedEvent(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onDeviceOrientationChanged() { return deviceEventStreamController.stream .whereType(); } @override Future lockCaptureOrientation( int cameraId, DeviceOrientation orientation, ) async { await _channel.invokeMethod( 'lockCaptureOrientation', { 'cameraId': cameraId, 'orientation': serializeDeviceOrientation(orientation) }, ); } @override Future unlockCaptureOrientation(int cameraId) async { await _channel.invokeMethod( 'unlockCaptureOrientation', {'cameraId': cameraId}, ); } @override Future takePicture(int cameraId) async { final String? path = await _channel.invokeMethod( 'takePicture', {'cameraId': cameraId}, ); if (path == null) { throw CameraException( 'INVALID_PATH', 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', ); } return XFile(path); } @override Future prepareForVideoRecording() => _channel.invokeMethod('prepareForVideoRecording'); @override Future startVideoRecording(int cameraId, {Duration? maxVideoDuration}) async { return startVideoCapturing( VideoCaptureOptions(cameraId, maxDuration: maxVideoDuration)); } @override Future startVideoCapturing(VideoCaptureOptions options) async { await _channel.invokeMethod( 'startVideoRecording', { 'cameraId': options.cameraId, 'maxVideoDuration': options.maxDuration?.inMilliseconds, 'enableStream': options.streamCallback != null, }, ); if (options.streamCallback != null) { _installStreamController().stream.listen(options.streamCallback); _startStreamListener(); } } @override Future stopVideoRecording(int cameraId) async { final String? path = await _channel.invokeMethod( 'stopVideoRecording', {'cameraId': cameraId}, ); if (path == null) { throw CameraException( 'INVALID_PATH', 'The platform "$defaultTargetPlatform" did not return a path while reporting success. The platform should always return a valid path or report an error.', ); } return XFile(path); } @override Future pauseVideoRecording(int cameraId) => _channel.invokeMethod( 'pauseVideoRecording', {'cameraId': cameraId}, ); @override Future resumeVideoRecording(int cameraId) => _channel.invokeMethod( 'resumeVideoRecording', {'cameraId': cameraId}, ); @override Stream onStreamedFrameAvailable(int cameraId, {CameraImageStreamOptions? options}) { _installStreamController(onListen: _onFrameStreamListen); return _frameStreamController!.stream; } StreamController _installStreamController( {Function()? onListen}) { _frameStreamController = StreamController( onListen: onListen ?? () {}, onPause: _onFrameStreamPauseResume, onResume: _onFrameStreamPauseResume, onCancel: _onFrameStreamCancel, ); return _frameStreamController!; } void _onFrameStreamListen() { _startPlatformStream(); } Future _startPlatformStream() async { await _channel.invokeMethod('startImageStream'); _startStreamListener(); } void _startStreamListener() { const EventChannel cameraEventChannel = EventChannel('plugins.flutter.io/camera/imageStream'); _platformImageStreamSubscription = cameraEventChannel.receiveBroadcastStream().listen((dynamic imageData) { if (defaultTargetPlatform == TargetPlatform.iOS) { try { _channel.invokeMethod('receivedImageStreamData'); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } _frameStreamController! .add(cameraImageFromPlatformData(imageData as Map)); }); } FutureOr _onFrameStreamCancel() async { await _channel.invokeMethod('stopImageStream'); await _platformImageStreamSubscription?.cancel(); _platformImageStreamSubscription = null; _frameStreamController = null; } void _onFrameStreamPauseResume() { throw CameraException('InvalidCall', 'Pause and resume are not supported for onStreamedFrameAvailable'); } @override Future setFlashMode(int cameraId, FlashMode mode) => _channel.invokeMethod( 'setFlashMode', { 'cameraId': cameraId, 'mode': _serializeFlashMode(mode), }, ); @override Future setExposureMode(int cameraId, ExposureMode mode) => _channel.invokeMethod( 'setExposureMode', { 'cameraId': cameraId, 'mode': serializeExposureMode(mode), }, ); @override Future setExposurePoint(int cameraId, Point? point) { assert(point == null || point.x >= 0 && point.x <= 1); assert(point == null || point.y >= 0 && point.y <= 1); return _channel.invokeMethod( 'setExposurePoint', { 'cameraId': cameraId, 'reset': point == null, 'x': point?.x, 'y': point?.y, }, ); } @override Future getMinExposureOffset(int cameraId) async { final double? minExposureOffset = await _channel.invokeMethod( 'getMinExposureOffset', {'cameraId': cameraId}, ); return minExposureOffset!; } @override Future getMaxExposureOffset(int cameraId) async { final double? maxExposureOffset = await _channel.invokeMethod( 'getMaxExposureOffset', {'cameraId': cameraId}, ); return maxExposureOffset!; } @override Future getExposureOffsetStepSize(int cameraId) async { final double? stepSize = await _channel.invokeMethod( 'getExposureOffsetStepSize', {'cameraId': cameraId}, ); return stepSize!; } @override Future setExposureOffset(int cameraId, double offset) async { final double? appliedOffset = await _channel.invokeMethod( 'setExposureOffset', { 'cameraId': cameraId, 'offset': offset, }, ); return appliedOffset!; } @override Future setFocusMode(int cameraId, FocusMode mode) => _channel.invokeMethod( 'setFocusMode', { 'cameraId': cameraId, 'mode': serializeFocusMode(mode), }, ); @override Future setFocusPoint(int cameraId, Point? point) { assert(point == null || point.x >= 0 && point.x <= 1); assert(point == null || point.y >= 0 && point.y <= 1); return _channel.invokeMethod( 'setFocusPoint', { 'cameraId': cameraId, 'reset': point == null, 'x': point?.x, 'y': point?.y, }, ); } @override Future getMaxZoomLevel(int cameraId) async { final double? maxZoomLevel = await _channel.invokeMethod( 'getMaxZoomLevel', {'cameraId': cameraId}, ); return maxZoomLevel!; } @override Future getMinZoomLevel(int cameraId) async { final double? minZoomLevel = await _channel.invokeMethod( 'getMinZoomLevel', {'cameraId': cameraId}, ); return minZoomLevel!; } @override Future setZoomLevel(int cameraId, double zoom) async { try { await _channel.invokeMethod( 'setZoomLevel', { 'cameraId': cameraId, 'zoom': zoom, }, ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } @override Future pausePreview(int cameraId) async { await _channel.invokeMethod( 'pausePreview', {'cameraId': cameraId}, ); } @override Future resumePreview(int cameraId) async { await _channel.invokeMethod( 'resumePreview', {'cameraId': cameraId}, ); } @override Future setDescriptionWhileRecording( CameraDescription description) async { await _channel.invokeMethod( 'setDescriptionWhileRecording', { 'cameraName': description.name, }, ); } @override Widget buildPreview(int cameraId) { return Texture(textureId: cameraId); } /// Returns the flash mode as a String. String _serializeFlashMode(FlashMode flashMode) { switch (flashMode) { case FlashMode.off: return 'off'; case FlashMode.auto: return 'auto'; case FlashMode.always: return 'always'; case FlashMode.torch: return 'torch'; } } /// Returns the resolution preset as a String. String _serializeResolutionPreset(ResolutionPreset resolutionPreset) { switch (resolutionPreset) { case ResolutionPreset.max: return 'max'; case ResolutionPreset.ultraHigh: return 'ultraHigh'; case ResolutionPreset.veryHigh: return 'veryHigh'; case ResolutionPreset.high: return 'high'; case ResolutionPreset.medium: return 'medium'; case ResolutionPreset.low: return 'low'; } } /// Converts messages received from the native platform into device events. /// /// This is only exposed for test purposes. It shouldn't be used by clients of /// the plugin as it may break or change at any time. Future handleDeviceMethodCall(MethodCall call) async { switch (call.method) { case 'orientation_changed': final Map arguments = _getArgumentDictionary(call); deviceEventStreamController.add(DeviceOrientationChangedEvent( deserializeDeviceOrientation(arguments['orientation']! as String))); break; default: throw MissingPluginException(); } } /// Converts messages received from the native platform into camera events. /// /// This is only exposed for test purposes. It shouldn't be used by clients of /// the plugin as it may break or change at any time. @visibleForTesting Future handleCameraMethodCall(MethodCall call, int cameraId) async { switch (call.method) { case 'initialized': final Map arguments = _getArgumentDictionary(call); cameraEventStreamController.add(CameraInitializedEvent( cameraId, 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, arguments['captureWidth']! as double, arguments['captureHeight']! as double, )); break; case 'camera_closing': cameraEventStreamController.add(CameraClosingEvent( cameraId, )); break; case 'video_recorded': final Map arguments = _getArgumentDictionary(call); cameraEventStreamController.add(VideoRecordedEvent( cameraId, 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, 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(); } } ================================================ FILE: packages/camera/camera_platform_interface/lib/src/method_channel/type_conversion.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import '../types/types.dart'; /// Converts method channel call [data] for `receivedImageStreamData` to a /// [CameraImageData]. CameraImageData cameraImageFromPlatformData(Map data) { return CameraImageData( format: _cameraImageFormatFromPlatformData(data['format']), height: data['height'] as int, width: data['width'] as int, lensAperture: data['lensAperture'] as double?, sensorExposureTime: data['sensorExposureTime'] as int?, sensorSensitivity: data['sensorSensitivity'] as double?, planes: List.unmodifiable( (data['planes'] as List).map( (dynamic planeData) => _cameraImagePlaneFromPlatformData( planeData as Map)))); } CameraImageFormat _cameraImageFormatFromPlatformData(dynamic data) { return CameraImageFormat(_imageFormatGroupFromPlatformData(data), raw: data); } ImageFormatGroup _imageFormatGroupFromPlatformData(dynamic data) { if (defaultTargetPlatform == TargetPlatform.android) { switch (data) { case 35: // android.graphics.ImageFormat.YUV_420_888 return ImageFormatGroup.yuv420; case 256: // android.graphics.ImageFormat.JPEG return ImageFormatGroup.jpeg; } } if (defaultTargetPlatform == TargetPlatform.iOS) { switch (data) { case 875704438: // kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange return ImageFormatGroup.yuv420; case 1111970369: // kCVPixelFormatType_32BGRA return ImageFormatGroup.bgra8888; } } return ImageFormatGroup.unknown; } CameraImagePlane _cameraImagePlaneFromPlatformData(Map data) { return CameraImagePlane( bytes: data['bytes'] as Uint8List, bytesPerPixel: data['bytesPerPixel'] as int?, bytesPerRow: data['bytesPerRow'] as int, height: data['height'] as int?, width: data['width'] as int?); } ================================================ FILE: packages/camera/camera_platform_interface/lib/src/platform_interface/camera_platform.dart ================================================ // Copyright 2013 The Flutter 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 'dart:math'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import '../../camera_platform_interface.dart'; import '../method_channel/method_channel_camera.dart'; /// The interface that implementations of camera must implement. /// /// Platform implementations should extend this class rather than implement it as `camera` /// does not consider newly added methods to be breaking changes. Extending this class /// (using `extends`) ensures that the subclass will get the default implementation, while /// platform implementations that `implements` this interface will be broken by newly added /// [CameraPlatform] methods. abstract class CameraPlatform extends PlatformInterface { /// Constructs a CameraPlatform. CameraPlatform() : super(token: _token); static final Object _token = Object(); static CameraPlatform _instance = MethodChannelCamera(); /// The default instance of [CameraPlatform] to use. /// /// Defaults to [MethodChannelCamera]. static CameraPlatform get instance => _instance; /// Platform-specific plugins should set this with their own platform-specific /// class that extends [CameraPlatform] when they register themselves. static set instance(CameraPlatform instance) { PlatformInterface.verify(instance, _token); _instance = instance; } /// Completes with a list of available cameras. /// /// This method returns an empty list when no cameras are available. Future> availableCameras() { throw UnimplementedError('availableCameras() is not implemented.'); } /// Creates an uninitialized camera instance and returns the cameraId. Future createCamera( CameraDescription cameraDescription, ResolutionPreset? resolutionPreset, { bool enableAudio = false, }) { throw UnimplementedError('createCamera() is not implemented.'); } /// Initializes the camera on the device. /// /// [imageFormatGroup] is used to specify the image formatting used. /// On Android this defaults to ImageFormat.YUV_420_888 and applies only to the imageStream. /// On iOS this defaults to kCVPixelFormatType_32BGRA. /// On Web this parameter is currently not supported. Future initializeCamera( int cameraId, { ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown, }) { throw UnimplementedError('initializeCamera() is not implemented.'); } /// The camera has been initialized. Stream onCameraInitialized(int cameraId) { throw UnimplementedError('onCameraInitialized() is not implemented.'); } /// The camera's resolution has changed. /// On Web this returns an empty stream. Stream onCameraResolutionChanged(int cameraId) { throw UnimplementedError('onResolutionChanged() is not implemented.'); } /// The camera started to close. Stream onCameraClosing(int cameraId) { throw UnimplementedError('onCameraClosing() is not implemented.'); } /// The camera experienced an error. Stream onCameraError(int cameraId) { throw UnimplementedError('onCameraError() is not implemented.'); } /// The camera finished recording a video. Stream onVideoRecordedEvent(int cameraId) { throw UnimplementedError('onCameraTimeLimitReached() is not implemented.'); } /// The ui orientation changed. /// /// Implementations for this: /// - Should support all 4 orientations. Stream onDeviceOrientationChanged() { throw UnimplementedError( 'onDeviceOrientationChanged() is not implemented.'); } /// Locks the capture orientation. Future lockCaptureOrientation( int cameraId, DeviceOrientation orientation) { throw UnimplementedError('lockCaptureOrientation() is not implemented.'); } /// Unlocks the capture orientation. Future unlockCaptureOrientation(int cameraId) { throw UnimplementedError('unlockCaptureOrientation() is not implemented.'); } /// Captures an image and returns the file where it was saved. Future takePicture(int cameraId) { throw UnimplementedError('takePicture() is not implemented.'); } /// Prepare the capture session for video recording. Future prepareForVideoRecording() { throw UnimplementedError('prepareForVideoRecording() is not implemented.'); } /// Starts a video recording. /// /// The length of the recording can be limited by specifying the [maxVideoDuration]. /// By default no maximum duration is specified, /// meaning the recording will continue until manually stopped. /// With [maxVideoDuration] set the video is returned in a [VideoRecordedEvent] /// through the [onVideoRecordedEvent] stream when the set duration is reached. /// /// This method is deprecated in favour of [startVideoCapturing]. Future startVideoRecording(int cameraId, {Duration? maxVideoDuration}) { throw UnimplementedError('startVideoRecording() is not implemented.'); } /// Starts a video recording and/or streaming session. /// /// Please see [VideoCaptureOptions] for documentation on the /// configuration options. Future startVideoCapturing(VideoCaptureOptions options) { return startVideoRecording(options.cameraId, maxVideoDuration: options.maxDuration); } /// Stops the video recording and returns the file where it was saved. Future stopVideoRecording(int cameraId) { throw UnimplementedError('stopVideoRecording() is not implemented.'); } /// Pause video recording. Future pauseVideoRecording(int cameraId) { throw UnimplementedError('pauseVideoRecording() is not implemented.'); } /// Resume video recording after pausing. Future resumeVideoRecording(int cameraId) { throw UnimplementedError('resumeVideoRecording() is not implemented.'); } /// A new streamed frame is available. /// /// Listening to this stream will start streaming, and canceling will stop. /// Pausing will throw a [CameraException], as pausing the stream would cause /// very high memory usage; to temporarily stop receiving frames, cancel, then /// listen again later. /// /// // TODO(bmparr): Add options to control streaming settings (e.g., // resolution and FPS). Stream onStreamedFrameAvailable(int cameraId, {CameraImageStreamOptions? options}) { throw UnimplementedError('onStreamedFrameAvailable() is not implemented.'); } /// Sets the flash mode for the selected camera. /// On Web [FlashMode.auto] corresponds to [FlashMode.always]. Future setFlashMode(int cameraId, FlashMode mode) { throw UnimplementedError('setFlashMode() is not implemented.'); } /// Sets the exposure mode for taking pictures. Future setExposureMode(int cameraId, ExposureMode mode) { throw UnimplementedError('setExposureMode() is not implemented.'); } /// Sets the exposure point for automatically determining the exposure values. /// /// Supplying `null` for the [point] argument will result in resetting to the /// original exposure point value. Future setExposurePoint(int cameraId, Point? point) { throw UnimplementedError('setExposurePoint() is not implemented.'); } /// Gets the minimum supported exposure offset for the selected camera in EV units. Future getMinExposureOffset(int cameraId) { throw UnimplementedError('getMinExposureOffset() is not implemented.'); } /// Gets the maximum supported exposure offset for the selected camera in EV units. Future getMaxExposureOffset(int cameraId) { throw UnimplementedError('getMaxExposureOffset() is not implemented.'); } /// Gets the supported step size for exposure offset for the selected camera in EV units. /// /// Returns 0 when the camera supports using a free value without stepping. Future getExposureOffsetStepSize(int cameraId) { throw UnimplementedError('getMinExposureOffset() is not implemented.'); } /// Sets the exposure offset for the selected camera. /// /// The supplied [offset] value should be in EV units. 1 EV unit represents a /// doubling in brightness. It should be between the minimum and maximum offsets /// obtained through `getMinExposureOffset` and `getMaxExposureOffset` respectively. /// Throws a `CameraException` when an illegal offset is supplied. /// /// When the supplied [offset] value does not align with the step size obtained /// through `getExposureStepSize`, it will automatically be rounded to the nearest step. /// /// Returns the (rounded) offset value that was set. Future setExposureOffset(int cameraId, double offset) { throw UnimplementedError('setExposureOffset() is not implemented.'); } /// Sets the focus mode for taking pictures. Future setFocusMode(int cameraId, FocusMode mode) { throw UnimplementedError('setFocusMode() is not implemented.'); } /// Sets the focus point for automatically determining the focus values. /// /// Supplying `null` for the [point] argument will result in resetting to the /// original focus point value. Future setFocusPoint(int cameraId, Point? point) { throw UnimplementedError('setFocusPoint() is not implemented.'); } /// Gets the maximum supported zoom level for the selected camera. Future getMaxZoomLevel(int cameraId) { throw UnimplementedError('getMaxZoomLevel() is not implemented.'); } /// Gets the minimum supported zoom level for the selected camera. Future getMinZoomLevel(int cameraId) { throw UnimplementedError('getMinZoomLevel() is not implemented.'); } /// Set the zoom level for the selected camera. /// /// The supplied [zoom] value should be between the minimum and the maximum supported /// zoom level returned by `getMinZoomLevel` and `getMaxZoomLevel`. Throws a `CameraException` /// when an illegal zoom level is supplied. Future setZoomLevel(int cameraId, double zoom) { throw UnimplementedError('setZoomLevel() is not implemented.'); } /// Pause the active preview on the current frame for the selected camera. Future pausePreview(int cameraId) { throw UnimplementedError('pausePreview() is not implemented.'); } /// Resume the paused preview for the selected camera. Future resumePreview(int cameraId) { throw UnimplementedError('pausePreview() is not implemented.'); } /// Sets the active camera while recording. Future setDescriptionWhileRecording(CameraDescription description) { throw UnimplementedError( 'setDescriptionWhileRecording() is not implemented.'); } /// Returns a widget showing a live camera preview. Widget buildPreview(int cameraId) { throw UnimplementedError('buildView() has not been implemented.'); } /// Releases the resources of this camera. Future dispose(int cameraId) { throw UnimplementedError('dispose() is not implemented.'); } } ================================================ FILE: packages/camera/camera_platform_interface/lib/src/types/camera_description.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; /// The direction the camera is facing. enum CameraLensDirection { /// Front facing camera (a user looking at the screen is seen by the camera). front, /// Back facing camera (a user looking at the screen is not seen by the camera). back, /// External camera which may not be mounted to the device. external, } /// Properties of a camera device. @immutable class CameraDescription { /// Creates a new camera description with the given properties. const CameraDescription({ required this.name, required this.lensDirection, required this.sensorOrientation, }); /// The name of the camera device. final String name; /// The direction the camera is facing. final CameraLensDirection lensDirection; /// Clockwise angle through which the output image needs to be rotated to be upright on the device screen in its native orientation. /// /// **Range of valid values:** /// 0, 90, 180, 270 /// /// On Android, also defines the direction of rolling shutter readout, which /// is from top to bottom in the sensor's coordinate system. final int sensorOrientation; @override bool operator ==(Object other) => identical(this, other) || other is CameraDescription && runtimeType == other.runtimeType && name == other.name && lensDirection == other.lensDirection; @override int get hashCode => Object.hash(name, lensDirection); @override String toString() { return '${objectRuntimeType(this, 'CameraDescription')}(' '$name, $lensDirection, $sensorOrientation)'; } } ================================================ FILE: packages/camera/camera_platform_interface/lib/src/types/camera_exception.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// This is thrown when the plugin reports an error. class CameraException implements Exception { /// Creates a new camera exception with the given error code and description. CameraException(this.code, this.description); /// Error code. // TODO(bparrishMines): Document possible error codes. // https://github.com/flutter/flutter/issues/69298 String code; /// Textual description of the error. String? description; @override String toString() => 'CameraException($code, $description)'; } ================================================ FILE: packages/camera/camera_platform_interface/lib/src/types/camera_image_data.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import '../../camera_platform_interface.dart'; /// Options for configuring camera streaming. /// /// Currently unused; this exists for future-proofing of the platform interface /// API. @immutable class CameraImageStreamOptions {} /// A single color plane of image data. /// /// The number and meaning of the planes in an image are determined by its /// format. @immutable class CameraImagePlane { /// Creates a new instance with the given bytes and optional metadata. const CameraImagePlane({ required this.bytes, required this.bytesPerRow, this.bytesPerPixel, this.height, this.width, }); /// Bytes representing this plane. final Uint8List bytes; /// The row stride for this color plane, in bytes. final int bytesPerRow; /// The distance between adjacent pixel samples in bytes, when available. final int? bytesPerPixel; /// Height of the pixel buffer, when available. final int? height; /// Width of the pixel buffer, when available. final int? width; } /// Describes how pixels are represented in an image. @immutable class CameraImageFormat { /// Create a new format with the given cross-platform group and raw underyling /// platform identifier. const CameraImageFormat(this.group, {required this.raw}); /// Describes the format group the raw image format falls into. final ImageFormatGroup group; /// Raw version of the format from the underlying platform. /// /// On Android, this should be an `int` from class /// `android.graphics.ImageFormat`. See /// https://developer.android.com/reference/android/graphics/ImageFormat /// /// On iOS, this should be a `FourCharCode` constant from Pixel Format /// Identifiers. See /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers final dynamic raw; } /// A single complete image buffer from the platform camera. /// /// This class allows for direct application access to the pixel data of an /// Image through one or more [Uint8List]. Each buffer is encapsulated in a /// [CameraImagePlane] that describes the layout of the pixel data in that /// plane. [CameraImageData] is not directly usable as a UI resource. /// /// Although not all image formats are planar on all platforms, this class /// treats 1-dimensional images as single planar images. @immutable class CameraImageData { /// Creates a new instance with the given format, planes, and metadata. const CameraImageData({ required this.format, required this.planes, required this.height, required this.width, this.lensAperture, this.sensorExposureTime, this.sensorSensitivity, }); /// Format of the image provided. /// /// Determines the number of planes needed to represent the image, and /// the general layout of the pixel data in each [Uint8List]. final CameraImageFormat format; /// Height of the image in pixels. /// /// For formats where some color channels are subsampled, this is the height /// of the largest-resolution plane. final int height; /// Width of the image in pixels. /// /// For formats where some color channels are subsampled, this is the width /// of the largest-resolution plane. final int width; /// The pixels planes for this image. /// /// The number of planes is determined by the format of the image. final List planes; /// The aperture settings for this image. /// /// Represented as an f-stop value. final double? lensAperture; /// The sensor exposure time for this image in nanoseconds. final int? sensorExposureTime; /// The sensor sensitivity in standard ISO arithmetic units. final double? sensorSensitivity; } ================================================ FILE: packages/camera/camera_platform_interface/lib/src/types/exposure_mode.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// The possible exposure modes that can be set for a camera. enum ExposureMode { /// Automatically determine exposure settings. auto, /// Lock the currently determined exposure settings. locked, } /// Returns the exposure mode as a String. String serializeExposureMode(ExposureMode exposureMode) { switch (exposureMode) { case ExposureMode.locked: return 'locked'; case ExposureMode.auto: return 'auto'; } } /// Returns the exposure mode for a given String. ExposureMode deserializeExposureMode(String str) { switch (str) { case 'locked': return ExposureMode.locked; case 'auto': return ExposureMode.auto; default: throw ArgumentError('"$str" is not a valid ExposureMode value'); } } ================================================ FILE: packages/camera/camera_platform_interface/lib/src/types/flash_mode.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// The possible flash modes that can be set for a camera enum FlashMode { /// Do not use the flash when taking a picture. off, /// Let the device decide whether to flash the camera when taking a picture. auto, /// Always use the flash when taking a picture. always, /// Turns on the flash light and keeps it on until switched off. torch, } ================================================ FILE: packages/camera/camera_platform_interface/lib/src/types/focus_mode.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// The possible focus modes that can be set for a camera. enum FocusMode { /// Automatically determine focus settings. auto, /// Lock the currently determined focus settings. locked, } /// Returns the focus mode as a String. String serializeFocusMode(FocusMode focusMode) { switch (focusMode) { case FocusMode.locked: return 'locked'; case FocusMode.auto: return 'auto'; } } /// Returns the focus mode for a given String. FocusMode deserializeFocusMode(String str) { switch (str) { case 'locked': return FocusMode.locked; case 'auto': return FocusMode.auto; default: throw ArgumentError('"$str" is not a valid FocusMode value'); } } ================================================ FILE: packages/camera/camera_platform_interface/lib/src/types/image_format_group.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Group of image formats that are comparable across Android and iOS platforms. enum ImageFormatGroup { /// The image format does not fit into any specific group. unknown, /// Multi-plane YUV 420 format. /// /// This format is a generic YCbCr format, capable of describing any 4:2:0 /// chroma-subsampled planar or semiplanar buffer (but not fully interleaved), /// with 8 bits per color sample. /// /// On Android, this is `android.graphics.ImageFormat.YUV_420_888`. See /// https://developer.android.com/reference/android/graphics/ImageFormat.html#YUV_420_888 /// /// On iOS, this is `kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange`. See /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_420ypcbcr8biplanarvideorange?language=objc yuv420, /// 32-bit BGRA. /// /// On iOS, this is `kCVPixelFormatType_32BGRA`. See /// https://developer.apple.com/documentation/corevideo/1563591-pixel_format_identifiers/kcvpixelformattype_32bgra?language=objc bgra8888, /// 32-big RGB image encoded into JPEG bytes. /// /// On Android, this is `android.graphics.ImageFormat.JPEG`. See /// https://developer.android.com/reference/android/graphics/ImageFormat#JPEG jpeg, } /// Extension on [ImageFormatGroup] to stringify the enum extension ImageFormatGroupName on ImageFormatGroup { /// returns a String value for [ImageFormatGroup] /// returns 'unknown' if platform is not supported /// or if [ImageFormatGroup] is not supported for the platform String name() { switch (this) { case ImageFormatGroup.bgra8888: return 'bgra8888'; case ImageFormatGroup.yuv420: return 'yuv420'; case ImageFormatGroup.jpeg: return 'jpeg'; case ImageFormatGroup.unknown: return 'unknown'; } } } ================================================ FILE: packages/camera/camera_platform_interface/lib/src/types/resolution_preset.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Affect the quality of video recording and image capture: /// /// If a preset is not available on the camera being used a preset of lower quality will be selected automatically. enum ResolutionPreset { /// 352x288 on iOS, 240p (320x240) on Android and Web low, /// 480p (640x480 on iOS, 720x480 on Android and Web) medium, /// 720p (1280x720) high, /// 1080p (1920x1080) veryHigh, /// 2160p (3840x2160 on Android and iOS, 4096x2160 on Web) ultraHigh, /// The highest resolution available. max, } ================================================ FILE: packages/camera/camera_platform_interface/lib/src/types/types.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'camera_description.dart'; export 'camera_exception.dart'; export 'camera_image_data.dart'; export 'exposure_mode.dart'; export 'flash_mode.dart'; export 'focus_mode.dart'; export 'image_format_group.dart'; export 'resolution_preset.dart'; export 'video_capture_options.dart'; ================================================ FILE: packages/camera/camera_platform_interface/lib/src/types/video_capture_options.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'camera_image_data.dart'; /// Options wrapper for [CameraPlatform.startVideoCapturing] parameters. @immutable class VideoCaptureOptions { /// Constructs a new instance. const VideoCaptureOptions( this.cameraId, { this.maxDuration, this.streamCallback, this.streamOptions, }) : assert( streamOptions == null || streamCallback != null, 'Must specify streamCallback if providing streamOptions.', ); /// The ID of the camera to use for capturing. final int cameraId; /// The maximum time to perform capturing for. /// /// By default there is no maximum on the capture time. final Duration? maxDuration; /// An optional callback to enable streaming. /// /// If set, then each image captured by the camera will be /// passed to this callback. final Function(CameraImageData image)? streamCallback; /// Configuration options for streaming. /// /// Should only be set if a streamCallback is also present. final CameraImageStreamOptions? streamOptions; @override bool operator ==(Object other) => identical(this, other) || other is VideoCaptureOptions && runtimeType == other.runtimeType && cameraId == other.cameraId && maxDuration == other.maxDuration && streamCallback == other.streamCallback && streamOptions == other.streamOptions; @override int get hashCode => Object.hash(cameraId, maxDuration, streamCallback, streamOptions); } ================================================ FILE: packages/camera/camera_platform_interface/lib/src/utils/utils.dart ================================================ // Copyright 2013 The Flutter 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 '../../camera_platform_interface.dart'; /// Parses a string into a corresponding CameraLensDirection. CameraLensDirection parseCameraLensDirection(String string) { switch (string) { case 'front': return CameraLensDirection.front; case 'back': return CameraLensDirection.back; case 'external': return CameraLensDirection.external; } throw ArgumentError('Unknown CameraLensDirection value'); } /// Returns the device orientation as a String. String serializeDeviceOrientation(DeviceOrientation orientation) { switch (orientation) { case DeviceOrientation.portraitUp: return 'portraitUp'; case DeviceOrientation.portraitDown: return 'portraitDown'; case DeviceOrientation.landscapeRight: return 'landscapeRight'; case DeviceOrientation.landscapeLeft: return 'landscapeLeft'; } } /// Returns the device orientation for a given String. DeviceOrientation deserializeDeviceOrientation(String str) { switch (str) { case 'portraitUp': return DeviceOrientation.portraitUp; case 'portraitDown': return DeviceOrientation.portraitDown; case 'landscapeRight': return DeviceOrientation.landscapeRight; case 'landscapeLeft': return DeviceOrientation.landscapeLeft; default: throw ArgumentError('"$str" is not a valid DeviceOrientation value'); } } ================================================ FILE: packages/camera/camera_platform_interface/pubspec.yaml ================================================ name: camera_platform_interface description: A common platform interface for the camera plugin. repository: https://github.com/flutter/plugins/tree/main/packages/camera/camera_platform_interface 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.4.0 environment: sdk: '>=2.12.0 <3.0.0' flutter: ">=3.0.0" dependencies: cross_file: ^0.3.1 flutter: sdk: flutter plugin_platform_interface: ^2.1.0 stream_transform: ^2.0.0 dev_dependencies: async: ^2.5.0 flutter_test: sdk: flutter ================================================ FILE: packages/camera/camera_platform_interface/test/camera_platform_interface_test.dart ================================================ // Copyright 2013 The Flutter 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_platform_interface/camera_platform_interface.dart'; import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$CameraPlatform', () { test('$MethodChannelCamera is the default instance', () { expect(CameraPlatform.instance, isA()); }); test('Cannot be implemented with `implements`', () { expect(() { CameraPlatform.instance = ImplementsCameraPlatform(); // In versions of `package:plugin_platform_interface` prior to fixing // https://github.com/flutter/flutter/issues/109339, an attempt to // implement a platform interface using `implements` would sometimes // throw a `NoSuchMethodError` and other times throw an // `AssertionError`. After the issue is fixed, an `AssertionError` will // always be thrown. For the purpose of this test, we don't really care // what exception is thrown, so just allow any exception. }, throwsA(anything)); }); test('Can be extended', () { CameraPlatform.instance = ExtendsCameraPlatform(); }); test( 'Default implementation of availableCameras() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.availableCameras(), throwsUnimplementedError, ); }); test( 'Default implementation of onCameraInitialized() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.onCameraInitialized(1), throwsUnimplementedError, ); }); test( 'Default implementation of onResolutionChanged() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.onCameraResolutionChanged(1), throwsUnimplementedError, ); }); test( 'Default implementation of onCameraClosing() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.onCameraClosing(1), throwsUnimplementedError, ); }); test( 'Default implementation of onCameraError() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.onCameraError(1), throwsUnimplementedError, ); }); test( 'Default implementation of onDeviceOrientationChanged() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.onDeviceOrientationChanged(), throwsUnimplementedError, ); }); test( 'Default implementation of lockCaptureOrientation() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.lockCaptureOrientation( 1, DeviceOrientation.portraitUp), throwsUnimplementedError, ); }); test( 'Default implementation of unlockCaptureOrientation() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.unlockCaptureOrientation(1), throwsUnimplementedError, ); }); test('Default implementation of dispose() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.dispose(1), throwsUnimplementedError, ); }); test( 'Default implementation of createCamera() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.createCamera( const CameraDescription( name: 'back', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ), throwsUnimplementedError, ); }); test( 'Default implementation of initializeCamera() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.initializeCamera(1), throwsUnimplementedError, ); }); test( 'Default implementation of pauseVideoRecording() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.pauseVideoRecording(1), throwsUnimplementedError, ); }); test( 'Default implementation of prepareForVideoRecording() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.prepareForVideoRecording(), throwsUnimplementedError, ); }); test( 'Default implementation of resumeVideoRecording() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.resumeVideoRecording(1), throwsUnimplementedError, ); }); test( 'Default implementation of setFlashMode() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.setFlashMode(1, FlashMode.auto), throwsUnimplementedError, ); }); test( 'Default implementation of setExposureMode() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.setExposureMode(1, ExposureMode.auto), throwsUnimplementedError, ); }); test( 'Default implementation of setExposurePoint() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.setExposurePoint(1, null), throwsUnimplementedError, ); }); test( 'Default implementation of getMinExposureOffset() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.getMinExposureOffset(1), throwsUnimplementedError, ); }); test( 'Default implementation of getMaxExposureOffset() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.getMaxExposureOffset(1), throwsUnimplementedError, ); }); test( 'Default implementation of getExposureOffsetStepSize() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.getExposureOffsetStepSize(1), throwsUnimplementedError, ); }); test( 'Default implementation of setExposureOffset() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.setExposureOffset(1, 2.0), throwsUnimplementedError, ); }); test( 'Default implementation of setFocusMode() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.setFocusMode(1, FocusMode.auto), throwsUnimplementedError, ); }); test( 'Default implementation of setFocusPoint() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.setFocusPoint(1, null), throwsUnimplementedError, ); }); test( 'Default implementation of startVideoRecording() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.startVideoRecording(1), throwsUnimplementedError, ); }); test( 'Default implementation of stopVideoRecording() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.stopVideoRecording(1), throwsUnimplementedError, ); }); test( 'Default implementation of takePicture() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.takePicture(1), throwsUnimplementedError, ); }); test( 'Default implementation of getMaxZoomLevel() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.getMaxZoomLevel(1), throwsUnimplementedError, ); }); test( 'Default implementation of getMinZoomLevel() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.getMinZoomLevel(1), throwsUnimplementedError, ); }); test( 'Default implementation of setZoomLevel() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.setZoomLevel(1, 1.0), throwsUnimplementedError, ); }); test( 'Default implementation of pausePreview() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.pausePreview(1), throwsUnimplementedError, ); }); test( 'Default implementation of resumePreview() should throw unimplemented error', () { // Arrange final ExtendsCameraPlatform cameraPlatform = ExtendsCameraPlatform(); // Act & Assert expect( () => cameraPlatform.resumePreview(1), throwsUnimplementedError, ); }); }); group('exports', () { test('CameraDescription is exported', () { const CameraDescription( name: 'abc-123', sensorOrientation: 1, lensDirection: CameraLensDirection.external); }); test('CameraException is exported', () { CameraException('1', 'error'); }); test('CameraImageData is exported', () { const CameraImageData( width: 1, height: 1, format: CameraImageFormat(ImageFormatGroup.bgra8888, raw: 1), planes: [], ); }); test('ExposureMode is exported', () { // ignore: unnecessary_statements ExposureMode.auto; }); test('FlashMode is exported', () { // ignore: unnecessary_statements FlashMode.auto; }); test('FocusMode is exported', () { // ignore: unnecessary_statements FocusMode.auto; }); test('ResolutionPreset is exported', () { // ignore: unnecessary_statements ResolutionPreset.high; }); test('VideoCaptureOptions is exported', () { const VideoCaptureOptions(123); }); }); } class ImplementsCameraPlatform implements CameraPlatform { @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } class ExtendsCameraPlatform extends CameraPlatform {} ================================================ FILE: packages/camera/camera_platform_interface/test/events/camera_event_test.dart ================================================ // Copyright 2013 The Flutter 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_platform_interface/camera_platform_interface.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('CameraInitializedEvent tests', () { test('Constructor should initialize all properties', () { const CameraInitializedEvent event = CameraInitializedEvent( 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); expect(event.cameraId, 1); expect(event.previewWidth, 1024); expect(event.previewHeight, 640); expect(event.exposureMode, ExposureMode.auto); expect(event.focusMode, FocusMode.auto); expect(event.exposurePointSupported, true); expect(event.focusPointSupported, true); }); test('fromJson should initialize all properties', () { final CameraInitializedEvent event = CameraInitializedEvent.fromJson(const { 'cameraId': 1, 'previewWidth': 1024.0, 'previewHeight': 640.0, 'exposureMode': 'auto', 'exposurePointSupported': true, 'focusMode': 'auto', 'focusPointSupported': true }); expect(event.cameraId, 1); expect(event.previewWidth, 1024); expect(event.previewHeight, 640); expect(event.exposureMode, ExposureMode.auto); expect(event.exposurePointSupported, true); expect(event.focusMode, FocusMode.auto); expect(event.focusPointSupported, true); }); test('toJson should return a map with all fields', () { const CameraInitializedEvent event = CameraInitializedEvent( 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); final Map jsonMap = event.toJson(); expect(jsonMap.length, 7); expect(jsonMap['cameraId'], 1); expect(jsonMap['previewWidth'], 1024); expect(jsonMap['previewHeight'], 640); expect(jsonMap['exposureMode'], 'auto'); expect(jsonMap['exposurePointSupported'], true); expect(jsonMap['focusMode'], 'auto'); expect(jsonMap['focusPointSupported'], true); }); test('equals should return true if objects are the same', () { const CameraInitializedEvent firstEvent = CameraInitializedEvent( 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); const CameraInitializedEvent secondEvent = CameraInitializedEvent( 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); expect(firstEvent == secondEvent, true); }); test('equals should return false if cameraId is different', () { const CameraInitializedEvent firstEvent = CameraInitializedEvent( 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); const CameraInitializedEvent secondEvent = CameraInitializedEvent( 2, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if previewWidth is different', () { const CameraInitializedEvent firstEvent = CameraInitializedEvent( 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); const CameraInitializedEvent secondEvent = CameraInitializedEvent( 1, 2048, 640, ExposureMode.auto, true, FocusMode.auto, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if previewHeight is different', () { const CameraInitializedEvent firstEvent = CameraInitializedEvent( 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); const CameraInitializedEvent secondEvent = CameraInitializedEvent( 1, 1024, 980, ExposureMode.auto, true, FocusMode.auto, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if exposureMode is different', () { const CameraInitializedEvent firstEvent = CameraInitializedEvent( 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); const CameraInitializedEvent secondEvent = CameraInitializedEvent( 1, 1024, 640, ExposureMode.locked, true, FocusMode.auto, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if exposurePointSupported is different', () { const CameraInitializedEvent firstEvent = CameraInitializedEvent( 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); const CameraInitializedEvent secondEvent = CameraInitializedEvent( 1, 1024, 640, ExposureMode.auto, false, FocusMode.auto, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if focusMode is different', () { const CameraInitializedEvent firstEvent = CameraInitializedEvent( 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); const CameraInitializedEvent secondEvent = CameraInitializedEvent( 1, 1024, 640, ExposureMode.auto, true, FocusMode.locked, true); expect(firstEvent == secondEvent, false); }); test('equals should return false if focusPointSupported is different', () { const CameraInitializedEvent firstEvent = CameraInitializedEvent( 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); const CameraInitializedEvent secondEvent = CameraInitializedEvent( 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, false); expect(firstEvent == secondEvent, false); }); test('hashCode should match hashCode of all properties', () { const CameraInitializedEvent event = CameraInitializedEvent( 1, 1024, 640, ExposureMode.auto, true, FocusMode.auto, true); final int expectedHashCode = Object.hash( event.cameraId.hashCode, event.previewWidth, event.previewHeight, event.exposureMode, event.exposurePointSupported, event.focusMode, event.focusPointSupported); expect(event.hashCode, expectedHashCode); }); }); group('CameraResolutionChangesEvent tests', () { test('Constructor should initialize all properties', () { const CameraResolutionChangedEvent event = CameraResolutionChangedEvent(1, 1024, 640); expect(event.cameraId, 1); expect(event.captureWidth, 1024); expect(event.captureHeight, 640); }); test('fromJson should initialize all properties', () { final CameraResolutionChangedEvent event = CameraResolutionChangedEvent.fromJson(const { 'cameraId': 1, 'captureWidth': 1024.0, 'captureHeight': 640.0, }); expect(event.cameraId, 1); expect(event.captureWidth, 1024); expect(event.captureHeight, 640); }); test('toJson should return a map with all fields', () { const CameraResolutionChangedEvent event = CameraResolutionChangedEvent(1, 1024, 640); final Map jsonMap = event.toJson(); expect(jsonMap.length, 3); expect(jsonMap['cameraId'], 1); expect(jsonMap['captureWidth'], 1024); expect(jsonMap['captureHeight'], 640); }); test('equals should return true if objects are the same', () { const CameraResolutionChangedEvent firstEvent = CameraResolutionChangedEvent(1, 1024, 640); const CameraResolutionChangedEvent secondEvent = CameraResolutionChangedEvent(1, 1024, 640); expect(firstEvent == secondEvent, true); }); test('equals should return false if cameraId is different', () { const CameraResolutionChangedEvent firstEvent = CameraResolutionChangedEvent(1, 1024, 640); const CameraResolutionChangedEvent secondEvent = CameraResolutionChangedEvent(2, 1024, 640); expect(firstEvent == secondEvent, false); }); test('equals should return false if captureWidth is different', () { const CameraResolutionChangedEvent firstEvent = CameraResolutionChangedEvent(1, 1024, 640); const CameraResolutionChangedEvent secondEvent = CameraResolutionChangedEvent(1, 2048, 640); expect(firstEvent == secondEvent, false); }); test('equals should return false if captureHeight is different', () { const CameraResolutionChangedEvent firstEvent = CameraResolutionChangedEvent(1, 1024, 640); const CameraResolutionChangedEvent secondEvent = CameraResolutionChangedEvent(1, 1024, 980); expect(firstEvent == secondEvent, false); }); test('hashCode should match hashCode of all properties', () { const CameraResolutionChangedEvent event = CameraResolutionChangedEvent(1, 1024, 640); final int expectedHashCode = Object.hash( event.cameraId.hashCode, event.captureWidth, event.captureHeight, ); expect(event.hashCode, expectedHashCode); }); }); group('CameraClosingEvent tests', () { test('Constructor should initialize all properties', () { const CameraClosingEvent event = CameraClosingEvent(1); expect(event.cameraId, 1); }); test('fromJson should initialize all properties', () { final CameraClosingEvent event = CameraClosingEvent.fromJson(const { 'cameraId': 1, }); expect(event.cameraId, 1); }); test('toJson should return a map with all fields', () { const CameraClosingEvent event = CameraClosingEvent(1); final Map jsonMap = event.toJson(); expect(jsonMap.length, 1); expect(jsonMap['cameraId'], 1); }); test('equals should return true if objects are the same', () { const CameraClosingEvent firstEvent = CameraClosingEvent(1); const CameraClosingEvent secondEvent = CameraClosingEvent(1); expect(firstEvent == secondEvent, true); }); test('equals should return false if cameraId is different', () { const CameraClosingEvent firstEvent = CameraClosingEvent(1); const CameraClosingEvent secondEvent = CameraClosingEvent(2); expect(firstEvent == secondEvent, false); }); test('hashCode should match hashCode of all properties', () { const CameraClosingEvent event = CameraClosingEvent(1); final int expectedHashCode = event.cameraId.hashCode; expect(event.hashCode, expectedHashCode); }); }); group('CameraErrorEvent tests', () { test('Constructor should initialize all properties', () { const CameraErrorEvent event = CameraErrorEvent(1, 'Error'); expect(event.cameraId, 1); expect(event.description, 'Error'); }); test('fromJson should initialize all properties', () { final CameraErrorEvent event = CameraErrorEvent.fromJson( const {'cameraId': 1, 'description': 'Error'}); expect(event.cameraId, 1); expect(event.description, 'Error'); }); test('toJson should return a map with all fields', () { const CameraErrorEvent event = CameraErrorEvent(1, 'Error'); final Map jsonMap = event.toJson(); expect(jsonMap.length, 2); expect(jsonMap['cameraId'], 1); expect(jsonMap['description'], 'Error'); }); test('equals should return true if objects are the same', () { const CameraErrorEvent firstEvent = CameraErrorEvent(1, 'Error'); const CameraErrorEvent secondEvent = CameraErrorEvent(1, 'Error'); expect(firstEvent == secondEvent, true); }); test('equals should return false if cameraId is different', () { const CameraErrorEvent firstEvent = CameraErrorEvent(1, 'Error'); const CameraErrorEvent secondEvent = CameraErrorEvent(2, 'Error'); expect(firstEvent == secondEvent, false); }); test('equals should return false if description is different', () { const CameraErrorEvent firstEvent = CameraErrorEvent(1, 'Error'); const CameraErrorEvent secondEvent = CameraErrorEvent(1, 'Ooops'); expect(firstEvent == secondEvent, false); }); test('hashCode should match hashCode of all properties', () { const CameraErrorEvent event = CameraErrorEvent(1, 'Error'); final int expectedHashCode = Object.hash(event.cameraId.hashCode, event.description); expect(event.hashCode, expectedHashCode); }); }); } ================================================ FILE: packages/camera/camera_platform_interface/test/events/device_event_test.dart ================================================ // Copyright 2013 The Flutter 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_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('DeviceOrientationChangedEvent tests', () { test('Constructor should initialize all properties', () { const DeviceOrientationChangedEvent event = DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); expect(event.orientation, DeviceOrientation.portraitUp); }); test('fromJson should initialize all properties', () { final DeviceOrientationChangedEvent event = DeviceOrientationChangedEvent.fromJson(const { 'orientation': 'portraitUp', }); expect(event.orientation, DeviceOrientation.portraitUp); }); test('toJson should return a map with all fields', () { const DeviceOrientationChangedEvent event = DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); final Map jsonMap = event.toJson(); expect(jsonMap.length, 1); expect(jsonMap['orientation'], 'portraitUp'); }); test('equals should return true if objects are the same', () { const DeviceOrientationChangedEvent firstEvent = DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); const DeviceOrientationChangedEvent secondEvent = DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); expect(firstEvent == secondEvent, true); }); test('equals should return false if orientation is different', () { const DeviceOrientationChangedEvent firstEvent = DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); const DeviceOrientationChangedEvent secondEvent = DeviceOrientationChangedEvent(DeviceOrientation.landscapeLeft); expect(firstEvent == secondEvent, false); }); test('hashCode should match hashCode of all properties', () { const DeviceOrientationChangedEvent event = DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); final int expectedHashCode = event.orientation.hashCode; expect(event.hashCode, expectedHashCode); }); }); } ================================================ FILE: packages/camera/camera_platform_interface/test/method_channel/method_channel_camera_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:math'; import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_platform_interface/src/method_channel/method_channel_camera.dart'; import 'package:camera_platform_interface/src/utils/utils.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import '../utils/method_channel_mock.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$MethodChannelCamera', () { group('Creation, Initialization & Disposal Tests', () { test('Should send creation data and receive back a camera id', () async { // Arrange final MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { 'create': { 'cameraId': 1, 'imageFormatGroup': 'unknown', } }); final MethodChannelCamera camera = MethodChannelCamera(); // Act final int cameraId = await camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0), ResolutionPreset.high, ); // Assert expect(cameraMockChannel.log, [ isMethodCall( 'create', arguments: { 'cameraName': 'Test', 'resolutionPreset': 'high', 'enableAudio': false }, ), ]); expect(cameraId, 1); }); test( 'Should throw CameraException when create throws a PlatformException', () { // Arrange MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { 'create': PlatformException( code: 'TESTING_ERROR_CODE', message: 'Mock error message used during testing.', ) }); final MethodChannelCamera camera = MethodChannelCamera(); // Act expect( () => camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ), throwsA( isA() .having( (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') .having((CameraException e) => e.description, 'description', 'Mock error message used during testing.'), ), ); }); test( 'Should throw CameraException when create throws a PlatformException', () { // Arrange MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { 'create': PlatformException( code: 'TESTING_ERROR_CODE', message: 'Mock error message used during testing.', ) }); final MethodChannelCamera camera = MethodChannelCamera(); // Act expect( () => camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ), throwsA( isA() .having( (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') .having((CameraException e) => e.description, 'description', 'Mock error message used during testing.'), ), ); }); test( 'Should throw CameraException when initialize throws a PlatformException', () { // Arrange MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { 'initialize': PlatformException( code: 'TESTING_ERROR_CODE', message: 'Mock error message used during testing.', ) }, ); final MethodChannelCamera camera = MethodChannelCamera(); // Act expect( () => camera.initializeCamera(0), throwsA( isA() .having((CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') .having( (CameraException e) => e.description, 'description', 'Mock error message used during testing.', ), ), ); }, ); test('Should send initialization data', () async { // Arrange final MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { 'create': { 'cameraId': 1, 'imageFormatGroup': 'unknown', }, 'initialize': null }); final MethodChannelCamera camera = MethodChannelCamera(); final int cameraId = await camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ); // Act final Future initializeFuture = camera.initializeCamera(cameraId); camera.cameraEventStreamController.add(CameraInitializedEvent( cameraId, 1920, 1080, ExposureMode.auto, true, FocusMode.auto, true, )); await initializeFuture; // Assert expect(cameraId, 1); expect(cameraMockChannel.log, [ anything, isMethodCall( 'initialize', arguments: { 'cameraId': 1, 'imageFormatGroup': 'unknown', }, ), ]); }); test('Should send a disposal call on dispose', () async { // Arrange final MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { 'create': {'cameraId': 1}, 'initialize': null, 'dispose': {'cameraId': 1} }); final MethodChannelCamera camera = MethodChannelCamera(); final int cameraId = await camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ); final Future initializeFuture = camera.initializeCamera(cameraId); camera.cameraEventStreamController.add(CameraInitializedEvent( cameraId, 1920, 1080, ExposureMode.auto, true, FocusMode.auto, true, )); await initializeFuture; // Act await camera.dispose(cameraId); // Assert expect(cameraId, 1); expect(cameraMockChannel.log, [ anything, anything, isMethodCall( 'dispose', arguments: {'cameraId': 1}, ), ]); }); }); group('Event Tests', () { late MethodChannelCamera camera; late int cameraId; setUp(() async { MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { 'create': {'cameraId': 1}, 'initialize': null }, ); camera = MethodChannelCamera(); cameraId = await camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ); final Future initializeFuture = camera.initializeCamera(cameraId); camera.cameraEventStreamController.add(CameraInitializedEvent( cameraId, 1920, 1080, ExposureMode.auto, true, FocusMode.auto, true, )); await initializeFuture; }); test('Should receive initialized event', () async { // Act final Stream eventStream = camera.onCameraInitialized(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); // Emit test events final CameraInitializedEvent event = CameraInitializedEvent( cameraId, 3840, 2160, ExposureMode.auto, true, FocusMode.auto, true, ); await camera.handleCameraMethodCall( MethodCall('initialized', event.toJson()), cameraId); // Assert expect(await streamQueue.next, event); // Clean up await streamQueue.cancel(); }); test('Should receive resolution changes', () async { // Act final Stream resolutionStream = camera.onCameraResolutionChanged(cameraId); final StreamQueue streamQueue = StreamQueue(resolutionStream); // Emit test events final CameraResolutionChangedEvent fhdEvent = CameraResolutionChangedEvent(cameraId, 1920, 1080); final CameraResolutionChangedEvent uhdEvent = CameraResolutionChangedEvent(cameraId, 3840, 2160); await camera.handleCameraMethodCall( MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('resolution_changed', fhdEvent.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('resolution_changed', uhdEvent.toJson()), cameraId); // Assert expect(await streamQueue.next, fhdEvent); expect(await streamQueue.next, uhdEvent); expect(await streamQueue.next, fhdEvent); expect(await streamQueue.next, uhdEvent); // Clean up await streamQueue.cancel(); }); test('Should receive camera closing events', () async { // Act final Stream eventStream = camera.onCameraClosing(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); // Emit test events final CameraClosingEvent event = CameraClosingEvent(cameraId); await camera.handleCameraMethodCall( MethodCall('camera_closing', event.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('camera_closing', event.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('camera_closing', event.toJson()), cameraId); // Assert expect(await streamQueue.next, event); expect(await streamQueue.next, event); expect(await streamQueue.next, event); // Clean up await streamQueue.cancel(); }); test('Should receive camera error events', () async { // Act final Stream errorStream = camera.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(errorStream); // Emit test events final CameraErrorEvent event = CameraErrorEvent(cameraId, 'Error Description'); await camera.handleCameraMethodCall( MethodCall('error', event.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('error', event.toJson()), cameraId); await camera.handleCameraMethodCall( MethodCall('error', event.toJson()), cameraId); // Assert expect(await streamQueue.next, event); expect(await streamQueue.next, event); expect(await streamQueue.next, event); // Clean up await streamQueue.cancel(); }); test('Should receive device orientation change events', () async { // Act final Stream eventStream = camera.onDeviceOrientationChanged(); final StreamQueue streamQueue = StreamQueue(eventStream); // Emit test events const DeviceOrientationChangedEvent event = DeviceOrientationChangedEvent(DeviceOrientation.portraitUp); await camera.handleDeviceMethodCall( MethodCall('orientation_changed', event.toJson())); await camera.handleDeviceMethodCall( MethodCall('orientation_changed', event.toJson())); await camera.handleDeviceMethodCall( MethodCall('orientation_changed', event.toJson())); // Assert expect(await streamQueue.next, event); expect(await streamQueue.next, event); expect(await streamQueue.next, event); // Clean up await streamQueue.cancel(); }); }); group('Function Tests', () { late MethodChannelCamera camera; late int cameraId; setUp(() async { MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { 'create': {'cameraId': 1}, 'initialize': null }, ); camera = MethodChannelCamera(); cameraId = await camera.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ); final Future initializeFuture = camera.initializeCamera(cameraId); camera.cameraEventStreamController.add( CameraInitializedEvent( cameraId, 1920, 1080, ExposureMode.auto, true, FocusMode.auto, true, ), ); await initializeFuture; }); test('Should fetch CameraDescription instances for available cameras', () async { // Arrange final List returnData = [ { 'name': 'Test 1', 'lensFacing': 'front', 'sensorOrientation': 1 }, { 'name': 'Test 2', 'lensFacing': 'back', 'sensorOrientation': 2 } ]; final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'availableCameras': returnData}, ); // Act final List cameras = await camera.availableCameras(); // Assert expect(channel.log, [ isMethodCall('availableCameras', arguments: null), ]); 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: typedData['name']! as String, lensDirection: parseCameraLensDirection(typedData['lensFacing']! as String), sensorOrientation: typedData['sensorOrientation']! as int, ); expect(cameras[i], cameraDescription); } }); test( 'Should throw CameraException when availableCameras throws a PlatformException', () { // Arrange MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { 'availableCameras': PlatformException( code: 'TESTING_ERROR_CODE', message: 'Mock error message used during testing.', ) }); // Act expect( camera.availableCameras, throwsA( isA() .having( (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') .having((CameraException e) => e.description, 'description', 'Mock error message used during testing.'), ), ); }); test('Should take a picture and return an XFile instance', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'takePicture': '/test/path.jpg'}); // Act final XFile file = await camera.takePicture(cameraId); // Assert expect(channel.log, [ isMethodCall('takePicture', arguments: { 'cameraId': cameraId, }), ]); expect(file.path, '/test/path.jpg'); }); test('Should prepare for video recording', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'prepareForVideoRecording': null}, ); // Act await camera.prepareForVideoRecording(); // Assert expect(channel.log, [ isMethodCall('prepareForVideoRecording', arguments: null), ]); }); test('Should start recording a video', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'startVideoRecording': null}, ); // Act await camera.startVideoRecording(cameraId); // Assert expect(channel.log, [ isMethodCall('startVideoRecording', arguments: { 'cameraId': cameraId, 'maxVideoDuration': null, 'enableStream': false, }), ]); }); test('Should set description while recording', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'setDescriptionWhileRecording': null}, ); // Act const CameraDescription cameraDescription = CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0); await camera.setDescriptionWhileRecording(cameraDescription); // Assert expect(channel.log, [ isMethodCall('setDescriptionWhileRecording', arguments: { 'cameraName': cameraDescription.name }), ]); }); test('Should pass maxVideoDuration when starting recording a video', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'startVideoRecording': null}, ); // Act await camera.startVideoRecording( cameraId, maxVideoDuration: const Duration(seconds: 10), ); // Assert expect(channel.log, [ isMethodCall('startVideoRecording', arguments: { 'cameraId': cameraId, 'maxVideoDuration': 10000, 'enableStream': false, }), ]); }); test('Should stop a video recording and return the file', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'stopVideoRecording': '/test/path.mp4'}, ); // Act final XFile file = await camera.stopVideoRecording(cameraId); // Assert expect(channel.log, [ isMethodCall('stopVideoRecording', arguments: { 'cameraId': cameraId, }), ]); expect(file.path, '/test/path.mp4'); }); test('Should pause a video recording', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'pauseVideoRecording': null}, ); // Act await camera.pauseVideoRecording(cameraId); // Assert expect(channel.log, [ isMethodCall('pauseVideoRecording', arguments: { 'cameraId': cameraId, }), ]); }); test('Should resume a video recording', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'resumeVideoRecording': null}, ); // Act await camera.resumeVideoRecording(cameraId); // Assert expect(channel.log, [ isMethodCall('resumeVideoRecording', arguments: { 'cameraId': cameraId, }), ]); }); test('Should set the flash mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'setFlashMode': null}, ); // Act await camera.setFlashMode(cameraId, FlashMode.torch); await camera.setFlashMode(cameraId, FlashMode.always); await camera.setFlashMode(cameraId, FlashMode.auto); await camera.setFlashMode(cameraId, FlashMode.off); // Assert expect(channel.log, [ isMethodCall('setFlashMode', arguments: { 'cameraId': cameraId, 'mode': 'torch' }), isMethodCall('setFlashMode', arguments: { 'cameraId': cameraId, 'mode': 'always' }), isMethodCall('setFlashMode', arguments: { 'cameraId': cameraId, 'mode': 'auto' }), isMethodCall('setFlashMode', arguments: { 'cameraId': cameraId, 'mode': 'off' }), ]); }); test('Should set the exposure mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'setExposureMode': null}, ); // Act await camera.setExposureMode(cameraId, ExposureMode.auto); await camera.setExposureMode(cameraId, ExposureMode.locked); // Assert expect(channel.log, [ isMethodCall('setExposureMode', arguments: { 'cameraId': cameraId, 'mode': 'auto' }), isMethodCall('setExposureMode', arguments: { 'cameraId': cameraId, 'mode': 'locked' }), ]); }); test('Should set the exposure point', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'setExposurePoint': null}, ); // Act await camera.setExposurePoint(cameraId, const Point(0.5, 0.5)); await camera.setExposurePoint(cameraId, null); // Assert expect(channel.log, [ isMethodCall('setExposurePoint', arguments: { 'cameraId': cameraId, 'x': 0.5, 'y': 0.5, 'reset': false }), isMethodCall('setExposurePoint', arguments: { 'cameraId': cameraId, 'x': null, 'y': null, 'reset': true }), ]); }); test('Should get the min exposure offset', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'getMinExposureOffset': 2.0}, ); // Act final double minExposureOffset = await camera.getMinExposureOffset(cameraId); // Assert expect(minExposureOffset, 2.0); expect(channel.log, [ isMethodCall('getMinExposureOffset', arguments: { 'cameraId': cameraId, }), ]); }); test('Should get the max exposure offset', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'getMaxExposureOffset': 2.0}, ); // Act final double maxExposureOffset = await camera.getMaxExposureOffset(cameraId); // Assert expect(maxExposureOffset, 2.0); expect(channel.log, [ isMethodCall('getMaxExposureOffset', arguments: { 'cameraId': cameraId, }), ]); }); test('Should get the exposure offset step size', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'getExposureOffsetStepSize': 0.25}, ); // Act final double stepSize = await camera.getExposureOffsetStepSize(cameraId); // Assert expect(stepSize, 0.25); expect(channel.log, [ isMethodCall('getExposureOffsetStepSize', arguments: { 'cameraId': cameraId, }), ]); }); test('Should set the exposure offset', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'setExposureOffset': 0.6}, ); // Act final double actualOffset = await camera.setExposureOffset(cameraId, 0.5); // Assert expect(actualOffset, 0.6); expect(channel.log, [ isMethodCall('setExposureOffset', arguments: { 'cameraId': cameraId, 'offset': 0.5, }), ]); }); test('Should set the focus mode', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'setFocusMode': null}, ); // Act await camera.setFocusMode(cameraId, FocusMode.auto); await camera.setFocusMode(cameraId, FocusMode.locked); // Assert expect(channel.log, [ isMethodCall('setFocusMode', arguments: { 'cameraId': cameraId, 'mode': 'auto' }), isMethodCall('setFocusMode', arguments: { 'cameraId': cameraId, 'mode': 'locked' }), ]); }); test('Should set the exposure point', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'setFocusPoint': null}, ); // Act await camera.setFocusPoint(cameraId, const Point(0.5, 0.5)); await camera.setFocusPoint(cameraId, null); // Assert expect(channel.log, [ isMethodCall('setFocusPoint', arguments: { 'cameraId': cameraId, 'x': 0.5, 'y': 0.5, 'reset': false }), isMethodCall('setFocusPoint', arguments: { 'cameraId': cameraId, 'x': null, 'y': null, 'reset': true }), ]); }); test('Should build a texture widget as preview widget', () async { // Act final Widget widget = camera.buildPreview(cameraId); // Act expect(widget is Texture, isTrue); expect((widget as Texture).textureId, cameraId); }); test('Should throw MissingPluginException when handling unknown method', () { final MethodChannelCamera camera = MethodChannelCamera(); expect( () => camera.handleCameraMethodCall( const MethodCall('unknown_method'), 1), throwsA(isA())); }); test('Should get the max zoom level', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'getMaxZoomLevel': 10.0}, ); // Act final double maxZoomLevel = await camera.getMaxZoomLevel(cameraId); // Assert expect(maxZoomLevel, 10.0); expect(channel.log, [ isMethodCall('getMaxZoomLevel', arguments: { 'cameraId': cameraId, }), ]); }); test('Should get the min zoom level', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'getMinZoomLevel': 1.0}, ); // Act final double maxZoomLevel = await camera.getMinZoomLevel(cameraId); // Assert expect(maxZoomLevel, 1.0); expect(channel.log, [ isMethodCall('getMinZoomLevel', arguments: { 'cameraId': cameraId, }), ]); }); test('Should set the zoom level', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'setZoomLevel': null}, ); // Act await camera.setZoomLevel(cameraId, 2.0); // Assert expect(channel.log, [ isMethodCall('setZoomLevel', arguments: {'cameraId': cameraId, 'zoom': 2.0}), ]); }); test('Should throw CameraException when illegal zoom level is supplied', () async { // Arrange MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { 'setZoomLevel': PlatformException( code: 'ZOOM_ERROR', message: 'Illegal zoom error', ) }, ); // Act & assert expect( () => camera.setZoomLevel(cameraId, -1.0), throwsA(isA() .having((CameraException e) => e.code, 'code', 'ZOOM_ERROR') .having((CameraException e) => e.description, 'description', 'Illegal zoom error'))); }); test('Should lock the capture orientation', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'lockCaptureOrientation': null}, ); // Act await camera.lockCaptureOrientation( cameraId, DeviceOrientation.portraitUp); // Assert expect(channel.log, [ isMethodCall('lockCaptureOrientation', arguments: { 'cameraId': cameraId, 'orientation': 'portraitUp' }), ]); }); test('Should unlock the capture orientation', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'unlockCaptureOrientation': null}, ); // Act await camera.unlockCaptureOrientation(cameraId); // Assert expect(channel.log, [ isMethodCall('unlockCaptureOrientation', arguments: {'cameraId': cameraId}), ]); }); test('Should pause the camera preview', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'pausePreview': null}, ); // Act await camera.pausePreview(cameraId); // Assert expect(channel.log, [ isMethodCall('pausePreview', arguments: {'cameraId': cameraId}), ]); }); test('Should resume the camera preview', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: {'resumePreview': null}, ); // Act await camera.resumePreview(cameraId); // Assert expect(channel.log, [ isMethodCall('resumePreview', arguments: {'cameraId': cameraId}), ]); }); test('Should start streaming', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { 'startImageStream': null, 'stopImageStream': null, }, ); // Act final StreamSubscription subscription = camera .onStreamedFrameAvailable(cameraId) .listen((CameraImageData imageData) {}); // Assert expect(channel.log, [ isMethodCall('startImageStream', arguments: null), ]); subscription.cancel(); }); test('Should stop streaming', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: 'plugins.flutter.io/camera', methods: { 'startImageStream': null, 'stopImageStream': null, }, ); // Act final StreamSubscription subscription = camera .onStreamedFrameAvailable(cameraId) .listen((CameraImageData imageData) {}); subscription.cancel(); // Assert expect(channel.log, [ isMethodCall('startImageStream', arguments: null), isMethodCall('stopImageStream', arguments: null), ]); }); }); }); } ================================================ FILE: packages/camera/camera_platform_interface/test/method_channel/type_conversion_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_platform_interface/src/method_channel/type_conversion.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('CameraImageData can be created', () { final CameraImageData cameraImage = cameraImageFromPlatformData({ 'format': 35, 'height': 1, 'width': 4, 'lensAperture': 1.8, 'sensorExposureTime': 9991324, 'sensorSensitivity': 92.0, 'planes': [ { 'bytes': Uint8List.fromList([1, 2, 3, 4]), 'bytesPerPixel': 1, 'bytesPerRow': 4, 'height': 1, 'width': 4 } ] }); expect(cameraImage.height, 1); expect(cameraImage.width, 4); expect(cameraImage.format.group, ImageFormatGroup.yuv420); expect(cameraImage.planes.length, 1); }); test('CameraImageData has ImageFormatGroup.yuv420 for iOS', () { debugDefaultTargetPlatformOverride = TargetPlatform.iOS; final CameraImageData cameraImage = cameraImageFromPlatformData({ 'format': 875704438, 'height': 1, 'width': 4, 'lensAperture': 1.8, 'sensorExposureTime': 9991324, 'sensorSensitivity': 92.0, 'planes': [ { 'bytes': Uint8List.fromList([1, 2, 3, 4]), 'bytesPerPixel': 1, 'bytesPerRow': 4, 'height': 1, 'width': 4 } ] }); expect(cameraImage.format.group, ImageFormatGroup.yuv420); }); test('CameraImageData has ImageFormatGroup.yuv420 for Android', () { debugDefaultTargetPlatformOverride = TargetPlatform.android; final CameraImageData cameraImage = cameraImageFromPlatformData({ 'format': 35, 'height': 1, 'width': 4, 'lensAperture': 1.8, 'sensorExposureTime': 9991324, 'sensorSensitivity': 92.0, 'planes': [ { 'bytes': Uint8List.fromList([1, 2, 3, 4]), 'bytesPerPixel': 1, 'bytesPerRow': 4, 'height': 1, 'width': 4 } ] }); expect(cameraImage.format.group, ImageFormatGroup.yuv420); }); } ================================================ FILE: packages/camera/camera_platform_interface/test/types/camera_description_test.dart ================================================ // Copyright 2013 The Flutter 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_platform_interface/camera_platform_interface.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('CameraLensDirection tests', () { test('CameraLensDirection should contain 3 options', () { const List values = CameraLensDirection.values; expect(values.length, 3); }); test('CameraLensDirection enum should have items in correct index', () { const List values = CameraLensDirection.values; expect(values[0], CameraLensDirection.front); expect(values[1], CameraLensDirection.back); expect(values[2], CameraLensDirection.external); }); }); group('CameraDescription tests', () { test('Constructor should initialize all properties', () { const CameraDescription description = CameraDescription( name: 'Test', lensDirection: CameraLensDirection.front, sensorOrientation: 90, ); expect(description.name, 'Test'); expect(description.lensDirection, CameraLensDirection.front); expect(description.sensorOrientation, 90); }); test('equals should return true if objects are the same', () { const CameraDescription firstDescription = CameraDescription( name: 'Test', lensDirection: CameraLensDirection.front, sensorOrientation: 90, ); const CameraDescription secondDescription = CameraDescription( name: 'Test', lensDirection: CameraLensDirection.front, sensorOrientation: 90, ); expect(firstDescription == secondDescription, true); }); test('equals should return false if name is different', () { const CameraDescription firstDescription = CameraDescription( name: 'Test', lensDirection: CameraLensDirection.front, sensorOrientation: 90, ); const CameraDescription secondDescription = CameraDescription( name: 'Testing', lensDirection: CameraLensDirection.front, sensorOrientation: 90, ); expect(firstDescription == secondDescription, false); }); test('equals should return false if lens direction is different', () { const CameraDescription firstDescription = CameraDescription( name: 'Test', lensDirection: CameraLensDirection.front, sensorOrientation: 90, ); const CameraDescription secondDescription = CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 90, ); expect(firstDescription == secondDescription, false); }); test('equals should return true if sensor orientation is different', () { const CameraDescription firstDescription = CameraDescription( name: 'Test', lensDirection: CameraLensDirection.front, sensorOrientation: 0, ); const CameraDescription secondDescription = CameraDescription( name: 'Test', lensDirection: CameraLensDirection.front, sensorOrientation: 90, ); expect(firstDescription == secondDescription, true); }); test('hashCode should match hashCode of all equality-tested properties', () { const CameraDescription description = CameraDescription( name: 'Test', lensDirection: CameraLensDirection.front, sensorOrientation: 0, ); final int expectedHashCode = Object.hash(description.name, description.lensDirection); expect(description.hashCode, expectedHashCode); }); }); } ================================================ FILE: packages/camera/camera_platform_interface/test/types/camera_exception_test.dart ================================================ // Copyright 2013 The Flutter 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_platform_interface/camera_platform_interface.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('constructor should initialize properties', () { const String code = 'TEST_ERROR'; const String description = 'This is a test error'; final CameraException exception = CameraException(code, description); expect(exception.code, code); expect(exception.description, description); }); test('toString: Should return a description of the exception', () { const String code = 'TEST_ERROR'; const String description = 'This is a test error'; const String expected = 'CameraException($code, $description)'; final CameraException exception = CameraException(code, description); final String actual = exception.toString(); expect(actual, expected); }); } ================================================ FILE: packages/camera/camera_platform_interface/test/types/camera_image_data_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('CameraImageData can be created', () { debugDefaultTargetPlatformOverride = TargetPlatform.android; final CameraImageData cameraImage = CameraImageData( format: const CameraImageFormat(ImageFormatGroup.jpeg, raw: 42), height: 100, width: 200, lensAperture: 1.8, sensorExposureTime: 11, sensorSensitivity: 92.0, planes: [ CameraImagePlane( bytes: Uint8List.fromList([1, 2, 3, 4]), bytesPerRow: 4, bytesPerPixel: 2, height: 100, width: 200) ], ); expect(cameraImage.format.group, ImageFormatGroup.jpeg); expect(cameraImage.lensAperture, 1.8); expect(cameraImage.sensorExposureTime, 11); expect(cameraImage.sensorSensitivity, 92.0); expect(cameraImage.height, 100); expect(cameraImage.width, 200); expect(cameraImage.planes.length, 1); }); } ================================================ FILE: packages/camera/camera_platform_interface/test/types/exposure_mode_test.dart ================================================ // Copyright 2013 The Flutter 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_platform_interface/camera_platform_interface.dart'; import 'package:camera_platform_interface/src/types/exposure_mode.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('ExposureMode should contain 2 options', () { const List values = ExposureMode.values; expect(values.length, 2); }); test('ExposureMode enum should have items in correct index', () { const List values = ExposureMode.values; expect(values[0], ExposureMode.auto); expect(values[1], ExposureMode.locked); }); test('serializeExposureMode() should serialize correctly', () { expect(serializeExposureMode(ExposureMode.auto), 'auto'); expect(serializeExposureMode(ExposureMode.locked), 'locked'); }); test('deserializeExposureMode() should deserialize correctly', () { expect(deserializeExposureMode('auto'), ExposureMode.auto); expect(deserializeExposureMode('locked'), ExposureMode.locked); }); } ================================================ FILE: packages/camera/camera_platform_interface/test/types/flash_mode_test.dart ================================================ // Copyright 2013 The Flutter 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_platform_interface/camera_platform_interface.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('FlashMode should contain 4 options', () { const List values = FlashMode.values; expect(values.length, 4); }); test('FlashMode enum should have items in correct index', () { const List values = FlashMode.values; expect(values[0], FlashMode.off); expect(values[1], FlashMode.auto); expect(values[2], FlashMode.always); expect(values[3], FlashMode.torch); }); } ================================================ FILE: packages/camera/camera_platform_interface/test/types/focus_mode_test.dart ================================================ // Copyright 2013 The Flutter 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_platform_interface/src/types/focus_mode.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('FocusMode should contain 2 options', () { const List values = FocusMode.values; expect(values.length, 2); }); test('FocusMode enum should have items in correct index', () { const List values = FocusMode.values; expect(values[0], FocusMode.auto); expect(values[1], FocusMode.locked); }); test('serializeFocusMode() should serialize correctly', () { expect(serializeFocusMode(FocusMode.auto), 'auto'); expect(serializeFocusMode(FocusMode.locked), 'locked'); }); test('deserializeFocusMode() should deserialize correctly', () { expect(deserializeFocusMode('auto'), FocusMode.auto); expect(deserializeFocusMode('locked'), FocusMode.locked); }); } ================================================ FILE: packages/camera/camera_platform_interface/test/types/image_group_test.dart ================================================ // Copyright 2013 The Flutter 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_platform_interface/src/types/types.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('$ImageFormatGroup tests', () { test('ImageFormatGroupName extension returns correct values', () { expect(ImageFormatGroup.bgra8888.name(), 'bgra8888'); expect(ImageFormatGroup.yuv420.name(), 'yuv420'); expect(ImageFormatGroup.jpeg.name(), 'jpeg'); expect(ImageFormatGroup.unknown.name(), 'unknown'); }); }); } ================================================ FILE: packages/camera/camera_platform_interface/test/types/resolution_preset_test.dart ================================================ // Copyright 2013 The Flutter 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_platform_interface/camera_platform_interface.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('ResolutionPreset should contain 6 options', () { const List values = ResolutionPreset.values; expect(values.length, 6); }); test('ResolutionPreset enum should have items in correct index', () { const List values = ResolutionPreset.values; expect(values[0], ResolutionPreset.low); expect(values[1], ResolutionPreset.medium); expect(values[2], ResolutionPreset.high); expect(values[3], ResolutionPreset.veryHigh); expect(values[4], ResolutionPreset.ultraHigh); expect(values[5], ResolutionPreset.max); }); } ================================================ FILE: packages/camera/camera_platform_interface/test/utils/method_channel_mock.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; class MethodChannelMock { MethodChannelMock({ required String channelName, this.delay, required this.methods, }) : methodChannel = MethodChannel(channelName) { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(methodChannel, _handler); } final Duration? delay; final MethodChannel methodChannel; final Map methods; final List log = []; Future _handler(MethodCall methodCall) async { log.add(methodCall); if (!methods.containsKey(methodCall.method)) { throw MissingPluginException('No implementation found for method ' '${methodCall.method} on channel ${methodChannel.name}'); } return Future.delayed(delay ?? Duration.zero, () { final dynamic result = methods[methodCall.method]; if (result is Exception) { throw result; } return Future.value(result); }); } } /// 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; ================================================ FILE: packages/camera/camera_platform_interface/test/utils/utils_test.dart ================================================ // Copyright 2013 The Flutter 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_platform_interface/camera_platform_interface.dart'; import 'package:camera_platform_interface/src/utils/utils.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('Utility methods', () { test( 'Should return CameraLensDirection when valid value is supplied when parsing camera lens direction', () { expect( parseCameraLensDirection('back'), CameraLensDirection.back, ); expect( parseCameraLensDirection('front'), CameraLensDirection.front, ); expect( parseCameraLensDirection('external'), CameraLensDirection.external, ); }); test( 'Should throw ArgumentException when invalid value is supplied when parsing camera lens direction', () { expect( () => parseCameraLensDirection('test'), throwsA(isArgumentError), ); }); test('serializeDeviceOrientation() should serialize correctly', () { expect(serializeDeviceOrientation(DeviceOrientation.portraitUp), 'portraitUp'); expect(serializeDeviceOrientation(DeviceOrientation.portraitDown), 'portraitDown'); expect(serializeDeviceOrientation(DeviceOrientation.landscapeRight), 'landscapeRight'); expect(serializeDeviceOrientation(DeviceOrientation.landscapeLeft), 'landscapeLeft'); }); test('deserializeDeviceOrientation() should deserialize correctly', () { expect(deserializeDeviceOrientation('portraitUp'), DeviceOrientation.portraitUp); expect(deserializeDeviceOrientation('portraitDown'), DeviceOrientation.portraitDown); expect(deserializeDeviceOrientation('landscapeRight'), DeviceOrientation.landscapeRight); expect(deserializeDeviceOrientation('landscapeLeft'), DeviceOrientation.landscapeLeft); }); }); } ================================================ FILE: packages/camera/camera_web/AUTHORS ================================================ # 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> ================================================ FILE: packages/camera/camera_web/CHANGELOG.md ================================================ ## 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). ## 0.3.0+1 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. * Fixes avoid_redundant_argument_values lint warnings and minor typos. * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/106316). ## 0.3.0 * **BREAKING CHANGE**: Renames error code `cameraPermission` to `CameraAccessDenied` to be consistent with other platforms. ## 0.2.1+6 * Minor fixes for new analysis options. ## 0.2.1+5 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.2.1+4 * Migrates from `ui.hash*` to `Object.hash*`. * Updates minimum Flutter version for changes in 0.2.1+3. ## 0.2.1+3 * Internal code cleanup for stricter analysis options. ## 0.2.1+2 * Fixes cameraNotReadable error that prevented access to the camera on some Android devices when initializing a camera. * Implemented support for new Dart SDKs with an async requestFullscreen API. ## 0.2.1+1 * Update usage documentation. ## 0.2.1 * Add video recording functionality. * Fix cameraNotReadable error that prevented access to the camera on some Android devices. ## 0.2.0 * Initial release, adapted from the Flutter [I/O Photobooth](https://photobooth.flutter.dev/) project. ================================================ FILE: packages/camera/camera_web/LICENSE ================================================ 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. ================================================ FILE: packages/camera/camera_web/README.md ================================================ # Camera Web Plugin The web implementation of [`camera`][camera]. *Note*: This plugin is under development. See [missing implementation](#missing-implementation). ## Usage ### Depend on the package This package is [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin), which means you can simply use `camera` normally. This package will be automatically included in your app when you do. ## Example Find the example in the [`camera` package](https://pub.dev/packages/camera#example). ## Limitations on the web platform ### Camera devices The camera devices are accessed with [Stream Web API](https://developer.mozilla.org/en-US/docs/Web/API/Media_Streams_API) with the following [browser support](https://caniuse.com/stream): ![Data on support for the Stream feature across the major browsers from caniuse.com](https://caniuse.bitsofco.de/image/stream.png) Accessing camera devices requires a [secure browsing context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts). Broadly speaking, this means that you need to serve your web application over HTTPS (or `localhost` for local development). For insecure contexts `CameraPlatform.availableCameras` might throw a `CameraException` with the `permissionDenied` error code. ### Device orientation The device orientation implementation is backed by [`Screen Orientation Web API`](https://www.w3.org/TR/screen-orientation/) with the following [browser support](https://caniuse.com/screen-orientation): ![Data on support for the Screen Orientation feature across the major browsers from caniuse.com](https://caniuse.bitsofco.de/image/screen-orientation.png) For the browsers that do not support the device orientation: - `CameraPlatform.onDeviceOrientationChanged` returns an empty stream. - `CameraPlatform.lockCaptureOrientation` and `CameraPlatform.unlockCaptureOrientation` throw a `PlatformException` with the `orientationNotSupported` error code. ### Flash mode and zoom level The flash mode and zoom level implementation is backed by [Image Capture Web API](https://w3c.github.io/mediacapture-image/) with the following [browser support](https://caniuse.com/mdn-api_imagecapture): ![Data on support for the Image Capture feature across the major browsers from caniuse.com](https://caniuse.bitsofco.de/static/v1/mdn-api__ImageCapture-1628778966589.png) For the browsers that do not support the flash mode: - `CameraPlatform.setFlashMode` throws a `PlatformException` with the `torchModeNotSupported` error code. For the browsers that do not support the zoom level: - `CameraPlatform.getMaxZoomLevel`, `CameraPlatform.getMinZoomLevel` and `CameraPlatform.setZoomLevel` throw a `PlatformException` with the `zoomLevelNotSupported` error code. ### Taking a picture The image capturing implementation is backed by [`URL.createObjectUrl` Web API](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL) with the following [browser support](https://caniuse.com/bloburls): ![Data on support for the Blob URLs feature across the major browsers from caniuse.com](https://caniuse.bitsofco.de/image/bloburls.png) The web platform does not support `dart:io`. Attempts to display a captured image using `Image.file` will throw an error. The capture image contains a network-accessible URL pointing to a location within the browser (blob) and can be displayed using `Image.network` or `Image.memory` after loading the image bytes to memory. See the example below: ```dart if (kIsWeb) { Image.network(capturedImage.path); } else { Image.file(File(capturedImage.path)); } ``` ### Video recording The video recording implementation is backed by [MediaRecorder Web API](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder) with the following [browser support](https://caniuse.com/mdn-api_mediarecorder): ![Data on support for the MediaRecorder feature across the major browsers from caniuse.com](https://caniuse.bitsofco.de/image/mediarecorder.png). A video is recorded in one of the following video MIME types: - video/webm (e.g. on Chrome or Firefox) - video/mp4 (e.g. on Safari) Pausing, resuming or stopping the video recording throws a `PlatformException` with the `videoRecordingNotStarted` error code if the video recording was not started. For the browsers that do not support the video recording: - `CameraPlatform.startVideoRecording` throws a `PlatformException` with the `notSupported` error code. ## Missing implementation The web implementation of [`camera`][camera] is missing the following features: - Exposure mode, point and offset - Focus mode and point - Sensor orientation - Image format group - Streaming of frames [camera]: https://pub.dev/packages/camera ================================================ FILE: packages/camera/camera_web/example/README.md ================================================ # 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. ## Testing This package uses `package:integration_test` to run its tests in a web browser. See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/wiki/Plugin-Tests#web-tests) in the Flutter wiki for instructions to setup and run the tests in this package. Check [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) for more info. ================================================ FILE: packages/camera/camera_web/example/integration_test/camera_error_code_test.dart ================================================ // Copyright 2013 The Flutter 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:html'; import 'package:camera_web/src/types/types.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'helpers/helpers.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('CameraErrorCode', () { group('toString returns a correct type for', () { testWidgets('notSupported', (WidgetTester tester) async { expect( CameraErrorCode.notSupported.toString(), equals('cameraNotSupported'), ); }); testWidgets('notFound', (WidgetTester tester) async { expect( CameraErrorCode.notFound.toString(), equals('cameraNotFound'), ); }); testWidgets('notReadable', (WidgetTester tester) async { expect( CameraErrorCode.notReadable.toString(), equals('cameraNotReadable'), ); }); testWidgets('overconstrained', (WidgetTester tester) async { expect( CameraErrorCode.overconstrained.toString(), equals('cameraOverconstrained'), ); }); testWidgets('permissionDenied', (WidgetTester tester) async { expect( CameraErrorCode.permissionDenied.toString(), equals('CameraAccessDenied'), ); }); testWidgets('type', (WidgetTester tester) async { expect( CameraErrorCode.type.toString(), equals('cameraType'), ); }); testWidgets('abort', (WidgetTester tester) async { expect( CameraErrorCode.abort.toString(), equals('cameraAbort'), ); }); testWidgets('security', (WidgetTester tester) async { expect( CameraErrorCode.security.toString(), equals('cameraSecurity'), ); }); testWidgets('missingMetadata', (WidgetTester tester) async { expect( CameraErrorCode.missingMetadata.toString(), equals('cameraMissingMetadata'), ); }); testWidgets('orientationNotSupported', (WidgetTester tester) async { expect( CameraErrorCode.orientationNotSupported.toString(), equals('orientationNotSupported'), ); }); testWidgets('torchModeNotSupported', (WidgetTester tester) async { expect( CameraErrorCode.torchModeNotSupported.toString(), equals('torchModeNotSupported'), ); }); testWidgets('zoomLevelNotSupported', (WidgetTester tester) async { expect( CameraErrorCode.zoomLevelNotSupported.toString(), equals('zoomLevelNotSupported'), ); }); testWidgets('zoomLevelInvalid', (WidgetTester tester) async { expect( CameraErrorCode.zoomLevelInvalid.toString(), equals('zoomLevelInvalid'), ); }); testWidgets('notStarted', (WidgetTester tester) async { expect( CameraErrorCode.notStarted.toString(), equals('cameraNotStarted'), ); }); testWidgets('videoRecordingNotStarted', (WidgetTester tester) async { expect( CameraErrorCode.videoRecordingNotStarted.toString(), equals('videoRecordingNotStarted'), ); }); testWidgets('unknown', (WidgetTester tester) async { expect( CameraErrorCode.unknown.toString(), equals('cameraUnknown'), ); }); group('fromMediaError', () { testWidgets('with aborted error code', (WidgetTester tester) async { expect( CameraErrorCode.fromMediaError( FakeMediaError(MediaError.MEDIA_ERR_ABORTED), ).toString(), equals('mediaErrorAborted'), ); }); testWidgets('with network error code', (WidgetTester tester) async { expect( CameraErrorCode.fromMediaError( FakeMediaError(MediaError.MEDIA_ERR_NETWORK), ).toString(), equals('mediaErrorNetwork'), ); }); testWidgets('with decode error code', (WidgetTester tester) async { expect( CameraErrorCode.fromMediaError( FakeMediaError(MediaError.MEDIA_ERR_DECODE), ).toString(), equals('mediaErrorDecode'), ); }); testWidgets('with source not supported error code', (WidgetTester tester) async { expect( CameraErrorCode.fromMediaError( FakeMediaError(MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED), ).toString(), equals('mediaErrorSourceNotSupported'), ); }); testWidgets('with unknown error code', (WidgetTester tester) async { expect( CameraErrorCode.fromMediaError( FakeMediaError(5), ).toString(), equals('mediaErrorUnknown'), ); }); }); }); }); } ================================================ FILE: packages/camera/camera_web/example/integration_test/camera_metadata_test.dart ================================================ // Copyright 2013 The Flutter 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_web/src/types/types.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('CameraMetadata', () { testWidgets('supports value equality', (WidgetTester tester) async { expect( const CameraMetadata( deviceId: 'deviceId', facingMode: 'environment', ), equals( const CameraMetadata( deviceId: 'deviceId', facingMode: 'environment', ), ), ); }); }); } ================================================ FILE: packages/camera/camera_web/example/integration_test/camera_options_test.dart ================================================ // Copyright 2013 The Flutter 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_web/src/types/types.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('CameraOptions', () { testWidgets('serializes correctly', (WidgetTester tester) async { final CameraOptions cameraOptions = CameraOptions( audio: const AudioConstraints(enabled: true), video: VideoConstraints( facingMode: FacingModeConstraint.exact(CameraType.user), ), ); expect( cameraOptions.toJson(), equals({ 'audio': cameraOptions.audio.toJson(), 'video': cameraOptions.video.toJson(), }), ); }); testWidgets('supports value equality', (WidgetTester tester) async { expect( CameraOptions( audio: const AudioConstraints(), video: VideoConstraints( facingMode: FacingModeConstraint(CameraType.environment), width: const VideoSizeConstraint(minimum: 10, ideal: 15, maximum: 20), height: const VideoSizeConstraint(minimum: 15, ideal: 20, maximum: 25), deviceId: 'deviceId', ), ), equals( CameraOptions( audio: const AudioConstraints(), video: VideoConstraints( facingMode: FacingModeConstraint(CameraType.environment), width: const VideoSizeConstraint( minimum: 10, ideal: 15, maximum: 20), height: const VideoSizeConstraint( minimum: 15, ideal: 20, maximum: 25), deviceId: 'deviceId', ), ), ), ); }); }); group('AudioConstraints', () { testWidgets('serializes correctly', (WidgetTester tester) async { expect( const AudioConstraints(enabled: true).toJson(), equals(true), ); }); testWidgets('supports value equality', (WidgetTester tester) async { expect( const AudioConstraints(enabled: true), equals(const AudioConstraints(enabled: true)), ); }); }); group('VideoConstraints', () { testWidgets('serializes correctly', (WidgetTester tester) async { final VideoConstraints videoConstraints = VideoConstraints( facingMode: FacingModeConstraint.exact(CameraType.user), width: const VideoSizeConstraint(ideal: 100, maximum: 100), height: const VideoSizeConstraint(ideal: 50, maximum: 50), deviceId: 'deviceId', ); expect( videoConstraints.toJson(), equals({ 'facingMode': videoConstraints.facingMode!.toJson(), 'width': videoConstraints.width!.toJson(), 'height': videoConstraints.height!.toJson(), 'deviceId': { 'exact': 'deviceId', } }), ); }); testWidgets('supports value equality', (WidgetTester tester) async { expect( VideoConstraints( facingMode: FacingModeConstraint.exact(CameraType.environment), width: const VideoSizeConstraint(minimum: 90, ideal: 100, maximum: 100), height: const VideoSizeConstraint(minimum: 40, ideal: 50, maximum: 50), deviceId: 'deviceId', ), equals( VideoConstraints( facingMode: FacingModeConstraint.exact(CameraType.environment), width: const VideoSizeConstraint( minimum: 90, ideal: 100, maximum: 100), height: const VideoSizeConstraint(minimum: 40, ideal: 50, maximum: 50), deviceId: 'deviceId', ), ), ); }); }); group('FacingModeConstraint', () { group('ideal', () { testWidgets( 'serializes correctly ' 'for environment camera type', (WidgetTester tester) async { expect( FacingModeConstraint(CameraType.environment).toJson(), equals({'ideal': 'environment'}), ); }); testWidgets( 'serializes correctly ' 'for user camera type', (WidgetTester tester) async { expect( FacingModeConstraint(CameraType.user).toJson(), equals({'ideal': 'user'}), ); }); testWidgets('supports value equality', (WidgetTester tester) async { expect( FacingModeConstraint(CameraType.user), equals(FacingModeConstraint(CameraType.user)), ); }); }); group('exact', () { testWidgets( 'serializes correctly ' 'for environment camera type', (WidgetTester tester) async { expect( FacingModeConstraint.exact(CameraType.environment).toJson(), equals({'exact': 'environment'}), ); }); testWidgets( 'serializes correctly ' 'for user camera type', (WidgetTester tester) async { expect( FacingModeConstraint.exact(CameraType.user).toJson(), equals({'exact': 'user'}), ); }); testWidgets('supports value equality', (WidgetTester tester) async { expect( FacingModeConstraint.exact(CameraType.environment), equals(FacingModeConstraint.exact(CameraType.environment)), ); }); }); }); group('VideoSizeConstraint ', () { testWidgets('serializes correctly', (WidgetTester tester) async { expect( const VideoSizeConstraint( minimum: 200, ideal: 400, maximum: 400, ).toJson(), equals({ 'min': 200, 'ideal': 400, 'max': 400, }), ); }); testWidgets('supports value equality', (WidgetTester tester) async { expect( const VideoSizeConstraint( minimum: 100, ideal: 200, maximum: 300, ), equals( const VideoSizeConstraint( minimum: 100, ideal: 200, maximum: 300, ), ), ); }); }); } ================================================ FILE: packages/camera/camera_web/example/integration_test/camera_service_test.dart ================================================ // Copyright 2013 The Flutter 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:html'; import 'dart:js_util' as js_util; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#106316) // ignore: unnecessary_import import 'dart:ui'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_web/src/camera.dart'; import 'package:camera_web/src/camera_service.dart'; import 'package:camera_web/src/shims/dart_js_util.dart'; import 'package:camera_web/src/types/types.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mocktail/mocktail.dart'; import 'helpers/helpers.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('CameraService', () { const int cameraId = 1; late Window window; late Navigator navigator; late MediaDevices mediaDevices; late CameraService cameraService; late JsUtil jsUtil; setUp(() async { window = MockWindow(); navigator = MockNavigator(); mediaDevices = MockMediaDevices(); jsUtil = MockJsUtil(); when(() => window.navigator).thenReturn(navigator); when(() => navigator.mediaDevices).thenReturn(mediaDevices); // Mock JsUtil to return the real getProperty from dart:js_util. when(() => jsUtil.getProperty(any(), any())).thenAnswer( (Invocation invocation) => js_util.getProperty( invocation.positionalArguments[0] as Object, invocation.positionalArguments[1] as Object, ), ); cameraService = CameraService()..window = window; }); group('getMediaStreamForOptions', () { testWidgets( 'calls MediaDevices.getUserMedia ' 'with provided options', (WidgetTester tester) async { when(() => mediaDevices.getUserMedia(any())) .thenAnswer((_) async => FakeMediaStream([])); final CameraOptions options = CameraOptions( video: VideoConstraints( facingMode: FacingModeConstraint.exact(CameraType.user), width: const VideoSizeConstraint(ideal: 200), ), ); await cameraService.getMediaStreamForOptions(options); verify( () => mediaDevices.getUserMedia(options.toJson()), ).called(1); }); testWidgets( 'throws PlatformException ' 'with notSupported error ' 'when there are no media devices', (WidgetTester tester) async { when(() => navigator.mediaDevices).thenReturn(null); expect( () => cameraService.getMediaStreamForOptions(const CameraOptions()), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.notSupported.toString(), ), ), ); }); group('throws CameraWebException', () { testWidgets( 'with notFound error ' 'when MediaDevices.getUserMedia throws DomException ' 'with NotFoundError', (WidgetTester tester) async { when(() => mediaDevices.getUserMedia(any())) .thenThrow(FakeDomException('NotFoundError')); expect( () => cameraService.getMediaStreamForOptions( const CameraOptions(), cameraId: cameraId, ), throwsA( isA() .having((CameraWebException e) => e.cameraId, 'cameraId', cameraId) .having((CameraWebException e) => e.code, 'code', CameraErrorCode.notFound), ), ); }); testWidgets( 'with notFound error ' 'when MediaDevices.getUserMedia throws DomException ' 'with DevicesNotFoundError', (WidgetTester tester) async { when(() => mediaDevices.getUserMedia(any())) .thenThrow(FakeDomException('DevicesNotFoundError')); expect( () => cameraService.getMediaStreamForOptions( const CameraOptions(), cameraId: cameraId, ), throwsA( isA() .having((CameraWebException e) => e.cameraId, 'cameraId', cameraId) .having((CameraWebException e) => e.code, 'code', CameraErrorCode.notFound), ), ); }); testWidgets( 'with notReadable error ' 'when MediaDevices.getUserMedia throws DomException ' 'with NotReadableError', (WidgetTester tester) async { when(() => mediaDevices.getUserMedia(any())) .thenThrow(FakeDomException('NotReadableError')); expect( () => cameraService.getMediaStreamForOptions( const CameraOptions(), cameraId: cameraId, ), throwsA( isA() .having((CameraWebException e) => e.cameraId, 'cameraId', cameraId) .having((CameraWebException e) => e.code, 'code', CameraErrorCode.notReadable), ), ); }); testWidgets( 'with notReadable error ' 'when MediaDevices.getUserMedia throws DomException ' 'with TrackStartError', (WidgetTester tester) async { when(() => mediaDevices.getUserMedia(any())) .thenThrow(FakeDomException('TrackStartError')); expect( () => cameraService.getMediaStreamForOptions( const CameraOptions(), cameraId: cameraId, ), throwsA( isA() .having((CameraWebException e) => e.cameraId, 'cameraId', cameraId) .having((CameraWebException e) => e.code, 'code', CameraErrorCode.notReadable), ), ); }); testWidgets( 'with overconstrained error ' 'when MediaDevices.getUserMedia throws DomException ' 'with OverconstrainedError', (WidgetTester tester) async { when(() => mediaDevices.getUserMedia(any())) .thenThrow(FakeDomException('OverconstrainedError')); expect( () => cameraService.getMediaStreamForOptions( const CameraOptions(), cameraId: cameraId, ), throwsA( isA() .having((CameraWebException e) => e.cameraId, 'cameraId', cameraId) .having((CameraWebException e) => e.code, 'code', CameraErrorCode.overconstrained), ), ); }); testWidgets( 'with overconstrained error ' 'when MediaDevices.getUserMedia throws DomException ' 'with ConstraintNotSatisfiedError', (WidgetTester tester) async { when(() => mediaDevices.getUserMedia(any())) .thenThrow(FakeDomException('ConstraintNotSatisfiedError')); expect( () => cameraService.getMediaStreamForOptions( const CameraOptions(), cameraId: cameraId, ), throwsA( isA() .having((CameraWebException e) => e.cameraId, 'cameraId', cameraId) .having((CameraWebException e) => e.code, 'code', CameraErrorCode.overconstrained), ), ); }); testWidgets( 'with permissionDenied error ' 'when MediaDevices.getUserMedia throws DomException ' 'with NotAllowedError', (WidgetTester tester) async { when(() => mediaDevices.getUserMedia(any())) .thenThrow(FakeDomException('NotAllowedError')); expect( () => cameraService.getMediaStreamForOptions( const CameraOptions(), cameraId: cameraId, ), throwsA( isA() .having((CameraWebException e) => e.cameraId, 'cameraId', cameraId) .having((CameraWebException e) => e.code, 'code', CameraErrorCode.permissionDenied), ), ); }); testWidgets( 'with permissionDenied error ' 'when MediaDevices.getUserMedia throws DomException ' 'with PermissionDeniedError', (WidgetTester tester) async { when(() => mediaDevices.getUserMedia(any())) .thenThrow(FakeDomException('PermissionDeniedError')); expect( () => cameraService.getMediaStreamForOptions( const CameraOptions(), cameraId: cameraId, ), throwsA( isA() .having((CameraWebException e) => e.cameraId, 'cameraId', cameraId) .having((CameraWebException e) => e.code, 'code', CameraErrorCode.permissionDenied), ), ); }); testWidgets( 'with type error ' 'when MediaDevices.getUserMedia throws DomException ' 'with TypeError', (WidgetTester tester) async { when(() => mediaDevices.getUserMedia(any())) .thenThrow(FakeDomException('TypeError')); expect( () => cameraService.getMediaStreamForOptions( const CameraOptions(), cameraId: cameraId, ), throwsA( isA() .having((CameraWebException e) => e.cameraId, 'cameraId', cameraId) .having((CameraWebException e) => e.code, 'code', CameraErrorCode.type), ), ); }); testWidgets( 'with abort error ' 'when MediaDevices.getUserMedia throws DomException ' 'with AbortError', (WidgetTester tester) async { when(() => mediaDevices.getUserMedia(any())) .thenThrow(FakeDomException('AbortError')); expect( () => cameraService.getMediaStreamForOptions( const CameraOptions(), cameraId: cameraId, ), throwsA( isA() .having((CameraWebException e) => e.cameraId, 'cameraId', cameraId) .having((CameraWebException e) => e.code, 'code', CameraErrorCode.abort), ), ); }); testWidgets( 'with security error ' 'when MediaDevices.getUserMedia throws DomException ' 'with SecurityError', (WidgetTester tester) async { when(() => mediaDevices.getUserMedia(any())) .thenThrow(FakeDomException('SecurityError')); expect( () => cameraService.getMediaStreamForOptions( const CameraOptions(), cameraId: cameraId, ), throwsA( isA() .having((CameraWebException e) => e.cameraId, 'cameraId', cameraId) .having((CameraWebException e) => e.code, 'code', CameraErrorCode.security), ), ); }); testWidgets( 'with unknown error ' 'when MediaDevices.getUserMedia throws DomException ' 'with an unknown error', (WidgetTester tester) async { when(() => mediaDevices.getUserMedia(any())) .thenThrow(FakeDomException('Unknown')); expect( () => cameraService.getMediaStreamForOptions( const CameraOptions(), cameraId: cameraId, ), throwsA( isA() .having((CameraWebException e) => e.cameraId, 'cameraId', cameraId) .having((CameraWebException e) => e.code, 'code', CameraErrorCode.unknown), ), ); }); testWidgets( 'with unknown error ' 'when MediaDevices.getUserMedia throws an unknown exception', (WidgetTester tester) async { when(() => mediaDevices.getUserMedia(any())).thenThrow(Exception()); expect( () => cameraService.getMediaStreamForOptions( const CameraOptions(), cameraId: cameraId, ), throwsA( isA() .having((CameraWebException e) => e.cameraId, 'cameraId', cameraId) .having((CameraWebException e) => e.code, 'code', CameraErrorCode.unknown), ), ); }); }); }); group('getZoomLevelCapabilityForCamera', () { late Camera camera; late List videoTracks; setUp(() { camera = MockCamera(); videoTracks = [ MockMediaStreamTrack(), MockMediaStreamTrack() ]; when(() => camera.textureId).thenReturn(0); when(() => camera.stream).thenReturn(FakeMediaStream(videoTracks)); cameraService.jsUtil = jsUtil; }); testWidgets( 'returns the zoom level capability ' 'based on the first video track', (WidgetTester tester) async { when(mediaDevices.getSupportedConstraints) .thenReturn({ 'zoom': true, }); when(videoTracks.first.getCapabilities).thenReturn({ 'zoom': js_util.jsify({ 'min': 100, 'max': 400, 'step': 2, }), }); final ZoomLevelCapability zoomLevelCapability = cameraService.getZoomLevelCapabilityForCamera(camera); expect(zoomLevelCapability.minimum, equals(100.0)); expect(zoomLevelCapability.maximum, equals(400.0)); expect(zoomLevelCapability.videoTrack, equals(videoTracks.first)); }); group('throws CameraWebException', () { testWidgets( 'with zoomLevelNotSupported error ' 'when there are no media devices', (WidgetTester tester) async { when(() => navigator.mediaDevices).thenReturn(null); expect( () => cameraService.getZoomLevelCapabilityForCamera(camera), throwsA( isA() .having( (CameraWebException e) => e.cameraId, 'cameraId', camera.textureId, ) .having( (CameraWebException e) => e.code, 'code', CameraErrorCode.zoomLevelNotSupported, ), ), ); }); testWidgets( 'with zoomLevelNotSupported error ' 'when the zoom level is not supported ' 'in the browser', (WidgetTester tester) async { when(mediaDevices.getSupportedConstraints) .thenReturn({ 'zoom': false, }); when(videoTracks.first.getCapabilities).thenReturn({ 'zoom': { 'min': 100, 'max': 400, 'step': 2, }, }); expect( () => cameraService.getZoomLevelCapabilityForCamera(camera), throwsA( isA() .having( (CameraWebException e) => e.cameraId, 'cameraId', camera.textureId, ) .having( (CameraWebException e) => e.code, 'code', CameraErrorCode.zoomLevelNotSupported, ), ), ); }); testWidgets( 'with zoomLevelNotSupported error ' 'when the zoom level is not supported ' 'by the camera', (WidgetTester tester) async { when(mediaDevices.getSupportedConstraints) .thenReturn({ 'zoom': true, }); when(videoTracks.first.getCapabilities) .thenReturn({}); expect( () => cameraService.getZoomLevelCapabilityForCamera(camera), throwsA( isA() .having( (CameraWebException e) => e.cameraId, 'cameraId', camera.textureId, ) .having( (CameraWebException e) => e.code, 'code', CameraErrorCode.zoomLevelNotSupported, ), ), ); }); testWidgets( 'with notStarted error ' 'when the camera stream has not been initialized', (WidgetTester tester) async { when(mediaDevices.getSupportedConstraints) .thenReturn({ 'zoom': true, }); // Create a camera stream with no video tracks. when(() => camera.stream) .thenReturn(FakeMediaStream([])); expect( () => cameraService.getZoomLevelCapabilityForCamera(camera), throwsA( isA() .having( (CameraWebException e) => e.cameraId, 'cameraId', camera.textureId, ) .having( (CameraWebException e) => e.code, 'code', CameraErrorCode.notStarted, ), ), ); }); }); }); group('getFacingModeForVideoTrack', () { setUp(() { cameraService.jsUtil = jsUtil; }); testWidgets( 'throws PlatformException ' 'with notSupported error ' 'when there are no media devices', (WidgetTester tester) async { when(() => navigator.mediaDevices).thenReturn(null); expect( () => cameraService.getFacingModeForVideoTrack(MockMediaStreamTrack()), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.notSupported.toString(), ), ), ); }); testWidgets( 'returns null ' 'when the facing mode is not supported', (WidgetTester tester) async { when(mediaDevices.getSupportedConstraints) .thenReturn({ 'facingMode': false, }); final String? facingMode = cameraService.getFacingModeForVideoTrack(MockMediaStreamTrack()); expect(facingMode, isNull); }); group('when the facing mode is supported', () { late MediaStreamTrack videoTrack; setUp(() { videoTrack = MockMediaStreamTrack(); when(() => jsUtil.hasProperty(videoTrack, 'getCapabilities')) .thenReturn(true); when(mediaDevices.getSupportedConstraints) .thenReturn({ 'facingMode': true, }); }); testWidgets( 'returns an appropriate facing mode ' 'based on the video track settings', (WidgetTester tester) async { when(videoTrack.getSettings) .thenReturn({'facingMode': 'user'}); final String? facingMode = cameraService.getFacingModeForVideoTrack(videoTrack); expect(facingMode, equals('user')); }); testWidgets( 'returns an appropriate facing mode ' 'based on the video track capabilities ' 'when the facing mode setting is empty', (WidgetTester tester) async { when(videoTrack.getSettings).thenReturn({}); when(videoTrack.getCapabilities).thenReturn({ 'facingMode': ['environment', 'left'] }); when(() => jsUtil.hasProperty(videoTrack, 'getCapabilities')) .thenReturn(true); final String? facingMode = cameraService.getFacingModeForVideoTrack(videoTrack); expect(facingMode, equals('environment')); }); testWidgets( 'returns null ' 'when the facing mode setting ' 'and capabilities are empty', (WidgetTester tester) async { when(videoTrack.getSettings).thenReturn({}); when(videoTrack.getCapabilities) .thenReturn({'facingMode': []}); final String? facingMode = cameraService.getFacingModeForVideoTrack(videoTrack); expect(facingMode, isNull); }); testWidgets( 'returns null ' 'when the facing mode setting is empty and ' 'the video track capabilities are not supported', (WidgetTester tester) async { when(videoTrack.getSettings).thenReturn({}); when(() => jsUtil.hasProperty(videoTrack, 'getCapabilities')) .thenReturn(false); final String? facingMode = cameraService.getFacingModeForVideoTrack(videoTrack); expect(facingMode, isNull); }); }); }); group('mapFacingModeToLensDirection', () { testWidgets( 'returns front ' 'when the facing mode is user', (WidgetTester tester) async { expect( cameraService.mapFacingModeToLensDirection('user'), equals(CameraLensDirection.front), ); }); testWidgets( 'returns back ' 'when the facing mode is environment', (WidgetTester tester) async { expect( cameraService.mapFacingModeToLensDirection('environment'), equals(CameraLensDirection.back), ); }); testWidgets( 'returns external ' 'when the facing mode is left', (WidgetTester tester) async { expect( cameraService.mapFacingModeToLensDirection('left'), equals(CameraLensDirection.external), ); }); testWidgets( 'returns external ' 'when the facing mode is right', (WidgetTester tester) async { expect( cameraService.mapFacingModeToLensDirection('right'), equals(CameraLensDirection.external), ); }); }); group('mapFacingModeToCameraType', () { testWidgets( 'returns user ' 'when the facing mode is user', (WidgetTester tester) async { expect( cameraService.mapFacingModeToCameraType('user'), equals(CameraType.user), ); }); testWidgets( 'returns environment ' 'when the facing mode is environment', (WidgetTester tester) async { expect( cameraService.mapFacingModeToCameraType('environment'), equals(CameraType.environment), ); }); testWidgets( 'returns user ' 'when the facing mode is left', (WidgetTester tester) async { expect( cameraService.mapFacingModeToCameraType('left'), equals(CameraType.user), ); }); testWidgets( 'returns user ' 'when the facing mode is right', (WidgetTester tester) async { expect( cameraService.mapFacingModeToCameraType('right'), equals(CameraType.user), ); }); }); group('mapResolutionPresetToSize', () { testWidgets( 'returns 4096x2160 ' 'when the resolution preset is max', (WidgetTester tester) async { expect( cameraService.mapResolutionPresetToSize(ResolutionPreset.max), equals(const Size(4096, 2160)), ); }); testWidgets( 'returns 4096x2160 ' 'when the resolution preset is ultraHigh', (WidgetTester tester) async { expect( cameraService.mapResolutionPresetToSize(ResolutionPreset.ultraHigh), equals(const Size(4096, 2160)), ); }); testWidgets( 'returns 1920x1080 ' 'when the resolution preset is veryHigh', (WidgetTester tester) async { expect( cameraService.mapResolutionPresetToSize(ResolutionPreset.veryHigh), equals(const Size(1920, 1080)), ); }); testWidgets( 'returns 1280x720 ' 'when the resolution preset is high', (WidgetTester tester) async { expect( cameraService.mapResolutionPresetToSize(ResolutionPreset.high), equals(const Size(1280, 720)), ); }); testWidgets( 'returns 720x480 ' 'when the resolution preset is medium', (WidgetTester tester) async { expect( cameraService.mapResolutionPresetToSize(ResolutionPreset.medium), equals(const Size(720, 480)), ); }); testWidgets( 'returns 320x240 ' 'when the resolution preset is low', (WidgetTester tester) async { expect( cameraService.mapResolutionPresetToSize(ResolutionPreset.low), equals(const Size(320, 240)), ); }); }); group('mapDeviceOrientationToOrientationType', () { testWidgets( 'returns portraitPrimary ' 'when the device orientation is portraitUp', (WidgetTester tester) async { expect( cameraService.mapDeviceOrientationToOrientationType( DeviceOrientation.portraitUp, ), equals(OrientationType.portraitPrimary), ); }); testWidgets( 'returns landscapePrimary ' 'when the device orientation is landscapeLeft', (WidgetTester tester) async { expect( cameraService.mapDeviceOrientationToOrientationType( DeviceOrientation.landscapeLeft, ), equals(OrientationType.landscapePrimary), ); }); testWidgets( 'returns portraitSecondary ' 'when the device orientation is portraitDown', (WidgetTester tester) async { expect( cameraService.mapDeviceOrientationToOrientationType( DeviceOrientation.portraitDown, ), equals(OrientationType.portraitSecondary), ); }); testWidgets( 'returns landscapeSecondary ' 'when the device orientation is landscapeRight', (WidgetTester tester) async { expect( cameraService.mapDeviceOrientationToOrientationType( DeviceOrientation.landscapeRight, ), equals(OrientationType.landscapeSecondary), ); }); }); group('mapOrientationTypeToDeviceOrientation', () { testWidgets( 'returns portraitUp ' 'when the orientation type is portraitPrimary', (WidgetTester tester) async { expect( cameraService.mapOrientationTypeToDeviceOrientation( OrientationType.portraitPrimary, ), equals(DeviceOrientation.portraitUp), ); }); testWidgets( 'returns landscapeLeft ' 'when the orientation type is landscapePrimary', (WidgetTester tester) async { expect( cameraService.mapOrientationTypeToDeviceOrientation( OrientationType.landscapePrimary, ), equals(DeviceOrientation.landscapeLeft), ); }); testWidgets( 'returns portraitDown ' 'when the orientation type is portraitSecondary', (WidgetTester tester) async { expect( cameraService.mapOrientationTypeToDeviceOrientation( OrientationType.portraitSecondary, ), equals(DeviceOrientation.portraitDown), ); }); testWidgets( 'returns portraitDown ' 'when the orientation type is portraitSecondary', (WidgetTester tester) async { expect( cameraService.mapOrientationTypeToDeviceOrientation( OrientationType.portraitSecondary, ), equals(DeviceOrientation.portraitDown), ); }); testWidgets( 'returns landscapeRight ' 'when the orientation type is landscapeSecondary', (WidgetTester tester) async { expect( cameraService.mapOrientationTypeToDeviceOrientation( OrientationType.landscapeSecondary, ), equals(DeviceOrientation.landscapeRight), ); }); testWidgets( 'returns portraitUp ' 'for an unknown orientation type', (WidgetTester tester) async { expect( cameraService.mapOrientationTypeToDeviceOrientation( 'unknown', ), equals(DeviceOrientation.portraitUp), ); }); }); }); } class JSNoSuchMethodError implements Exception {} ================================================ FILE: packages/camera/camera_web/example/integration_test/camera_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:html'; import 'dart:ui'; import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_web/src/camera.dart'; import 'package:camera_web/src/camera_service.dart'; import 'package:camera_web/src/types/types.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mocktail/mocktail.dart'; import 'helpers/helpers.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('Camera', () { const int textureId = 1; late Window window; late Navigator navigator; late MediaDevices mediaDevices; late MediaStream mediaStream; late CameraService cameraService; setUp(() { window = MockWindow(); navigator = MockNavigator(); mediaDevices = MockMediaDevices(); when(() => window.navigator).thenReturn(navigator); when(() => navigator.mediaDevices).thenReturn(mediaDevices); cameraService = MockCameraService(); final VideoElement videoElement = getVideoElementWithBlankStream(const Size(10, 10)); mediaStream = videoElement.captureStream(); when( () => cameraService.getMediaStreamForOptions( any(), cameraId: any(named: 'cameraId'), ), ).thenAnswer((_) => Future.value(mediaStream)); }); setUpAll(() { registerFallbackValue(MockCameraOptions()); }); group('initialize', () { testWidgets( 'calls CameraService.getMediaStreamForOptions ' 'with provided options', (WidgetTester tester) async { final CameraOptions options = CameraOptions( video: VideoConstraints( facingMode: FacingModeConstraint.exact(CameraType.user), width: const VideoSizeConstraint(ideal: 200), ), ); final Camera camera = Camera( textureId: textureId, options: options, cameraService: cameraService, ); await camera.initialize(); verify( () => cameraService.getMediaStreamForOptions( options, cameraId: textureId, ), ).called(1); }); testWidgets( 'creates a video element ' 'with correct properties', (WidgetTester tester) async { const AudioConstraints audioConstraints = AudioConstraints(enabled: true); final VideoConstraints videoConstraints = VideoConstraints( facingMode: FacingModeConstraint( CameraType.user, ), ); final Camera camera = Camera( textureId: textureId, options: CameraOptions( audio: audioConstraints, video: videoConstraints, ), cameraService: cameraService, ); await camera.initialize(); expect(camera.videoElement, isNotNull); expect(camera.videoElement.autoplay, isFalse); expect(camera.videoElement.muted, isTrue); expect(camera.videoElement.srcObject, mediaStream); expect(camera.videoElement.attributes.keys, contains('playsinline')); expect( camera.videoElement.style.transformOrigin, equals('center center')); expect(camera.videoElement.style.pointerEvents, equals('none')); expect(camera.videoElement.style.width, equals('100%')); expect(camera.videoElement.style.height, equals('100%')); expect(camera.videoElement.style.objectFit, equals('cover')); }); testWidgets( 'flips the video element horizontally ' 'for a back camera', (WidgetTester tester) async { final VideoConstraints videoConstraints = VideoConstraints( facingMode: FacingModeConstraint( CameraType.environment, ), ); final Camera camera = Camera( textureId: textureId, options: CameraOptions( video: videoConstraints, ), cameraService: cameraService, ); await camera.initialize(); expect(camera.videoElement.style.transform, equals('scaleX(-1)')); }); testWidgets( 'creates a wrapping div element ' 'with correct properties', (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); await camera.initialize(); expect(camera.divElement, isNotNull); expect(camera.divElement.style.objectFit, equals('cover')); expect(camera.divElement.children, contains(camera.videoElement)); }); testWidgets('initializes the camera stream', (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); await camera.initialize(); expect(camera.stream, mediaStream); }); testWidgets( 'throws an exception ' 'when CameraService.getMediaStreamForOptions throws', (WidgetTester tester) async { final Exception exception = Exception('A media stream exception occured.'); when(() => cameraService.getMediaStreamForOptions(any(), cameraId: any(named: 'cameraId'))).thenThrow(exception); final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); expect( camera.initialize, throwsA(exception), ); }); }); group('play', () { testWidgets('starts playing the video element', (WidgetTester tester) async { bool startedPlaying = false; final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); await camera.initialize(); final StreamSubscription cameraPlaySubscription = camera .videoElement.onPlay .listen((Event event) => startedPlaying = true); await camera.play(); expect(startedPlaying, isTrue); await cameraPlaySubscription.cancel(); }); testWidgets( 'initializes the camera stream ' 'from CameraService.getMediaStreamForOptions ' 'if it does not exist', (WidgetTester tester) async { const CameraOptions options = CameraOptions( video: VideoConstraints( width: VideoSizeConstraint(ideal: 100), ), ); final Camera camera = Camera( textureId: textureId, options: options, cameraService: cameraService, ); await camera.initialize(); /// Remove the video element's source /// by stopping the camera. camera.stop(); await camera.play(); // Should be called twice: for initialize and play. verify( () => cameraService.getMediaStreamForOptions( options, cameraId: textureId, ), ).called(2); expect(camera.videoElement.srcObject, mediaStream); expect(camera.stream, mediaStream); }); }); group('pause', () { testWidgets('pauses the camera stream', (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); await camera.initialize(); await camera.play(); expect(camera.videoElement.paused, isFalse); camera.pause(); expect(camera.videoElement.paused, isTrue); }); }); group('stop', () { testWidgets('resets the camera stream', (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); await camera.initialize(); await camera.play(); camera.stop(); expect(camera.videoElement.srcObject, isNull); expect(camera.stream, isNull); }); }); group('takePicture', () { testWidgets('returns a captured picture', (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); await camera.initialize(); await camera.play(); final XFile pictureFile = await camera.takePicture(); expect(pictureFile, isNotNull); }); group( 'enables the torch mode ' 'when taking a picture', () { late List videoTracks; late MediaStream videoStream; late VideoElement videoElement; setUp(() { videoTracks = [ MockMediaStreamTrack(), MockMediaStreamTrack() ]; videoStream = FakeMediaStream(videoTracks); videoElement = getVideoElementWithBlankStream(const Size(100, 100)) ..muted = true; when(() => videoTracks.first.applyConstraints(any())) .thenAnswer((_) async => {}); when(videoTracks.first.getCapabilities).thenReturn({ 'torch': true, }); }); testWidgets('if the flash mode is auto', (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ) ..window = window ..stream = videoStream ..videoElement = videoElement ..flashMode = FlashMode.auto; await camera.play(); final XFile _ = await camera.takePicture(); verify( () => videoTracks.first.applyConstraints({ 'advanced': [ { 'torch': true, } ] }), ).called(1); verify( () => videoTracks.first.applyConstraints({ 'advanced': [ { 'torch': false, } ] }), ).called(1); }); testWidgets('if the flash mode is always', (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ) ..window = window ..stream = videoStream ..videoElement = videoElement ..flashMode = FlashMode.always; await camera.play(); final XFile _ = await camera.takePicture(); verify( () => videoTracks.first.applyConstraints({ 'advanced': [ { 'torch': true, } ] }), ).called(1); verify( () => videoTracks.first.applyConstraints({ 'advanced': [ { 'torch': false, } ] }), ).called(1); }); }); }); group('getVideoSize', () { testWidgets( 'returns a size ' 'based on the first video track settings', (WidgetTester tester) async { const Size videoSize = Size(1280, 720); final VideoElement videoElement = getVideoElementWithBlankStream(videoSize); mediaStream = videoElement.captureStream(); final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); await camera.initialize(); expect( camera.getVideoSize(), equals(videoSize), ); }); testWidgets( 'returns Size.zero ' 'if the camera is missing video tracks', (WidgetTester tester) async { // Create a video stream with no video tracks. final VideoElement videoElement = VideoElement(); mediaStream = videoElement.captureStream(); final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); await camera.initialize(); expect( camera.getVideoSize(), equals(Size.zero), ); }); }); group('setFlashMode', () { late List videoTracks; late MediaStream videoStream; setUp(() { videoTracks = [ MockMediaStreamTrack(), MockMediaStreamTrack() ]; videoStream = FakeMediaStream(videoTracks); when(() => videoTracks.first.applyConstraints(any())) .thenAnswer((_) async => {}); when(videoTracks.first.getCapabilities) .thenReturn({}); }); testWidgets('sets the camera flash mode', (WidgetTester tester) async { when(mediaDevices.getSupportedConstraints) .thenReturn({ 'torch': true, }); when(videoTracks.first.getCapabilities).thenReturn({ 'torch': true, }); final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ) ..window = window ..stream = videoStream; const FlashMode flashMode = FlashMode.always; camera.setFlashMode(flashMode); expect( camera.flashMode, equals(flashMode), ); }); testWidgets( 'enables the torch mode ' 'if the flash mode is torch', (WidgetTester tester) async { when(mediaDevices.getSupportedConstraints) .thenReturn({ 'torch': true, }); when(videoTracks.first.getCapabilities).thenReturn({ 'torch': true, }); final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ) ..window = window ..stream = videoStream; camera.setFlashMode(FlashMode.torch); verify( () => videoTracks.first.applyConstraints({ 'advanced': [ { 'torch': true, } ] }), ).called(1); }); testWidgets( 'disables the torch mode ' 'if the flash mode is not torch', (WidgetTester tester) async { when(mediaDevices.getSupportedConstraints) .thenReturn({ 'torch': true, }); when(videoTracks.first.getCapabilities).thenReturn({ 'torch': true, }); final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ) ..window = window ..stream = videoStream; camera.setFlashMode(FlashMode.auto); verify( () => videoTracks.first.applyConstraints({ 'advanced': [ { 'torch': false, } ] }), ).called(1); }); group('throws a CameraWebException', () { testWidgets( 'with torchModeNotSupported error ' 'when there are no media devices', (WidgetTester tester) async { when(() => navigator.mediaDevices).thenReturn(null); final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ) ..window = window ..stream = videoStream; expect( () => camera.setFlashMode(FlashMode.always), throwsA( isA() .having( (CameraWebException e) => e.cameraId, 'cameraId', textureId, ) .having( (CameraWebException e) => e.code, 'code', CameraErrorCode.torchModeNotSupported, ), ), ); }); testWidgets( 'with torchModeNotSupported error ' 'when the torch mode is not supported ' 'in the browser', (WidgetTester tester) async { when(mediaDevices.getSupportedConstraints) .thenReturn({ 'torch': false, }); when(videoTracks.first.getCapabilities).thenReturn({ 'torch': true, }); final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ) ..window = window ..stream = videoStream; expect( () => camera.setFlashMode(FlashMode.always), throwsA( isA() .having( (CameraWebException e) => e.cameraId, 'cameraId', textureId, ) .having( (CameraWebException e) => e.code, 'code', CameraErrorCode.torchModeNotSupported, ), ), ); }); testWidgets( 'with torchModeNotSupported error ' 'when the torch mode is not supported ' 'by the camera', (WidgetTester tester) async { when(mediaDevices.getSupportedConstraints) .thenReturn({ 'torch': true, }); when(videoTracks.first.getCapabilities).thenReturn({ 'torch': false, }); final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ) ..window = window ..stream = videoStream; expect( () => camera.setFlashMode(FlashMode.always), throwsA( isA() .having( (CameraWebException e) => e.cameraId, 'cameraId', textureId, ) .having( (CameraWebException e) => e.code, 'code', CameraErrorCode.torchModeNotSupported, ), ), ); }); testWidgets( 'with notStarted error ' 'when the camera stream has not been initialized', (WidgetTester tester) async { when(mediaDevices.getSupportedConstraints) .thenReturn({ 'torch': true, }); when(videoTracks.first.getCapabilities).thenReturn({ 'torch': true, }); final Camera camera = Camera( textureId: textureId, cameraService: cameraService, )..window = window; expect( () => camera.setFlashMode(FlashMode.always), throwsA( isA() .having( (CameraWebException e) => e.cameraId, 'cameraId', textureId, ) .having( (CameraWebException e) => e.code, 'code', CameraErrorCode.notStarted, ), ), ); }); }); }); group('zoomLevel', () { group('getMaxZoomLevel', () { testWidgets( 'returns maximum ' 'from CameraService.getZoomLevelCapabilityForCamera', (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); final ZoomLevelCapability zoomLevelCapability = ZoomLevelCapability( minimum: 50.0, maximum: 100.0, videoTrack: MockMediaStreamTrack(), ); when(() => cameraService.getZoomLevelCapabilityForCamera(camera)) .thenReturn(zoomLevelCapability); final double maximumZoomLevel = camera.getMaxZoomLevel(); verify(() => cameraService.getZoomLevelCapabilityForCamera(camera)) .called(1); expect( maximumZoomLevel, equals(zoomLevelCapability.maximum), ); }); }); group('getMinZoomLevel', () { testWidgets( 'returns minimum ' 'from CameraService.getZoomLevelCapabilityForCamera', (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); final ZoomLevelCapability zoomLevelCapability = ZoomLevelCapability( minimum: 50.0, maximum: 100.0, videoTrack: MockMediaStreamTrack(), ); when(() => cameraService.getZoomLevelCapabilityForCamera(camera)) .thenReturn(zoomLevelCapability); final double minimumZoomLevel = camera.getMinZoomLevel(); verify(() => cameraService.getZoomLevelCapabilityForCamera(camera)) .called(1); expect( minimumZoomLevel, equals(zoomLevelCapability.minimum), ); }); }); group('setZoomLevel', () { testWidgets( 'applies zoom on the video track ' 'from CameraService.getZoomLevelCapabilityForCamera', (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); final MockMediaStreamTrack videoTrack = MockMediaStreamTrack(); final ZoomLevelCapability zoomLevelCapability = ZoomLevelCapability( minimum: 50.0, maximum: 100.0, videoTrack: videoTrack, ); when(() => videoTrack.applyConstraints(any())) .thenAnswer((_) async {}); when(() => cameraService.getZoomLevelCapabilityForCamera(camera)) .thenReturn(zoomLevelCapability); const double zoom = 75.0; camera.setZoomLevel(zoom); verify( () => videoTrack.applyConstraints({ 'advanced': [ { ZoomLevelCapability.constraintName: zoom, } ] }), ).called(1); }); group('throws a CameraWebException', () { testWidgets( 'with zoomLevelInvalid error ' 'when the provided zoom level is below minimum', (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); final ZoomLevelCapability zoomLevelCapability = ZoomLevelCapability( minimum: 50.0, maximum: 100.0, videoTrack: MockMediaStreamTrack(), ); when(() => cameraService.getZoomLevelCapabilityForCamera(camera)) .thenReturn(zoomLevelCapability); expect( () => camera.setZoomLevel(45.0), throwsA( isA() .having( (CameraWebException e) => e.cameraId, 'cameraId', textureId, ) .having( (CameraWebException e) => e.code, 'code', CameraErrorCode.zoomLevelInvalid, ), )); }); testWidgets( 'with zoomLevelInvalid error ' 'when the provided zoom level is below minimum', (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); final ZoomLevelCapability zoomLevelCapability = ZoomLevelCapability( minimum: 50.0, maximum: 100.0, videoTrack: MockMediaStreamTrack(), ); when(() => cameraService.getZoomLevelCapabilityForCamera(camera)) .thenReturn(zoomLevelCapability); expect( () => camera.setZoomLevel(105.0), throwsA( isA() .having( (CameraWebException e) => e.cameraId, 'cameraId', textureId, ) .having( (CameraWebException e) => e.code, 'code', CameraErrorCode.zoomLevelInvalid, ), ), ); }); }); }); }); group('getLensDirection', () { testWidgets( 'returns a lens direction ' 'based on the first video track settings', (WidgetTester tester) async { final MockVideoElement videoElement = MockVideoElement(); final Camera camera = Camera( textureId: textureId, cameraService: cameraService, )..videoElement = videoElement; final MockMediaStreamTrack firstVideoTrack = MockMediaStreamTrack(); when(() => videoElement.srcObject).thenReturn( FakeMediaStream([ firstVideoTrack, MockMediaStreamTrack(), ]), ); when(firstVideoTrack.getSettings) .thenReturn({'facingMode': 'environment'}); when(() => cameraService.mapFacingModeToLensDirection('environment')) .thenReturn(CameraLensDirection.external); expect( camera.getLensDirection(), equals(CameraLensDirection.external), ); }); testWidgets( 'returns null ' 'if the first video track is missing the facing mode', (WidgetTester tester) async { final MockVideoElement videoElement = MockVideoElement(); final Camera camera = Camera( textureId: textureId, cameraService: cameraService, )..videoElement = videoElement; final MockMediaStreamTrack firstVideoTrack = MockMediaStreamTrack(); when(() => videoElement.srcObject).thenReturn( FakeMediaStream([ firstVideoTrack, MockMediaStreamTrack(), ]), ); when(firstVideoTrack.getSettings).thenReturn({}); expect( camera.getLensDirection(), isNull, ); }); testWidgets( 'returns null ' 'if the camera is missing video tracks', (WidgetTester tester) async { // Create a video stream with no video tracks. final VideoElement videoElement = VideoElement(); mediaStream = videoElement.captureStream(); final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); await camera.initialize(); expect( camera.getLensDirection(), isNull, ); }); }); group('getViewType', () { testWidgets('returns a correct view type', (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); await camera.initialize(); expect( camera.getViewType(), equals('plugins.flutter.io/camera_$textureId'), ); }); }); group('video recording', () { const String supportedVideoType = 'video/webm'; late MediaRecorder mediaRecorder; bool isVideoTypeSupported(String type) => type == supportedVideoType; setUp(() { mediaRecorder = MockMediaRecorder(); when(() => mediaRecorder.onError) .thenAnswer((_) => const Stream.empty()); }); group('startVideoRecording', () { testWidgets( 'creates a media recorder ' 'with appropriate options', (WidgetTester tester) async { final Camera camera = Camera( textureId: 1, cameraService: cameraService, )..isVideoTypeSupported = isVideoTypeSupported; await camera.initialize(); await camera.play(); await camera.startVideoRecording(); expect( camera.mediaRecorder!.stream, equals(camera.stream), ); expect( camera.mediaRecorder!.mimeType, equals(supportedVideoType), ); expect( camera.mediaRecorder!.state, equals('recording'), ); }); testWidgets('listens to the media recorder data events', (WidgetTester tester) async { final Camera camera = Camera( textureId: 1, cameraService: cameraService, ) ..mediaRecorder = mediaRecorder ..isVideoTypeSupported = isVideoTypeSupported; await camera.initialize(); await camera.play(); await camera.startVideoRecording(); verify( () => mediaRecorder.addEventListener('dataavailable', any()), ).called(1); }); testWidgets('listens to the media recorder stop events', (WidgetTester tester) async { final Camera camera = Camera( textureId: 1, cameraService: cameraService, ) ..mediaRecorder = mediaRecorder ..isVideoTypeSupported = isVideoTypeSupported; await camera.initialize(); await camera.play(); await camera.startVideoRecording(); verify( () => mediaRecorder.addEventListener('stop', any()), ).called(1); }); testWidgets('starts a video recording', (WidgetTester tester) async { final Camera camera = Camera( textureId: 1, cameraService: cameraService, ) ..mediaRecorder = mediaRecorder ..isVideoTypeSupported = isVideoTypeSupported; await camera.initialize(); await camera.play(); await camera.startVideoRecording(); verify(mediaRecorder.start).called(1); }); testWidgets( 'starts a video recording ' 'with maxVideoDuration', (WidgetTester tester) async { const Duration maxVideoDuration = Duration(hours: 1); final Camera camera = Camera( textureId: 1, cameraService: cameraService, ) ..mediaRecorder = mediaRecorder ..isVideoTypeSupported = isVideoTypeSupported; await camera.initialize(); await camera.play(); await camera.startVideoRecording(maxVideoDuration: maxVideoDuration); verify(() => mediaRecorder.start(maxVideoDuration.inMilliseconds)) .called(1); }); group('throws a CameraWebException', () { testWidgets( 'with notSupported error ' 'when maxVideoDuration is 0 milliseconds or less', (WidgetTester tester) async { final Camera camera = Camera( textureId: 1, cameraService: cameraService, ) ..mediaRecorder = mediaRecorder ..isVideoTypeSupported = isVideoTypeSupported; await camera.initialize(); await camera.play(); expect( () => camera.startVideoRecording(maxVideoDuration: Duration.zero), throwsA( isA() .having( (CameraWebException e) => e.cameraId, 'cameraId', textureId, ) .having( (CameraWebException e) => e.code, 'code', CameraErrorCode.notSupported, ), ), ); }); testWidgets( 'with notSupported error ' 'when no video types are supported', (WidgetTester tester) async { final Camera camera = Camera( textureId: 1, cameraService: cameraService, )..isVideoTypeSupported = (String type) => false; await camera.initialize(); await camera.play(); expect( camera.startVideoRecording, throwsA( isA() .having( (CameraWebException e) => e.cameraId, 'cameraId', textureId, ) .having( (CameraWebException e) => e.code, 'code', CameraErrorCode.notSupported, ), ), ); }); }); }); group('pauseVideoRecording', () { testWidgets('pauses a video recording', (WidgetTester tester) async { final Camera camera = Camera( textureId: 1, cameraService: cameraService, )..mediaRecorder = mediaRecorder; await camera.pauseVideoRecording(); verify(mediaRecorder.pause).called(1); }); testWidgets( 'throws a CameraWebException ' 'with videoRecordingNotStarted error ' 'if the video recording was not started', (WidgetTester tester) async { final Camera camera = Camera( textureId: 1, cameraService: cameraService, ); expect( camera.pauseVideoRecording, throwsA( isA() .having( (CameraWebException e) => e.cameraId, 'cameraId', textureId, ) .having( (CameraWebException e) => e.code, 'code', CameraErrorCode.videoRecordingNotStarted, ), ), ); }); }); group('resumeVideoRecording', () { testWidgets('resumes a video recording', (WidgetTester tester) async { final Camera camera = Camera( textureId: 1, cameraService: cameraService, )..mediaRecorder = mediaRecorder; await camera.resumeVideoRecording(); verify(mediaRecorder.resume).called(1); }); testWidgets( 'throws a CameraWebException ' 'with videoRecordingNotStarted error ' 'if the video recording was not started', (WidgetTester tester) async { final Camera camera = Camera( textureId: 1, cameraService: cameraService, ); expect( camera.resumeVideoRecording, throwsA( isA() .having( (CameraWebException e) => e.cameraId, 'cameraId', textureId, ) .having( (CameraWebException e) => e.code, 'code', CameraErrorCode.videoRecordingNotStarted, ), ), ); }); }); group('stopVideoRecording', () { testWidgets( 'stops a video recording and ' 'returns the captured file ' 'based on all video data parts', (WidgetTester tester) async { final Camera camera = Camera( textureId: 1, cameraService: cameraService, ) ..mediaRecorder = mediaRecorder ..isVideoTypeSupported = isVideoTypeSupported; await camera.initialize(); await camera.play(); late void Function(Event) videoDataAvailableListener; late void Function(Event) videoRecordingStoppedListener; when( () => mediaRecorder.addEventListener('dataavailable', any()), ).thenAnswer((Invocation invocation) { videoDataAvailableListener = invocation.positionalArguments[1] as void Function(Event); }); when( () => mediaRecorder.addEventListener('stop', any()), ).thenAnswer((Invocation invocation) { videoRecordingStoppedListener = invocation.positionalArguments[1] as void Function(Event); }); Blob? finalVideo; List? videoParts; camera.blobBuilder = (List blobs, String videoType) { videoParts = [...blobs]; finalVideo = Blob(blobs, videoType); return finalVideo!; }; await camera.startVideoRecording(); final Future videoFileFuture = camera.stopVideoRecording(); final Blob capturedVideoPartOne = Blob([]); final Blob capturedVideoPartTwo = Blob([]); final List capturedVideoParts = [ capturedVideoPartOne, capturedVideoPartTwo, ]; videoDataAvailableListener(FakeBlobEvent(capturedVideoPartOne)); videoDataAvailableListener(FakeBlobEvent(capturedVideoPartTwo)); videoRecordingStoppedListener(Event('stop')); final XFile videoFile = await videoFileFuture; verify(mediaRecorder.stop).called(1); expect( videoFile, isNotNull, ); expect( videoFile.mimeType, equals(supportedVideoType), ); expect( videoFile.name, equals(finalVideo.hashCode.toString()), ); expect( videoParts, equals(capturedVideoParts), ); }); testWidgets( 'throws a CameraWebException ' 'with videoRecordingNotStarted error ' 'if the video recording was not started', (WidgetTester tester) async { final Camera camera = Camera( textureId: 1, cameraService: cameraService, ); expect( camera.stopVideoRecording, throwsA( isA() .having( (CameraWebException e) => e.cameraId, 'cameraId', textureId, ) .having( (CameraWebException e) => e.code, 'code', CameraErrorCode.videoRecordingNotStarted, ), ), ); }); }); group('on video data available', () { late void Function(Event) videoDataAvailableListener; setUp(() { when( () => mediaRecorder.addEventListener('dataavailable', any()), ).thenAnswer((Invocation invocation) { videoDataAvailableListener = invocation.positionalArguments[1] as void Function(Event); }); }); testWidgets( 'stops a video recording ' 'if maxVideoDuration is given and ' 'the recording was not stopped manually', (WidgetTester tester) async { const Duration maxVideoDuration = Duration(hours: 1); final Camera camera = Camera( textureId: 1, cameraService: cameraService, ) ..mediaRecorder = mediaRecorder ..isVideoTypeSupported = isVideoTypeSupported; await camera.initialize(); await camera.play(); await camera.startVideoRecording(maxVideoDuration: maxVideoDuration); when(() => mediaRecorder.state).thenReturn('recording'); videoDataAvailableListener(FakeBlobEvent(Blob([]))); await Future.microtask(() {}); verify(mediaRecorder.stop).called(1); }); }); group('on video recording stopped', () { late void Function(Event) videoRecordingStoppedListener; setUp(() { when( () => mediaRecorder.addEventListener('stop', any()), ).thenAnswer((Invocation invocation) { videoRecordingStoppedListener = invocation.positionalArguments[1] as void Function(Event); }); }); testWidgets('stops listening to the media recorder data events', (WidgetTester tester) async { final Camera camera = Camera( textureId: 1, cameraService: cameraService, ) ..mediaRecorder = mediaRecorder ..isVideoTypeSupported = isVideoTypeSupported; await camera.initialize(); await camera.play(); await camera.startVideoRecording(); videoRecordingStoppedListener(Event('stop')); await Future.microtask(() {}); verify( () => mediaRecorder.removeEventListener('dataavailable', any()), ).called(1); }); testWidgets('stops listening to the media recorder stop events', (WidgetTester tester) async { final Camera camera = Camera( textureId: 1, cameraService: cameraService, ) ..mediaRecorder = mediaRecorder ..isVideoTypeSupported = isVideoTypeSupported; await camera.initialize(); await camera.play(); await camera.startVideoRecording(); videoRecordingStoppedListener(Event('stop')); await Future.microtask(() {}); verify( () => mediaRecorder.removeEventListener('stop', any()), ).called(1); }); testWidgets('stops listening to the media recorder errors', (WidgetTester tester) async { final StreamController onErrorStreamController = StreamController(); final Camera camera = Camera( textureId: 1, cameraService: cameraService, ) ..mediaRecorder = mediaRecorder ..isVideoTypeSupported = isVideoTypeSupported; when(() => mediaRecorder.onError) .thenAnswer((_) => onErrorStreamController.stream); await camera.initialize(); await camera.play(); await camera.startVideoRecording(); videoRecordingStoppedListener(Event('stop')); await Future.microtask(() {}); expect( onErrorStreamController.hasListener, isFalse, ); }); }); }); group('dispose', () { testWidgets("resets the video element's source", (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); await camera.initialize(); await camera.dispose(); expect(camera.videoElement.srcObject, isNull); }); testWidgets('closes the onEnded stream', (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); await camera.initialize(); await camera.dispose(); expect( camera.onEndedController.isClosed, isTrue, ); }); testWidgets('closes the onVideoRecordedEvent stream', (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); await camera.initialize(); await camera.dispose(); expect( camera.videoRecorderController.isClosed, isTrue, ); }); testWidgets('closes the onVideoRecordingError stream', (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); await camera.initialize(); await camera.dispose(); expect( camera.videoRecordingErrorController.isClosed, isTrue, ); }); }); group('events', () { group('onVideoRecordedEvent', () { testWidgets( 'emits a VideoRecordedEvent ' 'when a video recording is created', (WidgetTester tester) async { const Duration maxVideoDuration = Duration(hours: 1); const String supportedVideoType = 'video/webm'; final MockMediaRecorder mediaRecorder = MockMediaRecorder(); when(() => mediaRecorder.onError) .thenAnswer((_) => const Stream.empty()); final Camera camera = Camera( textureId: 1, cameraService: cameraService, ) ..mediaRecorder = mediaRecorder ..isVideoTypeSupported = (String type) => type == 'video/webm'; await camera.initialize(); await camera.play(); late void Function(Event) videoDataAvailableListener; late void Function(Event) videoRecordingStoppedListener; when( () => mediaRecorder.addEventListener('dataavailable', any()), ).thenAnswer((Invocation invocation) { videoDataAvailableListener = invocation.positionalArguments[1] as void Function(Event); }); when( () => mediaRecorder.addEventListener('stop', any()), ).thenAnswer((Invocation invocation) { videoRecordingStoppedListener = invocation.positionalArguments[1] as void Function(Event); }); final StreamQueue streamQueue = StreamQueue(camera.onVideoRecordedEvent); await camera.startVideoRecording(maxVideoDuration: maxVideoDuration); Blob? finalVideo; camera.blobBuilder = (List blobs, String videoType) { finalVideo = Blob(blobs, videoType); return finalVideo!; }; videoDataAvailableListener(FakeBlobEvent(Blob([]))); videoRecordingStoppedListener(Event('stop')); expect( await streamQueue.next, equals( isA() .having( (VideoRecordedEvent e) => e.cameraId, 'cameraId', textureId, ) .having( (VideoRecordedEvent e) => e.file, 'file', isA() .having( (XFile f) => f.mimeType, 'mimeType', supportedVideoType, ) .having( (XFile f) => f.name, 'name', finalVideo.hashCode.toString(), ), ) .having( (VideoRecordedEvent e) => e.maxVideoDuration, 'maxVideoDuration', maxVideoDuration, ), ), ); await streamQueue.cancel(); }); }); group('onEnded', () { testWidgets( 'emits the default video track ' 'when it emits an ended event', (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); final StreamQueue streamQueue = StreamQueue(camera.onEnded); await camera.initialize(); final List videoTracks = camera.stream!.getVideoTracks(); final MediaStreamTrack defaultVideoTrack = videoTracks.first; defaultVideoTrack.dispatchEvent(Event('ended')); expect( await streamQueue.next, equals(defaultVideoTrack), ); await streamQueue.cancel(); }); testWidgets( 'emits the default video track ' 'when the camera is stopped', (WidgetTester tester) async { final Camera camera = Camera( textureId: textureId, cameraService: cameraService, ); final StreamQueue streamQueue = StreamQueue(camera.onEnded); await camera.initialize(); final List videoTracks = camera.stream!.getVideoTracks(); final MediaStreamTrack defaultVideoTrack = videoTracks.first; camera.stop(); expect( await streamQueue.next, equals(defaultVideoTrack), ); await streamQueue.cancel(); }); }); group('onVideoRecordingError', () { testWidgets( 'emits an ErrorEvent ' 'when the media recorder fails ' 'when recording a video', (WidgetTester tester) async { final MockMediaRecorder mediaRecorder = MockMediaRecorder(); final StreamController errorController = StreamController(); final Camera camera = Camera( textureId: textureId, cameraService: cameraService, )..mediaRecorder = mediaRecorder; when(() => mediaRecorder.onError) .thenAnswer((_) => errorController.stream); final StreamQueue streamQueue = StreamQueue(camera.onVideoRecordingError); await camera.initialize(); await camera.play(); await camera.startVideoRecording(); final ErrorEvent errorEvent = ErrorEvent('type'); errorController.add(errorEvent); expect( await streamQueue.next, equals(errorEvent), ); await streamQueue.cancel(); }); }); }); }); } ================================================ FILE: packages/camera/camera_web/example/integration_test/camera_web_exception_test.dart ================================================ // Copyright 2013 The Flutter 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_web/src/types/types.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('CameraWebException', () { testWidgets('sets all properties', (WidgetTester tester) async { const int cameraId = 1; const CameraErrorCode code = CameraErrorCode.notFound; const String description = 'The camera is not found.'; final CameraWebException exception = CameraWebException(cameraId, code, description); expect(exception.cameraId, equals(cameraId)); expect(exception.code, equals(code)); expect(exception.description, equals(description)); }); testWidgets('toString includes all properties', (WidgetTester tester) async { const int cameraId = 2; const CameraErrorCode code = CameraErrorCode.notReadable; const String description = 'The camera is not readable.'; final CameraWebException exception = CameraWebException(cameraId, code, description); expect( exception.toString(), equals('CameraWebException($cameraId, $code, $description)'), ); }); }); } ================================================ FILE: packages/camera/camera_web/example/integration_test/camera_web_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:html'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#106316) // ignore: unnecessary_import import 'dart:ui'; import 'package:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_web/camera_web.dart'; import 'package:camera_web/src/camera.dart'; import 'package:camera_web/src/camera_service.dart'; import 'package:camera_web/src/types/types.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart' as widgets; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mocktail/mocktail.dart'; import 'helpers/helpers.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('CameraPlugin', () { const int cameraId = 1; late Window window; late Navigator navigator; late MediaDevices mediaDevices; late VideoElement videoElement; late Screen screen; late ScreenOrientation screenOrientation; late Document document; late Element documentElement; late CameraService cameraService; setUp(() async { window = MockWindow(); navigator = MockNavigator(); mediaDevices = MockMediaDevices(); videoElement = getVideoElementWithBlankStream(const Size(10, 10)); when(() => window.navigator).thenReturn(navigator); when(() => navigator.mediaDevices).thenReturn(mediaDevices); screen = MockScreen(); screenOrientation = MockScreenOrientation(); when(() => screen.orientation).thenReturn(screenOrientation); when(() => window.screen).thenReturn(screen); document = MockDocument(); documentElement = MockElement(); when(() => document.documentElement).thenReturn(documentElement); when(() => window.document).thenReturn(document); cameraService = MockCameraService(); when( () => cameraService.getMediaStreamForOptions( any(), cameraId: any(named: 'cameraId'), ), ).thenAnswer( (_) async => videoElement.captureStream(), ); CameraPlatform.instance = CameraPlugin( cameraService: cameraService, )..window = window; }); setUpAll(() { registerFallbackValue(MockMediaStreamTrack()); registerFallbackValue(MockCameraOptions()); registerFallbackValue(FlashMode.off); }); testWidgets('CameraPlugin is the live instance', (WidgetTester tester) async { expect(CameraPlatform.instance, isA()); }); group('availableCameras', () { setUp(() { when( () => cameraService.getFacingModeForVideoTrack( any(), ), ).thenReturn(null); when(mediaDevices.enumerateDevices).thenAnswer( (_) async => [], ); }); testWidgets('requests video and audio permissions', (WidgetTester tester) async { final List _ = await CameraPlatform.instance.availableCameras(); verify( () => cameraService.getMediaStreamForOptions( const CameraOptions( audio: AudioConstraints(enabled: true), ), ), ).called(1); }); testWidgets( 'releases the camera stream ' 'used to request video and audio permissions', (WidgetTester tester) async { final MockMediaStreamTrack videoTrack = MockMediaStreamTrack(); bool videoTrackStopped = false; when(videoTrack.stop).thenAnswer((Invocation _) { videoTrackStopped = true; }); when( () => cameraService.getMediaStreamForOptions( const CameraOptions( audio: AudioConstraints(enabled: true), ), ), ).thenAnswer( (_) => Future.value( FakeMediaStream([videoTrack]), ), ); final List _ = await CameraPlatform.instance.availableCameras(); expect(videoTrackStopped, isTrue); }); testWidgets( 'gets a video stream ' 'for a video input device', (WidgetTester tester) async { final FakeMediaDeviceInfo videoDevice = FakeMediaDeviceInfo( '1', 'Camera 1', MediaDeviceKind.videoInput, ); when(mediaDevices.enumerateDevices).thenAnswer( (_) => Future>.value([videoDevice]), ); final List _ = await CameraPlatform.instance.availableCameras(); verify( () => cameraService.getMediaStreamForOptions( CameraOptions( video: VideoConstraints( deviceId: videoDevice.deviceId, ), ), ), ).called(1); }); testWidgets( 'does not get a video stream ' 'for the video input device ' 'with an empty device id', (WidgetTester tester) async { final FakeMediaDeviceInfo videoDevice = FakeMediaDeviceInfo( '', 'Camera 1', MediaDeviceKind.videoInput, ); when(mediaDevices.enumerateDevices).thenAnswer( (_) => Future>.value([videoDevice]), ); final List _ = await CameraPlatform.instance.availableCameras(); verifyNever( () => cameraService.getMediaStreamForOptions( CameraOptions( video: VideoConstraints( deviceId: videoDevice.deviceId, ), ), ), ); }); testWidgets( 'gets the facing mode ' 'from the first available video track ' 'of the video input device', (WidgetTester tester) async { final FakeMediaDeviceInfo videoDevice = FakeMediaDeviceInfo( '1', 'Camera 1', MediaDeviceKind.videoInput, ); final FakeMediaStream videoStream = FakeMediaStream( [MockMediaStreamTrack(), MockMediaStreamTrack()]); when( () => cameraService.getMediaStreamForOptions( CameraOptions( video: VideoConstraints(deviceId: videoDevice.deviceId), ), ), ).thenAnswer((Invocation _) => Future.value(videoStream)); when(mediaDevices.enumerateDevices).thenAnswer( (_) => Future>.value([videoDevice]), ); final List _ = await CameraPlatform.instance.availableCameras(); verify( () => cameraService.getFacingModeForVideoTrack( videoStream.getVideoTracks().first, ), ).called(1); }); testWidgets( 'returns appropriate camera descriptions ' 'for multiple video devices ' 'based on video streams', (WidgetTester tester) async { final FakeMediaDeviceInfo firstVideoDevice = FakeMediaDeviceInfo( '1', 'Camera 1', MediaDeviceKind.videoInput, ); final FakeMediaDeviceInfo secondVideoDevice = FakeMediaDeviceInfo( '4', 'Camera 4', MediaDeviceKind.videoInput, ); // Create a video stream for the first video device. final FakeMediaStream firstVideoStream = FakeMediaStream( [MockMediaStreamTrack(), MockMediaStreamTrack()]); // Create a video stream for the second video device. final FakeMediaStream secondVideoStream = FakeMediaStream([MockMediaStreamTrack()]); // Mock media devices to return two video input devices // and two audio devices. when(mediaDevices.enumerateDevices).thenAnswer( (_) => Future>.value([ firstVideoDevice, FakeMediaDeviceInfo( '2', 'Audio Input 2', MediaDeviceKind.audioInput, ), FakeMediaDeviceInfo( '3', 'Audio Output 3', MediaDeviceKind.audioOutput, ), secondVideoDevice, ]), ); // Mock camera service to return the first video stream // for the first video device. when( () => cameraService.getMediaStreamForOptions( CameraOptions( video: VideoConstraints(deviceId: firstVideoDevice.deviceId), ), ), ).thenAnswer( (Invocation _) => Future.value(firstVideoStream)); // Mock camera service to return the second video stream // for the second video device. when( () => cameraService.getMediaStreamForOptions( CameraOptions( video: VideoConstraints(deviceId: secondVideoDevice.deviceId), ), ), ).thenAnswer( (Invocation _) => Future.value(secondVideoStream)); // Mock camera service to return a user facing mode // for the first video stream. when( () => cameraService.getFacingModeForVideoTrack( firstVideoStream.getVideoTracks().first, ), ).thenReturn('user'); when(() => cameraService.mapFacingModeToLensDirection('user')) .thenReturn(CameraLensDirection.front); // Mock camera service to return an environment facing mode // for the second video stream. when( () => cameraService.getFacingModeForVideoTrack( secondVideoStream.getVideoTracks().first, ), ).thenReturn('environment'); when(() => cameraService.mapFacingModeToLensDirection('environment')) .thenReturn(CameraLensDirection.back); final List cameras = await CameraPlatform.instance.availableCameras(); // Expect two cameras and ignore two audio devices. expect( cameras, equals([ CameraDescription( name: firstVideoDevice.label!, lensDirection: CameraLensDirection.front, sensorOrientation: 0, ), CameraDescription( name: secondVideoDevice.label!, lensDirection: CameraLensDirection.back, sensorOrientation: 0, ) ]), ); }); testWidgets( 'sets camera metadata ' 'for the camera description', (WidgetTester tester) async { final FakeMediaDeviceInfo videoDevice = FakeMediaDeviceInfo( '1', 'Camera 1', MediaDeviceKind.videoInput, ); final FakeMediaStream videoStream = FakeMediaStream( [MockMediaStreamTrack(), MockMediaStreamTrack()]); when(mediaDevices.enumerateDevices).thenAnswer( (_) => Future>.value([videoDevice]), ); when( () => cameraService.getMediaStreamForOptions( CameraOptions( video: VideoConstraints(deviceId: videoDevice.deviceId), ), ), ).thenAnswer((Invocation _) => Future.value(videoStream)); when( () => cameraService.getFacingModeForVideoTrack( videoStream.getVideoTracks().first, ), ).thenReturn('left'); when(() => cameraService.mapFacingModeToLensDirection('left')) .thenReturn(CameraLensDirection.external); final CameraDescription camera = (await CameraPlatform.instance.availableCameras()).first; expect( (CameraPlatform.instance as CameraPlugin).camerasMetadata, equals({ camera: CameraMetadata( deviceId: videoDevice.deviceId!, facingMode: 'left', ) }), ); }); testWidgets( 'releases the video stream ' 'of a video input device', (WidgetTester tester) async { final FakeMediaDeviceInfo videoDevice = FakeMediaDeviceInfo( '1', 'Camera 1', MediaDeviceKind.videoInput, ); final FakeMediaStream videoStream = FakeMediaStream( [MockMediaStreamTrack(), MockMediaStreamTrack()]); when(mediaDevices.enumerateDevices).thenAnswer( (_) => Future>.value([videoDevice]), ); when( () => cameraService.getMediaStreamForOptions( CameraOptions( video: VideoConstraints(deviceId: videoDevice.deviceId), ), ), ).thenAnswer((Invocation _) => Future.value(videoStream)); final List _ = await CameraPlatform.instance.availableCameras(); for (final MediaStreamTrack videoTrack in videoStream.getVideoTracks()) { verify(videoTrack.stop).called(1); } }); group('throws CameraException', () { testWidgets( 'with notSupported error ' 'when there are no media devices', (WidgetTester tester) async { when(() => navigator.mediaDevices).thenReturn(null); expect( () => CameraPlatform.instance.availableCameras(), throwsA( isA().having( (CameraException e) => e.code, 'code', CameraErrorCode.notSupported.toString(), ), ), ); }); testWidgets('when MediaDevices.enumerateDevices throws DomException', (WidgetTester tester) async { final FakeDomException exception = FakeDomException(DomException.UNKNOWN); when(mediaDevices.enumerateDevices).thenThrow(exception); expect( () => CameraPlatform.instance.availableCameras(), throwsA( isA().having( (CameraException e) => e.code, 'code', exception.name, ), ), ); }); testWidgets( 'when CameraService.getMediaStreamForOptions ' 'throws CameraWebException', (WidgetTester tester) async { final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.security, 'description', ); when(() => cameraService.getMediaStreamForOptions(any())) .thenThrow(exception); expect( () => CameraPlatform.instance.availableCameras(), throwsA( isA().having( (CameraException e) => e.code, 'code', exception.code.toString(), ), ), ); }); testWidgets( 'when CameraService.getMediaStreamForOptions ' 'throws PlatformException', (WidgetTester tester) async { final PlatformException exception = PlatformException( code: CameraErrorCode.notSupported.toString(), message: 'message', ); when(() => cameraService.getMediaStreamForOptions(any())) .thenThrow(exception); expect( () => CameraPlatform.instance.availableCameras(), throwsA( isA().having( (CameraException e) => e.code, 'code', exception.code, ), ), ); }); }); }); group('createCamera', () { group('creates a camera', () { const Size ultraHighResolutionSize = Size(3840, 2160); const Size maxResolutionSize = Size(3840, 2160); const CameraDescription cameraDescription = CameraDescription( name: 'name', lensDirection: CameraLensDirection.front, sensorOrientation: 0, ); const CameraMetadata cameraMetadata = CameraMetadata( deviceId: 'deviceId', facingMode: 'user', ); setUp(() { // Add metadata for the camera description. (CameraPlatform.instance as CameraPlugin) .camerasMetadata[cameraDescription] = cameraMetadata; when( () => cameraService.mapFacingModeToCameraType('user'), ).thenReturn(CameraType.user); }); testWidgets('with appropriate options', (WidgetTester tester) async { when( () => cameraService .mapResolutionPresetToSize(ResolutionPreset.ultraHigh), ).thenReturn(ultraHighResolutionSize); final int cameraId = await CameraPlatform.instance.createCamera( cameraDescription, ResolutionPreset.ultraHigh, enableAudio: true, ); expect( (CameraPlatform.instance as CameraPlugin).cameras[cameraId], isA() .having( (Camera camera) => camera.textureId, 'textureId', cameraId, ) .having( (Camera camera) => camera.options, 'options', CameraOptions( audio: const AudioConstraints(enabled: true), video: VideoConstraints( facingMode: FacingModeConstraint(CameraType.user), width: VideoSizeConstraint( ideal: ultraHighResolutionSize.width.toInt(), ), height: VideoSizeConstraint( ideal: ultraHighResolutionSize.height.toInt(), ), deviceId: cameraMetadata.deviceId, ), ), ), ); }); testWidgets( 'with a max resolution preset ' 'and enabled audio set to false ' 'when no options are specified', (WidgetTester tester) async { when( () => cameraService.mapResolutionPresetToSize(ResolutionPreset.max), ).thenReturn(maxResolutionSize); final int cameraId = await CameraPlatform.instance.createCamera( cameraDescription, null, ); expect( (CameraPlatform.instance as CameraPlugin).cameras[cameraId], isA().having( (Camera camera) => camera.options, 'options', CameraOptions( audio: const AudioConstraints(), video: VideoConstraints( facingMode: FacingModeConstraint(CameraType.user), width: VideoSizeConstraint( ideal: maxResolutionSize.width.toInt(), ), height: VideoSizeConstraint( ideal: maxResolutionSize.height.toInt(), ), deviceId: cameraMetadata.deviceId, ), ), ), ); }); }); testWidgets( 'throws CameraException ' 'with missingMetadata error ' 'if there is no metadata ' 'for the given camera description', (WidgetTester tester) async { expect( () => CameraPlatform.instance.createCamera( const CameraDescription( name: 'name', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.ultraHigh, ), throwsA( isA().having( (CameraException e) => e.code, 'code', CameraErrorCode.missingMetadata.toString(), ), ), ); }); }); group('initializeCamera', () { late Camera camera; late VideoElement videoElement; late StreamController errorStreamController, abortStreamController; late StreamController endedStreamController; setUp(() { camera = MockCamera(); videoElement = MockVideoElement(); errorStreamController = StreamController(); abortStreamController = StreamController(); endedStreamController = StreamController(); when(camera.getVideoSize).thenReturn(const Size(10, 10)); when(camera.initialize) .thenAnswer((Invocation _) => Future.value()); when(camera.play).thenAnswer((Invocation _) => Future.value()); when(() => camera.videoElement).thenReturn(videoElement); when(() => videoElement.onError).thenAnswer((Invocation _) => FakeElementStream(errorStreamController.stream)); when(() => videoElement.onAbort).thenAnswer((Invocation _) => FakeElementStream(abortStreamController.stream)); when(() => camera.onEnded) .thenAnswer((Invocation _) => endedStreamController.stream); }); testWidgets('initializes and plays the camera', (WidgetTester tester) async { // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; await CameraPlatform.instance.initializeCamera(cameraId); verify(camera.initialize).called(1); verify(camera.play).called(1); }); testWidgets('starts listening to the camera video error and abort events', (WidgetTester tester) async { // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect(errorStreamController.hasListener, isFalse); expect(abortStreamController.hasListener, isFalse); await CameraPlatform.instance.initializeCamera(cameraId); expect(errorStreamController.hasListener, isTrue); expect(abortStreamController.hasListener, isTrue); }); testWidgets('starts listening to the camera ended events', (WidgetTester tester) async { // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect(endedStreamController.hasListener, isFalse); await CameraPlatform.instance.initializeCamera(cameraId); expect(endedStreamController.hasListener, isTrue); }); group('throws PlatformException', () { testWidgets( 'with notFound error ' 'if the camera does not exist', (WidgetTester tester) async { expect( () => CameraPlatform.instance.initializeCamera(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.notFound.toString(), ), ), ); }); testWidgets('when camera throws CameraWebException', (WidgetTester tester) async { final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.permissionDenied, 'description', ); when(camera.initialize).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () => CameraPlatform.instance.initializeCamera(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.code.toString(), ), ), ); }); testWidgets('when camera throws DomException', (WidgetTester tester) async { final FakeDomException exception = FakeDomException(DomException.NOT_ALLOWED); when(camera.initialize) .thenAnswer((Invocation _) => Future.value()); when(camera.play).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () => CameraPlatform.instance.initializeCamera(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.name, ), ), ); }); }); }); group('lockCaptureOrientation', () { setUp(() { when( () => cameraService.mapDeviceOrientationToOrientationType(any()), ).thenReturn(OrientationType.portraitPrimary); }); testWidgets( 'requests full-screen mode ' 'on documentElement', (WidgetTester tester) async { await CameraPlatform.instance.lockCaptureOrientation( cameraId, DeviceOrientation.portraitUp, ); verify(documentElement.requestFullscreen).called(1); }); testWidgets( 'locks the capture orientation ' 'based on the given device orientation', (WidgetTester tester) async { when( () => cameraService.mapDeviceOrientationToOrientationType( DeviceOrientation.landscapeRight, ), ).thenReturn(OrientationType.landscapeSecondary); await CameraPlatform.instance.lockCaptureOrientation( cameraId, DeviceOrientation.landscapeRight, ); verify( () => cameraService.mapDeviceOrientationToOrientationType( DeviceOrientation.landscapeRight, ), ).called(1); verify( () => screenOrientation.lock( OrientationType.landscapeSecondary, ), ).called(1); }); group('throws PlatformException', () { testWidgets( 'with orientationNotSupported error ' 'when screen is not supported', (WidgetTester tester) async { when(() => window.screen).thenReturn(null); expect( () => CameraPlatform.instance.lockCaptureOrientation( cameraId, DeviceOrientation.portraitUp, ), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.orientationNotSupported.toString(), ), ), ); }); testWidgets( 'with orientationNotSupported error ' 'when screen orientation is not supported', (WidgetTester tester) async { when(() => screen.orientation).thenReturn(null); expect( () => CameraPlatform.instance.lockCaptureOrientation( cameraId, DeviceOrientation.portraitUp, ), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.orientationNotSupported.toString(), ), ), ); }); testWidgets( 'with orientationNotSupported error ' 'when documentElement is not available', (WidgetTester tester) async { when(() => document.documentElement).thenReturn(null); expect( () => CameraPlatform.instance.lockCaptureOrientation( cameraId, DeviceOrientation.portraitUp, ), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.orientationNotSupported.toString(), ), ), ); }); testWidgets('when lock throws DomException', (WidgetTester tester) async { final FakeDomException exception = FakeDomException(DomException.NOT_ALLOWED); when(() => screenOrientation.lock(any())).thenThrow(exception); expect( () => CameraPlatform.instance.lockCaptureOrientation( cameraId, DeviceOrientation.portraitDown, ), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.name, ), ), ); }); }); }); group('unlockCaptureOrientation', () { setUp(() { when( () => cameraService.mapDeviceOrientationToOrientationType(any()), ).thenReturn(OrientationType.portraitPrimary); }); testWidgets('unlocks the capture orientation', (WidgetTester tester) async { await CameraPlatform.instance.unlockCaptureOrientation( cameraId, ); verify(screenOrientation.unlock).called(1); }); group('throws PlatformException', () { testWidgets( 'with orientationNotSupported error ' 'when screen is not supported', (WidgetTester tester) async { when(() => window.screen).thenReturn(null); expect( () => CameraPlatform.instance.unlockCaptureOrientation( cameraId, ), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.orientationNotSupported.toString(), ), ), ); }); testWidgets( 'with orientationNotSupported error ' 'when screen orientation is not supported', (WidgetTester tester) async { when(() => screen.orientation).thenReturn(null); expect( () => CameraPlatform.instance.unlockCaptureOrientation( cameraId, ), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.orientationNotSupported.toString(), ), ), ); }); testWidgets( 'with orientationNotSupported error ' 'when documentElement is not available', (WidgetTester tester) async { when(() => document.documentElement).thenReturn(null); expect( () => CameraPlatform.instance.unlockCaptureOrientation( cameraId, ), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.orientationNotSupported.toString(), ), ), ); }); testWidgets('when unlock throws DomException', (WidgetTester tester) async { final FakeDomException exception = FakeDomException(DomException.NOT_ALLOWED); when(screenOrientation.unlock).thenThrow(exception); expect( () => CameraPlatform.instance.unlockCaptureOrientation( cameraId, ), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.name, ), ), ); }); }); }); group('takePicture', () { testWidgets('captures a picture', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final MockXFile capturedPicture = MockXFile(); when(camera.takePicture) .thenAnswer((Invocation _) => Future.value(capturedPicture)); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; final XFile picture = await CameraPlatform.instance.takePicture(cameraId); verify(camera.takePicture).called(1); expect(picture, equals(capturedPicture)); }); group('throws PlatformException', () { testWidgets( 'with notFound error ' 'if the camera does not exist', (WidgetTester tester) async { expect( () => CameraPlatform.instance.takePicture(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.notFound.toString(), ), ), ); }); testWidgets('when takePicture throws DomException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final FakeDomException exception = FakeDomException(DomException.NOT_SUPPORTED); when(camera.takePicture).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () => CameraPlatform.instance.takePicture(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.name, ), ), ); }); testWidgets('when takePicture throws CameraWebException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.notStarted, 'description', ); when(camera.takePicture).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () => CameraPlatform.instance.takePicture(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.code.toString(), ), ), ); }); }); }); group('startVideoRecording', () { late Camera camera; setUp(() { camera = MockCamera(); when(camera.startVideoRecording).thenAnswer((Invocation _) async {}); when(() => camera.onVideoRecordingError) .thenAnswer((Invocation _) => const Stream.empty()); }); testWidgets('starts a video recording', (WidgetTester tester) async { // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; await CameraPlatform.instance.startVideoRecording(cameraId); verify(camera.startVideoRecording).called(1); }); testWidgets('listens to the onVideoRecordingError stream', (WidgetTester tester) async { final StreamController videoRecordingErrorController = StreamController(); when(() => camera.onVideoRecordingError) .thenAnswer((Invocation _) => videoRecordingErrorController.stream); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; await CameraPlatform.instance.startVideoRecording(cameraId); expect( videoRecordingErrorController.hasListener, isTrue, ); }); group('throws PlatformException', () { testWidgets( 'with notFound error ' 'if the camera does not exist', (WidgetTester tester) async { expect( () => CameraPlatform.instance.startVideoRecording(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.notFound.toString(), ), ), ); }); testWidgets('when startVideoRecording throws DomException', (WidgetTester tester) async { final FakeDomException exception = FakeDomException(DomException.INVALID_STATE); when(camera.startVideoRecording).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () => CameraPlatform.instance.startVideoRecording(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.name, ), ), ); }); testWidgets('when startVideoRecording throws CameraWebException', (WidgetTester tester) async { final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.notStarted, 'description', ); when(camera.startVideoRecording).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () => CameraPlatform.instance.startVideoRecording(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.code.toString(), ), ), ); }); }); }); group('startVideoCapturing', () { late Camera camera; setUp(() { camera = MockCamera(); when(camera.startVideoRecording).thenAnswer((Invocation _) async {}); when(() => camera.onVideoRecordingError) .thenAnswer((Invocation _) => const Stream.empty()); }); testWidgets('fails if trying to stream', (WidgetTester tester) async { // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () => CameraPlatform.instance.startVideoCapturing(VideoCaptureOptions( cameraId, streamCallback: (CameraImageData imageData) {})), throwsA( isA(), ), ); }); }); group('stopVideoRecording', () { testWidgets('stops a video recording', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final MockXFile capturedVideo = MockXFile(); when(camera.stopVideoRecording) .thenAnswer((Invocation _) => Future.value(capturedVideo)); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; final XFile video = await CameraPlatform.instance.stopVideoRecording(cameraId); verify(camera.stopVideoRecording).called(1); expect(video, capturedVideo); }); testWidgets('stops listening to the onVideoRecordingError stream', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final StreamController videoRecordingErrorController = StreamController(); when(camera.startVideoRecording).thenAnswer((Invocation _) async {}); when(camera.stopVideoRecording) .thenAnswer((Invocation _) => Future.value(MockXFile())); when(() => camera.onVideoRecordingError) .thenAnswer((Invocation _) => videoRecordingErrorController.stream); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; await CameraPlatform.instance.startVideoRecording(cameraId); final XFile _ = await CameraPlatform.instance.stopVideoRecording(cameraId); expect( videoRecordingErrorController.hasListener, isFalse, ); }); group('throws PlatformException', () { testWidgets( 'with notFound error ' 'if the camera does not exist', (WidgetTester tester) async { expect( () => CameraPlatform.instance.stopVideoRecording(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.notFound.toString(), ), ), ); }); testWidgets('when stopVideoRecording throws DomException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final FakeDomException exception = FakeDomException(DomException.INVALID_STATE); when(camera.stopVideoRecording).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () => CameraPlatform.instance.stopVideoRecording(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.name, ), ), ); }); testWidgets('when stopVideoRecording throws CameraWebException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.notStarted, 'description', ); when(camera.stopVideoRecording).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () => CameraPlatform.instance.stopVideoRecording(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.code.toString(), ), ), ); }); }); }); group('pauseVideoRecording', () { testWidgets('pauses a video recording', (WidgetTester tester) async { final MockCamera camera = MockCamera(); when(camera.pauseVideoRecording).thenAnswer((Invocation _) async {}); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; await CameraPlatform.instance.pauseVideoRecording(cameraId); verify(camera.pauseVideoRecording).called(1); }); group('throws PlatformException', () { testWidgets( 'with notFound error ' 'if the camera does not exist', (WidgetTester tester) async { expect( () => CameraPlatform.instance.pauseVideoRecording(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.notFound.toString(), ), ), ); }); testWidgets('when pauseVideoRecording throws DomException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final FakeDomException exception = FakeDomException(DomException.INVALID_STATE); when(camera.pauseVideoRecording).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () => CameraPlatform.instance.pauseVideoRecording(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.name, ), ), ); }); testWidgets('when pauseVideoRecording throws CameraWebException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.notStarted, 'description', ); when(camera.pauseVideoRecording).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () => CameraPlatform.instance.pauseVideoRecording(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.code.toString(), ), ), ); }); }); }); group('resumeVideoRecording', () { testWidgets('resumes a video recording', (WidgetTester tester) async { final MockCamera camera = MockCamera(); when(camera.resumeVideoRecording).thenAnswer((Invocation _) async {}); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; await CameraPlatform.instance.resumeVideoRecording(cameraId); verify(camera.resumeVideoRecording).called(1); }); group('throws PlatformException', () { testWidgets( 'with notFound error ' 'if the camera does not exist', (WidgetTester tester) async { expect( () => CameraPlatform.instance.resumeVideoRecording(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.notFound.toString(), ), ), ); }); testWidgets('when resumeVideoRecording throws DomException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final FakeDomException exception = FakeDomException(DomException.INVALID_STATE); when(camera.resumeVideoRecording).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () => CameraPlatform.instance.resumeVideoRecording(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.name, ), ), ); }); testWidgets('when resumeVideoRecording throws CameraWebException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.notStarted, 'description', ); when(camera.resumeVideoRecording).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () => CameraPlatform.instance.resumeVideoRecording(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.code.toString(), ), ), ); }); }); }); group('setFlashMode', () { testWidgets('calls setFlashMode on the camera', (WidgetTester tester) async { final MockCamera camera = MockCamera(); const FlashMode flashMode = FlashMode.always; // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; await CameraPlatform.instance.setFlashMode( cameraId, flashMode, ); verify(() => camera.setFlashMode(flashMode)).called(1); }); group('throws PlatformException', () { testWidgets( 'with notFound error ' 'if the camera does not exist', (WidgetTester tester) async { expect( () => CameraPlatform.instance.setFlashMode( cameraId, FlashMode.always, ), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.notFound.toString(), ), ), ); }); testWidgets('when setFlashMode throws DomException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final FakeDomException exception = FakeDomException(DomException.NOT_SUPPORTED); when(() => camera.setFlashMode(any())).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () => CameraPlatform.instance.setFlashMode( cameraId, FlashMode.always, ), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.name, ), ), ); }); testWidgets('when setFlashMode throws CameraWebException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.notStarted, 'description', ); when(() => camera.setFlashMode(any())).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () => CameraPlatform.instance.setFlashMode( cameraId, FlashMode.torch, ), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.code.toString(), ), ), ); }); }); }); testWidgets('setExposureMode throws UnimplementedError', (WidgetTester tester) async { expect( () => CameraPlatform.instance.setExposureMode( cameraId, ExposureMode.auto, ), throwsUnimplementedError, ); }); testWidgets('setExposurePoint throws UnimplementedError', (WidgetTester tester) async { expect( () => CameraPlatform.instance.setExposurePoint( cameraId, const Point(0, 0), ), throwsUnimplementedError, ); }); testWidgets('getMinExposureOffset throws UnimplementedError', (WidgetTester tester) async { expect( () => CameraPlatform.instance.getMinExposureOffset(cameraId), throwsUnimplementedError, ); }); testWidgets('getMaxExposureOffset throws UnimplementedError', (WidgetTester tester) async { expect( () => CameraPlatform.instance.getMaxExposureOffset(cameraId), throwsUnimplementedError, ); }); testWidgets('getExposureOffsetStepSize throws UnimplementedError', (WidgetTester tester) async { expect( () => CameraPlatform.instance.getExposureOffsetStepSize(cameraId), throwsUnimplementedError, ); }); testWidgets('setExposureOffset throws UnimplementedError', (WidgetTester tester) async { expect( () => CameraPlatform.instance.setExposureOffset( cameraId, 0, ), throwsUnimplementedError, ); }); testWidgets('setFocusMode throws UnimplementedError', (WidgetTester tester) async { expect( () => CameraPlatform.instance.setFocusMode( cameraId, FocusMode.auto, ), throwsUnimplementedError, ); }); testWidgets('setFocusPoint throws UnimplementedError', (WidgetTester tester) async { expect( () => CameraPlatform.instance.setFocusPoint( cameraId, const Point(0, 0), ), throwsUnimplementedError, ); }); group('getMaxZoomLevel', () { testWidgets('calls getMaxZoomLevel on the camera', (WidgetTester tester) async { final MockCamera camera = MockCamera(); const double maximumZoomLevel = 100.0; when(camera.getMaxZoomLevel).thenReturn(maximumZoomLevel); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( await CameraPlatform.instance.getMaxZoomLevel( cameraId, ), equals(maximumZoomLevel), ); verify(camera.getMaxZoomLevel).called(1); }); group('throws PlatformException', () { testWidgets( 'with notFound error ' 'if the camera does not exist', (WidgetTester tester) async { expect( () async => CameraPlatform.instance.getMaxZoomLevel( cameraId, ), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.notFound.toString(), ), ), ); }); testWidgets('when getMaxZoomLevel throws DomException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final FakeDomException exception = FakeDomException(DomException.NOT_SUPPORTED); when(camera.getMaxZoomLevel).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () async => CameraPlatform.instance.getMaxZoomLevel( cameraId, ), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.name, ), ), ); }); testWidgets('when getMaxZoomLevel throws CameraWebException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.notStarted, 'description', ); when(camera.getMaxZoomLevel).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () async => CameraPlatform.instance.getMaxZoomLevel( cameraId, ), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.code.toString(), ), ), ); }); }); }); group('getMinZoomLevel', () { testWidgets('calls getMinZoomLevel on the camera', (WidgetTester tester) async { final MockCamera camera = MockCamera(); const double minimumZoomLevel = 100.0; when(camera.getMinZoomLevel).thenReturn(minimumZoomLevel); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( await CameraPlatform.instance.getMinZoomLevel( cameraId, ), equals(minimumZoomLevel), ); verify(camera.getMinZoomLevel).called(1); }); group('throws PlatformException', () { testWidgets( 'with notFound error ' 'if the camera does not exist', (WidgetTester tester) async { expect( () async => CameraPlatform.instance.getMinZoomLevel( cameraId, ), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.notFound.toString(), ), ), ); }); testWidgets('when getMinZoomLevel throws DomException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final FakeDomException exception = FakeDomException(DomException.NOT_SUPPORTED); when(camera.getMinZoomLevel).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () async => CameraPlatform.instance.getMinZoomLevel( cameraId, ), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.name, ), ), ); }); testWidgets('when getMinZoomLevel throws CameraWebException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.notStarted, 'description', ); when(camera.getMinZoomLevel).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () async => CameraPlatform.instance.getMinZoomLevel( cameraId, ), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.code.toString(), ), ), ); }); }); }); group('setZoomLevel', () { testWidgets('calls setZoomLevel on the camera', (WidgetTester tester) async { final MockCamera camera = MockCamera(); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; const double zoom = 100.0; await CameraPlatform.instance.setZoomLevel(cameraId, zoom); verify(() => camera.setZoomLevel(zoom)).called(1); }); group('throws CameraException', () { testWidgets( 'with notFound error ' 'if the camera does not exist', (WidgetTester tester) async { expect( () async => CameraPlatform.instance.setZoomLevel( cameraId, 100.0, ), throwsA( isA().having( (CameraException e) => e.code, 'code', CameraErrorCode.notFound.toString(), ), ), ); }); testWidgets('when setZoomLevel throws DomException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final FakeDomException exception = FakeDomException(DomException.NOT_SUPPORTED); when(() => camera.setZoomLevel(any())).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () async => CameraPlatform.instance.setZoomLevel( cameraId, 100.0, ), throwsA( isA().having( (CameraException e) => e.code, 'code', exception.name, ), ), ); }); testWidgets('when setZoomLevel throws PlatformException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final PlatformException exception = PlatformException( code: CameraErrorCode.notSupported.toString(), message: 'message', ); when(() => camera.setZoomLevel(any())).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () async => CameraPlatform.instance.setZoomLevel( cameraId, 100.0, ), throwsA( isA().having( (CameraException e) => e.code, 'code', exception.code, ), ), ); }); testWidgets('when setZoomLevel throws CameraWebException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.notStarted, 'description', ); when(() => camera.setZoomLevel(any())).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () async => CameraPlatform.instance.setZoomLevel( cameraId, 100.0, ), throwsA( isA().having( (CameraException e) => e.code, 'code', exception.code.toString(), ), ), ); }); }); }); group('pausePreview', () { testWidgets('calls pause on the camera', (WidgetTester tester) async { final MockCamera camera = MockCamera(); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; await CameraPlatform.instance.pausePreview(cameraId); verify(camera.pause).called(1); }); group('throws PlatformException', () { testWidgets( 'with notFound error ' 'if the camera does not exist', (WidgetTester tester) async { expect( () async => CameraPlatform.instance.pausePreview(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.notFound.toString(), ), ), ); }); testWidgets('when pause throws DomException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final FakeDomException exception = FakeDomException(DomException.NOT_SUPPORTED); when(camera.pause).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () async => CameraPlatform.instance.pausePreview(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.name, ), ), ); }); }); }); group('resumePreview', () { testWidgets('calls play on the camera', (WidgetTester tester) async { final MockCamera camera = MockCamera(); when(camera.play).thenAnswer((Invocation _) async {}); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; await CameraPlatform.instance.resumePreview(cameraId); verify(camera.play).called(1); }); group('throws PlatformException', () { testWidgets( 'with notFound error ' 'if the camera does not exist', (WidgetTester tester) async { expect( () async => CameraPlatform.instance.resumePreview(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.notFound.toString(), ), ), ); }); testWidgets('when play throws DomException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final FakeDomException exception = FakeDomException(DomException.NOT_SUPPORTED); when(camera.play).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () async => CameraPlatform.instance.resumePreview(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.name, ), ), ); }); testWidgets('when play throws CameraWebException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.unknown, 'description', ); when(camera.play).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () async => CameraPlatform.instance.resumePreview(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.code.toString(), ), ), ); }); }); }); testWidgets( 'buildPreview returns an HtmlElementView ' 'with an appropriate view type', (WidgetTester tester) async { final Camera camera = Camera( textureId: cameraId, cameraService: cameraService, ); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( CameraPlatform.instance.buildPreview(cameraId), isA().having( (widgets.HtmlElementView view) => view.viewType, 'viewType', camera.getViewType(), ), ); }); group('dispose', () { late Camera camera; late VideoElement videoElement; late StreamController errorStreamController, abortStreamController; late StreamController endedStreamController; late StreamController videoRecordingErrorController; setUp(() { camera = MockCamera(); videoElement = MockVideoElement(); errorStreamController = StreamController(); abortStreamController = StreamController(); endedStreamController = StreamController(); videoRecordingErrorController = StreamController(); when(camera.getVideoSize).thenReturn(const Size(10, 10)); when(camera.initialize) .thenAnswer((Invocation _) => Future.value()); when(camera.play).thenAnswer((Invocation _) => Future.value()); when(camera.dispose).thenAnswer((Invocation _) => Future.value()); when(() => camera.videoElement).thenReturn(videoElement); when(() => videoElement.onError).thenAnswer((Invocation _) => FakeElementStream(errorStreamController.stream)); when(() => videoElement.onAbort).thenAnswer((Invocation _) => FakeElementStream(abortStreamController.stream)); when(() => camera.onEnded) .thenAnswer((Invocation _) => endedStreamController.stream); when(() => camera.onVideoRecordingError) .thenAnswer((Invocation _) => videoRecordingErrorController.stream); when(camera.startVideoRecording).thenAnswer((Invocation _) async {}); }); testWidgets('disposes the correct camera', (WidgetTester tester) async { const int firstCameraId = 0; const int secondCameraId = 1; final MockCamera firstCamera = MockCamera(); final MockCamera secondCamera = MockCamera(); when(firstCamera.dispose) .thenAnswer((Invocation _) => Future.value()); when(secondCamera.dispose) .thenAnswer((Invocation _) => Future.value()); // Save cameras in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras.addAll({ firstCameraId: firstCamera, secondCameraId: secondCamera, }); // Dispose the first camera. await CameraPlatform.instance.dispose(firstCameraId); // The first camera should be disposed. verify(firstCamera.dispose).called(1); verifyNever(secondCamera.dispose); // The first camera should be removed from the camera plugin. expect( (CameraPlatform.instance as CameraPlugin).cameras, equals({ secondCameraId: secondCamera, }), ); }); testWidgets('cancels the camera video error and abort subscriptions', (WidgetTester tester) async { // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; await CameraPlatform.instance.initializeCamera(cameraId); await CameraPlatform.instance.dispose(cameraId); expect(errorStreamController.hasListener, isFalse); expect(abortStreamController.hasListener, isFalse); }); testWidgets('cancels the camera ended subscriptions', (WidgetTester tester) async { // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; await CameraPlatform.instance.initializeCamera(cameraId); await CameraPlatform.instance.dispose(cameraId); expect(endedStreamController.hasListener, isFalse); }); testWidgets('cancels the camera video recording error subscriptions', (WidgetTester tester) async { // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; await CameraPlatform.instance.initializeCamera(cameraId); await CameraPlatform.instance.startVideoRecording(cameraId); await CameraPlatform.instance.dispose(cameraId); expect(videoRecordingErrorController.hasListener, isFalse); }); group('throws PlatformException', () { testWidgets( 'with notFound error ' 'if the camera does not exist', (WidgetTester tester) async { expect( () => CameraPlatform.instance.dispose(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.notFound.toString(), ), ), ); }); testWidgets('when dispose throws DomException', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final FakeDomException exception = FakeDomException(DomException.INVALID_ACCESS); when(camera.dispose).thenThrow(exception); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( () => CameraPlatform.instance.dispose(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', exception.name, ), ), ); }); }); }); group('getCamera', () { testWidgets('returns the correct camera', (WidgetTester tester) async { final Camera camera = Camera( textureId: cameraId, cameraService: cameraService, ); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; expect( (CameraPlatform.instance as CameraPlugin).getCamera(cameraId), equals(camera), ); }); testWidgets( 'throws PlatformException ' 'with notFound error ' 'if the camera does not exist', (WidgetTester tester) async { expect( () => (CameraPlatform.instance as CameraPlugin).getCamera(cameraId), throwsA( isA().having( (PlatformException e) => e.code, 'code', CameraErrorCode.notFound.toString(), ), ), ); }); }); group('events', () { late Camera camera; late VideoElement videoElement; late StreamController errorStreamController, abortStreamController; late StreamController endedStreamController; late StreamController videoRecordingErrorController; setUp(() { camera = MockCamera(); videoElement = MockVideoElement(); errorStreamController = StreamController(); abortStreamController = StreamController(); endedStreamController = StreamController(); videoRecordingErrorController = StreamController(); when(camera.getVideoSize).thenReturn(const Size(10, 10)); when(camera.initialize) .thenAnswer((Invocation _) => Future.value()); when(camera.play).thenAnswer((Invocation _) => Future.value()); when(() => camera.videoElement).thenReturn(videoElement); when(() => videoElement.onError).thenAnswer((Invocation _) => FakeElementStream(errorStreamController.stream)); when(() => videoElement.onAbort).thenAnswer((Invocation _) => FakeElementStream(abortStreamController.stream)); when(() => camera.onEnded) .thenAnswer((Invocation _) => endedStreamController.stream); when(() => camera.onVideoRecordingError) .thenAnswer((Invocation _) => videoRecordingErrorController.stream); when(() => camera.startVideoRecording()) .thenAnswer((Invocation _) async {}); }); testWidgets( 'onCameraInitialized emits a CameraInitializedEvent ' 'on initializeCamera', (WidgetTester tester) async { // Mock the camera to use a blank video stream of size 1280x720. const Size videoSize = Size(1280, 720); videoElement = getVideoElementWithBlankStream(videoSize); when( () => cameraService.getMediaStreamForOptions( any(), cameraId: cameraId, ), ).thenAnswer((Invocation _) async => videoElement.captureStream()); final Camera camera = Camera( textureId: cameraId, cameraService: cameraService, ); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; final Stream eventStream = CameraPlatform.instance.onCameraInitialized(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); await CameraPlatform.instance.initializeCamera(cameraId); expect( await streamQueue.next, equals( CameraInitializedEvent( cameraId, videoSize.width, videoSize.height, ExposureMode.auto, false, FocusMode.auto, false, ), ), ); await streamQueue.cancel(); }); testWidgets('onCameraResolutionChanged emits an empty stream', (WidgetTester tester) async { expect( CameraPlatform.instance.onCameraResolutionChanged(cameraId), emits(isEmpty), ); }); testWidgets( 'onCameraClosing emits a CameraClosingEvent ' 'on the camera ended event', (WidgetTester tester) async { // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; final Stream eventStream = CameraPlatform.instance.onCameraClosing(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); await CameraPlatform.instance.initializeCamera(cameraId); endedStreamController.add(MockMediaStreamTrack()); expect( await streamQueue.next, equals( const CameraClosingEvent(cameraId), ), ); await streamQueue.cancel(); }); group('onCameraError', () { setUp(() { // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; }); testWidgets( 'emits a CameraErrorEvent ' 'on the camera video error event ' 'with a message', (WidgetTester tester) async { final Stream eventStream = CameraPlatform.instance.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); await CameraPlatform.instance.initializeCamera(cameraId); final FakeMediaError error = FakeMediaError( MediaError.MEDIA_ERR_NETWORK, 'A network error occured.', ); final CameraErrorCode errorCode = CameraErrorCode.fromMediaError(error); when(() => videoElement.error).thenReturn(error); errorStreamController.add(Event('error')); expect( await streamQueue.next, equals( CameraErrorEvent( cameraId, 'Error code: $errorCode, error message: ${error.message}', ), ), ); await streamQueue.cancel(); }); testWidgets( 'emits a CameraErrorEvent ' 'on the camera video error event ' 'with no message', (WidgetTester tester) async { final Stream eventStream = CameraPlatform.instance.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); await CameraPlatform.instance.initializeCamera(cameraId); final FakeMediaError error = FakeMediaError(MediaError.MEDIA_ERR_NETWORK); final CameraErrorCode errorCode = CameraErrorCode.fromMediaError(error); when(() => videoElement.error).thenReturn(error); errorStreamController.add(Event('error')); expect( await streamQueue.next, equals( CameraErrorEvent( cameraId, 'Error code: $errorCode, error message: No further diagnostic information can be determined or provided.', ), ), ); await streamQueue.cancel(); }); testWidgets( 'emits a CameraErrorEvent ' 'on the camera video abort event', (WidgetTester tester) async { final Stream eventStream = CameraPlatform.instance.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); await CameraPlatform.instance.initializeCamera(cameraId); abortStreamController.add(Event('abort')); expect( await streamQueue.next, equals( CameraErrorEvent( cameraId, "Error code: ${CameraErrorCode.abort}, error message: The video element's source has not fully loaded.", ), ), ); await streamQueue.cancel(); }); testWidgets( 'emits a CameraErrorEvent ' 'on takePicture error', (WidgetTester tester) async { final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.notStarted, 'description', ); when(camera.takePicture).thenThrow(exception); final Stream eventStream = CameraPlatform.instance.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); expect( () async => CameraPlatform.instance.takePicture(cameraId), throwsA( isA(), ), ); expect( await streamQueue.next, equals( CameraErrorEvent( cameraId, 'Error code: ${exception.code}, error message: ${exception.description}', ), ), ); await streamQueue.cancel(); }); testWidgets( 'emits a CameraErrorEvent ' 'on setFlashMode error', (WidgetTester tester) async { final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.notStarted, 'description', ); when(() => camera.setFlashMode(any())).thenThrow(exception); final Stream eventStream = CameraPlatform.instance.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); expect( () async => CameraPlatform.instance.setFlashMode( cameraId, FlashMode.always, ), throwsA( isA(), ), ); expect( await streamQueue.next, equals( CameraErrorEvent( cameraId, 'Error code: ${exception.code}, error message: ${exception.description}', ), ), ); await streamQueue.cancel(); }); testWidgets( 'emits a CameraErrorEvent ' 'on getMaxZoomLevel error', (WidgetTester tester) async { final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.zoomLevelNotSupported, 'description', ); when(camera.getMaxZoomLevel).thenThrow(exception); final Stream eventStream = CameraPlatform.instance.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); expect( () async => CameraPlatform.instance.getMaxZoomLevel( cameraId, ), throwsA( isA(), ), ); expect( await streamQueue.next, equals( CameraErrorEvent( cameraId, 'Error code: ${exception.code}, error message: ${exception.description}', ), ), ); await streamQueue.cancel(); }); testWidgets( 'emits a CameraErrorEvent ' 'on getMinZoomLevel error', (WidgetTester tester) async { final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.zoomLevelNotSupported, 'description', ); when(camera.getMinZoomLevel).thenThrow(exception); final Stream eventStream = CameraPlatform.instance.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); expect( () async => CameraPlatform.instance.getMinZoomLevel( cameraId, ), throwsA( isA(), ), ); expect( await streamQueue.next, equals( CameraErrorEvent( cameraId, 'Error code: ${exception.code}, error message: ${exception.description}', ), ), ); await streamQueue.cancel(); }); testWidgets( 'emits a CameraErrorEvent ' 'on setZoomLevel error', (WidgetTester tester) async { final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.zoomLevelNotSupported, 'description', ); when(() => camera.setZoomLevel(any())).thenThrow(exception); final Stream eventStream = CameraPlatform.instance.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); expect( () async => CameraPlatform.instance.setZoomLevel( cameraId, 100.0, ), throwsA( isA(), ), ); expect( await streamQueue.next, equals( CameraErrorEvent( cameraId, 'Error code: ${exception.code}, error message: ${exception.description}', ), ), ); await streamQueue.cancel(); }); testWidgets( 'emits a CameraErrorEvent ' 'on resumePreview error', (WidgetTester tester) async { final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.unknown, 'description', ); when(camera.play).thenThrow(exception); final Stream eventStream = CameraPlatform.instance.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); expect( () async => CameraPlatform.instance.resumePreview(cameraId), throwsA( isA(), ), ); expect( await streamQueue.next, equals( CameraErrorEvent( cameraId, 'Error code: ${exception.code}, error message: ${exception.description}', ), ), ); await streamQueue.cancel(); }); testWidgets( 'emits a CameraErrorEvent ' 'on startVideoRecording error', (WidgetTester tester) async { final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.notStarted, 'description', ); when(() => camera.onVideoRecordingError) .thenAnswer((Invocation _) => const Stream.empty()); when( () => camera.startVideoRecording( maxVideoDuration: any(named: 'maxVideoDuration'), ), ).thenThrow(exception); final Stream eventStream = CameraPlatform.instance.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); expect( () async => CameraPlatform.instance.startVideoRecording(cameraId), throwsA( isA(), ), ); expect( await streamQueue.next, equals( CameraErrorEvent( cameraId, 'Error code: ${exception.code}, error message: ${exception.description}', ), ), ); await streamQueue.cancel(); }); testWidgets( 'emits a CameraErrorEvent ' 'on the camera video recording error event', (WidgetTester tester) async { final Stream eventStream = CameraPlatform.instance.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); await CameraPlatform.instance.initializeCamera(cameraId); await CameraPlatform.instance.startVideoRecording(cameraId); final FakeErrorEvent errorEvent = FakeErrorEvent('type', 'message'); videoRecordingErrorController.add(errorEvent); expect( await streamQueue.next, equals( CameraErrorEvent( cameraId, 'Error code: ${errorEvent.type}, error message: ${errorEvent.message}.', ), ), ); await streamQueue.cancel(); }); testWidgets( 'emits a CameraErrorEvent ' 'on stopVideoRecording error', (WidgetTester tester) async { final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.notStarted, 'description', ); when(camera.stopVideoRecording).thenThrow(exception); final Stream eventStream = CameraPlatform.instance.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); expect( () async => CameraPlatform.instance.stopVideoRecording(cameraId), throwsA( isA(), ), ); expect( await streamQueue.next, equals( CameraErrorEvent( cameraId, 'Error code: ${exception.code}, error message: ${exception.description}', ), ), ); await streamQueue.cancel(); }); testWidgets( 'emits a CameraErrorEvent ' 'on pauseVideoRecording error', (WidgetTester tester) async { final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.notStarted, 'description', ); when(camera.pauseVideoRecording).thenThrow(exception); final Stream eventStream = CameraPlatform.instance.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); expect( () async => CameraPlatform.instance.pauseVideoRecording(cameraId), throwsA( isA(), ), ); expect( await streamQueue.next, equals( CameraErrorEvent( cameraId, 'Error code: ${exception.code}, error message: ${exception.description}', ), ), ); await streamQueue.cancel(); }); testWidgets( 'emits a CameraErrorEvent ' 'on resumeVideoRecording error', (WidgetTester tester) async { final CameraWebException exception = CameraWebException( cameraId, CameraErrorCode.notStarted, 'description', ); when(camera.resumeVideoRecording).thenThrow(exception); final Stream eventStream = CameraPlatform.instance.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); expect( () async => CameraPlatform.instance.resumeVideoRecording(cameraId), throwsA( isA(), ), ); expect( await streamQueue.next, equals( CameraErrorEvent( cameraId, 'Error code: ${exception.code}, error message: ${exception.description}', ), ), ); await streamQueue.cancel(); }); }); testWidgets('onVideoRecordedEvent emits a VideoRecordedEvent', (WidgetTester tester) async { final MockCamera camera = MockCamera(); final MockXFile capturedVideo = MockXFile(); final Stream stream = Stream.value( VideoRecordedEvent(cameraId, capturedVideo, Duration.zero)); when(() => camera.onVideoRecordedEvent) .thenAnswer((Invocation _) => stream); // Save the camera in the camera plugin. (CameraPlatform.instance as CameraPlugin).cameras[cameraId] = camera; final StreamQueue streamQueue = StreamQueue( CameraPlatform.instance.onVideoRecordedEvent(cameraId)); expect( await streamQueue.next, equals( VideoRecordedEvent(cameraId, capturedVideo, Duration.zero), ), ); }); group('onDeviceOrientationChanged', () { group('emits an empty stream', () { testWidgets('when screen is not supported', (WidgetTester tester) async { when(() => window.screen).thenReturn(null); expect( CameraPlatform.instance.onDeviceOrientationChanged(), emits(isEmpty), ); }); testWidgets('when screen orientation is not supported', (WidgetTester tester) async { when(() => screen.orientation).thenReturn(null); expect( CameraPlatform.instance.onDeviceOrientationChanged(), emits(isEmpty), ); }); }); testWidgets('emits the initial DeviceOrientationChangedEvent', (WidgetTester tester) async { when( () => cameraService.mapOrientationTypeToDeviceOrientation( OrientationType.portraitPrimary, ), ).thenReturn(DeviceOrientation.portraitUp); // Set the initial screen orientation to portraitPrimary. when(() => screenOrientation.type) .thenReturn(OrientationType.portraitPrimary); final StreamController eventStreamController = StreamController(); when(() => screenOrientation.onChange) .thenAnswer((Invocation _) => eventStreamController.stream); final Stream eventStream = CameraPlatform.instance.onDeviceOrientationChanged(); final StreamQueue streamQueue = StreamQueue(eventStream); expect( await streamQueue.next, equals( const DeviceOrientationChangedEvent( DeviceOrientation.portraitUp, ), ), ); await streamQueue.cancel(); }); testWidgets( 'emits a DeviceOrientationChangedEvent ' 'when the screen orientation is changed', (WidgetTester tester) async { when( () => cameraService.mapOrientationTypeToDeviceOrientation( OrientationType.landscapePrimary, ), ).thenReturn(DeviceOrientation.landscapeLeft); when( () => cameraService.mapOrientationTypeToDeviceOrientation( OrientationType.portraitSecondary, ), ).thenReturn(DeviceOrientation.portraitDown); final StreamController eventStreamController = StreamController(); when(() => screenOrientation.onChange) .thenAnswer((Invocation _) => eventStreamController.stream); final Stream eventStream = CameraPlatform.instance.onDeviceOrientationChanged(); final StreamQueue streamQueue = StreamQueue(eventStream); // Change the screen orientation to landscapePrimary and // emit an event on the screenOrientation.onChange stream. when(() => screenOrientation.type) .thenReturn(OrientationType.landscapePrimary); eventStreamController.add(Event('change')); expect( await streamQueue.next, equals( const DeviceOrientationChangedEvent( DeviceOrientation.landscapeLeft, ), ), ); // Change the screen orientation to portraitSecondary and // emit an event on the screenOrientation.onChange stream. when(() => screenOrientation.type) .thenReturn(OrientationType.portraitSecondary); eventStreamController.add(Event('change')); expect( await streamQueue.next, equals( const DeviceOrientationChangedEvent( DeviceOrientation.portraitDown, ), ), ); await streamQueue.cancel(); }); }); }); }); } ================================================ FILE: packages/camera/camera_web/example/integration_test/helpers/helpers.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'mocks.dart'; ================================================ FILE: packages/camera/camera_web/example/integration_test/helpers/mocks.dart ================================================ // Copyright 2013 The Flutter 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: avoid_implementing_value_types import 'dart:async'; import 'dart:html'; import 'dart:ui'; import 'package:camera_web/src/camera.dart'; import 'package:camera_web/src/camera_service.dart'; import 'package:camera_web/src/shims/dart_js_util.dart'; import 'package:camera_web/src/types/types.dart'; import 'package:cross_file/cross_file.dart'; import 'package:mocktail/mocktail.dart'; class MockWindow extends Mock implements Window {} class MockScreen extends Mock implements Screen {} class MockScreenOrientation extends Mock implements ScreenOrientation {} class MockDocument extends Mock implements Document {} class MockElement extends Mock implements Element {} class MockNavigator extends Mock implements Navigator {} class MockMediaDevices extends Mock implements MediaDevices {} class MockCameraService extends Mock implements CameraService {} class MockMediaStreamTrack extends Mock implements MediaStreamTrack {} class MockCamera extends Mock implements Camera {} class MockCameraOptions extends Mock implements CameraOptions {} class MockVideoElement extends Mock implements VideoElement {} class MockXFile extends Mock implements XFile {} class MockJsUtil extends Mock implements JsUtil {} class MockMediaRecorder extends Mock implements MediaRecorder {} /// A fake [MediaStream] that returns the provided [_videoTracks]. class FakeMediaStream extends Fake implements MediaStream { FakeMediaStream(this._videoTracks); final List _videoTracks; @override List getVideoTracks() => _videoTracks; } /// A fake [MediaDeviceInfo] that returns the provided [_deviceId], [_label] and [_kind]. class FakeMediaDeviceInfo extends Fake implements MediaDeviceInfo { FakeMediaDeviceInfo(this._deviceId, this._label, this._kind); final String _deviceId; final String _label; final String _kind; @override String? get deviceId => _deviceId; @override String? get label => _label; @override String? get kind => _kind; } /// A fake [MediaError] that returns the provided error [_code] and [_message]. class FakeMediaError extends Fake implements MediaError { FakeMediaError( this._code, [ String message = '', ]) : _message = message; final int _code; final String _message; @override int get code => _code; @override String? get message => _message; } /// A fake [DomException] that returns the provided error [_name] and [_message]. class FakeDomException extends Fake implements DomException { FakeDomException( this._name, [ String? message, ]) : _message = message; final String _name; final String? _message; @override String get name => _name; @override String? get message => _message; } /// A fake [ElementStream] that listens to the provided [_stream] on [listen]. class FakeElementStream extends Fake implements ElementStream { FakeElementStream(this._stream); final Stream _stream; @override StreamSubscription listen(void Function(T event)? onData, {Function? onError, void Function()? onDone, bool? cancelOnError}) { return _stream.listen( onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError, ); } } /// A fake [BlobEvent] that returns the provided blob [data]. class FakeBlobEvent extends Fake implements BlobEvent { FakeBlobEvent(this._blob); final Blob? _blob; @override Blob? get data => _blob; } /// A fake [DomException] that returns the provided error [_name] and [_message]. class FakeErrorEvent extends Fake implements ErrorEvent { FakeErrorEvent( String type, [ String? message, ]) : _type = type, _message = message; final String _type; final String? _message; @override String get type => _type; @override String? get message => _message; } /// Returns a video element with a blank stream of size [videoSize]. /// /// Can be used to mock a video stream: /// ```dart /// final videoElement = getVideoElementWithBlankStream(Size(100, 100)); /// final videoStream = videoElement.captureStream(); /// ``` VideoElement getVideoElementWithBlankStream(Size videoSize) { final CanvasElement canvasElement = CanvasElement( width: videoSize.width.toInt(), height: videoSize.height.toInt(), )..context2D.fillRect(0, 0, videoSize.width, videoSize.height); final VideoElement videoElement = VideoElement() ..srcObject = canvasElement.captureStream(); return videoElement; } ================================================ FILE: packages/camera/camera_web/example/integration_test/zoom_level_capability_test.dart ================================================ // Copyright 2013 The Flutter 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_web/src/types/types.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'helpers/helpers.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('ZoomLevelCapability', () { testWidgets('sets all properties', (WidgetTester tester) async { const double minimum = 100.0; const double maximum = 400.0; final MockMediaStreamTrack videoTrack = MockMediaStreamTrack(); final ZoomLevelCapability capability = ZoomLevelCapability( minimum: minimum, maximum: maximum, videoTrack: videoTrack, ); expect(capability.minimum, equals(minimum)); expect(capability.maximum, equals(maximum)); expect(capability.videoTrack, equals(videoTrack)); }); testWidgets('supports value equality', (WidgetTester tester) async { final MockMediaStreamTrack videoTrack = MockMediaStreamTrack(); expect( ZoomLevelCapability( minimum: 0.0, maximum: 100.0, videoTrack: videoTrack, ), equals( ZoomLevelCapability( minimum: 0.0, maximum: 100.0, videoTrack: videoTrack, ), ), ); }); }); } ================================================ FILE: packages/camera/camera_web/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); /// App for testing class MyApp extends StatelessWidget { /// Default Constructor const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const Directionality( textDirection: TextDirection.ltr, child: Text('Testing... Look at the console output for results!'), ); } } ================================================ FILE: packages/camera/camera_web/example/pubspec.yaml ================================================ name: camera_web_integration_tests publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter dev_dependencies: async: ^2.5.0 camera_platform_interface: ^2.1.0 camera_web: path: ../ cross_file: ^0.3.1 flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter mocktail: ^0.3.0 ================================================ FILE: packages/camera/camera_web/example/run_test.sh ================================================ #!/usr/bin/env bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. if pgrep -lf chromedriver > /dev/null; then echo "chromedriver is running." if [ $# -eq 0 ]; then echo "No target specified, running all tests..." find integration_test/ -iname *_test.dart | xargs -n1 -I{} -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target='{}' else echo "Running test target: $1..." set -x flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target=$1 fi else echo "chromedriver is not running." echo "Please, check the README.md for instructions on how to use run_test.sh" fi ================================================ FILE: packages/camera/camera_web/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/camera/camera_web/example/web/index.html ================================================ Browser Tests ================================================ FILE: packages/camera/camera_web/lib/camera_web.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. library camera_web; export 'src/camera_web.dart'; ================================================ FILE: packages/camera/camera_web/lib/src/camera.dart ================================================ // Copyright 2013 The Flutter 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 'dart:html' as html; import 'dart:ui'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'camera_service.dart'; import 'shims/dart_ui.dart' as ui; import 'types/types.dart'; String _getViewType(int cameraId) => 'plugins.flutter.io/camera_$cameraId'; /// A camera initialized from the media devices in the current window. /// See: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices /// /// The obtained camera stream is constrained by [options] and fetched /// with [CameraService.getMediaStreamForOptions]. /// /// The camera stream is displayed in the [videoElement] wrapped in the /// [divElement] to avoid overriding the custom styles applied to /// the video element in [_applyDefaultVideoStyles]. /// See: https://github.com/flutter/flutter/issues/79519 /// /// The camera stream can be played/stopped by calling [play]/[stop], /// may capture a picture by calling [takePicture] or capture a video /// by calling [startVideoRecording], [pauseVideoRecording], /// [resumeVideoRecording] or [stopVideoRecording]. /// /// The camera zoom may be adjusted with [setZoomLevel]. The provided /// zoom level must be a value in the range of [getMinZoomLevel] to /// [getMaxZoomLevel]. /// /// The [textureId] is used to register a camera view with the id /// defined by [_getViewType]. class Camera { /// Creates a new instance of [Camera] /// with the given [textureId] and optional /// [options] and [window]. Camera({ required this.textureId, required CameraService cameraService, this.options = const CameraOptions(), }) : _cameraService = cameraService; // A torch mode constraint name. // See: https://w3c.github.io/mediacapture-image/#dom-mediatracksupportedconstraints-torch static const String _torchModeKey = 'torch'; /// The texture id used to register the camera view. final int textureId; /// The camera options used to initialize a camera, empty by default. final CameraOptions options; /// The video element that displays the camera stream. /// Initialized in [initialize]. late final html.VideoElement videoElement; /// The wrapping element for the [videoElement] to avoid overriding /// the custom styles applied in [_applyDefaultVideoStyles]. /// Initialized in [initialize]. late final html.DivElement divElement; /// The camera stream displayed in the [videoElement]. /// Initialized in [initialize] and [play], reset in [stop]. html.MediaStream? stream; /// The stream of the camera video tracks that have ended playing. /// /// This occurs when there is no more camera stream data, e.g. /// the user has stopped the stream by changing the camera device, /// revoked the camera permissions or ejected the camera device. /// /// MediaStreamTrack.onended: /// https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/onended Stream get onEnded => onEndedController.stream; /// The stream controller for the [onEnded] stream. @visibleForTesting final StreamController onEndedController = StreamController.broadcast(); StreamSubscription? _onEndedSubscription; /// The stream of the camera video recording errors. /// /// This occurs when the video recording is not allowed or an unsupported /// codec is used. /// /// MediaRecorder.error: /// https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/error_event Stream get onVideoRecordingError => videoRecordingErrorController.stream; /// The stream controller for the [onVideoRecordingError] stream. @visibleForTesting final StreamController videoRecordingErrorController = StreamController.broadcast(); StreamSubscription? _onVideoRecordingErrorSubscription; /// The camera flash mode. @visibleForTesting FlashMode? flashMode; /// The camera service used to get the media stream for the camera. final CameraService _cameraService; /// The current browser window used to access media devices. @visibleForTesting html.Window? window = html.window; /// The recorder used to record a video from the camera. @visibleForTesting html.MediaRecorder? mediaRecorder; /// Whether the video of the given type is supported. @visibleForTesting bool Function(String) isVideoTypeSupported = html.MediaRecorder.isTypeSupported; /// The list of consecutive video data files recorded with [mediaRecorder]. final List _videoData = []; /// Completes when the video recording is stopped/finished. Completer? _videoAvailableCompleter; /// A data listener fired when a new part of video data is available. void Function(html.Event)? _videoDataAvailableListener; /// A listener fired when a video recording is stopped. void Function(html.Event)? _videoRecordingStoppedListener; /// A builder to merge a list of blobs into a single blob. @visibleForTesting // TODO(stuartmorgan): Remove this 'ignore' once we don't analyze using 2.10 // any more. It's a false positive that is fixed in later versions. // ignore: prefer_function_declarations_over_variables html.Blob Function(List blobs, String type) blobBuilder = (List blobs, String type) => html.Blob(blobs, type); /// The stream that emits a [VideoRecordedEvent] when a video recording is created. Stream get onVideoRecordedEvent => videoRecorderController.stream; /// The stream controller for the [onVideoRecordedEvent] stream. @visibleForTesting final StreamController videoRecorderController = StreamController.broadcast(); /// Initializes the camera stream displayed in the [videoElement]. /// Registers the camera view with [textureId] under [_getViewType] type. /// Emits the camera default video track on the [onEnded] stream when it ends. Future initialize() async { stream = await _cameraService.getMediaStreamForOptions( options, cameraId: textureId, ); videoElement = html.VideoElement(); divElement = html.DivElement() ..style.setProperty('object-fit', 'cover') ..append(videoElement); ui.platformViewRegistry.registerViewFactory( _getViewType(textureId), (_) => divElement, ); videoElement ..autoplay = false ..muted = true ..srcObject = stream ..setAttribute('playsinline', ''); _applyDefaultVideoStyles(videoElement); final List videoTracks = stream!.getVideoTracks(); if (videoTracks.isNotEmpty) { final html.MediaStreamTrack defaultVideoTrack = videoTracks.first; _onEndedSubscription = defaultVideoTrack.onEnded.listen((html.Event _) { onEndedController.add(defaultVideoTrack); }); } } /// Starts the camera stream. /// /// Initializes the camera source if the camera was previously stopped. Future play() async { if (videoElement.srcObject == null) { stream = await _cameraService.getMediaStreamForOptions( options, cameraId: textureId, ); videoElement.srcObject = stream; } await videoElement.play(); } /// Pauses the camera stream on the current frame. void pause() { videoElement.pause(); } /// Stops the camera stream and resets the camera source. void stop() { final List videoTracks = stream!.getVideoTracks(); if (videoTracks.isNotEmpty) { onEndedController.add(videoTracks.first); } final List? tracks = stream?.getTracks(); if (tracks != null) { for (final html.MediaStreamTrack track in tracks) { track.stop(); } } videoElement.srcObject = null; stream = null; } /// Captures a picture and returns the saved file in a JPEG format. /// /// Enables the camera flash (torch mode) for a period of taking a picture /// if the flash mode is either [FlashMode.auto] or [FlashMode.always]. Future takePicture() async { final bool shouldEnableTorchMode = flashMode == FlashMode.auto || flashMode == FlashMode.always; if (shouldEnableTorchMode) { _setTorchMode(enabled: true); } final int videoWidth = videoElement.videoWidth; final int videoHeight = videoElement.videoHeight; final html.CanvasElement canvas = html.CanvasElement(width: videoWidth, height: videoHeight); final bool isBackCamera = getLensDirection() == CameraLensDirection.back; // Flip the picture horizontally if it is not taken from a back camera. if (!isBackCamera) { canvas.context2D ..translate(videoWidth, 0) ..scale(-1, 1); } canvas.context2D .drawImageScaled(videoElement, 0, 0, videoWidth, videoHeight); final html.Blob blob = await canvas.toBlob('image/jpeg'); if (shouldEnableTorchMode) { _setTorchMode(enabled: false); } return XFile(html.Url.createObjectUrl(blob)); } /// Returns a size of the camera video based on its first video track size. /// /// Returns [Size.zero] if the camera is missing a video track or /// the video track does not include the width or height setting. Size getVideoSize() { final List videoTracks = videoElement.srcObject?.getVideoTracks() ?? []; if (videoTracks.isEmpty) { return Size.zero; } final html.MediaStreamTrack defaultVideoTrack = videoTracks.first; final Map defaultVideoTrackSettings = defaultVideoTrack.getSettings(); final double? width = defaultVideoTrackSettings['width'] as double?; final double? height = defaultVideoTrackSettings['height'] as double?; if (width != null && height != null) { return Size(width, height); } else { return Size.zero; } } /// Sets the camera flash mode to [mode] by modifying the camera /// torch mode constraint. /// /// The torch mode is enabled for [FlashMode.torch] and /// disabled for [FlashMode.off]. /// /// For [FlashMode.auto] and [FlashMode.always] the torch mode is enabled /// only for a period of taking a picture in [takePicture]. /// /// Throws a [CameraWebException] if the torch mode is not supported /// or the camera has not been initialized or started. void setFlashMode(FlashMode mode) { final html.MediaDevices? mediaDevices = window?.navigator.mediaDevices; final Map? supportedConstraints = mediaDevices?.getSupportedConstraints(); final bool torchModeSupported = supportedConstraints?[_torchModeKey] as bool? ?? false; if (!torchModeSupported) { throw CameraWebException( textureId, CameraErrorCode.torchModeNotSupported, 'The torch mode is not supported in the current browser.', ); } // Save the updated flash mode to be used later when taking a picture. flashMode = mode; // Enable the torch mode only if the flash mode is torch. _setTorchMode(enabled: mode == FlashMode.torch); } /// Sets the camera torch mode constraint to [enabled]. /// /// Throws a [CameraWebException] if the torch mode is not supported /// or the camera has not been initialized or started. void _setTorchMode({required bool enabled}) { final List videoTracks = stream?.getVideoTracks() ?? []; if (videoTracks.isNotEmpty) { final html.MediaStreamTrack defaultVideoTrack = videoTracks.first; final bool canEnableTorchMode = defaultVideoTrack.getCapabilities()[_torchModeKey] as bool? ?? false; if (canEnableTorchMode) { defaultVideoTrack.applyConstraints({ 'advanced': [ { _torchModeKey: enabled, } ] }); } else { throw CameraWebException( textureId, CameraErrorCode.torchModeNotSupported, 'The torch mode is not supported by the current camera.', ); } } else { throw CameraWebException( textureId, CameraErrorCode.notStarted, 'The camera has not been initialized or started.', ); } } /// Returns the camera maximum zoom level. /// /// Throws a [CameraWebException] if the zoom level is not supported /// or the camera has not been initialized or started. double getMaxZoomLevel() => _cameraService.getZoomLevelCapabilityForCamera(this).maximum; /// Returns the camera minimum zoom level. /// /// Throws a [CameraWebException] if the zoom level is not supported /// or the camera has not been initialized or started. double getMinZoomLevel() => _cameraService.getZoomLevelCapabilityForCamera(this).minimum; /// Sets the camera zoom level to [zoom]. /// /// Throws a [CameraWebException] if the zoom level is invalid, /// not supported or the camera has not been initialized or started. void setZoomLevel(double zoom) { final ZoomLevelCapability zoomLevelCapability = _cameraService.getZoomLevelCapabilityForCamera(this); if (zoom < zoomLevelCapability.minimum || zoom > zoomLevelCapability.maximum) { throw CameraWebException( textureId, CameraErrorCode.zoomLevelInvalid, 'The provided zoom level must be in the range of ${zoomLevelCapability.minimum} to ${zoomLevelCapability.maximum}.', ); } zoomLevelCapability.videoTrack.applyConstraints({ 'advanced': [ { ZoomLevelCapability.constraintName: zoom, } ] }); } /// Returns a lens direction of this camera. /// /// Returns null if the camera is missing a video track or /// the video track does not include the facing mode setting. CameraLensDirection? getLensDirection() { final List videoTracks = videoElement.srcObject?.getVideoTracks() ?? []; if (videoTracks.isEmpty) { return null; } final html.MediaStreamTrack defaultVideoTrack = videoTracks.first; final Map defaultVideoTrackSettings = defaultVideoTrack.getSettings(); final String? facingMode = defaultVideoTrackSettings['facingMode'] as String?; if (facingMode != null) { return _cameraService.mapFacingModeToLensDirection(facingMode); } else { return null; } } /// Returns the registered view type of the camera. String getViewType() => _getViewType(textureId); /// Starts a new video recording using [html.MediaRecorder]. /// /// Throws a [CameraWebException] if the provided maximum video duration is invalid /// or the browser does not support any of the available video mime types /// from [_videoMimeType]. Future startVideoRecording({Duration? maxVideoDuration}) async { if (maxVideoDuration != null && maxVideoDuration.inMilliseconds <= 0) { throw CameraWebException( textureId, CameraErrorCode.notSupported, 'The maximum video duration must be greater than 0 milliseconds.', ); } mediaRecorder ??= html.MediaRecorder(videoElement.srcObject!, { 'mimeType': _videoMimeType, }); _videoAvailableCompleter = Completer(); _videoDataAvailableListener = (html.Event event) => _onVideoDataAvailable(event, maxVideoDuration); _videoRecordingStoppedListener = (html.Event event) => _onVideoRecordingStopped(event, maxVideoDuration); mediaRecorder!.addEventListener( 'dataavailable', _videoDataAvailableListener, ); mediaRecorder!.addEventListener( 'stop', _videoRecordingStoppedListener, ); _onVideoRecordingErrorSubscription = mediaRecorder!.onError.listen((html.Event event) { final html.ErrorEvent error = event as html.ErrorEvent; if (error != null) { videoRecordingErrorController.add(error); } }); if (maxVideoDuration != null) { mediaRecorder!.start(maxVideoDuration.inMilliseconds); } else { // Don't pass the null duration as that will fire a `dataavailable` event directly. mediaRecorder!.start(); } } void _onVideoDataAvailable( html.Event event, [ Duration? maxVideoDuration, ]) { final html.Blob? blob = (event as html.BlobEvent).data; // Append the recorded part of the video to the list of all video data files. if (blob != null) { _videoData.add(blob); } // Stop the recorder if the video has a maxVideoDuration // and the recording was not stopped manually. if (maxVideoDuration != null && mediaRecorder!.state == 'recording') { mediaRecorder!.stop(); } } Future _onVideoRecordingStopped( html.Event event, [ Duration? maxVideoDuration, ]) async { if (_videoData.isNotEmpty) { // Concatenate all video data files into a single blob. final String videoType = _videoData.first.type; final html.Blob videoBlob = blobBuilder(_videoData, videoType); // Create a file containing the video blob. final XFile file = XFile( html.Url.createObjectUrl(videoBlob), mimeType: _videoMimeType, name: videoBlob.hashCode.toString(), ); // Emit an event containing the recorded video file. videoRecorderController.add( VideoRecordedEvent(textureId, file, maxVideoDuration), ); _videoAvailableCompleter?.complete(file); } // Clean up the media recorder with its event listeners and video data. mediaRecorder!.removeEventListener( 'dataavailable', _videoDataAvailableListener, ); mediaRecorder!.removeEventListener( 'stop', _videoDataAvailableListener, ); await _onVideoRecordingErrorSubscription?.cancel(); mediaRecorder = null; _videoDataAvailableListener = null; _videoRecordingStoppedListener = null; _videoData.clear(); } /// Pauses the current video recording. /// /// Throws a [CameraWebException] if the video recorder is uninitialized. Future pauseVideoRecording() async { if (mediaRecorder == null) { throw _videoRecordingNotStartedException; } mediaRecorder!.pause(); } /// Resumes the current video recording. /// /// Throws a [CameraWebException] if the video recorder is uninitialized. Future resumeVideoRecording() async { if (mediaRecorder == null) { throw _videoRecordingNotStartedException; } mediaRecorder!.resume(); } /// Stops the video recording and returns the captured video file. /// /// Throws a [CameraWebException] if the video recorder is uninitialized. Future stopVideoRecording() async { if (mediaRecorder == null || _videoAvailableCompleter == null) { throw _videoRecordingNotStartedException; } mediaRecorder!.stop(); return _videoAvailableCompleter!.future; } /// Disposes the camera by stopping the camera stream, /// the video recording and reloading the camera source. Future dispose() async { // Stop the camera stream. stop(); await videoRecorderController.close(); mediaRecorder = null; _videoDataAvailableListener = null; // Reset the [videoElement] to its initial state. videoElement ..srcObject = null ..load(); await _onEndedSubscription?.cancel(); _onEndedSubscription = null; await onEndedController.close(); await _onVideoRecordingErrorSubscription?.cancel(); _onVideoRecordingErrorSubscription = null; await videoRecordingErrorController.close(); } /// Returns the first supported video mime type (amongst mp4 and webm) /// to use when recording a video. /// /// Throws a [CameraWebException] if the browser does not support /// any of the available video mime types. String get _videoMimeType { const List types = [ 'video/mp4', 'video/webm', ]; return types.firstWhere( (String type) => isVideoTypeSupported(type), orElse: () => throw CameraWebException( textureId, CameraErrorCode.notSupported, 'The browser does not support any of the following video types: ${types.join(',')}.', ), ); } CameraWebException get _videoRecordingNotStartedException => CameraWebException( textureId, CameraErrorCode.videoRecordingNotStarted, 'The video recorder is uninitialized. The recording might not have been started. Make sure to call `startVideoRecording` first.', ); /// Applies default styles to the video [element]. void _applyDefaultVideoStyles(html.VideoElement element) { final bool isBackCamera = getLensDirection() == CameraLensDirection.back; // Flip the video horizontally if it is not taken from a back camera. if (!isBackCamera) { element.style.transform = 'scaleX(-1)'; } element.style ..transformOrigin = 'center' ..pointerEvents = 'none' ..width = '100%' ..height = '100%' ..objectFit = 'cover'; } } ================================================ FILE: packages/camera/camera_web/lib/src/camera_service.dart ================================================ // Copyright 2013 The Flutter 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:html' as html; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#106316) // ignore: unnecessary_import import 'dart:ui'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'camera.dart'; import 'shims/dart_js_util.dart'; import 'types/types.dart'; /// A service to fetch, map camera settings and /// obtain the camera stream. class CameraService { // A facing mode constraint name. static const String _facingModeKey = 'facingMode'; /// The current browser window used to access media devices. @visibleForTesting html.Window? window = html.window; /// The utility to manipulate JavaScript interop objects. @visibleForTesting JsUtil jsUtil = JsUtil(); /// Returns a media stream associated with the camera device /// with [cameraId] and constrained by [options]. Future getMediaStreamForOptions( CameraOptions options, { int cameraId = 0, }) async { final html.MediaDevices? mediaDevices = window?.navigator.mediaDevices; // Throw a not supported exception if the current browser window // does not support any media devices. if (mediaDevices == null) { throw PlatformException( code: CameraErrorCode.notSupported.toString(), message: 'The camera is not supported on this device.', ); } try { final Map constraints = options.toJson(); return await mediaDevices.getUserMedia(constraints); } on html.DomException catch (e) { switch (e.name) { case 'NotFoundError': case 'DevicesNotFoundError': throw CameraWebException( cameraId, CameraErrorCode.notFound, 'No camera found for the given camera options.', ); case 'NotReadableError': case 'TrackStartError': throw CameraWebException( cameraId, CameraErrorCode.notReadable, 'The camera is not readable due to a hardware error ' 'that prevented access to the device.', ); case 'OverconstrainedError': case 'ConstraintNotSatisfiedError': throw CameraWebException( cameraId, CameraErrorCode.overconstrained, 'The camera options are impossible to satisfy.', ); case 'NotAllowedError': case 'PermissionDeniedError': throw CameraWebException( cameraId, CameraErrorCode.permissionDenied, 'The camera cannot be used or the permission ' 'to access the camera is not granted.', ); case 'TypeError': throw CameraWebException( cameraId, CameraErrorCode.type, 'The camera options are incorrect or attempted ' 'to access the media input from an insecure context.', ); case 'AbortError': throw CameraWebException( cameraId, CameraErrorCode.abort, 'Some problem occurred that prevented the camera from being used.', ); case 'SecurityError': throw CameraWebException( cameraId, CameraErrorCode.security, 'The user media support is disabled in the current browser.', ); default: throw CameraWebException( cameraId, CameraErrorCode.unknown, 'An unknown error occured when fetching the camera stream.', ); } } catch (_) { throw CameraWebException( cameraId, CameraErrorCode.unknown, 'An unknown error occured when fetching the camera stream.', ); } } /// Returns the zoom level capability for the given [camera]. /// /// Throws a [CameraWebException] if the zoom level is not supported /// or the camera has not been initialized or started. ZoomLevelCapability getZoomLevelCapabilityForCamera( Camera camera, ) { final html.MediaDevices? mediaDevices = window?.navigator.mediaDevices; final Map? supportedConstraints = mediaDevices?.getSupportedConstraints(); final bool zoomLevelSupported = supportedConstraints?[ZoomLevelCapability.constraintName] as bool? ?? false; if (!zoomLevelSupported) { throw CameraWebException( camera.textureId, CameraErrorCode.zoomLevelNotSupported, 'The zoom level is not supported in the current browser.', ); } final List videoTracks = camera.stream?.getVideoTracks() ?? []; if (videoTracks.isNotEmpty) { final html.MediaStreamTrack defaultVideoTrack = videoTracks.first; /// The zoom level capability is represented by MediaSettingsRange. /// See: https://developer.mozilla.org/en-US/docs/Web/API/MediaSettingsRange final Object zoomLevelCapability = defaultVideoTrack .getCapabilities()[ZoomLevelCapability.constraintName] as Object? ?? {}; // The zoom level capability is a nested JS object, therefore // we need to access its properties with the js_util library. // See: https://api.dart.dev/stable/2.13.4/dart-js_util/getProperty.html final num? minimumZoomLevel = jsUtil.getProperty(zoomLevelCapability, 'min') as num?; final num? maximumZoomLevel = jsUtil.getProperty(zoomLevelCapability, 'max') as num?; if (minimumZoomLevel != null && maximumZoomLevel != null) { return ZoomLevelCapability( minimum: minimumZoomLevel.toDouble(), maximum: maximumZoomLevel.toDouble(), videoTrack: defaultVideoTrack, ); } else { throw CameraWebException( camera.textureId, CameraErrorCode.zoomLevelNotSupported, 'The zoom level is not supported by the current camera.', ); } } else { throw CameraWebException( camera.textureId, CameraErrorCode.notStarted, 'The camera has not been initialized or started.', ); } } /// Returns a facing mode of the [videoTrack] /// (null if the facing mode is not available). String? getFacingModeForVideoTrack(html.MediaStreamTrack videoTrack) { final html.MediaDevices? mediaDevices = window?.navigator.mediaDevices; // Throw a not supported exception if the current browser window // does not support any media devices. if (mediaDevices == null) { throw PlatformException( code: CameraErrorCode.notSupported.toString(), message: 'The camera is not supported on this device.', ); } // Check if the camera facing mode is supported by the current browser. final Map supportedConstraints = mediaDevices.getSupportedConstraints(); final bool facingModeSupported = supportedConstraints[_facingModeKey] as bool? ?? false; // Return null if the facing mode is not supported. if (!facingModeSupported) { return null; } // Extract the facing mode from the video track settings. // The property may not be available if it's not supported // by the browser or not available due to context. // // MediaTrackSettings: // https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings final Map videoTrackSettings = videoTrack.getSettings(); final String? facingMode = videoTrackSettings[_facingModeKey] as String?; if (facingMode == null) { // If the facing mode does not exist in the video track settings, // check for the facing mode in the video track capabilities. // // MediaTrackCapabilities: // https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackcapabilities // Check if getting the video track capabilities is supported. // // The method may not be supported on Firefox. // See: https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/getCapabilities#browser_compatibility if (!jsUtil.hasProperty(videoTrack, 'getCapabilities')) { // Return null if the video track capabilites are not supported. return null; } final Map videoTrackCapabilities = videoTrack.getCapabilities(); // A list of facing mode capabilities as // the camera may support multiple facing modes. final List facingModeCapabilities = List.from( (videoTrackCapabilities[_facingModeKey] as List?) ?.cast() ?? []); if (facingModeCapabilities.isNotEmpty) { final String facingModeCapability = facingModeCapabilities.first; return facingModeCapability; } else { // Return null if there are no facing mode capabilities. return null; } } return facingMode; } /// Maps the given [facingMode] to [CameraLensDirection]. /// /// The following values for the facing mode are supported: /// https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings/facingMode CameraLensDirection mapFacingModeToLensDirection(String facingMode) { switch (facingMode) { case 'user': return CameraLensDirection.front; case 'environment': return CameraLensDirection.back; case 'left': case 'right': default: return CameraLensDirection.external; } } /// Maps the given [facingMode] to [CameraType]. /// /// See [CameraMetadata.facingMode] for more details. CameraType mapFacingModeToCameraType(String facingMode) { switch (facingMode) { case 'user': return CameraType.user; case 'environment': return CameraType.environment; case 'left': case 'right': default: return CameraType.user; } } /// Maps the given [resolutionPreset] to [Size]. Size mapResolutionPresetToSize(ResolutionPreset resolutionPreset) { switch (resolutionPreset) { case ResolutionPreset.max: case ResolutionPreset.ultraHigh: return const Size(4096, 2160); case ResolutionPreset.veryHigh: return const Size(1920, 1080); case ResolutionPreset.high: return const Size(1280, 720); case ResolutionPreset.medium: return const Size(720, 480); case ResolutionPreset.low: 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]. String mapDeviceOrientationToOrientationType( DeviceOrientation deviceOrientation, ) { switch (deviceOrientation) { case DeviceOrientation.portraitUp: return OrientationType.portraitPrimary; case DeviceOrientation.landscapeLeft: return OrientationType.landscapePrimary; case DeviceOrientation.portraitDown: return OrientationType.portraitSecondary; case DeviceOrientation.landscapeRight: return OrientationType.landscapeSecondary; } } /// Maps the given [orientationType] to [DeviceOrientation]. DeviceOrientation mapOrientationTypeToDeviceOrientation( String orientationType, ) { switch (orientationType) { case OrientationType.portraitPrimary: return DeviceOrientation.portraitUp; case OrientationType.landscapePrimary: return DeviceOrientation.landscapeLeft; case OrientationType.portraitSecondary: return DeviceOrientation.portraitDown; case OrientationType.landscapeSecondary: return DeviceOrientation.landscapeRight; default: return DeviceOrientation.portraitUp; } } } ================================================ FILE: packages/camera/camera_web/lib/src/camera_web.dart ================================================ // Copyright 2013 The Flutter 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 'dart:html' as html; import 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:stream_transform/stream_transform.dart'; import 'camera.dart'; import 'camera_service.dart'; import 'types/types.dart'; // The default error message, when the error is an empty string. // See: https://developer.mozilla.org/en-US/docs/Web/API/MediaError/message const String _kDefaultErrorMessage = 'No further diagnostic information can be determined or provided.'; /// The web implementation of [CameraPlatform]. /// /// This class implements the `package:camera` functionality for the web. class CameraPlugin extends CameraPlatform { /// Creates a new instance of [CameraPlugin] /// with the given [cameraService]. CameraPlugin({required CameraService cameraService}) : _cameraService = cameraService; /// Registers this class as the default instance of [CameraPlatform]. static void registerWith(Registrar registrar) { CameraPlatform.instance = CameraPlugin( cameraService: CameraService(), ); } final CameraService _cameraService; /// The cameras managed by the [CameraPlugin]. @visibleForTesting final Map cameras = {}; int _textureCounter = 1; /// Metadata associated with each camera description. /// Populated in [availableCameras]. @visibleForTesting final Map camerasMetadata = {}; /// The controller used to broadcast different camera events. /// /// It is `broadcast` as multiple controllers may subscribe /// to different stream views of this controller. @visibleForTesting final StreamController cameraEventStreamController = StreamController.broadcast(); final Map> _cameraVideoErrorSubscriptions = >{}; final Map> _cameraVideoAbortSubscriptions = >{}; final Map> _cameraEndedSubscriptions = >{}; final Map> _cameraVideoRecordingErrorSubscriptions = >{}; /// Returns a stream of camera events for the given [cameraId]. Stream _cameraEvents(int cameraId) => cameraEventStreamController.stream .where((CameraEvent event) => event.cameraId == cameraId); /// The current browser window used to access media devices. @visibleForTesting html.Window? window = html.window; @override Future> availableCameras() async { try { final html.MediaDevices? mediaDevices = window?.navigator.mediaDevices; final List cameras = []; // Throw a not supported exception if the current browser window // does not support any media devices. if (mediaDevices == null) { throw PlatformException( code: CameraErrorCode.notSupported.toString(), message: 'The camera is not supported on this device.', ); } // Request video and audio permissions. final html.MediaStream cameraStream = await _cameraService.getMediaStreamForOptions( const CameraOptions( audio: AudioConstraints(enabled: true), ), ); // Release the camera stream used to request video and audio permissions. cameraStream .getVideoTracks() .forEach((html.MediaStreamTrack videoTrack) => videoTrack.stop()); // Request available media devices. final List devices = await mediaDevices.enumerateDevices(); // Filter video input devices. final Iterable videoInputDevices = devices .whereType() .where((html.MediaDeviceInfo device) => device.kind == MediaDeviceKind.videoInput) /// The device id property is currently not supported on Internet Explorer: /// https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo/deviceId#browser_compatibility .where( (html.MediaDeviceInfo device) => device.deviceId != null && device.deviceId!.isNotEmpty, ); // Map video input devices to camera descriptions. for (final html.MediaDeviceInfo videoInputDevice in videoInputDevices) { // Get the video stream for the current video input device // to later use for the available video tracks. final html.MediaStream videoStream = await _getVideoStreamForDevice( videoInputDevice.deviceId!, ); // Get all video tracks in the video stream // to later extract the lens direction from the first track. final List videoTracks = videoStream.getVideoTracks(); if (videoTracks.isNotEmpty) { // Get the facing mode from the first available video track. final String? facingMode = _cameraService.getFacingModeForVideoTrack(videoTracks.first); // Get the lens direction based on the facing mode. // Fallback to the external lens direction // if the facing mode is not available. final CameraLensDirection lensDirection = facingMode != null ? _cameraService.mapFacingModeToLensDirection(facingMode) : CameraLensDirection.external; // Create a camera description. // // The name is a camera label which might be empty // if no permissions to media devices have been granted. // // MediaDeviceInfo.label: // https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo/label // // Sensor orientation is currently not supported. final String cameraLabel = videoInputDevice.label ?? ''; final CameraDescription camera = CameraDescription( name: cameraLabel, lensDirection: lensDirection, sensorOrientation: 0, ); final CameraMetadata cameraMetadata = CameraMetadata( deviceId: videoInputDevice.deviceId!, facingMode: facingMode, ); cameras.add(camera); camerasMetadata[camera] = cameraMetadata; // Release the camera stream of the current video input device. for (final html.MediaStreamTrack videoTrack in videoTracks) { videoTrack.stop(); } } else { // Ignore as no video tracks exist in the current video input device. continue; } } return cameras; } on html.DomException catch (e) { throw CameraException(e.name, e.message); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } on CameraWebException catch (e) { _addCameraErrorEvent(e); throw CameraException(e.code.toString(), e.description); } } @override Future createCamera( CameraDescription cameraDescription, ResolutionPreset? resolutionPreset, { bool enableAudio = false, }) async { try { if (!camerasMetadata.containsKey(cameraDescription)) { throw PlatformException( code: CameraErrorCode.missingMetadata.toString(), message: 'Missing camera metadata. Make sure to call `availableCameras` before creating a camera.', ); } final int textureId = _textureCounter++; final CameraMetadata cameraMetadata = camerasMetadata[cameraDescription]!; final CameraType? cameraType = cameraMetadata.facingMode != null ? _cameraService.mapFacingModeToCameraType(cameraMetadata.facingMode!) : null; // Use the highest resolution possible // if the resolution preset is not specified. final Size videoSize = _cameraService .mapResolutionPresetToSize(resolutionPreset ?? ResolutionPreset.max); // Create a camera with the given audio and video constraints. // Sensor orientation is currently not supported. final Camera camera = Camera( textureId: textureId, cameraService: _cameraService, options: CameraOptions( audio: AudioConstraints(enabled: enableAudio), video: VideoConstraints( facingMode: cameraType != null ? FacingModeConstraint(cameraType) : null, width: VideoSizeConstraint( ideal: videoSize.width.toInt(), ), height: VideoSizeConstraint( ideal: videoSize.height.toInt(), ), deviceId: cameraMetadata.deviceId, ), ), ); cameras[textureId] = camera; return textureId; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } @override Future initializeCamera( int cameraId, { // The image format group is currently not supported. ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown, }) async { try { final Camera camera = getCamera(cameraId); await camera.initialize(); // Add camera's video error events to the camera events stream. // The error event fires when the video element's source has failed to load, or can't be used. _cameraVideoErrorSubscriptions[cameraId] = camera.videoElement.onError.listen((html.Event _) { // The Event itself (_) doesn't contain information about the actual error. // We need to look at the HTMLMediaElement.error. // See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/error final html.MediaError error = camera.videoElement.error!; final CameraErrorCode errorCode = CameraErrorCode.fromMediaError(error); final String? errorMessage = error.message != '' ? error.message : _kDefaultErrorMessage; cameraEventStreamController.add( CameraErrorEvent( cameraId, 'Error code: $errorCode, error message: $errorMessage', ), ); }); // Add camera's video abort events to the camera events stream. // The abort event fires when the video element's source has not fully loaded. _cameraVideoAbortSubscriptions[cameraId] = camera.videoElement.onAbort.listen((html.Event _) { cameraEventStreamController.add( CameraErrorEvent( cameraId, "Error code: ${CameraErrorCode.abort}, error message: The video element's source has not fully loaded.", ), ); }); await camera.play(); // Add camera's closing events to the camera events stream. // The onEnded stream fires when there is no more camera stream data. _cameraEndedSubscriptions[cameraId] = camera.onEnded.listen((html.MediaStreamTrack _) { cameraEventStreamController.add( CameraClosingEvent(cameraId), ); }); final Size cameraSize = camera.getVideoSize(); cameraEventStreamController.add( CameraInitializedEvent( cameraId, cameraSize.width, cameraSize.height, // TODO(bselwe): Add support for exposure mode and point (https://github.com/flutter/flutter/issues/86857). ExposureMode.auto, false, // TODO(bselwe): Add support for focus mode and point (https://github.com/flutter/flutter/issues/86858). FocusMode.auto, false, ), ); } on html.DomException catch (e) { throw PlatformException(code: e.name, message: e.message); } on CameraWebException catch (e) { _addCameraErrorEvent(e); throw PlatformException(code: e.code.toString(), message: e.description); } } @override Stream onCameraInitialized(int cameraId) { return _cameraEvents(cameraId).whereType(); } /// Emits an empty stream as there is no event corresponding to a change /// in the camera resolution on the web. /// /// In order to change the camera resolution a new camera with appropriate /// [CameraOptions.video] constraints has to be created and initialized. @override Stream onCameraResolutionChanged(int cameraId) { return const Stream.empty(); } @override Stream onCameraClosing(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onCameraError(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onVideoRecordedEvent(int cameraId) { return getCamera(cameraId).onVideoRecordedEvent; } @override Stream onDeviceOrientationChanged() { final html.ScreenOrientation? orientation = window?.screen?.orientation; if (orientation != null) { // Create an initial orientation event that emits the device orientation // as soon as subscribed to this stream. final html.Event initialOrientationEvent = html.Event('change'); return orientation.onChange.startWith(initialOrientationEvent).map( (html.Event _) { final DeviceOrientation deviceOrientation = _cameraService .mapOrientationTypeToDeviceOrientation(orientation.type!); return DeviceOrientationChangedEvent(deviceOrientation); }, ); } else { return const Stream.empty(); } } @override Future lockCaptureOrientation( int cameraId, DeviceOrientation orientation, ) async { try { final html.ScreenOrientation? screenOrientation = window?.screen?.orientation; final html.Element? documentElement = window?.document.documentElement; if (screenOrientation != null && documentElement != null) { final String orientationType = _cameraService.mapDeviceOrientationToOrientationType(orientation); // Full-screen mode may be required to modify the device orientation. // See: https://w3c.github.io/screen-orientation/#interaction-with-fullscreen-api // Recent versions of Dart changed requestFullscreen to return a Future instead of void. // This wrapper allows use of both the old and new APIs. dynamic fullScreen() => documentElement.requestFullscreen(); await fullScreen(); await screenOrientation.lock(orientationType); } else { throw PlatformException( code: CameraErrorCode.orientationNotSupported.toString(), message: 'Orientation is not supported in the current browser.', ); } } on html.DomException catch (e) { throw PlatformException(code: e.name, message: e.message); } } @override Future unlockCaptureOrientation(int cameraId) async { try { final html.ScreenOrientation? orientation = window?.screen?.orientation; final html.Element? documentElement = window?.document.documentElement; if (orientation != null && documentElement != null) { orientation.unlock(); } else { throw PlatformException( code: CameraErrorCode.orientationNotSupported.toString(), message: 'Orientation is not supported in the current browser.', ); } } on html.DomException catch (e) { throw PlatformException(code: e.name, message: e.message); } } @override Future takePicture(int cameraId) { try { return getCamera(cameraId).takePicture(); } on html.DomException catch (e) { throw PlatformException(code: e.name, message: e.message); } on CameraWebException catch (e) { _addCameraErrorEvent(e); throw PlatformException(code: e.code.toString(), message: e.description); } } @override Future prepareForVideoRecording() async { // This is a no-op as it is not required for the web. } @override Future startVideoRecording(int cameraId, {Duration? maxVideoDuration}) { return startVideoCapturing( VideoCaptureOptions(cameraId, maxDuration: maxVideoDuration)); } @override Future startVideoCapturing(VideoCaptureOptions options) { if (options.streamCallback != null || options.streamOptions != null) { throw UnimplementedError('Streaming is not currently supported on web'); } try { final Camera camera = getCamera(options.cameraId); // Add camera's video recording errors to the camera events stream. // The error event fires when the video recording is not allowed or an unsupported // codec is used. _cameraVideoRecordingErrorSubscriptions[options.cameraId] = camera.onVideoRecordingError.listen((html.ErrorEvent errorEvent) { cameraEventStreamController.add( CameraErrorEvent( options.cameraId, 'Error code: ${errorEvent.type}, error message: ${errorEvent.message}.', ), ); }); return camera.startVideoRecording(maxVideoDuration: options.maxDuration); } on html.DomException catch (e) { throw PlatformException(code: e.name, message: e.message); } on CameraWebException catch (e) { _addCameraErrorEvent(e); throw PlatformException(code: e.code.toString(), message: e.description); } } @override Future stopVideoRecording(int cameraId) async { try { final XFile videoRecording = await getCamera(cameraId).stopVideoRecording(); await _cameraVideoRecordingErrorSubscriptions[cameraId]?.cancel(); return videoRecording; } on html.DomException catch (e) { throw PlatformException(code: e.name, message: e.message); } on CameraWebException catch (e) { _addCameraErrorEvent(e); throw PlatformException(code: e.code.toString(), message: e.description); } } @override Future pauseVideoRecording(int cameraId) { try { return getCamera(cameraId).pauseVideoRecording(); } on html.DomException catch (e) { throw PlatformException(code: e.name, message: e.message); } on CameraWebException catch (e) { _addCameraErrorEvent(e); throw PlatformException(code: e.code.toString(), message: e.description); } } @override Future resumeVideoRecording(int cameraId) { try { return getCamera(cameraId).resumeVideoRecording(); } on html.DomException catch (e) { throw PlatformException(code: e.name, message: e.message); } on CameraWebException catch (e) { _addCameraErrorEvent(e); throw PlatformException(code: e.code.toString(), message: e.description); } } @override Future setFlashMode(int cameraId, FlashMode mode) async { try { getCamera(cameraId).setFlashMode(mode); } on html.DomException catch (e) { throw PlatformException(code: e.name, message: e.message); } on CameraWebException catch (e) { _addCameraErrorEvent(e); throw PlatformException(code: e.code.toString(), message: e.description); } } @override Future setExposureMode(int cameraId, ExposureMode mode) { throw UnimplementedError('setExposureMode() is not implemented.'); } @override Future setExposurePoint(int cameraId, Point? point) { throw UnimplementedError('setExposurePoint() is not implemented.'); } @override Future getMinExposureOffset(int cameraId) { throw UnimplementedError('getMinExposureOffset() is not implemented.'); } @override Future getMaxExposureOffset(int cameraId) { throw UnimplementedError('getMaxExposureOffset() is not implemented.'); } @override Future getExposureOffsetStepSize(int cameraId) { throw UnimplementedError('getExposureOffsetStepSize() is not implemented.'); } @override Future setExposureOffset(int cameraId, double offset) { throw UnimplementedError('setExposureOffset() is not implemented.'); } @override Future setFocusMode(int cameraId, FocusMode mode) { throw UnimplementedError('setFocusMode() is not implemented.'); } @override Future setFocusPoint(int cameraId, Point? point) { throw UnimplementedError('setFocusPoint() is not implemented.'); } @override Future getMaxZoomLevel(int cameraId) async { try { return getCamera(cameraId).getMaxZoomLevel(); } on html.DomException catch (e) { throw PlatformException(code: e.name, message: e.message); } on CameraWebException catch (e) { _addCameraErrorEvent(e); throw PlatformException(code: e.code.toString(), message: e.description); } } @override Future getMinZoomLevel(int cameraId) async { try { return getCamera(cameraId).getMinZoomLevel(); } on html.DomException catch (e) { throw PlatformException(code: e.name, message: e.message); } on CameraWebException catch (e) { _addCameraErrorEvent(e); throw PlatformException(code: e.code.toString(), message: e.description); } } @override Future setZoomLevel(int cameraId, double zoom) async { try { getCamera(cameraId).setZoomLevel(zoom); } on html.DomException catch (e) { throw CameraException(e.name, e.message); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } on CameraWebException catch (e) { _addCameraErrorEvent(e); throw CameraException(e.code.toString(), e.description); } } @override Future pausePreview(int cameraId) async { try { getCamera(cameraId).pause(); } on html.DomException catch (e) { throw PlatformException(code: e.name, message: e.message); } } @override Future resumePreview(int cameraId) async { try { await getCamera(cameraId).play(); } on html.DomException catch (e) { throw PlatformException(code: e.name, message: e.message); } on CameraWebException catch (e) { _addCameraErrorEvent(e); throw PlatformException(code: e.code.toString(), message: e.description); } } @override Widget buildPreview(int cameraId) { return HtmlElementView( viewType: getCamera(cameraId).getViewType(), ); } @override Future dispose(int cameraId) async { try { await getCamera(cameraId).dispose(); await _cameraVideoErrorSubscriptions[cameraId]?.cancel(); await _cameraVideoAbortSubscriptions[cameraId]?.cancel(); await _cameraEndedSubscriptions[cameraId]?.cancel(); await _cameraVideoRecordingErrorSubscriptions[cameraId]?.cancel(); cameras.remove(cameraId); _cameraVideoErrorSubscriptions.remove(cameraId); _cameraVideoAbortSubscriptions.remove(cameraId); _cameraEndedSubscriptions.remove(cameraId); } on html.DomException catch (e) { throw PlatformException(code: e.name, message: e.message); } } /// Returns a media video stream for the device with the given [deviceId]. Future _getVideoStreamForDevice( String deviceId, ) { // Create camera options with the desired device id. final CameraOptions cameraOptions = CameraOptions( video: VideoConstraints(deviceId: deviceId), ); return _cameraService.getMediaStreamForOptions(cameraOptions); } /// Returns a camera for the given [cameraId]. /// /// Throws a [CameraException] if the camera does not exist. @visibleForTesting Camera getCamera(int cameraId) { final Camera? camera = cameras[cameraId]; if (camera == null) { throw PlatformException( code: CameraErrorCode.notFound.toString(), message: 'No camera found for the given camera id $cameraId.', ); } return camera; } /// Adds a [CameraErrorEvent], associated with the [exception], /// to the stream of camera events. void _addCameraErrorEvent(CameraWebException exception) { cameraEventStreamController.add( CameraErrorEvent( exception.cameraId, 'Error code: ${exception.code}, error message: ${exception.description}', ), ); } } ================================================ FILE: packages/camera/camera_web/lib/src/shims/dart_js_util.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:js_util' as js_util; /// A utility that shims dart:js_util to manipulate JavaScript interop objects. class JsUtil { /// Returns true if the object [o] has the property [name]. bool hasProperty(Object o, Object name) => js_util.hasProperty(o, name); /// Returns the value of the property [name] in the object [o]. dynamic getProperty(Object o, Object name) => js_util.getProperty(o, name); } ================================================ FILE: packages/camera/camera_web/lib/src/shims/dart_ui.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// This file shims dart:ui in web-only scenarios, getting rid of the need to /// suppress analyzer warnings. // TODO(ditman): Remove this file once web-only dart:ui APIs are exposed from // a dedicated place. https://github.com/flutter/flutter/issues/55000 export 'dart_ui_fake.dart' if (dart.library.html) 'dart_ui_real.dart'; ================================================ FILE: packages/camera/camera_web/lib/src/shims/dart_ui_fake.dart ================================================ // Copyright 2013 The Flutter 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:html' as html; // Fake interface for the logic that this package needs from (web-only) dart:ui. // This is conditionally exported so the analyzer sees these methods as available. // ignore_for_file: avoid_classes_with_only_static_members // ignore_for_file: camel_case_types /// Shim for web_ui engine.PlatformViewRegistry /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/ui.dart#L62 class platformViewRegistry { /// Shim for registerViewFactory /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/ui.dart#L72 static bool registerViewFactory( String viewTypeId, html.Element Function(int viewId) viewFactory) { return false; } } /// Shim for web_ui engine.AssetManager. /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/src/engine/assets.dart#L12 class webOnlyAssetManager { /// Shim for getAssetUrl. /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/src/engine/assets.dart#L45 static String getAssetUrl(String asset) => ''; } /// Signature of callbacks that have no arguments and return no data. typedef VoidCallback = void Function(); ================================================ FILE: packages/camera/camera_web/lib/src/shims/dart_ui_real.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'dart:ui'; ================================================ FILE: packages/camera/camera_web/lib/src/types/camera_error_code.dart ================================================ // Copyright 2013 The Flutter 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:html' as html; /// Error codes that may occur during the camera initialization, /// configuration or video streaming. class CameraErrorCode { const CameraErrorCode._(this._type); final String _type; @override String toString() => _type; /// The camera is not supported. static const CameraErrorCode notSupported = CameraErrorCode._('cameraNotSupported'); /// The camera is not found. static const CameraErrorCode notFound = CameraErrorCode._('cameraNotFound'); /// The camera is not readable. static const CameraErrorCode notReadable = CameraErrorCode._('cameraNotReadable'); /// The camera options are impossible to satisfy. static const CameraErrorCode overconstrained = CameraErrorCode._('cameraOverconstrained'); /// The camera cannot be used or the permission /// to access the camera is not granted. static const CameraErrorCode permissionDenied = CameraErrorCode._('CameraAccessDenied'); /// The camera options are incorrect or attempted /// to access the media input from an insecure context. static const CameraErrorCode type = CameraErrorCode._('cameraType'); /// Some problem occurred that prevented the camera from being used. static const CameraErrorCode abort = CameraErrorCode._('cameraAbort'); /// The user media support is disabled in the current browser. static const CameraErrorCode security = CameraErrorCode._('cameraSecurity'); /// The camera metadata is missing. static const CameraErrorCode missingMetadata = CameraErrorCode._('cameraMissingMetadata'); /// The camera orientation is not supported. static const CameraErrorCode orientationNotSupported = CameraErrorCode._('orientationNotSupported'); /// The camera torch mode is not supported. static const CameraErrorCode torchModeNotSupported = CameraErrorCode._('torchModeNotSupported'); /// The camera zoom level is not supported. static const CameraErrorCode zoomLevelNotSupported = CameraErrorCode._('zoomLevelNotSupported'); /// The camera zoom level is invalid. static const CameraErrorCode zoomLevelInvalid = CameraErrorCode._('zoomLevelInvalid'); /// The camera has not been initialized or started. static const CameraErrorCode notStarted = CameraErrorCode._('cameraNotStarted'); /// The video recording was not started. static const CameraErrorCode videoRecordingNotStarted = CameraErrorCode._('videoRecordingNotStarted'); /// An unknown camera error. static const CameraErrorCode unknown = CameraErrorCode._('cameraUnknown'); /// Returns a camera error code based on the media error. /// /// See: https://developer.mozilla.org/en-US/docs/Web/API/MediaError/code static CameraErrorCode fromMediaError(html.MediaError error) { switch (error.code) { case html.MediaError.MEDIA_ERR_ABORTED: return const CameraErrorCode._('mediaErrorAborted'); case html.MediaError.MEDIA_ERR_NETWORK: return const CameraErrorCode._('mediaErrorNetwork'); case html.MediaError.MEDIA_ERR_DECODE: return const CameraErrorCode._('mediaErrorDecode'); case html.MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED: return const CameraErrorCode._('mediaErrorSourceNotSupported'); default: return const CameraErrorCode._('mediaErrorUnknown'); } } } ================================================ FILE: packages/camera/camera_web/lib/src/types/camera_metadata.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; /// Metadata used along the camera description /// to store additional web-specific camera details. @immutable class CameraMetadata { /// Creates a new instance of [CameraMetadata] /// with the given [deviceId] and [facingMode]. const CameraMetadata({required this.deviceId, required this.facingMode}); /// Uniquely identifies the camera device. /// /// See: https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo/deviceId final String deviceId; /// Describes the direction the camera is facing towards. /// May be `user`, `environment`, `left`, `right` /// or null if the facing mode is not available. /// /// See: https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings/facingMode final String? facingMode; @override bool operator ==(Object other) { if (identical(this, other)) { return true; } return other is CameraMetadata && other.deviceId == deviceId && other.facingMode == facingMode; } @override int get hashCode => Object.hash(deviceId.hashCode, facingMode.hashCode); } ================================================ FILE: packages/camera/camera_web/lib/src/types/camera_options.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; /// Options used to create a camera with the given /// [audio] and [video] media constraints. /// /// These options represent web `MediaStreamConstraints` /// and can be used to request the browser for media streams /// with audio and video tracks containing the requested types of media. /// /// https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints @immutable class CameraOptions { /// Creates a new instance of [CameraOptions] /// with the given [audio] and [video] constraints. const CameraOptions({ AudioConstraints? audio, VideoConstraints? video, }) : audio = audio ?? const AudioConstraints(), video = video ?? const VideoConstraints(); /// The audio constraints for the camera. final AudioConstraints audio; /// The video constraints for the camera. final VideoConstraints video; /// Converts the current instance to a Map. Map toJson() { return { 'audio': audio.toJson(), 'video': video.toJson(), }; } @override bool operator ==(Object other) { if (identical(this, other)) { return true; } return other is CameraOptions && other.audio == audio && other.video == video; } @override int get hashCode => Object.hash(audio, video); } /// Indicates whether the audio track is requested. /// /// By default, the audio track is not requested. @immutable class AudioConstraints { /// Creates a new instance of [AudioConstraints] /// with the given [enabled] constraint. const AudioConstraints({this.enabled = false}); /// Whether the audio track should be enabled. final bool enabled; /// Converts the current instance to a Map. Object toJson() => enabled; @override bool operator ==(Object other) { if (identical(this, other)) { return true; } return other is AudioConstraints && other.enabled == enabled; } @override int get hashCode => enabled.hashCode; } /// Defines constraints that the video track must have /// to be considered acceptable. @immutable class VideoConstraints { /// Creates a new instance of [VideoConstraints] /// with the given constraints. const VideoConstraints({ this.facingMode, this.width, this.height, this.deviceId, }); /// The facing mode of the video track. final FacingModeConstraint? facingMode; /// The width of the video track. final VideoSizeConstraint? width; /// The height of the video track. final VideoSizeConstraint? height; /// The device id of the video track. final String? deviceId; /// Converts the current instance to a Map. Object toJson() { final Map json = {}; if (width != null) { json['width'] = width!.toJson(); } if (height != null) { json['height'] = height!.toJson(); } if (facingMode != null) { json['facingMode'] = facingMode!.toJson(); } if (deviceId != null) { json['deviceId'] = {'exact': deviceId!}; } return json; } @override bool operator ==(Object other) { if (identical(this, other)) { return true; } return other is VideoConstraints && other.facingMode == facingMode && other.width == width && other.height == height && other.deviceId == deviceId; } @override int get hashCode => Object.hash(facingMode, width, height, deviceId); } /// The camera type used in [FacingModeConstraint]. /// /// Specifies whether the requested camera should be facing away /// or toward the user. class CameraType { const CameraType._(this._type); final String _type; @override String toString() => _type; /// The camera is facing away from the user, viewing their environment. /// This includes the back camera on a smartphone. static const CameraType environment = CameraType._('environment'); /// The camera is facing toward the user. /// This includes the front camera on a smartphone. static const CameraType user = CameraType._('user'); } /// Indicates the direction in which the desired camera should be pointing. @immutable class FacingModeConstraint { /// Creates a new instance of [FacingModeConstraint] /// with [ideal] constraint set to [type]. factory FacingModeConstraint(CameraType type) => FacingModeConstraint._(ideal: type); /// Creates a new instance of [FacingModeConstraint] /// with the given [ideal] and [exact] constraints. const FacingModeConstraint._({this.ideal, this.exact}); /// Creates a new instance of [FacingModeConstraint] /// with [exact] constraint set to [type]. factory FacingModeConstraint.exact(CameraType type) => FacingModeConstraint._(exact: type); /// The ideal facing mode constraint. /// /// If this constraint is used, then the camera would ideally have /// the desired facing [type] but it may be considered optional. final CameraType? ideal; /// The exact facing mode constraint. /// /// If this constraint is used, then the camera must have /// the desired facing [type] to be considered acceptable. final CameraType? exact; /// Converts the current instance to a Map. Object toJson() { return { if (ideal != null) 'ideal': ideal.toString(), if (exact != null) 'exact': exact.toString(), }; } @override bool operator ==(Object other) { if (identical(this, other)) { return true; } return other is FacingModeConstraint && other.ideal == ideal && other.exact == exact; } @override int get hashCode => Object.hash(ideal, exact); } /// The size of the requested video track used in /// [VideoConstraints.width] and [VideoConstraints.height]. /// /// The obtained video track will have a size between [minimum] and [maximum] /// with ideally a size of [ideal]. The size is determined by /// the capabilities of the hardware and the other specified constraints. @immutable class VideoSizeConstraint { /// Creates a new instance of [VideoSizeConstraint] with the given /// [minimum], [ideal] and [maximum] constraints. const VideoSizeConstraint({this.minimum, this.ideal, this.maximum}); /// The minimum video size. final int? minimum; /// The ideal video size. /// /// The video would ideally have the [ideal] size /// but it may be considered optional. If not possible /// to satisfy, the size will be as close as possible /// to [ideal]. final int? ideal; /// The maximum video size. final int? maximum; /// Converts the current instance to a Map. Object toJson() { final Map json = {}; if (ideal != null) { json['ideal'] = ideal; } if (minimum != null) { json['min'] = minimum; } if (maximum != null) { json['max'] = maximum; } return json; } @override bool operator ==(Object other) { if (identical(this, other)) { return true; } return other is VideoSizeConstraint && other.minimum == minimum && other.ideal == ideal && other.maximum == maximum; } @override int get hashCode => Object.hash(minimum, ideal, maximum); } ================================================ FILE: packages/camera/camera_web/lib/src/types/camera_web_exception.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'types.dart'; /// An exception thrown when the camera with id [cameraId] reports /// an initialization, configuration or video streaming error, /// or enters into an unexpected state. /// /// This error should be emitted on the `onCameraError` stream /// of the camera platform. class CameraWebException implements Exception { /// Creates a new instance of [CameraWebException] /// with the given error [cameraId], [code] and [description]. CameraWebException(this.cameraId, this.code, this.description); /// The id of the camera this exception is associated to. int cameraId; /// The error code of this exception. CameraErrorCode code; /// The description of this exception. String description; @override String toString() => 'CameraWebException($cameraId, $code, $description)'; } ================================================ FILE: packages/camera/camera_web/lib/src/types/media_device_kind.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// A kind of a media device. /// /// See: https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo/kind abstract class MediaDeviceKind { /// A video input media device kind. static const String videoInput = 'videoinput'; /// An audio input media device kind. static const String audioInput = 'audioinput'; /// An audio output media device kind. static const String audioOutput = 'audiooutput'; } ================================================ FILE: packages/camera/camera_web/lib/src/types/orientation_type.dart ================================================ // Copyright 2013 The Flutter 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'; /// A screen orientation type. /// /// See: https://developer.mozilla.org/en-US/docs/Web/API/ScreenOrientation/type abstract class OrientationType { /// The primary portrait mode orientation. /// Corresponds to [DeviceOrientation.portraitUp]. static const String portraitPrimary = 'portrait-primary'; /// The secondary portrait mode orientation. /// Corresponds to [DeviceOrientation.portraitSecondary]. static const String portraitSecondary = 'portrait-secondary'; /// The primary landscape mode orientation. /// Corresponds to [DeviceOrientation.landscapeLeft]. static const String landscapePrimary = 'landscape-primary'; /// The secondary landscape mode orientation. /// Corresponds to [DeviceOrientation.landscapeRight]. static const String landscapeSecondary = 'landscape-secondary'; } ================================================ FILE: packages/camera/camera_web/lib/src/types/types.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'camera_error_code.dart'; export 'camera_metadata.dart'; export 'camera_options.dart'; export 'camera_web_exception.dart'; export 'media_device_kind.dart'; export 'orientation_type.dart'; export 'zoom_level_capability.dart'; ================================================ FILE: packages/camera/camera_web/lib/src/types/zoom_level_capability.dart ================================================ // Copyright 2013 The Flutter 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:html' as html; import 'package:flutter/foundation.dart'; /// The possible range of values for the zoom level configurable /// on the camera video track. @immutable class ZoomLevelCapability { /// Creates a new instance of [ZoomLevelCapability] with the given /// zoom level range of [minimum] to [maximum] configurable /// on the [videoTrack]. const ZoomLevelCapability({ required this.minimum, required this.maximum, required this.videoTrack, }); /// The zoom level constraint name. /// See: https://w3c.github.io/mediacapture-image/#dom-mediatracksupportedconstraints-zoom static const String constraintName = 'zoom'; /// The minimum zoom level. final double minimum; /// The maximum zoom level. final double maximum; /// The video track capable of configuring the zoom level. final html.MediaStreamTrack videoTrack; @override bool operator ==(Object other) { if (identical(this, other)) { return true; } return other is ZoomLevelCapability && other.minimum == minimum && other.maximum == maximum && other.videoTrack == videoTrack; } @override int get hashCode => Object.hash(minimum, maximum, videoTrack); } ================================================ FILE: packages/camera/camera_web/pubspec.yaml ================================================ 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+1 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: camera platforms: web: pluginClass: CameraPlugin fileName: camera_web.dart dependencies: camera_platform_interface: ^2.3.1 flutter: sdk: flutter flutter_web_plugins: sdk: flutter stream_transform: ^2.0.0 dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/camera/camera_web/test/README.md ================================================ ## test This package uses integration tests for testing. See `example/README.md` for more info. ================================================ FILE: packages/camera/camera_web/test/more_tests_exist_elsewhere_test.dart ================================================ // Copyright 2013 The Flutter 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: avoid_print import 'package:flutter_test/flutter_test.dart'; void main() { test('Tell the user where to find more tests', () { print('---'); print('This package also uses integration_test for its tests.'); print('See `example/README.md` for more info.'); print('---'); }); } ================================================ FILE: packages/camera/camera_windows/.gitignore ================================================ .DS_Store .dart_tool/ .packages .pub/ build/ ================================================ FILE: packages/camera/camera_windows/.metadata ================================================ # 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: 18116933e77adc82f80866c928266a5b4f1ed645 channel: stable project_type: plugin ================================================ FILE: packages/camera/camera_windows/AUTHORS ================================================ # 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. Joonas Kerttula Codemate Ltd. ================================================ FILE: packages/camera/camera_windows/CHANGELOG.md ================================================ ## 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). ## 0.2.1+2 * Updates code for `no_leading_underscores_for_local_identifiers` lint. * Updates minimum Flutter version to 2.10. ## 0.2.1+1 * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 0.2.1 * Adds a check for string size before Win32 MultiByte <-> WideChar conversions ## 0.2.0 **BREAKING CHANGES**: * `CameraException.code` now has value `"CameraAccessDenied"` if camera access permission was denied. * `CameraException.code` now has value `"camera_error"` if error occurs during capture. ## 0.1.0+5 * Fixes bugs in in error handling. ## 0.1.0+4 * Allows retrying camera initialization after error. ## 0.1.0+3 * Updates the README to better explain how to use the unendorsed package. ## 0.1.0+2 * Updates references to the obsolete master branch. ## 0.1.0+1 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.1.0 * Initial release ================================================ FILE: packages/camera/camera_windows/LICENSE ================================================ 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. ================================================ FILE: packages/camera/camera_windows/README.md ================================================ # Camera Windows Plugin The Windows implementation of [`camera`][camera]. *Note*: This plugin is under development. See [missing implementations and limitations](#missing-features-on-the-windows-platform). ## Usage ### Depend on the package This package is not an [endorsed][endorsed-federated-plugin] implementation of the [`camera`][camera] plugin, so in addition to depending on [`camera`][camera] you'll need to [add `camera_windows` to your pubspec.yaml explicitly][install]. Once you do, you can use the [`camera`][camera] APIs as you normally would. ## Missing features on the Windows platform ### Device orientation Device orientation detection is not yet implemented: [issue #97540][device-orientation-issue]. ### Pause and Resume video recording Pausing and resuming the video recording is not supported due to Windows API limitations. ### Exposure mode, point and offset Support for explosure mode and offset is not yet implemented: [issue #97537][camera-control-issue]. Exposure points are not supported due to limitations of the Windows API. ### Focus mode and point Support for explosure mode and offset is not yet implemented: [issue #97537][camera-control-issue]. ### Flash mode Support for flash mode is not yet implemented: [issue #97537][camera-control-issue]. Focus points are not supported due to current limitations of the Windows API. ### Streaming of frames Support for image streaming is not yet implemented: [issue #97542][image-streams-issue]. ## Error handling Camera errors can be listened using the platform's `onCameraError` method. Listening to errors is important, and in certain situations, disposing of the camera is the only way to reset the situation. [camera]: https://pub.dev/packages/camera [endorsed-federated-plugin]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin [install]: https://pub.dev/packages/camera_windows/install [camera-control-issue]: https://github.com/flutter/flutter/issues/97537 [device-orientation-issue]: https://github.com/flutter/flutter/issues/97540 [image-streams-issue]: https://github.com/flutter/flutter/issues/97542 ================================================ FILE: packages/camera/camera_windows/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Android Studio will place build artifacts here /android/app/debug /android/app/profile /android/app/release ================================================ FILE: packages/camera/camera_windows/example/.metadata ================================================ # 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: 18116933e77adc82f80866c928266a5b4f1ed645 channel: stable project_type: app ================================================ FILE: packages/camera/camera_windows/example/README.md ================================================ # 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. ================================================ FILE: packages/camera/camera_windows/example/integration_test/camera_test.dart ================================================ // Copyright 2013 The Flutter 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:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; // Note that these integration tests do not currently cover // most features and code paths, as they can only be tested if // one or more cameras are available in the test environment. // Native unit tests with better coverage are available at // the native part of the plugin implementation. void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('initializeCamera', () { testWidgets('throws exception if camera is not created', (WidgetTester _) async { final CameraPlatform camera = CameraPlatform.instance; expect(() async => camera.initializeCamera(1234), throwsA(isA())); }); }); group('takePicture', () { testWidgets('throws exception if camera is not created', (WidgetTester _) async { final CameraPlatform camera = CameraPlatform.instance; expect(() async => camera.takePicture(1234), throwsA(isA())); }); }); group('startVideoRecording', () { testWidgets('throws exception if camera is not created', (WidgetTester _) async { final CameraPlatform camera = CameraPlatform.instance; expect(() async => camera.startVideoRecording(1234), throwsA(isA())); }); }); group('stopVideoRecording', () { testWidgets('throws exception if camera is not created', (WidgetTester _) async { final CameraPlatform camera = CameraPlatform.instance; expect(() async => camera.stopVideoRecording(1234), throwsA(isA())); }); }); group('pausePreview', () { testWidgets('throws exception if camera is not created', (WidgetTester _) async { final CameraPlatform camera = CameraPlatform.instance; expect(() async => camera.pausePreview(1234), throwsA(isA())); }); }); group('resumePreview', () { testWidgets('throws exception if camera is not created', (WidgetTester _) async { final CameraPlatform camera = CameraPlatform.instance; expect(() async => camera.resumePreview(1234), throwsA(isA())); }); }); group('onDeviceOrientationChanged', () { testWidgets('emits the initial DeviceOrientationChangedEvent', (WidgetTester _) async { final Stream eventStream = CameraPlatform.instance.onDeviceOrientationChanged(); final StreamQueue streamQueue = StreamQueue(eventStream); expect( await streamQueue.next, equals( const DeviceOrientationChangedEvent( DeviceOrientation.landscapeRight, ), ), ); }); }); } ================================================ FILE: packages/camera/camera_windows/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() { runApp(const MyApp()); } /// Example app for Camera Windows plugin. class MyApp extends StatefulWidget { /// Default Constructor const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { String _cameraInfo = 'Unknown'; List _cameras = []; int _cameraIndex = 0; int _cameraId = -1; bool _initialized = false; bool _recording = false; bool _recordingTimed = false; bool _recordAudio = true; bool _previewPaused = false; Size? _previewSize; ResolutionPreset _resolutionPreset = ResolutionPreset.veryHigh; StreamSubscription? _errorStreamSubscription; StreamSubscription? _cameraClosingStreamSubscription; @override void initState() { super.initState(); WidgetsFlutterBinding.ensureInitialized(); _fetchCameras(); } @override void dispose() { _disposeCurrentCamera(); _errorStreamSubscription?.cancel(); _errorStreamSubscription = null; _cameraClosingStreamSubscription?.cancel(); _cameraClosingStreamSubscription = null; super.dispose(); } /// Fetches list of available cameras from camera_windows plugin. Future _fetchCameras() async { String cameraInfo; List cameras = []; int cameraIndex = 0; try { cameras = await CameraPlatform.instance.availableCameras(); if (cameras.isEmpty) { cameraInfo = 'No available cameras'; } else { cameraIndex = _cameraIndex % cameras.length; cameraInfo = 'Found camera: ${cameras[cameraIndex].name}'; } } on PlatformException catch (e) { cameraInfo = 'Failed to get cameras: ${e.code}: ${e.message}'; } if (mounted) { setState(() { _cameraIndex = cameraIndex; _cameras = cameras; _cameraInfo = cameraInfo; }); } } /// Initializes the camera on the device. Future _initializeCamera() async { assert(!_initialized); if (_cameras.isEmpty) { return; } int cameraId = -1; try { final int cameraIndex = _cameraIndex % _cameras.length; final CameraDescription camera = _cameras[cameraIndex]; cameraId = await CameraPlatform.instance.createCamera( camera, _resolutionPreset, enableAudio: _recordAudio, ); _errorStreamSubscription?.cancel(); _errorStreamSubscription = CameraPlatform.instance .onCameraError(cameraId) .listen(_onCameraError); _cameraClosingStreamSubscription?.cancel(); _cameraClosingStreamSubscription = CameraPlatform.instance .onCameraClosing(cameraId) .listen(_onCameraClosing); final Future initialized = CameraPlatform.instance.onCameraInitialized(cameraId).first; await CameraPlatform.instance.initializeCamera( cameraId, ); final CameraInitializedEvent event = await initialized; _previewSize = Size( event.previewWidth, event.previewHeight, ); if (mounted) { setState(() { _initialized = true; _cameraId = cameraId; _cameraIndex = cameraIndex; _cameraInfo = 'Capturing camera: ${camera.name}'; }); } } on CameraException catch (e) { try { if (cameraId >= 0) { await CameraPlatform.instance.dispose(cameraId); } } on CameraException catch (e) { debugPrint('Failed to dispose camera: ${e.code}: ${e.description}'); } // Reset state. if (mounted) { setState(() { _initialized = false; _cameraId = -1; _cameraIndex = 0; _previewSize = null; _recording = false; _recordingTimed = false; _cameraInfo = 'Failed to initialize camera: ${e.code}: ${e.description}'; }); } } } Future _disposeCurrentCamera() async { if (_cameraId >= 0 && _initialized) { try { await CameraPlatform.instance.dispose(_cameraId); if (mounted) { setState(() { _initialized = false; _cameraId = -1; _previewSize = null; _recording = false; _recordingTimed = false; _previewPaused = false; _cameraInfo = 'Camera disposed'; }); } } on CameraException catch (e) { if (mounted) { setState(() { _cameraInfo = 'Failed to dispose camera: ${e.code}: ${e.description}'; }); } } } } Widget _buildPreview() { return CameraPlatform.instance.buildPreview(_cameraId); } Future _takePicture() async { final XFile file = await CameraPlatform.instance.takePicture(_cameraId); _showInSnackBar('Picture captured to: ${file.path}'); } Future _recordTimed(int seconds) async { if (_initialized && _cameraId > 0 && !_recordingTimed) { CameraPlatform.instance .onVideoRecordedEvent(_cameraId) .first .then((VideoRecordedEvent event) async { if (mounted) { setState(() { _recordingTimed = false; }); _showInSnackBar('Video captured to: ${event.file.path}'); } }); await CameraPlatform.instance.startVideoRecording( _cameraId, maxVideoDuration: Duration(seconds: seconds), ); if (mounted) { setState(() { _recordingTimed = true; }); } } } Future _toggleRecord() async { if (_initialized && _cameraId > 0) { if (_recordingTimed) { /// Request to stop timed recording short. await CameraPlatform.instance.stopVideoRecording(_cameraId); } else { if (!_recording) { await CameraPlatform.instance.startVideoRecording(_cameraId); } else { final XFile file = await CameraPlatform.instance.stopVideoRecording(_cameraId); _showInSnackBar('Video captured to: ${file.path}'); } if (mounted) { setState(() { _recording = !_recording; }); } } } } Future _togglePreview() async { if (_initialized && _cameraId >= 0) { if (!_previewPaused) { await CameraPlatform.instance.pausePreview(_cameraId); } else { await CameraPlatform.instance.resumePreview(_cameraId); } if (mounted) { setState(() { _previewPaused = !_previewPaused; }); } } } Future _switchCamera() async { if (_cameras.isNotEmpty) { // select next index; _cameraIndex = (_cameraIndex + 1) % _cameras.length; if (_initialized && _cameraId >= 0) { await _disposeCurrentCamera(); await _fetchCameras(); if (_cameras.isNotEmpty) { await _initializeCamera(); } } else { await _fetchCameras(); } } } Future _onResolutionChange(ResolutionPreset newValue) async { setState(() { _resolutionPreset = newValue; }); if (_initialized && _cameraId >= 0) { // Re-inits camera with new resolution preset. await _disposeCurrentCamera(); await _initializeCamera(); } } Future _onAudioChange(bool recordAudio) async { setState(() { _recordAudio = recordAudio; }); if (_initialized && _cameraId >= 0) { // Re-inits camera with new record audio setting. await _disposeCurrentCamera(); await _initializeCamera(); } } void _onCameraError(CameraErrorEvent event) { if (mounted) { _scaffoldMessengerKey.currentState?.showSnackBar( SnackBar(content: Text('Error: ${event.description}'))); // Dispose camera on camera error as it can not be used anymore. _disposeCurrentCamera(); _fetchCameras(); } } void _onCameraClosing(CameraClosingEvent event) { if (mounted) { _showInSnackBar('Camera is closing'); } } void _showInSnackBar(String message) { _scaffoldMessengerKey.currentState?.showSnackBar(SnackBar( content: Text(message), duration: const Duration(seconds: 1), )); } final GlobalKey _scaffoldMessengerKey = GlobalKey(); @override Widget build(BuildContext context) { final List> resolutionItems = ResolutionPreset.values .map>((ResolutionPreset value) { return DropdownMenuItem( value: value, child: Text(value.toString()), ); }).toList(); return MaterialApp( scaffoldMessengerKey: _scaffoldMessengerKey, home: Scaffold( appBar: AppBar( title: const Text('Plugin example app'), ), body: ListView( children: [ Padding( padding: const EdgeInsets.symmetric( vertical: 5, horizontal: 10, ), child: Text(_cameraInfo), ), if (_cameras.isEmpty) ElevatedButton( onPressed: _fetchCameras, child: const Text('Re-check available cameras'), ), if (_cameras.isNotEmpty) Row( mainAxisAlignment: MainAxisAlignment.center, children: [ DropdownButton( value: _resolutionPreset, onChanged: (ResolutionPreset? value) { if (value != null) { _onResolutionChange(value); } }, items: resolutionItems, ), const SizedBox(width: 20), const Text('Audio:'), Switch( value: _recordAudio, onChanged: (bool state) => _onAudioChange(state)), const SizedBox(width: 20), ElevatedButton( onPressed: _initialized ? _disposeCurrentCamera : _initializeCamera, child: Text(_initialized ? 'Dispose camera' : 'Create camera'), ), const SizedBox(width: 5), ElevatedButton( onPressed: _initialized ? _takePicture : null, child: const Text('Take picture'), ), const SizedBox(width: 5), ElevatedButton( onPressed: _initialized ? _togglePreview : null, child: Text( _previewPaused ? 'Resume preview' : 'Pause preview', ), ), const SizedBox(width: 5), ElevatedButton( onPressed: _initialized ? _toggleRecord : null, child: Text( (_recording || _recordingTimed) ? 'Stop recording' : 'Record Video', ), ), const SizedBox(width: 5), ElevatedButton( onPressed: (_initialized && !_recording && !_recordingTimed) ? () => _recordTimed(5) : null, child: const Text( 'Record 5 seconds', ), ), if (_cameras.length > 1) ...[ const SizedBox(width: 5), ElevatedButton( onPressed: _switchCamera, child: const Text( 'Switch camera', ), ), ] ], ), const SizedBox(height: 5), if (_initialized && _cameraId > 0 && _previewSize != null) Padding( padding: const EdgeInsets.symmetric( vertical: 10, ), child: Align( child: Container( constraints: const BoxConstraints( maxHeight: 500, ), child: AspectRatio( aspectRatio: _previewSize!.width / _previewSize!.height, child: _buildPreview(), ), ), ), ), if (_previewSize != null) Center( child: Text( 'Preview size: ${_previewSize!.width.toStringAsFixed(0)}x${_previewSize!.height.toStringAsFixed(0)}', ), ), ], ), ), ); } } ================================================ FILE: packages/camera/camera_windows/example/pubspec.yaml ================================================ name: camera_windows_example description: Demonstrates how to use the camera_windows plugin. publish_to: 'none' environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: camera_platform_interface: ^2.1.2 camera_windows: # When depending on this package from a real application you should use: # camera_windows: ^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: ../ flutter: sdk: flutter dev_dependencies: async: ^2.5.0 flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/camera/camera_windows/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/camera/camera_windows/example/windows/.gitignore ================================================ flutter/ephemeral/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: packages/camera/camera_windows/example/windows/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) project(camera_windows_example LANGUAGES CXX) set(BINARY_NAME "camera_windows_example") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" CACHE STRING "" FORCE) else() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() endif() set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") # Use Unicode for all projects. add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") target_compile_options(${TARGET} PRIVATE /EHsc) target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build add_subdirectory("runner") # Enable the test target. set(include_camera_windows_tests TRUE) # Provide an alias for the test target using the name expected by repo tooling. add_custom_target(unit_tests DEPENDS camera_windows_test) # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # Support files are copied into place next to the executable, so that it can # run in place. This is done instead of making a separate bundle (as on Linux) # so that building and running from within Visual Studio will work. set(BUILD_BUNDLE_DIR "$") # Make the "install" step default, as it's required to run. set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" CONFIGURATIONS Profile;Release COMPONENT Runtime) ================================================ FILE: packages/camera/camera_windows/example/windows/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "flutter_export.h" "flutter_windows.h" "flutter_messenger.h" "flutter_plugin_registrar.h" "flutter_texture_registrar.h" ) list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") add_dependencies(flutter flutter_assemble) # === Wrapper === list(APPEND CPP_WRAPPER_SOURCES_CORE "core_implementations.cc" "standard_codec.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_PLUGIN "plugin_registrar.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_APP "flutter_engine.cc" "flutter_view_controller.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") # Wrapper sources needed for a plugin. add_library(flutter_wrapper_plugin STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ) apply_standard_settings(flutter_wrapper_plugin) set_target_properties(flutter_wrapper_plugin PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(flutter_wrapper_plugin PROPERTIES CXX_VISIBILITY_PRESET hidden) target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) target_include_directories(flutter_wrapper_plugin PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_plugin flutter_assemble) # Wrapper sources needed for the runner. add_library(flutter_wrapper_app STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_APP} ) apply_standard_settings(flutter_wrapper_app) target_link_libraries(flutter_wrapper_app PUBLIC flutter) target_include_directories(flutter_wrapper_app PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_app flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" windows-x64 $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ) ================================================ FILE: packages/camera/camera_windows/example/windows/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST camera_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/camera/camera_windows/example/windows/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) project(runner LANGUAGES CXX) add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) apply_standard_settings(${BINARY_NAME}) target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") add_dependencies(${BINARY_NAME} flutter_assemble) ================================================ FILE: packages/camera/camera_windows/example/windows/runner/Runner.rc ================================================ // Microsoft Visual C++ generated resource script. // #pragma code_page(65001) #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APP_ICON ICON "resources\\app_icon.ico" ///////////////////////////////////////////////////////////////////////////// // // Version // #ifdef FLUTTER_BUILD_NUMBER #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER #else #define VERSION_AS_NUMBER 1,0,0 #endif #ifdef FLUTTER_BUILD_NAME #define VERSION_AS_STRING #FLUTTER_BUILD_NAME #else #define VERSION_AS_STRING "1.0.0" #endif VS_VERSION_INFO VERSIONINFO FILEVERSION VERSION_AS_NUMBER PRODUCTVERSION VERSION_AS_NUMBER FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "com.example" "\0" VALUE "FileDescription", "Demonstrates how to use the camera_windows plugin." "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "camera_windows_example" "\0" VALUE "LegalCopyright", "Copyright (C) 2021 com.example. All rights reserved." "\0" VALUE "OriginalFilename", "camera_windows_example.exe" "\0" VALUE "ProductName", "camera_windows_example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: packages/camera/camera_windows/example/windows/runner/flutter_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "flutter_window.h" #include #include "flutter/generated_plugin_registrant.h" FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {} FlutterWindow::~FlutterWindow() {} bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opportunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) { return *result; } } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); } ================================================ FILE: packages/camera/camera_windows/example/windows/runner/flutter_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_ #include #include #include #include "win32_window.h" // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { public: // Creates a new FlutterWindow hosting a Flutter view running |project|. explicit FlutterWindow(const flutter::DartProject& project); virtual ~FlutterWindow(); protected: // Win32Window: bool OnCreate() override; void OnDestroy() override; LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; private: // The project to run. flutter::DartProject project_; // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; }; #endif // RUNNER_FLUTTER_WINDOW_H_ ================================================ FILE: packages/camera/camera_windows/example/windows/runner/main.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include "flutter_window.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t* command_line, _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { CreateAndAttachConsole(); } // Initialize COM, so that it is available for use in the library and/or // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); flutter::DartProject project(L"data"); std::vector command_line_arguments = GetCommandLineArguments(); project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.CreateAndShow(L"camera_windows_example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); ::MSG msg; while (::GetMessage(&msg, nullptr, 0, 0)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } ::CoUninitialize(); return EXIT_SUCCESS; } ================================================ FILE: packages/camera/camera_windows/example/windows/runner/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Runner.rc // #define IDI_APP_ICON 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: packages/camera/camera_windows/example/windows/runner/runner.exe.manifest ================================================ PerMonitorV2 ================================================ FILE: packages/camera/camera_windows/example/windows/runner/utils.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "utils.h" #include #include #include #include #include void CreateAndAttachConsole() { if (::AllocConsole()) { FILE* unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } if (freopen_s(&unused, "CONOUT$", "w", stderr)) { _dup2(_fileno(stdout), 2); } std::ios::sync_with_stdio(); FlutterDesktopResyncOutputStreams(); } } std::vector GetCommandLineArguments() { // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. int argc; wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); if (argv == nullptr) { return std::vector(); } std::vector command_line_arguments; // Skip the first argument as it's the binary name. for (int i = 1; i < argc; i++) { command_line_arguments.push_back(Utf8FromUtf16(argv[i])); } ::LocalFree(argv); return command_line_arguments; } std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); } int target_length = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, nullptr, 0, nullptr, nullptr); if (target_length == 0) { return std::string(); } std::string utf8_string; utf8_string.resize(target_length); int converted_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); } return utf8_string; } ================================================ FILE: packages/camera/camera_windows/example/windows/runner/utils.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_UTILS_H_ #define RUNNER_UTILS_H_ #include #include // Creates a console for the process, and redirects stdout and stderr to // it for both the runner and the Flutter library. void CreateAndAttachConsole(); // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string // encoded in UTF-8. Returns an empty std::string on failure. std::string Utf8FromUtf16(const wchar_t* utf16_string); // Gets the command line arguments passed in as a std::vector, // encoded in UTF-8. Returns an empty std::vector on failure. std::vector GetCommandLineArguments(); #endif // RUNNER_UTILS_H_ ================================================ FILE: packages/camera/camera_windows/example/windows/runner/win32_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "win32_window.h" #include #include "resource.h" namespace { constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); // Scale helper to convert logical scaler values to physical using passed in // scale factor int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); if (!user32_module) { return; } auto enable_non_client_dpi_scaling = reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); FreeLibrary(user32_module); } } } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; // Returns the singleton registar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; } // Returns the name of the window class, registering the class if it hasn't // previously been registered. const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; bool class_registered_ = false; }; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; RegisterClass(&window_class); class_registered_ = true; } return kWindowClassName; } void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, const Size& size) { Destroy(); const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), static_cast(origin.y)}; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } return OnCreate(); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { case WM_DESTROY: window_handle_ = nullptr; Destroy(); if (quit_on_close_) { PostQuitMessage(0); } return 0; case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left; LONG newHeight = newRectSize->bottom - newRectSize->top; SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_SIZE: { RECT rect = GetClientArea(); if (child_content_ != nullptr) { // Size and position the child window. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); } return 0; } case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_); } return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } void Win32Window::Destroy() { OnDestroy(); if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, true); SetFocus(child_content_); } RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } void Win32Window::OnDestroy() { // No-op; provided for subclasses. } ================================================ FILE: packages/camera/camera_windows/example/windows/runner/win32_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_ #include #include #include #include // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { public: struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) : width(width), height(height) {} }; Win32Window(); virtual ~Win32Window(); // Creates and shows a win32 window with |title| and position and size using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a // consistent size to will treat the width height passed in to this function // as logical pixels and scale to appropriate for the default monitor. Returns // true if the window was created successfully. bool CreateAndShow(const std::wstring& title, const Point& origin, const Size& size); // Release OS resources associated with window. void Destroy(); // Inserts |content| into the window tree. void SetChildContent(HWND content); // Returns the backing Window handle to enable clients to set icon and other // window properties. Returns nullptr if the window has been destroyed. HWND GetHandle(); // If true, closing this window will quit the application. void SetQuitOnClose(bool quit_on_close); // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Called when CreateAndShow is called, allowing subclass window-related // setup. Subclasses should return false if setup fails. virtual bool OnCreate(); // Called when Destroy is called. virtual void OnDestroy(); private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically // responsponds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; bool quit_on_close_ = false; // window handle for top level window. HWND window_handle_ = nullptr; // window handle for hosted content. HWND child_content_ = nullptr; }; #endif // RUNNER_WIN32_WINDOW_H_ ================================================ FILE: packages/camera/camera_windows/lib/camera_windows.dart ================================================ // Copyright 2013 The Flutter 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 'dart:math'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:stream_transform/stream_transform.dart'; /// An implementation of [CameraPlatform] for Windows. class CameraWindows extends CameraPlatform { /// Registers the Windows implementation of CameraPlatform. static void registerWith() { CameraPlatform.instance = CameraWindows(); } /// The method channel used to interact with the native platform. @visibleForTesting final MethodChannel pluginChannel = const MethodChannel('plugins.flutter.io/camera_windows'); /// Camera specific method channels to allow communicating with specific cameras. final Map _cameraChannels = {}; /// The controller that broadcasts events coming from handleCameraMethodCall /// /// It is a `broadcast` because multiple controllers will connect to /// different stream views of this Controller. /// This is only exposed for test purposes. It shouldn't be used by clients of /// the plugin as it may break or change at any time. @visibleForTesting final StreamController cameraEventStreamController = StreamController.broadcast(); /// Returns a stream of camera events for the given [cameraId]. Stream _cameraEvents(int cameraId) => cameraEventStreamController.stream .where((CameraEvent event) => event.cameraId == cameraId); @override Future> availableCameras() async { try { final List>? cameras = await pluginChannel .invokeListMethod>('availableCameras'); if (cameras == null) { return []; } return cameras.map((Map camera) { return CameraDescription( name: camera['name'] as String, lensDirection: parseCameraLensDirection(camera['lensFacing'] as String), sensorOrientation: camera['sensorOrientation'] as int, ); }).toList(); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } @override Future createCamera( CameraDescription cameraDescription, ResolutionPreset? resolutionPreset, { bool enableAudio = false, }) async { try { // If resolutionPreset is not specified, plugin selects the highest resolution possible. final Map? reply = await pluginChannel .invokeMapMethod('create', { 'cameraName': cameraDescription.name, 'resolutionPreset': _serializeResolutionPreset(resolutionPreset), 'enableAudio': enableAudio, }); if (reply == null) { throw CameraException('System', 'Cannot create camera'); } return reply['cameraId']! as int; } on PlatformException catch (e) { throw CameraException(e.code, e.message); } } @override Future initializeCamera( int cameraId, { ImageFormatGroup imageFormatGroup = ImageFormatGroup.unknown, }) async { final int requestedCameraId = cameraId; /// Creates channel for camera events. _cameraChannels.putIfAbsent(requestedCameraId, () { final MethodChannel channel = MethodChannel( 'plugins.flutter.io/camera_windows/camera$requestedCameraId'); channel.setMethodCallHandler( (MethodCall call) => handleCameraMethodCall(call, requestedCameraId), ); return channel; }); final Map? reply; try { reply = await pluginChannel.invokeMapMethod( 'initialize', { 'cameraId': requestedCameraId, }, ); } on PlatformException catch (e) { throw CameraException(e.code, e.message); } cameraEventStreamController.add( CameraInitializedEvent( requestedCameraId, reply!['previewWidth']!, reply['previewHeight']!, ExposureMode.auto, false, FocusMode.auto, false, ), ); } @override Future dispose(int cameraId) async { await pluginChannel.invokeMethod( 'dispose', {'cameraId': cameraId}, ); // Destroy method channel after camera is disposed to be able to handle last messages. if (_cameraChannels.containsKey(cameraId)) { final MethodChannel? cameraChannel = _cameraChannels[cameraId]; cameraChannel?.setMethodCallHandler(null); _cameraChannels.remove(cameraId); } } @override Stream onCameraInitialized(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onCameraResolutionChanged(int cameraId) { /// Windows API does not automatically change the camera's resolution /// during capture so these events are never send from the platform. /// Support for changing resolution should be implemented, if support for /// requesting resolution change is added to camera platform interface. return const Stream.empty(); } @override Stream onCameraClosing(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onCameraError(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onVideoRecordedEvent(int cameraId) { return _cameraEvents(cameraId).whereType(); } @override Stream onDeviceOrientationChanged() { // TODO(jokerttu): Implement device orientation detection, https://github.com/flutter/flutter/issues/97540. // Force device orientation to landscape as by default camera plugin uses portraitUp orientation. return Stream.value( const DeviceOrientationChangedEvent(DeviceOrientation.landscapeRight), ); } @override Future lockCaptureOrientation( int cameraId, DeviceOrientation orientation, ) async { // TODO(jokerttu): Implement lock capture orientation feature, https://github.com/flutter/flutter/issues/97540. throw UnimplementedError('lockCaptureOrientation() is not implemented.'); } @override Future unlockCaptureOrientation(int cameraId) async { // TODO(jokerttu): Implement unlock capture orientation feature, https://github.com/flutter/flutter/issues/97540. throw UnimplementedError('unlockCaptureOrientation() is not implemented.'); } @override Future takePicture(int cameraId) async { final String? path; path = await pluginChannel.invokeMethod( 'takePicture', {'cameraId': cameraId}, ); return XFile(path!); } @override Future prepareForVideoRecording() => pluginChannel.invokeMethod('prepareForVideoRecording'); @override Future startVideoRecording(int cameraId, {Duration? maxVideoDuration}) async { return startVideoCapturing( VideoCaptureOptions(cameraId, maxDuration: maxVideoDuration)); } @override Future startVideoCapturing(VideoCaptureOptions options) async { if (options.streamCallback != null || options.streamOptions != null) { throw UnimplementedError( 'Streaming is not currently supported on Windows'); } await pluginChannel.invokeMethod( 'startVideoRecording', { 'cameraId': options.cameraId, 'maxVideoDuration': options.maxDuration?.inMilliseconds, }, ); } @override Future stopVideoRecording(int cameraId) async { final String? path; path = await pluginChannel.invokeMethod( 'stopVideoRecording', {'cameraId': cameraId}, ); return XFile(path!); } @override Future pauseVideoRecording(int cameraId) async { throw UnsupportedError( 'pauseVideoRecording() is not supported due to Win32 API limitations.'); } @override Future resumeVideoRecording(int cameraId) async { throw UnsupportedError( 'resumeVideoRecording() is not supported due to Win32 API limitations.'); } @override Future setFlashMode(int cameraId, FlashMode mode) async { // TODO(jokerttu): Implement flash mode support, https://github.com/flutter/flutter/issues/97537. throw UnimplementedError('setFlashMode() is not implemented.'); } @override Future setExposureMode(int cameraId, ExposureMode mode) async { // TODO(jokerttu): Implement explosure mode support, https://github.com/flutter/flutter/issues/97537. throw UnimplementedError('setExposureMode() is not implemented.'); } @override Future setExposurePoint(int cameraId, Point? point) async { assert(point == null || point.x >= 0 && point.x <= 1); assert(point == null || point.y >= 0 && point.y <= 1); throw UnsupportedError( 'setExposurePoint() is not supported due to Win32 API limitations.'); } @override Future getMinExposureOffset(int cameraId) async { // TODO(jokerttu): Implement exposure control support, https://github.com/flutter/flutter/issues/97537. // Value is returned to support existing implementations. return 0.0; } @override Future getMaxExposureOffset(int cameraId) async { // TODO(jokerttu): Implement exposure control support, https://github.com/flutter/flutter/issues/97537. // Value is returned to support existing implementations. return 0.0; } @override Future getExposureOffsetStepSize(int cameraId) async { // TODO(jokerttu): Implement exposure control support, https://github.com/flutter/flutter/issues/97537. // Value is returned to support existing implementations. return 1.0; } @override Future setExposureOffset(int cameraId, double offset) async { // TODO(jokerttu): Implement exposure control support, https://github.com/flutter/flutter/issues/97537. throw UnimplementedError('setExposureOffset() is not implemented.'); } @override Future setFocusMode(int cameraId, FocusMode mode) async { // TODO(jokerttu): Implement focus mode support, https://github.com/flutter/flutter/issues/97537. throw UnimplementedError('setFocusMode() is not implemented.'); } @override Future setFocusPoint(int cameraId, Point? point) async { assert(point == null || point.x >= 0 && point.x <= 1); assert(point == null || point.y >= 0 && point.y <= 1); throw UnsupportedError( 'setFocusPoint() is not supported due to Win32 API limitations.'); } @override Future getMinZoomLevel(int cameraId) async { // TODO(jokerttu): Implement zoom level support, https://github.com/flutter/flutter/issues/97537. // Value is returned to support existing implementations. return 1.0; } @override Future getMaxZoomLevel(int cameraId) async { // TODO(jokerttu): Implement zoom level support, https://github.com/flutter/flutter/issues/97537. // Value is returned to support existing implementations. return 1.0; } @override Future setZoomLevel(int cameraId, double zoom) async { // TODO(jokerttu): Implement zoom level support, https://github.com/flutter/flutter/issues/97537. throw UnimplementedError('setZoomLevel() is not implemented.'); } @override Future pausePreview(int cameraId) async { await pluginChannel.invokeMethod( 'pausePreview', {'cameraId': cameraId}, ); } @override Future resumePreview(int cameraId) async { await pluginChannel.invokeMethod( 'resumePreview', {'cameraId': cameraId}, ); } @override Widget buildPreview(int cameraId) { return Texture(textureId: cameraId); } /// Returns the resolution preset as a nullable String. String? _serializeResolutionPreset(ResolutionPreset? resolutionPreset) { switch (resolutionPreset) { case null: return null; case ResolutionPreset.max: return 'max'; case ResolutionPreset.ultraHigh: return 'ultraHigh'; case ResolutionPreset.veryHigh: return 'veryHigh'; case ResolutionPreset.high: return 'high'; case ResolutionPreset.medium: return 'medium'; case ResolutionPreset.low: return 'low'; } } /// Converts messages received from the native platform into camera events. /// /// This is only exposed for test purposes. It shouldn't be used by clients /// of the plugin as it may break or change at any time. @visibleForTesting Future handleCameraMethodCall(MethodCall call, int cameraId) async { switch (call.method) { case 'camera_closing': cameraEventStreamController.add( CameraClosingEvent( cameraId, ), ); 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(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, arguments['description']! as String, ), ); break; default: throw UnimplementedError(); } } /// Parses string presentation of the camera lens direction and returns enum value. @visibleForTesting CameraLensDirection parseCameraLensDirection(String string) { switch (string) { case 'front': return CameraLensDirection.front; case 'back': return CameraLensDirection.back; case 'external': return CameraLensDirection.external; } throw ArgumentError('Unknown CameraLensDirection value'); } } ================================================ FILE: packages/camera/camera_windows/pubspec.yaml ================================================ 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+4 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: camera platforms: windows: pluginClass: CameraWindows dartPluginClass: CameraWindows dependencies: camera_platform_interface: ^2.3.1 cross_file: ^0.3.1 flutter: sdk: flutter stream_transform: ^2.0.0 dev_dependencies: async: ^2.5.0 flutter_test: sdk: flutter ================================================ FILE: packages/camera/camera_windows/test/camera_windows_test.dart ================================================ // Copyright 2013 The Flutter 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:async/async.dart'; import 'package:camera_platform_interface/camera_platform_interface.dart'; import 'package:camera_windows/camera_windows.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import './utils/method_channel_mock.dart'; void main() { const String pluginChannelName = 'plugins.flutter.io/camera_windows'; TestWidgetsFlutterBinding.ensureInitialized(); group('$CameraWindows()', () { test('registered instance', () { CameraWindows.registerWith(); expect(CameraPlatform.instance, isA()); }); group('Creation, Initialization & Disposal Tests', () { test('Should send creation data and receive back a camera id', () async { // Arrange final MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: pluginChannelName, methods: { 'create': { 'cameraId': 1, 'imageFormatGroup': 'unknown', } }); final CameraWindows plugin = CameraWindows(); // Act final int cameraId = await plugin.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.front, sensorOrientation: 0), ResolutionPreset.high, ); // Assert expect(cameraMockChannel.log, [ isMethodCall( 'create', arguments: { 'cameraName': 'Test', 'resolutionPreset': 'high', 'enableAudio': false }, ), ]); expect(cameraId, 1); }); test( 'Should throw CameraException when create throws a PlatformException', () { // Arrange MethodChannelMock( channelName: pluginChannelName, methods: { 'create': PlatformException( code: 'TESTING_ERROR_CODE', message: 'Mock error message used during testing.', ) }); final CameraWindows plugin = CameraWindows(); // Act expect( () => plugin.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ), throwsA( isA() .having( (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') .having((CameraException e) => e.description, 'description', 'Mock error message used during testing.'), ), ); }); test( 'Should throw CameraException when initialize throws a PlatformException', () { // Arrange MethodChannelMock( channelName: pluginChannelName, methods: { 'initialize': PlatformException( code: 'TESTING_ERROR_CODE', message: 'Mock error message used during testing.', ) }, ); final CameraWindows plugin = CameraWindows(); // Act expect( () => plugin.initializeCamera(0), throwsA( isA() .having((CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') .having( (CameraException e) => e.description, 'description', 'Mock error message used during testing.', ), ), ); }, ); test('Should send initialization data', () async { // Arrange final MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: pluginChannelName, methods: { 'create': { 'cameraId': 1, 'imageFormatGroup': 'unknown', }, 'initialize': { 'previewWidth': 1920.toDouble(), 'previewHeight': 1080.toDouble() }, }); final CameraWindows plugin = CameraWindows(); final int cameraId = await plugin.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ); // Act await plugin.initializeCamera(cameraId); // Assert expect(cameraId, 1); expect(cameraMockChannel.log, [ anything, isMethodCall( 'initialize', arguments: {'cameraId': 1}, ), ]); }); test('Should send a disposal call on dispose', () async { // Arrange final MethodChannelMock cameraMockChannel = MethodChannelMock( channelName: pluginChannelName, methods: { 'create': {'cameraId': 1}, 'initialize': { 'previewWidth': 1920.toDouble(), 'previewHeight': 1080.toDouble() }, 'dispose': {'cameraId': 1} }); final CameraWindows plugin = CameraWindows(); final int cameraId = await plugin.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ); await plugin.initializeCamera(cameraId); // Act await plugin.dispose(cameraId); // Assert expect(cameraId, 1); expect(cameraMockChannel.log, [ anything, anything, isMethodCall( 'dispose', arguments: {'cameraId': 1}, ), ]); }); }); group('Event Tests', () { late CameraWindows plugin; late int cameraId; setUp(() async { MethodChannelMock( channelName: pluginChannelName, methods: { 'create': {'cameraId': 1}, 'initialize': { 'previewWidth': 1920.toDouble(), 'previewHeight': 1080.toDouble() }, }, ); plugin = CameraWindows(); cameraId = await plugin.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ); await plugin.initializeCamera(cameraId); }); test('Should receive camera closing events', () async { // Act final Stream eventStream = plugin.onCameraClosing(cameraId); final StreamQueue streamQueue = StreamQueue(eventStream); // Emit test events final CameraClosingEvent event = CameraClosingEvent(cameraId); await plugin.handleCameraMethodCall( MethodCall('camera_closing', event.toJson()), cameraId); await plugin.handleCameraMethodCall( MethodCall('camera_closing', event.toJson()), cameraId); await plugin.handleCameraMethodCall( MethodCall('camera_closing', event.toJson()), cameraId); // Assert expect(await streamQueue.next, event); expect(await streamQueue.next, event); expect(await streamQueue.next, event); // Clean up await streamQueue.cancel(); }); test('Should receive camera error events', () async { // Act final Stream errorStream = plugin.onCameraError(cameraId); final StreamQueue streamQueue = StreamQueue(errorStream); // Emit test events final CameraErrorEvent event = CameraErrorEvent(cameraId, 'Error Description'); await plugin.handleCameraMethodCall( MethodCall('error', event.toJson()), cameraId); await plugin.handleCameraMethodCall( MethodCall('error', event.toJson()), cameraId); await plugin.handleCameraMethodCall( MethodCall('error', event.toJson()), cameraId); // Assert expect(await streamQueue.next, event); expect(await streamQueue.next, event); expect(await streamQueue.next, event); // Clean up await streamQueue.cancel(); }); }); group('Function Tests', () { late CameraWindows plugin; late int cameraId; setUp(() async { MethodChannelMock( channelName: pluginChannelName, methods: { 'create': {'cameraId': 1}, 'initialize': { 'previewWidth': 1920.toDouble(), 'previewHeight': 1080.toDouble() }, }, ); plugin = CameraWindows(); cameraId = await plugin.createCamera( const CameraDescription( name: 'Test', lensDirection: CameraLensDirection.back, sensorOrientation: 0, ), ResolutionPreset.high, ); await plugin.initializeCamera(cameraId); }); test('Should fetch CameraDescription instances for available cameras', () async { // Arrange final List returnData = [ { 'name': 'Test 1', 'lensFacing': 'front', 'sensorOrientation': 1 }, { 'name': 'Test 2', 'lensFacing': 'back', 'sensorOrientation': 2 } ]; final MethodChannelMock channel = MethodChannelMock( channelName: pluginChannelName, methods: {'availableCameras': returnData}, ); // Act final List cameras = await plugin.availableCameras(); // Assert expect(channel.log, [ isMethodCall('availableCameras', arguments: null), ]); 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: typedData['name']! as String, lensDirection: plugin .parseCameraLensDirection(typedData['lensFacing']! as String), sensorOrientation: typedData['sensorOrientation']! as int, ); expect(cameras[i], cameraDescription); } }); test( 'Should throw CameraException when availableCameras throws a PlatformException', () { // Arrange MethodChannelMock( channelName: pluginChannelName, methods: { 'availableCameras': PlatformException( code: 'TESTING_ERROR_CODE', message: 'Mock error message used during testing.', ) }); // Act expect( plugin.availableCameras, throwsA( isA() .having( (CameraException e) => e.code, 'code', 'TESTING_ERROR_CODE') .having((CameraException e) => e.description, 'description', 'Mock error message used during testing.'), ), ); }); test('Should take a picture and return an XFile instance', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: pluginChannelName, methods: {'takePicture': '/test/path.jpg'}); // Act final XFile file = await plugin.takePicture(cameraId); // Assert expect(channel.log, [ isMethodCall('takePicture', arguments: { 'cameraId': cameraId, }), ]); expect(file.path, '/test/path.jpg'); }); test('Should prepare for video recording', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: pluginChannelName, methods: {'prepareForVideoRecording': null}, ); // Act await plugin.prepareForVideoRecording(); // Assert expect(channel.log, [ isMethodCall('prepareForVideoRecording', arguments: null), ]); }); test('Should start recording a video', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: pluginChannelName, methods: {'startVideoRecording': null}, ); // Act await plugin.startVideoRecording(cameraId); // Assert expect(channel.log, [ isMethodCall('startVideoRecording', arguments: { 'cameraId': cameraId, 'maxVideoDuration': null, }), ]); }); test('Should pass maxVideoDuration when starting recording a video', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: pluginChannelName, methods: {'startVideoRecording': null}, ); // Act await plugin.startVideoRecording( cameraId, maxVideoDuration: const Duration(seconds: 10), ); // Assert expect(channel.log, [ isMethodCall('startVideoRecording', arguments: { 'cameraId': cameraId, 'maxVideoDuration': 10000 }), ]); }); test('capturing fails if trying to stream', () async { // Act and Assert expect( () => plugin.startVideoCapturing(VideoCaptureOptions(cameraId, streamCallback: (CameraImageData imageData) {})), throwsA(isA()), ); }); test('Should stop a video recording and return the file', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: pluginChannelName, methods: {'stopVideoRecording': '/test/path.mp4'}, ); // Act final XFile file = await plugin.stopVideoRecording(cameraId); // Assert expect(channel.log, [ isMethodCall('stopVideoRecording', arguments: { 'cameraId': cameraId, }), ]); expect(file.path, '/test/path.mp4'); }); test('Should throw UnsupportedError when pause video recording is called', () async { // Act expect( () => plugin.pauseVideoRecording(cameraId), throwsA(isA()), ); }); test( 'Should throw UnsupportedError when resume video recording is called', () async { // Act expect( () => plugin.resumeVideoRecording(cameraId), throwsA(isA()), ); }); test('Should throw UnimplementedError when flash mode is set', () async { // Act expect( () => plugin.setFlashMode(cameraId, FlashMode.torch), throwsA(isA()), ); }); test('Should throw UnimplementedError when exposure mode is set', () async { // Act expect( () => plugin.setExposureMode(cameraId, ExposureMode.auto), throwsA(isA()), ); }); test('Should throw UnsupportedError when exposure point is set', () async { // Act expect( () => plugin.setExposurePoint(cameraId, null), throwsA(isA()), ); }); test('Should get the min exposure offset', () async { // Act final double minExposureOffset = await plugin.getMinExposureOffset(cameraId); // Assert expect(minExposureOffset, 0.0); }); test('Should get the max exposure offset', () async { // Act final double maxExposureOffset = await plugin.getMaxExposureOffset(cameraId); // Assert expect(maxExposureOffset, 0.0); }); test('Should get the exposure offset step size', () async { // Act final double stepSize = await plugin.getExposureOffsetStepSize(cameraId); // Assert expect(stepSize, 1.0); }); test('Should throw UnimplementedError when exposure offset is set', () async { // Act expect( () => plugin.setExposureOffset(cameraId, 0.5), throwsA(isA()), ); }); test('Should throw UnimplementedError when focus mode is set', () async { // Act expect( () => plugin.setFocusMode(cameraId, FocusMode.auto), throwsA(isA()), ); }); test('Should throw UnsupportedError when exposure point is set', () async { // Act expect( () => plugin.setFocusMode(cameraId, FocusMode.auto), throwsA(isA()), ); }); test('Should build a texture widget as preview widget', () async { // Act final Widget widget = plugin.buildPreview(cameraId); // Act expect(widget is Texture, isTrue); expect((widget as Texture).textureId, cameraId); }); test('Should throw UnimplementedError when handling unknown method', () { final CameraWindows plugin = CameraWindows(); expect( () => plugin.handleCameraMethodCall( const MethodCall('unknown_method'), 1), throwsA(isA())); }); test('Should get the max zoom level', () async { // Act final double maxZoomLevel = await plugin.getMaxZoomLevel(cameraId); // Assert expect(maxZoomLevel, 1.0); }); test('Should get the min zoom level', () async { // Act final double maxZoomLevel = await plugin.getMinZoomLevel(cameraId); // Assert expect(maxZoomLevel, 1.0); }); test('Should throw UnimplementedError when zoom level is set', () async { // Act expect( () => plugin.setZoomLevel(cameraId, 2.0), throwsA(isA()), ); }); test( 'Should throw UnimplementedError when lock capture orientation is called', () async { // Act expect( () => plugin.setZoomLevel(cameraId, 2.0), throwsA(isA()), ); }); test( 'Should throw UnimplementedError when unlock capture orientation is called', () async { // Act expect( () => plugin.unlockCaptureOrientation(cameraId), throwsA(isA()), ); }); test('Should pause the camera preview', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: pluginChannelName, methods: {'pausePreview': null}, ); // Act await plugin.pausePreview(cameraId); // Assert expect(channel.log, [ isMethodCall('pausePreview', arguments: {'cameraId': cameraId}), ]); }); test('Should resume the camera preview', () async { // Arrange final MethodChannelMock channel = MethodChannelMock( channelName: pluginChannelName, methods: {'resumePreview': null}, ); // Act await plugin.resumePreview(cameraId); // Assert expect(channel.log, [ isMethodCall('resumePreview', arguments: {'cameraId': cameraId}), ]); }); }); }); } ================================================ FILE: packages/camera/camera_windows/test/utils/method_channel_mock.dart ================================================ // Copyright 2013 The Flutter 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'; /// A mock [MethodChannel] implementation for use in tests. class MethodChannelMock { /// Creates a new instance with the specified channel name. /// /// This method channel will handle all method invocations specified by /// returning the value mapped to the method name key. If a delay is /// specified, results are returned after the delay has elapsed. MethodChannelMock({ required String channelName, this.delay, required this.methods, }) : methodChannel = MethodChannel(channelName) { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(methodChannel, _handler); } final Duration? delay; final MethodChannel methodChannel; final Map methods; final List log = []; Future _handler(MethodCall methodCall) async { log.add(methodCall); if (!methods.containsKey(methodCall.method)) { throw MissingPluginException('No TEST implementation found for method ' '${methodCall.method} on channel ${methodChannel.name}'); } return Future.delayed(delay ?? Duration.zero, () { final dynamic result = methods[methodCall.method]; if (result is Exception) { throw result; } return Future.value(result); }); } } /// 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; ================================================ FILE: packages/camera/camera_windows/windows/.gitignore ================================================ flutter/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: packages/camera/camera_windows/windows/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) set(PROJECT_NAME "camera_windows") project(${PROJECT_NAME} LANGUAGES CXX) # This value is used when generating builds using this plugin, so it must # not be changed set(PLUGIN_NAME "${PROJECT_NAME}_plugin") list(APPEND PLUGIN_SOURCES "camera_plugin.h" "camera_plugin.cpp" "camera.h" "camera.cpp" "capture_controller.h" "capture_controller.cpp" "capture_controller_listener.h" "capture_engine_listener.h" "capture_engine_listener.cpp" "string_utils.h" "string_utils.cpp" "capture_device_info.h" "capture_device_info.cpp" "preview_handler.h" "preview_handler.cpp" "record_handler.h" "record_handler.cpp" "photo_handler.h" "photo_handler.cpp" "texture_handler.h" "texture_handler.cpp" "com_heap_ptr.h" ) add_library(${PLUGIN_NAME} SHARED "camera_windows.cpp" "include/camera_windows/camera_windows.h" ${PLUGIN_SOURCES} ) apply_standard_settings(${PLUGIN_NAME}) set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden) target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) target_include_directories(${PLUGIN_NAME} INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) target_link_libraries(${PLUGIN_NAME} PRIVATE mf mfplat mfuuid d3d11) # List of absolute paths to libraries that should be bundled with the plugin set(camera_windows_bundled_libraries "" PARENT_SCOPE ) # === Tests === if (${include_${PROJECT_NAME}_tests}) set(TEST_RUNNER "${PROJECT_NAME}_test") enable_testing() # TODO(stuartmorgan): Consider using a single shared, pre-checked-in googletest # instance rather than downloading for each plugin. This approach makes sense # for a template, but not for a monorepo with many plugins. include(FetchContent) FetchContent_Declare( googletest URL https://github.com/google/googletest/archive/release-1.11.0.zip ) # Prevent overriding the parent project's compiler/linker settings set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Disable install commands for gtest so it doesn't end up in the bundle. set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE) FetchContent_MakeAvailable(googletest) # The plugin's C API is not very useful for unit testing, so build the sources # directly into the test binary rather than using the DLL. add_executable(${TEST_RUNNER} test/mocks.h test/camera_plugin_test.cpp test/camera_test.cpp test/capture_controller_test.cpp ${PLUGIN_SOURCES} ) apply_standard_settings(${TEST_RUNNER}) target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") target_link_libraries(${TEST_RUNNER} PRIVATE flutter_wrapper_plugin) target_link_libraries(${TEST_RUNNER} PRIVATE mf mfplat mfuuid d3d11) target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock) # flutter_wrapper_plugin has link dependencies on the Flutter DLL. add_custom_command(TARGET ${TEST_RUNNER} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${FLUTTER_LIBRARY}" $ ) include(GoogleTest) gtest_discover_tests(${TEST_RUNNER}) endif() ================================================ FILE: packages/camera/camera_windows/windows/camera.cpp ================================================ // Copyright 2013 The Flutter 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 "camera.h" namespace camera_windows { using flutter::EncodableList; using flutter::EncodableMap; using flutter::EncodableValue; // Camera channel events. constexpr char kCameraMethodChannelBaseName[] = "plugins.flutter.io/camera_windows/camera"; constexpr char kVideoRecordedEvent[] = "video_recorded"; constexpr char kCameraClosingEvent[] = "camera_closing"; constexpr char kErrorEvent[] = "error"; // Camera error codes constexpr char kCameraAccessDenied[] = "CameraAccessDenied"; constexpr char kCameraError[] = "camera_error"; constexpr char kPluginDisposed[] = "plugin_disposed"; std::string GetErrorCode(CameraResult result) { assert(result != CameraResult::kSuccess); switch (result) { case CameraResult::kAccessDenied: return kCameraAccessDenied; case CameraResult::kSuccess: case CameraResult::kError: default: return kCameraError; } } CameraImpl::CameraImpl(const std::string& device_id) : device_id_(device_id), Camera(device_id) {} CameraImpl::~CameraImpl() { // Sends camera closing event. OnCameraClosing(); capture_controller_ = nullptr; SendErrorForPendingResults(kPluginDisposed, "Plugin disposed before request was handled"); } bool CameraImpl::InitCamera(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, bool record_audio, ResolutionPreset resolution_preset) { auto capture_controller_factory = std::make_unique(); return InitCamera(std::move(capture_controller_factory), texture_registrar, messenger, record_audio, resolution_preset); } bool CameraImpl::InitCamera( std::unique_ptr capture_controller_factory, flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, bool record_audio, ResolutionPreset resolution_preset) { assert(!device_id_.empty()); messenger_ = messenger; capture_controller_ = capture_controller_factory->CreateCaptureController(this); return capture_controller_->InitCaptureDevice( texture_registrar, device_id_, record_audio, resolution_preset); } bool CameraImpl::AddPendingResult( PendingResultType type, std::unique_ptr> result) { assert(result); auto it = pending_results_.find(type); if (it != pending_results_.end()) { result->Error("Duplicate request", "Method handler already called"); return false; } pending_results_.insert(std::make_pair(type, std::move(result))); return true; } std::unique_ptr> CameraImpl::GetPendingResultByType( PendingResultType type) { auto it = pending_results_.find(type); if (it == pending_results_.end()) { return nullptr; } auto result = std::move(it->second); pending_results_.erase(it); return result; } bool CameraImpl::HasPendingResultByType(PendingResultType type) const { auto it = pending_results_.find(type); if (it == pending_results_.end()) { return false; } return it->second != nullptr; } void CameraImpl::SendErrorForPendingResults(const std::string& error_code, const std::string& description) { for (const auto& pending_result : pending_results_) { pending_result.second->Error(error_code, description); } pending_results_.clear(); } MethodChannel<>* CameraImpl::GetMethodChannel() { assert(messenger_); assert(camera_id_); // Use existing channel if initialized if (camera_channel_) { return camera_channel_.get(); } auto channel_name = std::string(kCameraMethodChannelBaseName) + std::to_string(camera_id_); camera_channel_ = std::make_unique>( messenger_, channel_name, &flutter::StandardMethodCodec::GetInstance()); return camera_channel_.get(); } void CameraImpl::OnCreateCaptureEngineSucceeded(int64_t texture_id) { // Use texture id as camera id camera_id_ = texture_id; auto pending_result = GetPendingResultByType(PendingResultType::kCreateCamera); if (pending_result) { pending_result->Success(EncodableMap( {{EncodableValue("cameraId"), EncodableValue(texture_id)}})); } } void CameraImpl::OnCreateCaptureEngineFailed(CameraResult result, const std::string& error) { auto pending_result = GetPendingResultByType(PendingResultType::kCreateCamera); if (pending_result) { std::string error_code = GetErrorCode(result); pending_result->Error(error_code, error); } } void CameraImpl::OnStartPreviewSucceeded(int32_t width, int32_t height) { auto pending_result = GetPendingResultByType(PendingResultType::kInitialize); if (pending_result) { pending_result->Success(EncodableValue(EncodableMap({ {EncodableValue("previewWidth"), EncodableValue(static_cast(width))}, {EncodableValue("previewHeight"), EncodableValue(static_cast(height))}, }))); } }; void CameraImpl::OnStartPreviewFailed(CameraResult result, const std::string& error) { auto pending_result = GetPendingResultByType(PendingResultType::kInitialize); if (pending_result) { std::string error_code = GetErrorCode(result); pending_result->Error(error_code, error); } }; void CameraImpl::OnResumePreviewSucceeded() { auto pending_result = GetPendingResultByType(PendingResultType::kResumePreview); if (pending_result) { pending_result->Success(); } } void CameraImpl::OnResumePreviewFailed(CameraResult result, const std::string& error) { auto pending_result = GetPendingResultByType(PendingResultType::kResumePreview); if (pending_result) { std::string error_code = GetErrorCode(result); pending_result->Error(error_code, error); } } void CameraImpl::OnPausePreviewSucceeded() { auto pending_result = GetPendingResultByType(PendingResultType::kPausePreview); if (pending_result) { pending_result->Success(); } } void CameraImpl::OnPausePreviewFailed(CameraResult result, const std::string& error) { auto pending_result = GetPendingResultByType(PendingResultType::kPausePreview); if (pending_result) { std::string error_code = GetErrorCode(result); pending_result->Error(error_code, error); } } void CameraImpl::OnStartRecordSucceeded() { auto pending_result = GetPendingResultByType(PendingResultType::kStartRecord); if (pending_result) { pending_result->Success(); } }; void CameraImpl::OnStartRecordFailed(CameraResult result, const std::string& error) { auto pending_result = GetPendingResultByType(PendingResultType::kStartRecord); if (pending_result) { std::string error_code = GetErrorCode(result); pending_result->Error(error_code, error); } }; void CameraImpl::OnStopRecordSucceeded(const std::string& file_path) { auto pending_result = GetPendingResultByType(PendingResultType::kStopRecord); if (pending_result) { pending_result->Success(EncodableValue(file_path)); } }; void CameraImpl::OnStopRecordFailed(CameraResult result, const std::string& error) { auto pending_result = GetPendingResultByType(PendingResultType::kStopRecord); if (pending_result) { std::string error_code = GetErrorCode(result); pending_result->Error(error_code, error); } }; void CameraImpl::OnTakePictureSucceeded(const std::string& file_path) { auto pending_result = GetPendingResultByType(PendingResultType::kTakePicture); if (pending_result) { pending_result->Success(EncodableValue(file_path)); } }; void CameraImpl::OnTakePictureFailed(CameraResult result, const std::string& error) { auto pending_take_picture_result = GetPendingResultByType(PendingResultType::kTakePicture); if (pending_take_picture_result) { std::string error_code = GetErrorCode(result); pending_take_picture_result->Error(error_code, error); } }; void CameraImpl::OnVideoRecordSucceeded(const std::string& file_path, int64_t video_duration_ms) { if (messenger_ && camera_id_ >= 0) { auto channel = GetMethodChannel(); std::unique_ptr message_data = std::make_unique( EncodableMap({{EncodableValue("path"), EncodableValue(file_path)}, {EncodableValue("maxVideoDuration"), EncodableValue(video_duration_ms)}})); channel->InvokeMethod(kVideoRecordedEvent, std::move(message_data)); } } void CameraImpl::OnVideoRecordFailed(CameraResult result, const std::string& error){}; void CameraImpl::OnCaptureError(CameraResult result, const std::string& error) { if (messenger_ && camera_id_ >= 0) { auto channel = GetMethodChannel(); std::unique_ptr message_data = std::make_unique(EncodableMap( {{EncodableValue("description"), EncodableValue(error)}})); channel->InvokeMethod(kErrorEvent, std::move(message_data)); } std::string error_code = GetErrorCode(result); SendErrorForPendingResults(error_code, error); } void CameraImpl::OnCameraClosing() { if (messenger_ && camera_id_ >= 0) { auto channel = GetMethodChannel(); channel->InvokeMethod(kCameraClosingEvent, std::move(std::make_unique())); } } } // namespace camera_windows ================================================ FILE: packages/camera/camera_windows/windows/camera.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAMERA_H_ #define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAMERA_H_ #include #include #include #include "capture_controller.h" namespace camera_windows { using flutter::EncodableMap; using flutter::MethodChannel; using flutter::MethodResult; // A set of result types that are stored // for processing asynchronous commands. enum class PendingResultType { kCreateCamera, kInitialize, kTakePicture, kStartRecord, kStopRecord, kPausePreview, kResumePreview, }; // Interface implemented by cameras. // // Access is provided to an associated |CaptureController|, which can be used // to capture video or photo from the camera. class Camera : public CaptureControllerListener { public: explicit Camera(const std::string& device_id) {} virtual ~Camera() = default; // Disallow copy and move. Camera(const Camera&) = delete; Camera& operator=(const Camera&) = delete; // Tests if this camera has the specified device ID. virtual bool HasDeviceId(std::string& device_id) const = 0; // Tests if this camera has the specified camera ID. virtual bool HasCameraId(int64_t camera_id) const = 0; // Adds a pending result. // // Returns an error result if the result has already been added. virtual bool AddPendingResult(PendingResultType type, std::unique_ptr> result) = 0; // Checks if a pending result of the specified type already exists. virtual bool HasPendingResultByType(PendingResultType type) const = 0; // Returns a |CaptureController| that allows capturing video or still photos // from this camera. virtual camera_windows::CaptureController* GetCaptureController() = 0; // Initializes this camera and its associated capture controller. // // Returns false if initialization fails. virtual bool InitCamera(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, bool record_audio, ResolutionPreset resolution_preset) = 0; }; // Concrete implementation of the |Camera| interface. // // This implementation is responsible for initializing the capture controller, // listening for camera events, processing pending results, and notifying // application code of processed events via the method channel. class CameraImpl : public Camera { public: explicit CameraImpl(const std::string& device_id); virtual ~CameraImpl(); // Disallow copy and move. CameraImpl(const CameraImpl&) = delete; CameraImpl& operator=(const CameraImpl&) = delete; // CaptureControllerListener void OnCreateCaptureEngineSucceeded(int64_t texture_id) override; void OnCreateCaptureEngineFailed(CameraResult result, const std::string& error) override; void OnStartPreviewSucceeded(int32_t width, int32_t height) override; void OnStartPreviewFailed(CameraResult result, const std::string& error) override; void OnPausePreviewSucceeded() override; void OnPausePreviewFailed(CameraResult result, const std::string& error) override; void OnResumePreviewSucceeded() override; void OnResumePreviewFailed(CameraResult result, const std::string& error) override; void OnStartRecordSucceeded() override; void OnStartRecordFailed(CameraResult result, const std::string& error) override; void OnStopRecordSucceeded(const std::string& file_path) override; void OnStopRecordFailed(CameraResult result, const std::string& error) override; void OnTakePictureSucceeded(const std::string& file_path) override; void OnTakePictureFailed(CameraResult result, const std::string& error) override; void OnVideoRecordSucceeded(const std::string& file_path, int64_t video_duration) override; void OnVideoRecordFailed(CameraResult result, const std::string& error) override; void OnCaptureError(CameraResult result, const std::string& error) override; // Camera bool HasDeviceId(std::string& device_id) const override { return device_id_ == device_id; } bool HasCameraId(int64_t camera_id) const override { return camera_id_ == camera_id; } bool AddPendingResult(PendingResultType type, std::unique_ptr> result) override; bool HasPendingResultByType(PendingResultType type) const override; camera_windows::CaptureController* GetCaptureController() override { return capture_controller_.get(); } bool InitCamera(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, bool record_audio, ResolutionPreset resolution_preset) override; // Initializes the camera and its associated capture controller. // // This is a convenience method called by |InitCamera| but also used in // tests. // // Returns false if initialization fails. bool InitCamera( std::unique_ptr capture_controller_factory, flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, bool record_audio, ResolutionPreset resolution_preset); private: // Loops through all pending results and calls their error handler with given // error ID and description. Pending results are cleared in the process. // // error_code: A string error code describing the error. // description: A user-readable error message (optional). void SendErrorForPendingResults(const std::string& error_code, const std::string& description); // Called when camera is disposed. // Sends camera closing message to the cameras method channel. void OnCameraClosing(); // Initializes method channel instance and returns pointer it. MethodChannel<>* GetMethodChannel(); // Finds pending result by type. // Returns nullptr if type is not present. std::unique_ptr> GetPendingResultByType( PendingResultType type); std::map>> pending_results_; std::unique_ptr capture_controller_; std::unique_ptr> camera_channel_; flutter::BinaryMessenger* messenger_ = nullptr; int64_t camera_id_ = -1; std::string device_id_; }; // Factory class for creating |Camera| instances from a specified device ID. class CameraFactory { public: CameraFactory() {} virtual ~CameraFactory() = default; // Disallow copy and move. CameraFactory(const CameraFactory&) = delete; CameraFactory& operator=(const CameraFactory&) = delete; // Creates camera for given device id. virtual std::unique_ptr CreateCamera( const std::string& device_id) = 0; }; // Concrete implementation of |CameraFactory|. class CameraFactoryImpl : public CameraFactory { public: CameraFactoryImpl() {} virtual ~CameraFactoryImpl() = default; // Disallow copy and move. CameraFactoryImpl(const CameraFactoryImpl&) = delete; CameraFactoryImpl& operator=(const CameraFactoryImpl&) = delete; std::unique_ptr CreateCamera(const std::string& device_id) override { return std::make_unique(device_id); } }; } // namespace camera_windows #endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAMERA_H_ ================================================ FILE: packages/camera/camera_windows/windows/camera_plugin.cpp ================================================ // Copyright 2013 The Flutter 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 "camera_plugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "capture_device_info.h" #include "com_heap_ptr.h" #include "string_utils.h" namespace camera_windows { using flutter::EncodableList; using flutter::EncodableMap; using flutter::EncodableValue; namespace { // Channel events constexpr char kChannelName[] = "plugins.flutter.io/camera_windows"; constexpr char kAvailableCamerasMethod[] = "availableCameras"; constexpr char kCreateMethod[] = "create"; constexpr char kInitializeMethod[] = "initialize"; constexpr char kTakePictureMethod[] = "takePicture"; constexpr char kStartVideoRecordingMethod[] = "startVideoRecording"; constexpr char kStopVideoRecordingMethod[] = "stopVideoRecording"; constexpr char kPausePreview[] = "pausePreview"; constexpr char kResumePreview[] = "resumePreview"; constexpr char kDisposeMethod[] = "dispose"; constexpr char kCameraNameKey[] = "cameraName"; constexpr char kResolutionPresetKey[] = "resolutionPreset"; constexpr char kEnableAudioKey[] = "enableAudio"; constexpr char kCameraIdKey[] = "cameraId"; constexpr char kMaxVideoDurationKey[] = "maxVideoDuration"; constexpr char kResolutionPresetValueLow[] = "low"; constexpr char kResolutionPresetValueMedium[] = "medium"; constexpr char kResolutionPresetValueHigh[] = "high"; constexpr char kResolutionPresetValueVeryHigh[] = "veryHigh"; constexpr char kResolutionPresetValueUltraHigh[] = "ultraHigh"; constexpr char kResolutionPresetValueMax[] = "max"; const std::string kPictureCaptureExtension = "jpeg"; const std::string kVideoCaptureExtension = "mp4"; // Looks for |key| in |map|, returning the associated value if it is present, or // a nullptr if not. const EncodableValue* ValueOrNull(const EncodableMap& map, const char* key) { auto it = map.find(EncodableValue(key)); if (it == map.end()) { return nullptr; } return &(it->second); } // Looks for |key| in |map|, returning the associated int64 value if it is // present, or std::nullopt if not. std::optional GetInt64ValueOrNull(const EncodableMap& map, const char* key) { auto value = ValueOrNull(map, key); if (!value) { return std::nullopt; } if (std::holds_alternative(*value)) { return static_cast(std::get(*value)); } auto val64 = std::get_if(value); if (!val64) { return std::nullopt; } return *val64; } // Parses resolution preset argument to enum value. ResolutionPreset ParseResolutionPreset(const std::string& resolution_preset) { if (resolution_preset.compare(kResolutionPresetValueLow) == 0) { return ResolutionPreset::kLow; } else if (resolution_preset.compare(kResolutionPresetValueMedium) == 0) { return ResolutionPreset::kMedium; } else if (resolution_preset.compare(kResolutionPresetValueHigh) == 0) { return ResolutionPreset::kHigh; } else if (resolution_preset.compare(kResolutionPresetValueVeryHigh) == 0) { return ResolutionPreset::kVeryHigh; } else if (resolution_preset.compare(kResolutionPresetValueUltraHigh) == 0) { return ResolutionPreset::kUltraHigh; } else if (resolution_preset.compare(kResolutionPresetValueMax) == 0) { return ResolutionPreset::kMax; } return ResolutionPreset::kAuto; } // Builds CaptureDeviceInfo object from given device holding device name and id. std::unique_ptr GetDeviceInfo(IMFActivate* device) { assert(device); auto device_info = std::make_unique(); ComHeapPtr name; UINT32 name_size; HRESULT hr = device->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &name, &name_size); if (FAILED(hr)) { return device_info; } ComHeapPtr id; UINT32 id_size; hr = device->GetAllocatedString( MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &id, &id_size); if (FAILED(hr)) { return device_info; } device_info->SetDisplayName(Utf8FromUtf16(std::wstring(name, name_size))); device_info->SetDeviceID(Utf8FromUtf16(std::wstring(id, id_size))); return device_info; } // Builds datetime string from current time. // Used as part of the filenames for captured pictures and videos. std::string GetCurrentTimeString() { std::chrono::system_clock::duration now = std::chrono::system_clock::now().time_since_epoch(); auto s = std::chrono::duration_cast(now).count(); auto ms = std::chrono::duration_cast(now).count() % 1000; struct tm newtime; localtime_s(&newtime, &s); std::string time_start = ""; time_start.resize(80); size_t len = strftime(&time_start[0], time_start.size(), "%Y_%m%d_%H%M%S_", &newtime); if (len > 0) { time_start.resize(len); } // Add milliseconds to make sure the filename is unique return time_start + std::to_string(ms); } // Builds file path for picture capture. std::optional GetFilePathForPicture() { ComHeapPtr known_folder_path; HRESULT hr = SHGetKnownFolderPath(FOLDERID_Pictures, KF_FLAG_CREATE, nullptr, &known_folder_path); if (FAILED(hr)) { return std::nullopt; } std::string path = Utf8FromUtf16(std::wstring(known_folder_path)); return path + "\\" + "PhotoCapture_" + GetCurrentTimeString() + "." + kPictureCaptureExtension; } // Builds file path for video capture. std::optional GetFilePathForVideo() { ComHeapPtr known_folder_path; HRESULT hr = SHGetKnownFolderPath(FOLDERID_Videos, KF_FLAG_CREATE, nullptr, &known_folder_path); if (FAILED(hr)) { return std::nullopt; } std::string path = Utf8FromUtf16(std::wstring(known_folder_path)); return path + "\\" + "VideoCapture_" + GetCurrentTimeString() + "." + kVideoCaptureExtension; } } // namespace // static void CameraPlugin::RegisterWithRegistrar( flutter::PluginRegistrarWindows* registrar) { auto channel = std::make_unique>( registrar->messenger(), kChannelName, &flutter::StandardMethodCodec::GetInstance()); std::unique_ptr plugin = std::make_unique( registrar->texture_registrar(), registrar->messenger()); channel->SetMethodCallHandler( [plugin_pointer = plugin.get()](const auto& call, auto result) { plugin_pointer->HandleMethodCall(call, std::move(result)); }); registrar->AddPlugin(std::move(plugin)); } CameraPlugin::CameraPlugin(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger) : texture_registrar_(texture_registrar), messenger_(messenger), camera_factory_(std::make_unique()) {} CameraPlugin::CameraPlugin(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, std::unique_ptr camera_factory) : texture_registrar_(texture_registrar), messenger_(messenger), camera_factory_(std::move(camera_factory)) {} CameraPlugin::~CameraPlugin() {} void CameraPlugin::HandleMethodCall( const flutter::MethodCall<>& method_call, std::unique_ptr> result) { const std::string& method_name = method_call.method_name(); if (method_name.compare(kAvailableCamerasMethod) == 0) { return AvailableCamerasMethodHandler(std::move(result)); } else if (method_name.compare(kCreateMethod) == 0) { const auto* arguments = std::get_if(method_call.arguments()); assert(arguments); return CreateMethodHandler(*arguments, std::move(result)); } else if (method_name.compare(kInitializeMethod) == 0) { const auto* arguments = std::get_if(method_call.arguments()); assert(arguments); return this->InitializeMethodHandler(*arguments, std::move(result)); } else if (method_name.compare(kTakePictureMethod) == 0) { const auto* arguments = std::get_if(method_call.arguments()); assert(arguments); return TakePictureMethodHandler(*arguments, std::move(result)); } else if (method_name.compare(kStartVideoRecordingMethod) == 0) { const auto* arguments = std::get_if(method_call.arguments()); assert(arguments); return StartVideoRecordingMethodHandler(*arguments, std::move(result)); } else if (method_name.compare(kStopVideoRecordingMethod) == 0) { const auto* arguments = std::get_if(method_call.arguments()); assert(arguments); return StopVideoRecordingMethodHandler(*arguments, std::move(result)); } else if (method_name.compare(kPausePreview) == 0) { const auto* arguments = std::get_if(method_call.arguments()); assert(arguments); return PausePreviewMethodHandler(*arguments, std::move(result)); } else if (method_name.compare(kResumePreview) == 0) { const auto* arguments = std::get_if(method_call.arguments()); assert(arguments); return ResumePreviewMethodHandler(*arguments, std::move(result)); } else if (method_name.compare(kDisposeMethod) == 0) { const auto* arguments = std::get_if(method_call.arguments()); assert(arguments); return DisposeMethodHandler(*arguments, std::move(result)); } else { result->NotImplemented(); } } Camera* CameraPlugin::GetCameraByDeviceId(std::string& device_id) { for (auto it = begin(cameras_); it != end(cameras_); ++it) { if ((*it)->HasDeviceId(device_id)) { return it->get(); } } return nullptr; } Camera* CameraPlugin::GetCameraByCameraId(int64_t camera_id) { for (auto it = begin(cameras_); it != end(cameras_); ++it) { if ((*it)->HasCameraId(camera_id)) { return it->get(); } } return nullptr; } void CameraPlugin::DisposeCameraByCameraId(int64_t camera_id) { for (auto it = begin(cameras_); it != end(cameras_); ++it) { if ((*it)->HasCameraId(camera_id)) { cameras_.erase(it); return; } } } void CameraPlugin::AvailableCamerasMethodHandler( std::unique_ptr> result) { // Enumerate devices. ComHeapPtr devices; UINT32 count = 0; if (!this->EnumerateVideoCaptureDeviceSources(&devices, &count)) { result->Error("System error", "Failed to get available cameras"); // No need to free devices here, cos allocation failed. return; } if (count == 0) { result->Success(EncodableValue(EncodableList())); return; } // Format found devices to the response. EncodableList devices_list; for (UINT32 i = 0; i < count; ++i) { auto device_info = GetDeviceInfo(devices[i]); auto deviceName = device_info->GetUniqueDeviceName(); devices_list.push_back(EncodableMap({ {EncodableValue("name"), EncodableValue(deviceName)}, {EncodableValue("lensFacing"), EncodableValue("front")}, {EncodableValue("sensorOrientation"), EncodableValue(0)}, })); } result->Success(std::move(EncodableValue(devices_list))); } bool CameraPlugin::EnumerateVideoCaptureDeviceSources(IMFActivate*** devices, UINT32* count) { return CaptureControllerImpl::EnumerateVideoCaptureDeviceSources(devices, count); } void CameraPlugin::CreateMethodHandler( const EncodableMap& args, std::unique_ptr> result) { // Parse enableAudio argument. const auto* record_audio = std::get_if(ValueOrNull(args, kEnableAudioKey)); if (!record_audio) { return result->Error("argument_error", std::string(kEnableAudioKey) + " argument missing"); } // Parse cameraName argument. const auto* camera_name = std::get_if(ValueOrNull(args, kCameraNameKey)); if (!camera_name) { return result->Error("argument_error", std::string(kCameraNameKey) + " argument missing"); } auto device_info = std::make_unique(); if (!device_info->ParseDeviceInfoFromCameraName(*camera_name)) { return result->Error( "camera_error", "Cannot parse argument " + std::string(kCameraNameKey)); } auto device_id = device_info->GetDeviceId(); if (GetCameraByDeviceId(device_id)) { return result->Error("camera_error", "Camera with given device id already exists. Existing " "camera must be disposed before creating it again."); } std::unique_ptr camera = camera_factory_->CreateCamera(device_id); if (camera->HasPendingResultByType(PendingResultType::kCreateCamera)) { return result->Error("camera_error", "Pending camera creation request exists"); } if (camera->AddPendingResult(PendingResultType::kCreateCamera, std::move(result))) { // Parse resolution preset argument. const auto* resolution_preset_argument = std::get_if(ValueOrNull(args, kResolutionPresetKey)); ResolutionPreset resolution_preset; if (resolution_preset_argument) { resolution_preset = ParseResolutionPreset(*resolution_preset_argument); } else { resolution_preset = ResolutionPreset::kAuto; } bool initialized = camera->InitCamera(texture_registrar_, messenger_, *record_audio, resolution_preset); if (initialized) { cameras_.push_back(std::move(camera)); } } } void CameraPlugin::InitializeMethodHandler( const EncodableMap& args, std::unique_ptr> result) { auto camera_id = GetInt64ValueOrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); } auto camera = GetCameraByCameraId(*camera_id); if (!camera) { return result->Error("camera_error", "Camera not created"); } if (camera->HasPendingResultByType(PendingResultType::kInitialize)) { return result->Error("camera_error", "Pending initialization request exists"); } if (camera->AddPendingResult(PendingResultType::kInitialize, std::move(result))) { auto cc = camera->GetCaptureController(); assert(cc); cc->StartPreview(); } } void CameraPlugin::PausePreviewMethodHandler( const EncodableMap& args, std::unique_ptr> result) { auto camera_id = GetInt64ValueOrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); } auto camera = GetCameraByCameraId(*camera_id); if (!camera) { return result->Error("camera_error", "Camera not created"); } if (camera->HasPendingResultByType(PendingResultType::kPausePreview)) { return result->Error("camera_error", "Pending pause preview request exists"); } if (camera->AddPendingResult(PendingResultType::kPausePreview, std::move(result))) { auto cc = camera->GetCaptureController(); assert(cc); cc->PausePreview(); } } void CameraPlugin::ResumePreviewMethodHandler( const EncodableMap& args, std::unique_ptr> result) { auto camera_id = GetInt64ValueOrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); } auto camera = GetCameraByCameraId(*camera_id); if (!camera) { return result->Error("camera_error", "Camera not created"); } if (camera->HasPendingResultByType(PendingResultType::kResumePreview)) { return result->Error("camera_error", "Pending resume preview request exists"); } if (camera->AddPendingResult(PendingResultType::kResumePreview, std::move(result))) { auto cc = camera->GetCaptureController(); assert(cc); cc->ResumePreview(); } } void CameraPlugin::StartVideoRecordingMethodHandler( const EncodableMap& args, std::unique_ptr> result) { auto camera_id = GetInt64ValueOrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); } auto camera = GetCameraByCameraId(*camera_id); if (!camera) { return result->Error("camera_error", "Camera not created"); } if (camera->HasPendingResultByType(PendingResultType::kStartRecord)) { return result->Error("camera_error", "Pending start recording request exists"); } int64_t max_video_duration_ms = -1; auto requested_max_video_duration_ms = std::get_if(ValueOrNull(args, kMaxVideoDurationKey)); if (requested_max_video_duration_ms != nullptr) { max_video_duration_ms = *requested_max_video_duration_ms; } std::optional path = GetFilePathForVideo(); if (path) { if (camera->AddPendingResult(PendingResultType::kStartRecord, std::move(result))) { auto cc = camera->GetCaptureController(); assert(cc); cc->StartRecord(*path, max_video_duration_ms); } } else { return result->Error("system_error", "Failed to get path for video capture"); } } void CameraPlugin::StopVideoRecordingMethodHandler( const EncodableMap& args, std::unique_ptr> result) { auto camera_id = GetInt64ValueOrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); } auto camera = GetCameraByCameraId(*camera_id); if (!camera) { return result->Error("camera_error", "Camera not created"); } if (camera->HasPendingResultByType(PendingResultType::kStopRecord)) { return result->Error("camera_error", "Pending stop recording request exists"); } if (camera->AddPendingResult(PendingResultType::kStopRecord, std::move(result))) { auto cc = camera->GetCaptureController(); assert(cc); cc->StopRecord(); } } void CameraPlugin::TakePictureMethodHandler( const EncodableMap& args, std::unique_ptr> result) { auto camera_id = GetInt64ValueOrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); } auto camera = GetCameraByCameraId(*camera_id); if (!camera) { return result->Error("camera_error", "Camera not created"); } if (camera->HasPendingResultByType(PendingResultType::kTakePicture)) { return result->Error("camera_error", "Pending take picture request exists"); } std::optional path = GetFilePathForPicture(); if (path) { if (camera->AddPendingResult(PendingResultType::kTakePicture, std::move(result))) { auto cc = camera->GetCaptureController(); assert(cc); cc->TakePicture(*path); } } else { return result->Error("system_error", "Failed to get capture path for picture"); } } void CameraPlugin::DisposeMethodHandler( const EncodableMap& args, std::unique_ptr> result) { auto camera_id = GetInt64ValueOrNull(args, kCameraIdKey); if (!camera_id) { return result->Error("argument_error", std::string(kCameraIdKey) + " missing"); } DisposeCameraByCameraId(*camera_id); result->Success(); } } // namespace camera_windows ================================================ FILE: packages/camera/camera_windows/windows/camera_plugin.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAMERA_PLUGIN_H_ #define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAMERA_PLUGIN_H_ #include #include #include #include #include #include "camera.h" #include "capture_controller.h" #include "capture_controller_listener.h" namespace camera_windows { using flutter::MethodResult; namespace test { namespace { // Forward declaration of test class. class MockCameraPlugin; } // namespace } // namespace test class CameraPlugin : public flutter::Plugin, public VideoCaptureDeviceEnumerator { public: static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); CameraPlugin(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger); // Creates a plugin instance with the given CameraFactory instance. // Exists for unit testing with mock implementations. CameraPlugin(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, std::unique_ptr camera_factory); virtual ~CameraPlugin(); // Disallow copy and move. CameraPlugin(const CameraPlugin&) = delete; CameraPlugin& operator=(const CameraPlugin&) = delete; // Called when a method is called on plugin channel. void HandleMethodCall(const flutter::MethodCall<>& method_call, std::unique_ptr> result); private: // Loops through cameras and returns camera // with matching device_id or nullptr. Camera* GetCameraByDeviceId(std::string& device_id); // Loops through cameras and returns camera // with matching camera_id or nullptr. Camera* GetCameraByCameraId(int64_t camera_id); // Disposes camera by camera id. void DisposeCameraByCameraId(int64_t camera_id); // Enumerates video capture devices. bool EnumerateVideoCaptureDeviceSources(IMFActivate*** devices, UINT32* count) override; // Handles availableCameras method calls. // Enumerates video capture devices and // returns list of available camera devices. void AvailableCamerasMethodHandler( std::unique_ptr> result); // Handles create method calls. // Creates camera and initializes capture controller for requested device. // Stores result object to be handled after request is processed. void CreateMethodHandler(const EncodableMap& args, std::unique_ptr> result); // Handles initialize method calls. // Requests existing camera controller to start preview. // Stores result object to be handled after request is processed. void InitializeMethodHandler(const EncodableMap& args, std::unique_ptr> result); // Handles takePicture method calls. // Requests existing camera controller to take photo. // Stores result object to be handled after request is processed. void TakePictureMethodHandler(const EncodableMap& args, std::unique_ptr> result); // Handles startVideoRecording method calls. // Requests existing camera controller to start recording. // Stores result object to be handled after request is processed. void StartVideoRecordingMethodHandler(const EncodableMap& args, std::unique_ptr> result); // Handles stopVideoRecording method calls. // Requests existing camera controller to stop recording. // Stores result object to be handled after request is processed. void StopVideoRecordingMethodHandler(const EncodableMap& args, std::unique_ptr> result); // Handles pausePreview method calls. // Requests existing camera controller to pause recording. // Stores result object to be handled after request is processed. void PausePreviewMethodHandler(const EncodableMap& args, std::unique_ptr> result); // Handles resumePreview method calls. // Requests existing camera controller to resume preview. // Stores result object to be handled after request is processed. void ResumePreviewMethodHandler(const EncodableMap& args, std::unique_ptr> result); // Handles dsipose method calls. // Disposes camera if exists. void DisposeMethodHandler(const EncodableMap& args, std::unique_ptr> result); std::unique_ptr camera_factory_; flutter::TextureRegistrar* texture_registrar_; flutter::BinaryMessenger* messenger_; std::vector> cameras_; friend class camera_windows::test::MockCameraPlugin; }; } // namespace camera_windows #endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAMERA_PLUGIN_H_ ================================================ FILE: packages/camera/camera_windows/windows/camera_windows.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "include/camera_windows/camera_windows.h" #include #include "camera_plugin.h" void CameraWindowsRegisterWithRegistrar( FlutterDesktopPluginRegistrarRef registrar) { camera_windows::CameraPlugin::RegisterWithRegistrar( flutter::PluginRegistrarManager::GetInstance() ->GetRegistrar(registrar)); } ================================================ FILE: packages/camera/camera_windows/windows/capture_controller.cpp ================================================ // Copyright 2013 The Flutter 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 "capture_controller.h" #include #include #include #include #include #include "com_heap_ptr.h" #include "photo_handler.h" #include "preview_handler.h" #include "record_handler.h" #include "string_utils.h" #include "texture_handler.h" namespace camera_windows { using Microsoft::WRL::ComPtr; CameraResult GetCameraResult(HRESULT hr) { if (SUCCEEDED(hr)) { return CameraResult::kSuccess; } return hr == E_ACCESSDENIED ? CameraResult::kAccessDenied : CameraResult::kError; } CaptureControllerImpl::CaptureControllerImpl( CaptureControllerListener* listener) : capture_controller_listener_(listener), CaptureController(){}; CaptureControllerImpl::~CaptureControllerImpl() { ResetCaptureController(); capture_controller_listener_ = nullptr; }; // static bool CaptureControllerImpl::EnumerateVideoCaptureDeviceSources( IMFActivate*** devices, UINT32* count) { ComPtr attributes; HRESULT hr = MFCreateAttributes(&attributes, 1); if (FAILED(hr)) { return false; } hr = attributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); if (FAILED(hr)) { return false; } hr = MFEnumDeviceSources(attributes.Get(), devices, count); if (FAILED(hr)) { return false; } return true; } HRESULT CaptureControllerImpl::CreateDefaultAudioCaptureSource() { audio_source_ = nullptr; ComHeapPtr devices; UINT32 count = 0; ComPtr attributes; HRESULT hr = MFCreateAttributes(&attributes, 1); if (SUCCEEDED(hr)) { hr = attributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_GUID); } if (SUCCEEDED(hr)) { hr = MFEnumDeviceSources(attributes.Get(), &devices, &count); } if (SUCCEEDED(hr) && count > 0) { ComHeapPtr audio_device_id; UINT32 audio_device_id_size; // Use first audio device. hr = devices[0]->GetAllocatedString( MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_ENDPOINT_ID, &audio_device_id, &audio_device_id_size); if (SUCCEEDED(hr)) { ComPtr audio_capture_source_attributes; hr = MFCreateAttributes(&audio_capture_source_attributes, 2); if (SUCCEEDED(hr)) { hr = audio_capture_source_attributes->SetGUID( MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_GUID); } if (SUCCEEDED(hr)) { hr = audio_capture_source_attributes->SetString( MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_ENDPOINT_ID, audio_device_id); } if (SUCCEEDED(hr)) { hr = MFCreateDeviceSource(audio_capture_source_attributes.Get(), audio_source_.GetAddressOf()); } } } return hr; } HRESULT CaptureControllerImpl::CreateVideoCaptureSourceForDevice( const std::string& video_device_id) { video_source_ = nullptr; ComPtr video_capture_source_attributes; HRESULT hr = MFCreateAttributes(&video_capture_source_attributes, 2); if (FAILED(hr)) { return hr; } hr = video_capture_source_attributes->SetGUID( MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); if (FAILED(hr)) { return hr; } hr = video_capture_source_attributes->SetString( MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, Utf16FromUtf8(video_device_id).c_str()); if (FAILED(hr)) { return hr; } hr = MFCreateDeviceSource(video_capture_source_attributes.Get(), video_source_.GetAddressOf()); return hr; } HRESULT CaptureControllerImpl::CreateD3DManagerWithDX11Device() { // TODO: Use existing ANGLE device HRESULT hr = S_OK; hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_VIDEO_SUPPORT, nullptr, 0, D3D11_SDK_VERSION, &dx11_device_, nullptr, nullptr); if (FAILED(hr)) { return hr; } // Enable multithread protection ComPtr multi_thread; hr = dx11_device_.As(&multi_thread); if (FAILED(hr)) { return hr; } multi_thread->SetMultithreadProtected(TRUE); hr = MFCreateDXGIDeviceManager(&dx_device_reset_token_, dxgi_device_manager_.GetAddressOf()); if (FAILED(hr)) { return hr; } hr = dxgi_device_manager_->ResetDevice(dx11_device_.Get(), dx_device_reset_token_); return hr; } HRESULT CaptureControllerImpl::CreateCaptureEngine() { assert(!video_device_id_.empty()); HRESULT hr = S_OK; ComPtr attributes; // Creates capture engine only if not already initialized by test framework if (!capture_engine_) { ComPtr capture_engine_factory; hr = CoCreateInstance(CLSID_MFCaptureEngineClassFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&capture_engine_factory)); if (FAILED(hr)) { return hr; } // Creates CaptureEngine. hr = capture_engine_factory->CreateInstance(CLSID_MFCaptureEngine, IID_PPV_ARGS(&capture_engine_)); if (FAILED(hr)) { return hr; } } hr = CreateD3DManagerWithDX11Device(); if (FAILED(hr)) { return hr; } // Creates video source only if not already initialized by test framework if (!video_source_) { hr = CreateVideoCaptureSourceForDevice(video_device_id_); if (FAILED(hr)) { return hr; } } // Creates audio source only if not already initialized by test framework if (record_audio_ && !audio_source_) { hr = CreateDefaultAudioCaptureSource(); if (FAILED(hr)) { return hr; } } if (!capture_engine_callback_handler_) { capture_engine_callback_handler_ = ComPtr(new CaptureEngineListener(this)); } hr = MFCreateAttributes(&attributes, 2); if (FAILED(hr)) { return hr; } hr = attributes->SetUnknown(MF_CAPTURE_ENGINE_D3D_MANAGER, dxgi_device_manager_.Get()); if (FAILED(hr)) { return hr; } hr = attributes->SetUINT32(MF_CAPTURE_ENGINE_USE_VIDEO_DEVICE_ONLY, !record_audio_); if (FAILED(hr)) { return hr; } // Check MF_CAPTURE_ENGINE_INITIALIZED event handling // for response process. hr = capture_engine_->Initialize(capture_engine_callback_handler_.Get(), attributes.Get(), audio_source_.Get(), video_source_.Get()); return hr; } void CaptureControllerImpl::ResetCaptureController() { if (record_handler_ && record_handler_->CanStop()) { if (record_handler_->IsContinuousRecording()) { StopRecord(); } else if (record_handler_->IsTimedRecording()) { StopTimedRecord(); } } if (preview_handler_) { StopPreview(); } // Shuts down the media foundation platform object. // Releases all resources including threads. // Application should call MFShutdown the same number of times as MFStartup if (media_foundation_started_) { MFShutdown(); } // States media_foundation_started_ = false; capture_engine_state_ = CaptureEngineState::kNotInitialized; preview_frame_width_ = 0; preview_frame_height_ = 0; capture_engine_callback_handler_ = nullptr; capture_engine_ = nullptr; audio_source_ = nullptr; video_source_ = nullptr; base_preview_media_type_ = nullptr; base_capture_media_type_ = nullptr; if (dxgi_device_manager_) { dxgi_device_manager_->ResetDevice(dx11_device_.Get(), dx_device_reset_token_); } dxgi_device_manager_ = nullptr; dx11_device_ = nullptr; record_handler_ = nullptr; preview_handler_ = nullptr; photo_handler_ = nullptr; texture_handler_ = nullptr; } bool CaptureControllerImpl::InitCaptureDevice( flutter::TextureRegistrar* texture_registrar, const std::string& device_id, bool record_audio, ResolutionPreset resolution_preset) { assert(capture_controller_listener_); if (IsInitialized()) { capture_controller_listener_->OnCreateCaptureEngineFailed( CameraResult::kError, "Capture device already initialized"); return false; } else if (capture_engine_state_ == CaptureEngineState::kInitializing) { capture_controller_listener_->OnCreateCaptureEngineFailed( CameraResult::kError, "Capture device already initializing"); return false; } capture_engine_state_ = CaptureEngineState::kInitializing; resolution_preset_ = resolution_preset; record_audio_ = record_audio; texture_registrar_ = texture_registrar; video_device_id_ = device_id; // MFStartup must be called before using Media Foundation. if (!media_foundation_started_) { HRESULT hr = MFStartup(MF_VERSION); if (FAILED(hr)) { capture_controller_listener_->OnCreateCaptureEngineFailed( GetCameraResult(hr), "Failed to create camera"); ResetCaptureController(); return false; } media_foundation_started_ = true; } HRESULT hr = CreateCaptureEngine(); if (FAILED(hr)) { capture_controller_listener_->OnCreateCaptureEngineFailed( GetCameraResult(hr), "Failed to create camera"); ResetCaptureController(); return false; } return true; } void CaptureControllerImpl::TakePicture(const std::string& file_path) { assert(capture_engine_callback_handler_); assert(capture_engine_); if (!IsInitialized()) { return OnPicture(CameraResult::kError, "Not initialized"); } HRESULT hr = S_OK; if (!base_capture_media_type_) { // Enumerates mediatypes and finds media type for video capture. hr = FindBaseMediaTypes(); if (FAILED(hr)) { return OnPicture(GetCameraResult(hr), "Failed to initialize photo capture"); } } if (!photo_handler_) { photo_handler_ = std::make_unique(); } else if (photo_handler_->IsTakingPhoto()) { return OnPicture(CameraResult::kError, "Photo already requested"); } // Check MF_CAPTURE_ENGINE_PHOTO_TAKEN event handling // for response process. hr = photo_handler_->TakePhoto(file_path, capture_engine_.Get(), base_capture_media_type_.Get()); if (FAILED(hr)) { // Destroy photo handler on error cases to make sure state is resetted. photo_handler_ = nullptr; return OnPicture(GetCameraResult(hr), "Failed to take photo"); } } uint32_t CaptureControllerImpl::GetMaxPreviewHeight() const { switch (resolution_preset_) { case ResolutionPreset::kLow: return 240; break; case ResolutionPreset::kMedium: return 480; break; case ResolutionPreset::kHigh: return 720; break; case ResolutionPreset::kVeryHigh: return 1080; break; case ResolutionPreset::kUltraHigh: return 2160; break; case ResolutionPreset::kMax: case ResolutionPreset::kAuto: default: // no limit. return 0xffffffff; break; } } // Finds best media type for given source stream index and max height; bool FindBestMediaType(DWORD source_stream_index, IMFCaptureSource* source, IMFMediaType** target_media_type, uint32_t max_height, uint32_t* target_frame_width, uint32_t* target_frame_height, float minimum_accepted_framerate = 15.f) { assert(source); ComPtr media_type; uint32_t best_width = 0; uint32_t best_height = 0; float best_framerate = 0.f; // Loop native media types. for (int i = 0;; i++) { if (FAILED(source->GetAvailableDeviceMediaType( source_stream_index, i, media_type.GetAddressOf()))) { break; } uint32_t frame_rate_numerator, frame_rate_denominator; if (FAILED(MFGetAttributeRatio(media_type.Get(), MF_MT_FRAME_RATE, &frame_rate_numerator, &frame_rate_denominator)) || !frame_rate_denominator) { continue; } float frame_rate = static_cast(frame_rate_numerator) / frame_rate_denominator; if (frame_rate < minimum_accepted_framerate) { continue; } uint32_t frame_width; uint32_t frame_height; if (SUCCEEDED(MFGetAttributeSize(media_type.Get(), MF_MT_FRAME_SIZE, &frame_width, &frame_height))) { // Update target mediatype if (frame_height <= max_height && (best_width < frame_width || best_height < frame_height || best_framerate < frame_rate)) { media_type.CopyTo(target_media_type); best_width = frame_width; best_height = frame_height; best_framerate = frame_rate; } } } if (target_frame_width && target_frame_height) { *target_frame_width = best_width; *target_frame_height = best_height; } return *target_media_type != nullptr; } HRESULT CaptureControllerImpl::FindBaseMediaTypes() { if (!IsInitialized()) { return E_FAIL; } ComPtr source; HRESULT hr = capture_engine_->GetSource(&source); if (FAILED(hr)) { return hr; } // Find base media type for previewing. if (!FindBestMediaType( (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW, source.Get(), base_preview_media_type_.GetAddressOf(), GetMaxPreviewHeight(), &preview_frame_width_, &preview_frame_height_)) { return E_FAIL; } // Find base media type for record and photo capture. if (!FindBestMediaType( (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_RECORD, source.Get(), base_capture_media_type_.GetAddressOf(), 0xffffffff, nullptr, nullptr)) { return E_FAIL; } return S_OK; } void CaptureControllerImpl::StartRecord(const std::string& file_path, int64_t max_video_duration_ms) { assert(capture_engine_); if (!IsInitialized()) { return OnRecordStarted(CameraResult::kError, "Camera not initialized. Camera should be " "disposed and reinitialized."); } HRESULT hr = S_OK; if (!base_capture_media_type_) { // Enumerates mediatypes and finds media type for video capture. hr = FindBaseMediaTypes(); if (FAILED(hr)) { return OnRecordStarted(GetCameraResult(hr), "Failed to initialize video recording"); } } if (!record_handler_) { record_handler_ = std::make_unique(record_audio_); } else if (!record_handler_->CanStart()) { return OnRecordStarted( CameraResult::kError, "Recording cannot be started. Previous recording must be stopped " "first."); } // Check MF_CAPTURE_ENGINE_RECORD_STARTED event handling for response // process. hr = record_handler_->StartRecord(file_path, max_video_duration_ms, capture_engine_.Get(), base_capture_media_type_.Get()); if (FAILED(hr)) { // Destroy record handler on error cases to make sure state is resetted. record_handler_ = nullptr; return OnRecordStarted(GetCameraResult(hr), "Failed to start video recording"); } } void CaptureControllerImpl::StopRecord() { assert(capture_controller_listener_); if (!IsInitialized()) { return OnRecordStopped(CameraResult::kError, "Camera not initialized. Camera should be " "disposed and reinitialized."); } if (!record_handler_ && !record_handler_->CanStop()) { return OnRecordStopped(CameraResult::kError, "Recording cannot be stopped."); } // Check MF_CAPTURE_ENGINE_RECORD_STOPPED event handling for response // process. HRESULT hr = record_handler_->StopRecord(capture_engine_.Get()); if (FAILED(hr)) { return OnRecordStopped(GetCameraResult(hr), "Failed to stop video recording"); } } // Stops timed recording. Called internally when requested time is passed. // Check MF_CAPTURE_ENGINE_RECORD_STOPPED event handling for response process. void CaptureControllerImpl::StopTimedRecord() { assert(capture_controller_listener_); if (!record_handler_ || !record_handler_->IsTimedRecording()) { return; } HRESULT hr = record_handler_->StopRecord(capture_engine_.Get()); if (FAILED(hr)) { // Destroy record handler on error cases to make sure state is resetted. record_handler_ = nullptr; return capture_controller_listener_->OnVideoRecordFailed( GetCameraResult(hr), "Failed to record video"); } } // Starts capturing preview frames using preview handler // After first frame is captured, OnPreviewStarted is called void CaptureControllerImpl::StartPreview() { assert(capture_engine_callback_handler_); assert(capture_engine_); assert(texture_handler_); if (!IsInitialized() || !texture_handler_) { return OnPreviewStarted(CameraResult::kError, "Camera not initialized. Camera should be " "disposed and reinitialized."); } HRESULT hr = S_OK; if (!base_preview_media_type_) { // Enumerates mediatypes and finds media type for video capture. hr = FindBaseMediaTypes(); if (FAILED(hr)) { return OnPreviewStarted(GetCameraResult(hr), "Failed to initialize video preview"); } } texture_handler_->UpdateTextureSize(preview_frame_width_, preview_frame_height_); // TODO(loic-sharma): This does not handle duplicate calls properly. // See: https://github.com/flutter/flutter/issues/108404 if (!preview_handler_) { preview_handler_ = std::make_unique(); } else if (preview_handler_->IsInitialized()) { return OnPreviewStarted(CameraResult::kSuccess, ""); } else { return OnPreviewStarted(CameraResult::kError, "Preview already exists"); } // Check MF_CAPTURE_ENGINE_PREVIEW_STARTED event handling for response // process. hr = preview_handler_->StartPreview(capture_engine_.Get(), base_preview_media_type_.Get(), capture_engine_callback_handler_.Get()); if (FAILED(hr)) { // Destroy preview handler on error cases to make sure state is resetted. preview_handler_ = nullptr; return OnPreviewStarted(GetCameraResult(hr), "Failed to start video preview"); } } // Stops preview. Called by destructor // Use PausePreview and ResumePreview methods to for // pausing and resuming the preview. // Check MF_CAPTURE_ENGINE_PREVIEW_STOPPED event handling for response // process. HRESULT CaptureControllerImpl::StopPreview() { assert(capture_engine_); if (!IsInitialized() || !preview_handler_) { return S_OK; } // Requests to stop preview. return preview_handler_->StopPreview(capture_engine_.Get()); } // Marks preview as paused. // When preview is paused, captured frames are not processed for preview // and flutter texture is not updated void CaptureControllerImpl::PausePreview() { assert(capture_controller_listener_); if (!preview_handler_ || !preview_handler_->IsInitialized()) { return capture_controller_listener_->OnPausePreviewFailed( CameraResult::kError, "Preview not started"); } if (preview_handler_->PausePreview()) { capture_controller_listener_->OnPausePreviewSucceeded(); } else { capture_controller_listener_->OnPausePreviewFailed( CameraResult::kError, "Failed to pause preview"); } } // Marks preview as not paused. // When preview is not paused, captured frames are processed for preview // and flutter texture is updated. void CaptureControllerImpl::ResumePreview() { assert(capture_controller_listener_); if (!preview_handler_ || !preview_handler_->IsInitialized()) { return capture_controller_listener_->OnResumePreviewFailed( CameraResult::kError, "Preview not started"); } if (preview_handler_->ResumePreview()) { capture_controller_listener_->OnResumePreviewSucceeded(); } else { capture_controller_listener_->OnResumePreviewFailed( CameraResult::kError, "Failed to pause preview"); } } // Handles capture engine events. // Called via IMFCaptureEngineOnEventCallback implementation. // Implements CaptureEngineObserver::OnEvent. void CaptureControllerImpl::OnEvent(IMFMediaEvent* event) { if (!IsInitialized() && capture_engine_state_ != CaptureEngineState::kInitializing) { return; } GUID extended_type_guid; if (SUCCEEDED(event->GetExtendedType(&extended_type_guid))) { std::string error; HRESULT event_hr; if (FAILED(event->GetStatus(&event_hr))) { return; } if (FAILED(event_hr)) { // Reads system error _com_error err(event_hr); error = Utf8FromUtf16(err.ErrorMessage()); } CameraResult event_result = GetCameraResult(event_hr); if (extended_type_guid == MF_CAPTURE_ENGINE_ERROR) { OnCaptureEngineError(event_result, error); } else if (extended_type_guid == MF_CAPTURE_ENGINE_INITIALIZED) { OnCaptureEngineInitialized(event_result, error); } else if (extended_type_guid == MF_CAPTURE_ENGINE_PREVIEW_STARTED) { // Preview is marked as started after first frame is captured. // This is because, CaptureEngine might inform that preview is started // even if error is thrown right after. } else if (extended_type_guid == MF_CAPTURE_ENGINE_PREVIEW_STOPPED) { OnPreviewStopped(event_result, error); } else if (extended_type_guid == MF_CAPTURE_ENGINE_RECORD_STARTED) { OnRecordStarted(event_result, error); } else if (extended_type_guid == MF_CAPTURE_ENGINE_RECORD_STOPPED) { OnRecordStopped(event_result, error); } else if (extended_type_guid == MF_CAPTURE_ENGINE_PHOTO_TAKEN) { OnPicture(event_result, error); } else if (extended_type_guid == MF_CAPTURE_ENGINE_CAMERA_STREAM_BLOCKED) { // TODO: Inform capture state to flutter. } else if (extended_type_guid == MF_CAPTURE_ENGINE_CAMERA_STREAM_UNBLOCKED) { // TODO: Inform capture state to flutter. } } } // Handles Picture event and informs CaptureControllerListener. void CaptureControllerImpl::OnPicture(CameraResult result, const std::string& error) { if (result == CameraResult::kSuccess && photo_handler_) { if (capture_controller_listener_) { std::string path = photo_handler_->GetPhotoPath(); capture_controller_listener_->OnTakePictureSucceeded(path); } photo_handler_->OnPhotoTaken(); } else { if (capture_controller_listener_) { capture_controller_listener_->OnTakePictureFailed(result, error); } // Destroy photo handler on error cases to make sure state is resetted. photo_handler_ = nullptr; } } // Handles CaptureEngineInitialized event and informs // CaptureControllerListener. void CaptureControllerImpl::OnCaptureEngineInitialized( CameraResult result, const std::string& error) { if (capture_controller_listener_) { if (result != CameraResult::kSuccess) { capture_controller_listener_->OnCreateCaptureEngineFailed( result, "Failed to initialize capture engine"); ResetCaptureController(); return; } // Create texture handler and register new texture. texture_handler_ = std::make_unique(texture_registrar_); int64_t texture_id = texture_handler_->RegisterTexture(); if (texture_id >= 0) { capture_controller_listener_->OnCreateCaptureEngineSucceeded(texture_id); capture_engine_state_ = CaptureEngineState::kInitialized; } else { capture_controller_listener_->OnCreateCaptureEngineFailed( CameraResult::kError, "Failed to create texture_id"); // Reset state ResetCaptureController(); } } } // Handles CaptureEngineError event and informs CaptureControllerListener. void CaptureControllerImpl::OnCaptureEngineError(CameraResult result, const std::string& error) { if (capture_controller_listener_) { capture_controller_listener_->OnCaptureError(result, error); } // TODO: If MF_CAPTURE_ENGINE_ERROR is returned, // should capture controller be reinitialized automatically? } // Handles PreviewStarted event and informs CaptureControllerListener. // This should be called only after first frame has been received or // in error cases. void CaptureControllerImpl::OnPreviewStarted(CameraResult result, const std::string& error) { if (preview_handler_ && result == CameraResult::kSuccess) { preview_handler_->OnPreviewStarted(); } else { // Destroy preview handler on error cases to make sure state is resetted. preview_handler_ = nullptr; } if (capture_controller_listener_) { if (result == CameraResult::kSuccess && preview_frame_width_ > 0 && preview_frame_height_ > 0) { capture_controller_listener_->OnStartPreviewSucceeded( preview_frame_width_, preview_frame_height_); } else { capture_controller_listener_->OnStartPreviewFailed(result, error); } } }; // Handles PreviewStopped event. void CaptureControllerImpl::OnPreviewStopped(CameraResult result, const std::string& error) { // Preview handler is destroyed if preview is stopped as it // does not have any use anymore. preview_handler_ = nullptr; }; // Handles RecordStarted event and informs CaptureControllerListener. void CaptureControllerImpl::OnRecordStarted(CameraResult result, const std::string& error) { if (result == CameraResult::kSuccess && record_handler_) { record_handler_->OnRecordStarted(); if (capture_controller_listener_) { capture_controller_listener_->OnStartRecordSucceeded(); } } else { if (capture_controller_listener_) { capture_controller_listener_->OnStartRecordFailed(result, error); } // Destroy record handler on error cases to make sure state is resetted. record_handler_ = nullptr; } }; // Handles RecordStopped event and informs CaptureControllerListener. void CaptureControllerImpl::OnRecordStopped(CameraResult result, const std::string& error) { if (capture_controller_listener_ && record_handler_) { // Always calls OnStopRecord listener methods // to handle separate stop record request for timed records. if (result == CameraResult::kSuccess) { std::string path = record_handler_->GetRecordPath(); capture_controller_listener_->OnStopRecordSucceeded(path); if (record_handler_->IsTimedRecording()) { capture_controller_listener_->OnVideoRecordSucceeded( path, (record_handler_->GetRecordedDuration() / 1000)); } } else { capture_controller_listener_->OnStopRecordFailed(result, error); if (record_handler_->IsTimedRecording()) { capture_controller_listener_->OnVideoRecordFailed(result, error); } } } if (result == CameraResult::kSuccess && record_handler_) { record_handler_->OnRecordStopped(); } else { // Destroy record handler on error cases to make sure state is resetted. record_handler_ = nullptr; } } // Updates texture handlers buffer with given data. // Called via IMFCaptureEngineOnSampleCallback implementation. // Implements CaptureEngineObserver::UpdateBuffer. bool CaptureControllerImpl::UpdateBuffer(uint8_t* buffer, uint32_t data_length) { if (!texture_handler_) { return false; } return texture_handler_->UpdateBuffer(buffer, data_length); } // Handles capture time update from each processed frame. // Stops timed recordings if requested recording duration has passed. // Called via IMFCaptureEngineOnSampleCallback implementation. // Implements CaptureEngineObserver::UpdateCaptureTime. void CaptureControllerImpl::UpdateCaptureTime(uint64_t capture_time_us) { if (!IsInitialized()) { return; } if (preview_handler_ && preview_handler_->IsStarting()) { // Informs that first frame is captured successfully and preview has // started. OnPreviewStarted(CameraResult::kSuccess, ""); } // Checks if max_video_duration_ms is passed. if (record_handler_) { record_handler_->UpdateRecordingTime(capture_time_us); if (record_handler_->ShouldStopTimedRecording()) { StopTimedRecord(); } } } } // namespace camera_windows ================================================ FILE: packages/camera/camera_windows/windows/capture_controller.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_CONTROLLER_H_ #define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_CONTROLLER_H_ #include #include #include #include #include #include #include #include #include #include #include "capture_controller_listener.h" #include "capture_engine_listener.h" #include "photo_handler.h" #include "preview_handler.h" #include "record_handler.h" #include "texture_handler.h" namespace camera_windows { using flutter::TextureRegistrar; using Microsoft::WRL::ComPtr; // Camera resolution presets. Used to request a capture resolution. enum class ResolutionPreset { // Automatic resolution, uses the highest resolution available. kAuto, // 240p (320x240) kLow, // 480p (720x480) kMedium, // 720p (1280x720) kHigh, // 1080p (1920x1080) kVeryHigh, // 2160p (4096x2160) kUltraHigh, // The highest resolution available. kMax, }; // Camera capture engine state. // // On creation, |CaptureControllers| start in state |kNotInitialized|. // On initialization, the capture controller transitions to the |kInitializing| // and then |kInitialized| state. enum class CaptureEngineState { kNotInitialized, kInitializing, kInitialized }; // Interface for a class that enumerates video capture device sources. class VideoCaptureDeviceEnumerator { private: virtual bool EnumerateVideoCaptureDeviceSources(IMFActivate*** devices, UINT32* count) = 0; }; // Interface implemented by capture controllers. // // Capture controllers are used to capture video streams or still photos from // their associated |Camera|. class CaptureController { public: CaptureController() {} virtual ~CaptureController() = default; // Disallow copy and move. CaptureController(const CaptureController&) = delete; CaptureController& operator=(const CaptureController&) = delete; // Initializes the capture controller with the specified device id. // // Returns false if the capture controller could not be initialized // or is already initialized. // // texture_registrar: Pointer to Flutter TextureRegistrar instance. Used to // register texture for capture preview. // device_id: A string that holds information of camera device id to // be captured. // record_audio: A boolean value telling if audio should be captured on // video recording. // resolution_preset: Maximum capture resolution height. virtual bool InitCaptureDevice(TextureRegistrar* texture_registrar, const std::string& device_id, bool record_audio, ResolutionPreset resolution_preset) = 0; // Returns preview frame width virtual uint32_t GetPreviewWidth() const = 0; // Returns preview frame height virtual uint32_t GetPreviewHeight() const = 0; // Starts the preview. virtual void StartPreview() = 0; // Pauses the preview. virtual void PausePreview() = 0; // Resumes the preview. virtual void ResumePreview() = 0; // Starts recording video. virtual void StartRecord(const std::string& file_path, int64_t max_video_duration_ms) = 0; // Stops the current video recording. virtual void StopRecord() = 0; // Captures a still photo. virtual void TakePicture(const std::string& file_path) = 0; }; // Concrete implementation of the |CaptureController| interface. // // Handles the video preview stream via a |PreviewHandler| instance, video // capture via a |RecordHandler| instance, and still photo capture via a // |PhotoHandler| instance. class CaptureControllerImpl : public CaptureController, public CaptureEngineObserver { public: static bool EnumerateVideoCaptureDeviceSources(IMFActivate*** devices, UINT32* count); explicit CaptureControllerImpl(CaptureControllerListener* listener); virtual ~CaptureControllerImpl(); // Disallow copy and move. CaptureControllerImpl(const CaptureControllerImpl&) = delete; CaptureControllerImpl& operator=(const CaptureControllerImpl&) = delete; // CaptureController bool InitCaptureDevice(TextureRegistrar* texture_registrar, const std::string& device_id, bool record_audio, ResolutionPreset resolution_preset) override; uint32_t GetPreviewWidth() const override { return preview_frame_width_; } uint32_t GetPreviewHeight() const override { return preview_frame_height_; } void StartPreview() override; void PausePreview() override; void ResumePreview() override; void StartRecord(const std::string& file_path, int64_t max_video_duration_ms) override; void StopRecord() override; void TakePicture(const std::string& file_path) override; // CaptureEngineObserver void OnEvent(IMFMediaEvent* event) override; bool IsReadyForSample() const override { return capture_engine_state_ == CaptureEngineState::kInitialized && preview_handler_ && preview_handler_->IsRunning(); } bool UpdateBuffer(uint8_t* data, uint32_t data_length) override; void UpdateCaptureTime(uint64_t capture_time) override; // Sets capture engine, for testing purposes. void SetCaptureEngine(IMFCaptureEngine* capture_engine) { capture_engine_ = capture_engine; } // Sets video source, for testing purposes. void SetVideoSource(IMFMediaSource* video_source) { video_source_ = video_source; } // Sets audio source, for testing purposes. void SetAudioSource(IMFMediaSource* audio_source) { audio_source_ = audio_source; } private: // Helper function to return initialized state as boolean; bool IsInitialized() const { return capture_engine_state_ == CaptureEngineState::kInitialized; } // Resets capture controller state. // This is called if capture engine creation fails or is disposed. void ResetCaptureController(); // Returns max preview height calculated from resolution present. uint32_t GetMaxPreviewHeight() const; // Uses first audio source to capture audio. // Note: Enumerating audio sources via platform interface is not supported. HRESULT CreateDefaultAudioCaptureSource(); // Initializes video capture source from camera device. HRESULT CreateVideoCaptureSourceForDevice(const std::string& video_device_id); // Creates DX11 Device and D3D Manager. HRESULT CreateD3DManagerWithDX11Device(); // Initializes capture engine object. HRESULT CreateCaptureEngine(); // Enumerates video_sources media types and finds out best resolution // for preview and video capture. HRESULT FindBaseMediaTypes(); // Stops timed video record. Called internally when record handler when max // recording time is exceeded. void StopTimedRecord(); // Stops preview. Called internally on camera reset and dispose. HRESULT StopPreview(); // Handles capture engine initalization event. void OnCaptureEngineInitialized(CameraResult result, const std::string& error); // Handles capture engine errors. void OnCaptureEngineError(CameraResult result, const std::string& error); // Handles picture events. void OnPicture(CameraResult result, const std::string& error); // Handles preview started events. void OnPreviewStarted(CameraResult result, const std::string& error); // Handles preview stopped events. void OnPreviewStopped(CameraResult result, const std::string& error); // Handles record started events. void OnRecordStarted(CameraResult result, const std::string& error); // Handles record stopped events. void OnRecordStopped(CameraResult result, const std::string& error); bool media_foundation_started_ = false; bool record_audio_ = false; uint32_t preview_frame_width_ = 0; uint32_t preview_frame_height_ = 0; UINT dx_device_reset_token_ = 0; std::unique_ptr record_handler_; std::unique_ptr preview_handler_; std::unique_ptr photo_handler_; std::unique_ptr texture_handler_; CaptureControllerListener* capture_controller_listener_; std::string video_device_id_; CaptureEngineState capture_engine_state_ = CaptureEngineState::kNotInitialized; ResolutionPreset resolution_preset_ = ResolutionPreset::kMedium; ComPtr capture_engine_; ComPtr capture_engine_callback_handler_; ComPtr dxgi_device_manager_; ComPtr dx11_device_; ComPtr base_capture_media_type_; ComPtr base_preview_media_type_; ComPtr video_source_; ComPtr audio_source_; TextureRegistrar* texture_registrar_ = nullptr; }; // Inferface for factory classes that create |CaptureController| instances. class CaptureControllerFactory { public: CaptureControllerFactory() {} virtual ~CaptureControllerFactory() = default; // Disallow copy and move. CaptureControllerFactory(const CaptureControllerFactory&) = delete; CaptureControllerFactory& operator=(const CaptureControllerFactory&) = delete; // Create and return a |CaptureController| that makes callbacks on the // specified |CaptureControllerListener|, which must not be null. virtual std::unique_ptr CreateCaptureController( CaptureControllerListener* listener) = 0; }; // Concreate implementation of |CaptureControllerFactory|. class CaptureControllerFactoryImpl : public CaptureControllerFactory { public: CaptureControllerFactoryImpl() {} virtual ~CaptureControllerFactoryImpl() = default; // Disallow copy and move. CaptureControllerFactoryImpl(const CaptureControllerFactoryImpl&) = delete; CaptureControllerFactoryImpl& operator=(const CaptureControllerFactoryImpl&) = delete; std::unique_ptr CreateCaptureController( CaptureControllerListener* listener) override { return std::make_unique(listener); } }; } // namespace camera_windows #endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_CONTROLLER_H_ ================================================ FILE: packages/camera/camera_windows/windows/capture_controller_listener.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_CONTROLLER_LISTENER_H_ #define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_CONTROLLER_LISTENER_H_ #include namespace camera_windows { // Results that can occur when interacting with the camera. enum class CameraResult { // Camera operation succeeded. kSuccess, // Camera operation failed. kError, // Camera access permission is denied. kAccessDenied, }; // Interface for classes that receives callbacks on events from the associated // |CaptureController|. class CaptureControllerListener { public: virtual ~CaptureControllerListener() = default; // Called by CaptureController on successful capture engine initialization. // // texture_id: A 64bit integer id registered by TextureRegistrar virtual void OnCreateCaptureEngineSucceeded(int64_t texture_id) = 0; // Called by CaptureController if initializing the capture engine fails. // // result: The kind of result. // error: A string describing the error. virtual void OnCreateCaptureEngineFailed(CameraResult result, const std::string& error) = 0; // Called by CaptureController on successfully started preview. // // width: Preview frame width. // height: Preview frame height. virtual void OnStartPreviewSucceeded(int32_t width, int32_t height) = 0; // Called by CaptureController if starting the preview fails. // // result: The kind of result. // error: A string describing the error. virtual void OnStartPreviewFailed(CameraResult result, const std::string& error) = 0; // Called by CaptureController on successfully paused preview. virtual void OnPausePreviewSucceeded() = 0; // Called by CaptureController if pausing the preview fails. // // result: The kind of result. // error: A string describing the error. virtual void OnPausePreviewFailed(CameraResult result, const std::string& error) = 0; // Called by CaptureController on successfully resumed preview. virtual void OnResumePreviewSucceeded() = 0; // Called by CaptureController if resuming the preview fails. // // result: The kind of result. // error: A string describing the error. virtual void OnResumePreviewFailed(CameraResult result, const std::string& error) = 0; // Called by CaptureController on successfully started recording. virtual void OnStartRecordSucceeded() = 0; // Called by CaptureController if starting the recording fails. // // result: The kind of result. // error: A string describing the error. virtual void OnStartRecordFailed(CameraResult result, const std::string& error) = 0; // Called by CaptureController on successfully stopped recording. // // file_path: Filesystem path of the recorded video file. virtual void OnStopRecordSucceeded(const std::string& file_path) = 0; // Called by CaptureController if stopping the recording fails. // // result: The kind of result. // error: A string describing the error. virtual void OnStopRecordFailed(CameraResult result, const std::string& error) = 0; // Called by CaptureController on successfully captured picture. // // file_path: Filesystem path of the captured image. virtual void OnTakePictureSucceeded(const std::string& file_path) = 0; // Called by CaptureController if taking picture fails. // // result: The kind of result. // error: A string describing the error. virtual void OnTakePictureFailed(CameraResult result, const std::string& error) = 0; // Called by CaptureController when timed recording is successfully recorded. // // file_path: Filesystem path of the captured image. // video_duration: Duration of recorded video in milliseconds. virtual void OnVideoRecordSucceeded(const std::string& file_path, int64_t video_duration_ms) = 0; // Called by CaptureController if timed recording fails. // // result: The kind of result. // error: A string describing the error. virtual void OnVideoRecordFailed(CameraResult result, const std::string& error) = 0; // Called by CaptureController if capture engine returns error. // For example when camera is disconnected while on use. // // result: The kind of result. // error: A string describing the error. virtual void OnCaptureError(CameraResult result, const std::string& error) = 0; }; } // namespace camera_windows #endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_CONTROLLER_LISTENER_H_ ================================================ FILE: packages/camera/camera_windows/windows/capture_device_info.cpp ================================================ // Copyright 2013 The Flutter 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 "capture_device_info.h" #include #include namespace camera_windows { std::string CaptureDeviceInfo::GetUniqueDeviceName() const { return display_name_ + " <" + device_id_ + ">"; } bool CaptureDeviceInfo::ParseDeviceInfoFromCameraName( const std::string& camera_name) { size_t delimeter_index = camera_name.rfind(' ', camera_name.length()); if (delimeter_index != std::string::npos) { auto deviceInfo = std::make_unique(); display_name_ = camera_name.substr(0, delimeter_index); device_id_ = camera_name.substr(delimeter_index + 2, camera_name.length() - delimeter_index - 3); return true; } return false; } } // namespace camera_windows ================================================ FILE: packages/camera/camera_windows/windows/capture_device_info.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_DEVICE_INFO_H_ #define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_DEVICE_INFO_H_ #include namespace camera_windows { // Name and device ID information for a capture device. class CaptureDeviceInfo { public: CaptureDeviceInfo() {} virtual ~CaptureDeviceInfo() = default; // Disallow copy and move. CaptureDeviceInfo(const CaptureDeviceInfo&) = delete; CaptureDeviceInfo& operator=(const CaptureDeviceInfo&) = delete; // Build unique device name from display name and device id. // Format: "display_name ". std::string GetUniqueDeviceName() const; // Parses display name and device id from unique device name format. // Format: "display_name ". bool CaptureDeviceInfo::ParseDeviceInfoFromCameraName( const std::string& camera_name); // Updates display name. void SetDisplayName(const std::string& display_name) { display_name_ = display_name; } // Updates device id. void SetDeviceID(const std::string& device_id) { device_id_ = device_id; } // Returns device id. std::string GetDeviceId() const { return device_id_; } private: std::string display_name_; std::string device_id_; }; } // namespace camera_windows #endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_DEVICE_INFO_H_ ================================================ FILE: packages/camera/camera_windows/windows/capture_engine_listener.cpp ================================================ // Copyright 2013 The Flutter 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 "capture_engine_listener.h" #include #include namespace camera_windows { using Microsoft::WRL::ComPtr; // IUnknown STDMETHODIMP_(ULONG) CaptureEngineListener::AddRef() { return InterlockedIncrement(&ref_); } // IUnknown STDMETHODIMP_(ULONG) CaptureEngineListener::Release() { LONG ref = InterlockedDecrement(&ref_); if (ref == 0) { delete this; } return ref; } // IUnknown STDMETHODIMP_(HRESULT) CaptureEngineListener::QueryInterface(const IID& riid, void** ppv) { *ppv = nullptr; if (riid == IID_IMFCaptureEngineOnEventCallback) { *ppv = static_cast(this); ((IUnknown*)*ppv)->AddRef(); return S_OK; } else if (riid == IID_IMFCaptureEngineOnSampleCallback) { *ppv = static_cast(this); ((IUnknown*)*ppv)->AddRef(); return S_OK; } return E_NOINTERFACE; } STDMETHODIMP CaptureEngineListener::OnEvent(IMFMediaEvent* event) { if (observer_) { observer_->OnEvent(event); } return S_OK; } // IMFCaptureEngineOnSampleCallback HRESULT CaptureEngineListener::OnSample(IMFSample* sample) { HRESULT hr = S_OK; if (this->observer_ && sample) { LONGLONG raw_time_stamp = 0; // Receives the presentation time, in 100-nanosecond units. sample->GetSampleTime(&raw_time_stamp); // Report time in microseconds. this->observer_->UpdateCaptureTime( static_cast(raw_time_stamp / 10)); if (!this->observer_->IsReadyForSample()) { // No texture target available or not previewing, just return status. return hr; } ComPtr buffer; hr = sample->ConvertToContiguousBuffer(&buffer); // Draw the frame. if (SUCCEEDED(hr) && buffer) { DWORD max_length = 0; DWORD current_length = 0; uint8_t* data; if (SUCCEEDED(buffer->Lock(&data, &max_length, ¤t_length))) { this->observer_->UpdateBuffer(data, current_length); } hr = buffer->Unlock(); } } return hr; } } // namespace camera_windows ================================================ FILE: packages/camera/camera_windows/windows/capture_engine_listener.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_ENGINE_LISTENER_H_ #define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_ENGINE_LISTENER_H_ #include #include #include namespace camera_windows { // A class that implements callbacks for events from a |CaptureEngineListener|. class CaptureEngineObserver { public: virtual ~CaptureEngineObserver() = default; // Returns true if sample can be processed. virtual bool IsReadyForSample() const = 0; // Handles Capture Engine media events. virtual void OnEvent(IMFMediaEvent* event) = 0; // Updates texture buffer virtual bool UpdateBuffer(uint8_t* data, uint32_t new_length) = 0; // Handles capture timestamps updates. // Used to stop timed recordings when recorded time is exceeded. virtual void UpdateCaptureTime(uint64_t capture_time) = 0; }; // Listener for Windows Media Foundation capture engine events and samples. // // Events are redirected to observers for processing. Samples are preprosessed // and sent to the associated observer if it is ready to process samples. class CaptureEngineListener : public IMFCaptureEngineOnSampleCallback, public IMFCaptureEngineOnEventCallback { public: CaptureEngineListener(CaptureEngineObserver* observer) : observer_(observer) { assert(observer); } ~CaptureEngineListener() {} // Disallow copy and move. CaptureEngineListener(const CaptureEngineListener&) = delete; CaptureEngineListener& operator=(const CaptureEngineListener&) = delete; // IUnknown STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); STDMETHODIMP_(HRESULT) QueryInterface(const IID& riid, void** ppv); // IMFCaptureEngineOnEventCallback STDMETHODIMP OnEvent(IMFMediaEvent* pEvent); // IMFCaptureEngineOnSampleCallback STDMETHODIMP_(HRESULT) OnSample(IMFSample* pSample); private: CaptureEngineObserver* observer_; volatile ULONG ref_ = 0; }; } // namespace camera_windows #endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_CAPTURE_ENGINE_LISTENER_H_ ================================================ FILE: packages/camera/camera_windows/windows/com_heap_ptr.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_COMHEAPPTR_H_ #define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_COMHEAPPTR_H_ #include #include namespace camera_windows { // Wrapper for COM object for automatic memory release support // Destructor uses CoTaskMemFree to release memory allocations. template class ComHeapPtr { public: ComHeapPtr() : p_obj_(nullptr) {} ComHeapPtr(T* p_obj) : p_obj_(p_obj) {} // Frees memory on destruction. ~ComHeapPtr() { Free(); } // Prevent copying / ownership transfer as not currently needed. ComHeapPtr(ComHeapPtr const&) = delete; ComHeapPtr& operator=(ComHeapPtr const&) = delete; // Returns the pointer to the memory. operator T*() { return p_obj_; } // Returns the pointer to the memory. T* operator->() { assert(p_obj_ != nullptr); return p_obj_; } // Returns the pointer to the memory. const T* operator->() const { assert(p_obj_ != nullptr); return p_obj_; } // Returns the pointer to the memory. T** operator&() { // Wrapped object must be nullptr to avoid memory leaks. // Object can be released with Reset(nullptr). assert(p_obj_ == nullptr); return &p_obj_; } // Frees the memory pointed to, and sets the pointer to nullptr. void Free() { if (p_obj_) { CoTaskMemFree(p_obj_); } p_obj_ = nullptr; } private: // Pointer to memory. T* p_obj_; }; } // namespace camera_windows #endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_COMHEAPPTR_H_ ================================================ FILE: packages/camera/camera_windows/windows/include/camera_windows/camera_windows.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_INCLUDE_CAMERA_WINDOWS_CAMERA_WINDOWS_H_ #define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_INCLUDE_CAMERA_WINDOWS_CAMERA_WINDOWS_H_ #include #ifdef FLUTTER_PLUGIN_IMPL #define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) #else #define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) #endif #if defined(__cplusplus) extern "C" { #endif FLUTTER_PLUGIN_EXPORT void CameraWindowsRegisterWithRegistrar( FlutterDesktopPluginRegistrarRef registrar); #if defined(__cplusplus) } // extern "C" #endif #endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_INCLUDE_CAMERA_WINDOWS_CAMERA_WINDOWS_H_ ================================================ FILE: packages/camera/camera_windows/windows/photo_handler.cpp ================================================ // Copyright 2013 The Flutter 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 "photo_handler.h" #include #include #include #include #include "capture_engine_listener.h" #include "string_utils.h" namespace camera_windows { using Microsoft::WRL::ComPtr; // Initializes media type for photo capture for jpeg images. HRESULT BuildMediaTypeForPhotoCapture(IMFMediaType* src_media_type, IMFMediaType** photo_media_type, GUID image_format) { assert(src_media_type); ComPtr new_media_type; HRESULT hr = MFCreateMediaType(&new_media_type); if (FAILED(hr)) { return hr; } // Clones everything from original media type. hr = src_media_type->CopyAllItems(new_media_type.Get()); if (FAILED(hr)) { return hr; } hr = new_media_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Image); if (FAILED(hr)) { return hr; } hr = new_media_type->SetGUID(MF_MT_SUBTYPE, image_format); if (FAILED(hr)) { return hr; } new_media_type.CopyTo(photo_media_type); return hr; } HRESULT PhotoHandler::InitPhotoSink(IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type) { assert(capture_engine); assert(base_media_type); HRESULT hr = S_OK; if (photo_sink_) { // If photo sink already exists, only update output filename. hr = photo_sink_->SetOutputFileName(Utf16FromUtf8(file_path_).c_str()); if (FAILED(hr)) { photo_sink_ = nullptr; } return hr; } ComPtr photo_media_type; ComPtr capture_sink; // Get sink with photo type. hr = capture_engine->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PHOTO, &capture_sink); if (FAILED(hr)) { return hr; } hr = capture_sink.As(&photo_sink_); if (FAILED(hr)) { photo_sink_ = nullptr; return hr; } hr = photo_sink_->RemoveAllStreams(); if (FAILED(hr)) { photo_sink_ = nullptr; return hr; } hr = BuildMediaTypeForPhotoCapture(base_media_type, photo_media_type.GetAddressOf(), GUID_ContainerFormatJpeg); if (FAILED(hr)) { photo_sink_ = nullptr; return hr; } DWORD photo_sink_stream_index; hr = photo_sink_->AddStream( (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_PHOTO, photo_media_type.Get(), nullptr, &photo_sink_stream_index); if (FAILED(hr)) { photo_sink_ = nullptr; return hr; } hr = photo_sink_->SetOutputFileName(Utf16FromUtf8(file_path_).c_str()); if (FAILED(hr)) { photo_sink_ = nullptr; return hr; } return hr; } HRESULT PhotoHandler::TakePhoto(const std::string& file_path, IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type) { assert(!file_path.empty()); assert(capture_engine); assert(base_media_type); file_path_ = file_path; HRESULT hr = InitPhotoSink(capture_engine, base_media_type); if (FAILED(hr)) { return hr; } photo_state_ = PhotoState::kTakingPhoto; return capture_engine->TakePhoto(); } void PhotoHandler::OnPhotoTaken() { assert(photo_state_ == PhotoState::kTakingPhoto); photo_state_ = PhotoState::kIdle; } } // namespace camera_windows ================================================ FILE: packages/camera/camera_windows/windows/photo_handler.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_PHOTO_HANDLER_H_ #define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_PHOTO_HANDLER_H_ #include #include #include #include #include #include "capture_engine_listener.h" namespace camera_windows { using Microsoft::WRL::ComPtr; // Various states that the photo handler can be in. // // When created, the handler is in |kNotStarted| state and transtions in // sequential order through the states. enum class PhotoState { kNotStarted, kIdle, kTakingPhoto, }; // Handles photo sink initialization and tracks photo capture states. class PhotoHandler { public: PhotoHandler() {} virtual ~PhotoHandler() = default; // Prevent copying. PhotoHandler(PhotoHandler const&) = delete; PhotoHandler& operator=(PhotoHandler const&) = delete; // Initializes photo sink if not initialized and requests the capture engine // to take photo. // // Sets photo state to: kTakingPhoto. // // capture_engine: A pointer to capture engine instance. // Called to take the photo. // base_media_type: A pointer to base media type used as a base // for the actual photo capture media type. // file_path: A string that hold file path for photo capture. HRESULT TakePhoto(const std::string& file_path, IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type); // Set the photo handler recording state to: kIdle. void OnPhotoTaken(); // Returns true if photo state is kIdle. bool IsInitialized() const { return photo_state_ == PhotoState::kIdle; } // Returns true if photo state is kTakingPhoto. bool IsTakingPhoto() const { return photo_state_ == PhotoState::kTakingPhoto; } // Returns the filesystem path of the captured photo. std::string GetPhotoPath() const { return file_path_; } private: // Initializes record sink for video file capture. HRESULT InitPhotoSink(IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type); std::string file_path_; PhotoState photo_state_ = PhotoState::kNotStarted; ComPtr photo_sink_; }; } // namespace camera_windows #endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_PHOTO_HANDLER_H_ ================================================ FILE: packages/camera/camera_windows/windows/preview_handler.cpp ================================================ // Copyright 2013 The Flutter 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 "preview_handler.h" #include #include #include #include "capture_engine_listener.h" #include "string_utils.h" namespace camera_windows { using Microsoft::WRL::ComPtr; // Initializes media type for video preview. HRESULT BuildMediaTypeForVideoPreview(IMFMediaType* src_media_type, IMFMediaType** preview_media_type) { assert(src_media_type); ComPtr new_media_type; HRESULT hr = MFCreateMediaType(&new_media_type); if (FAILED(hr)) { return hr; } // Clones everything from original media type. hr = src_media_type->CopyAllItems(new_media_type.Get()); if (FAILED(hr)) { return hr; } // Changes subtype to MFVideoFormat_RGB32. hr = new_media_type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32); if (FAILED(hr)) { return hr; } hr = new_media_type->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); if (FAILED(hr)) { return hr; } new_media_type.CopyTo(preview_media_type); return hr; } HRESULT PreviewHandler::InitPreviewSink( IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type, CaptureEngineListener* sample_callback) { assert(capture_engine); assert(base_media_type); assert(sample_callback); HRESULT hr = S_OK; if (preview_sink_) { // Preview sink already initialized. return hr; } ComPtr preview_media_type; ComPtr capture_sink; // Get sink with preview type. hr = capture_engine->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, &capture_sink); if (FAILED(hr)) { return hr; } hr = capture_sink.As(&preview_sink_); if (FAILED(hr)) { preview_sink_ = nullptr; return hr; } hr = preview_sink_->RemoveAllStreams(); if (FAILED(hr)) { preview_sink_ = nullptr; return hr; } hr = BuildMediaTypeForVideoPreview(base_media_type, preview_media_type.GetAddressOf()); if (FAILED(hr)) { preview_sink_ = nullptr; return hr; } DWORD preview_sink_stream_index; hr = preview_sink_->AddStream( (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW, preview_media_type.Get(), nullptr, &preview_sink_stream_index); if (FAILED(hr)) { return hr; } hr = preview_sink_->SetSampleCallback(preview_sink_stream_index, sample_callback); if (FAILED(hr)) { preview_sink_ = nullptr; return hr; } return hr; } HRESULT PreviewHandler::StartPreview(IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type, CaptureEngineListener* sample_callback) { assert(capture_engine); assert(base_media_type); HRESULT hr = InitPreviewSink(capture_engine, base_media_type, sample_callback); if (FAILED(hr)) { return hr; } preview_state_ = PreviewState::kStarting; return capture_engine->StartPreview(); } HRESULT PreviewHandler::StopPreview(IMFCaptureEngine* capture_engine) { if (preview_state_ == PreviewState::kStarting || preview_state_ == PreviewState::kRunning || preview_state_ == PreviewState::kPaused) { preview_state_ = PreviewState::kStopping; return capture_engine->StopPreview(); } return E_FAIL; } bool PreviewHandler::PausePreview() { if (preview_state_ != PreviewState::kRunning) { return false; } preview_state_ = PreviewState::kPaused; return true; } bool PreviewHandler::ResumePreview() { if (preview_state_ != PreviewState::kPaused) { return false; } preview_state_ = PreviewState::kRunning; return true; } void PreviewHandler::OnPreviewStarted() { assert(preview_state_ == PreviewState::kStarting); if (preview_state_ == PreviewState::kStarting) { preview_state_ = PreviewState::kRunning; } } } // namespace camera_windows ================================================ FILE: packages/camera/camera_windows/windows/preview_handler.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_PREVIEW_HANDLER_H_ #define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_PREVIEW_HANDLER_H_ #include #include #include #include #include #include "capture_engine_listener.h" namespace camera_windows { using Microsoft::WRL::ComPtr; // States the preview handler can be in. // // When created, the handler starts in |kNotStarted| state and mostly // transitions in sequential order of the states. When the preview is running, // it can be set to the |kPaused| state and later resumed to |kRunning| state. enum class PreviewState { kNotStarted, kStarting, kRunning, kPaused, kStopping }; // Handler for a camera's video preview. // // Handles preview sink initialization and manages the state of the video // preview. class PreviewHandler { public: PreviewHandler() {} virtual ~PreviewHandler() = default; // Prevent copying. PreviewHandler(PreviewHandler const&) = delete; PreviewHandler& operator=(PreviewHandler const&) = delete; // Initializes preview sink and requests capture engine to start previewing. // Sets preview state to: starting. // // capture_engine: A pointer to capture engine instance. Used to start // the actual recording. // base_media_type: A pointer to base media type used as a base // for the actual video capture media type. // sample_callback: A pointer to capture engine listener. // This is set as sample callback for preview sink. HRESULT StartPreview(IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type, CaptureEngineListener* sample_callback); // Stops existing recording. // // capture_engine: A pointer to capture engine instance. Used to stop // the ongoing recording. HRESULT StopPreview(IMFCaptureEngine* capture_engine); // Set the preview handler recording state to: paused. bool PausePreview(); // Set the preview handler recording state to: running. bool ResumePreview(); // Set the preview handler recording state to: running. void OnPreviewStarted(); // Returns true if preview state is running or paused. bool IsInitialized() const { return preview_state_ == PreviewState::kRunning || preview_state_ == PreviewState::kPaused; } // Returns true if preview state is running. bool IsRunning() const { return preview_state_ == PreviewState::kRunning; } // Return true if preview state is paused. bool IsPaused() const { return preview_state_ == PreviewState::kPaused; } // Returns true if preview state is starting. bool IsStarting() const { return preview_state_ == PreviewState::kStarting; } private: // Initializes record sink for video file capture. HRESULT InitPreviewSink(IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type, CaptureEngineListener* sample_callback); PreviewState preview_state_ = PreviewState::kNotStarted; ComPtr preview_sink_; }; } // namespace camera_windows #endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_PREVIEW_HANDLER_H_ ================================================ FILE: packages/camera/camera_windows/windows/record_handler.cpp ================================================ // Copyright 2013 The Flutter 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 "record_handler.h" #include #include #include #include "string_utils.h" namespace camera_windows { using Microsoft::WRL::ComPtr; // Initializes media type for video capture. HRESULT BuildMediaTypeForVideoCapture(IMFMediaType* src_media_type, IMFMediaType** video_record_media_type, GUID capture_format) { assert(src_media_type); ComPtr new_media_type; HRESULT hr = MFCreateMediaType(&new_media_type); if (FAILED(hr)) { return hr; } // Clones everything from original media type. hr = src_media_type->CopyAllItems(new_media_type.Get()); if (FAILED(hr)) { return hr; } hr = new_media_type->SetGUID(MF_MT_SUBTYPE, capture_format); if (FAILED(hr)) { return hr; } new_media_type.CopyTo(video_record_media_type); return S_OK; } // Queries interface object from collection. template HRESULT GetCollectionObject(IMFCollection* pCollection, DWORD index, Q** ppObj) { ComPtr pUnk; HRESULT hr = pCollection->GetElement(index, pUnk.GetAddressOf()); if (FAILED(hr)) { return hr; } return pUnk->QueryInterface(IID_PPV_ARGS(ppObj)); } // Initializes media type for audo capture. HRESULT BuildMediaTypeForAudioCapture(IMFMediaType** audio_record_media_type) { ComPtr audio_output_attributes; ComPtr src_media_type; ComPtr new_media_type; ComPtr available_output_types; DWORD mt_count = 0; HRESULT hr = MFCreateAttributes(&audio_output_attributes, 1); if (FAILED(hr)) { return hr; } // Enumerates only low latency audio outputs. hr = audio_output_attributes->SetUINT32(MF_LOW_LATENCY, TRUE); if (FAILED(hr)) { return hr; } DWORD mft_flags = (MFT_ENUM_FLAG_ALL & (~MFT_ENUM_FLAG_FIELDOFUSE)) | MFT_ENUM_FLAG_SORTANDFILTER; hr = MFTranscodeGetAudioOutputAvailableTypes( MFAudioFormat_AAC, mft_flags, audio_output_attributes.Get(), available_output_types.GetAddressOf()); if (FAILED(hr)) { return hr; } hr = GetCollectionObject(available_output_types.Get(), 0, src_media_type.GetAddressOf()); if (FAILED(hr)) { return hr; } hr = available_output_types->GetElementCount(&mt_count); if (FAILED(hr)) { return hr; } if (mt_count == 0) { // No sources found, mark process as failure. return E_FAIL; } // Create new media type to copy original media type to. hr = MFCreateMediaType(&new_media_type); if (FAILED(hr)) { return hr; } hr = src_media_type->CopyAllItems(new_media_type.Get()); if (FAILED(hr)) { return hr; } new_media_type.CopyTo(audio_record_media_type); return hr; } HRESULT RecordHandler::InitRecordSink(IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type) { assert(!file_path_.empty()); assert(capture_engine); assert(base_media_type); HRESULT hr = S_OK; if (record_sink_) { // If record sink already exists, only update output filename. hr = record_sink_->SetOutputFileName(Utf16FromUtf8(file_path_).c_str()); if (FAILED(hr)) { record_sink_ = nullptr; } return hr; } ComPtr video_record_media_type; ComPtr capture_sink; // Gets sink from capture engine with record type. hr = capture_engine->GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_RECORD, &capture_sink); if (FAILED(hr)) { return hr; } hr = capture_sink.As(&record_sink_); if (FAILED(hr)) { return hr; } // Removes existing streams if available. hr = record_sink_->RemoveAllStreams(); if (FAILED(hr)) { return hr; } hr = BuildMediaTypeForVideoCapture(base_media_type, video_record_media_type.GetAddressOf(), MFVideoFormat_H264); if (FAILED(hr)) { return hr; } DWORD video_record_sink_stream_index; hr = record_sink_->AddStream( (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_RECORD, video_record_media_type.Get(), nullptr, &video_record_sink_stream_index); if (FAILED(hr)) { return hr; } if (record_audio_) { ComPtr audio_record_media_type; HRESULT audio_capture_hr = S_OK; audio_capture_hr = BuildMediaTypeForAudioCapture(audio_record_media_type.GetAddressOf()); if (SUCCEEDED(audio_capture_hr)) { DWORD audio_record_sink_stream_index; hr = record_sink_->AddStream( (DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_AUDIO, audio_record_media_type.Get(), nullptr, &audio_record_sink_stream_index); } if (FAILED(hr)) { return hr; } } hr = record_sink_->SetOutputFileName(Utf16FromUtf8(file_path_).c_str()); return hr; } HRESULT RecordHandler::StartRecord(const std::string& file_path, int64_t max_duration, IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type) { assert(!file_path.empty()); assert(capture_engine); assert(base_media_type); type_ = max_duration < 0 ? RecordingType::kContinuous : RecordingType::kTimed; max_video_duration_ms_ = max_duration; file_path_ = file_path; recording_start_timestamp_us_ = -1; recording_duration_us_ = 0; HRESULT hr = InitRecordSink(capture_engine, base_media_type); if (FAILED(hr)) { return hr; } recording_state_ = RecordState::kStarting; return capture_engine->StartRecord(); } HRESULT RecordHandler::StopRecord(IMFCaptureEngine* capture_engine) { if (recording_state_ == RecordState::kRunning) { recording_state_ = RecordState::kStopping; return capture_engine->StopRecord(true, false); } return E_FAIL; } void RecordHandler::OnRecordStarted() { if (recording_state_ == RecordState::kStarting) { recording_state_ = RecordState::kRunning; } } void RecordHandler::OnRecordStopped() { if (recording_state_ == RecordState::kStopping) { file_path_ = ""; recording_start_timestamp_us_ = -1; recording_duration_us_ = 0; max_video_duration_ms_ = -1; recording_state_ = RecordState::kNotStarted; type_ = RecordingType::kNone; } } void RecordHandler::UpdateRecordingTime(uint64_t timestamp) { if (recording_start_timestamp_us_ < 0) { recording_start_timestamp_us_ = timestamp; } recording_duration_us_ = (timestamp - recording_start_timestamp_us_); } bool RecordHandler::ShouldStopTimedRecording() const { return type_ == RecordingType::kTimed && recording_state_ == RecordState::kRunning && max_video_duration_ms_ > 0 && recording_duration_us_ >= (static_cast(max_video_duration_ms_) * 1000); } } // namespace camera_windows ================================================ FILE: packages/camera/camera_windows/windows/record_handler.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_RECORD_HANDLER_H_ #define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_RECORD_HANDLER_H_ #include #include #include #include #include namespace camera_windows { using Microsoft::WRL::ComPtr; enum class RecordingType { // Camera is not recording. kNone, // Recording continues until it is stopped with a separate stop command. kContinuous, // Recording stops automatically after requested record time is passed. kTimed }; // States that the record handler can be in. // // When created, the handler starts in |kNotStarted| state and transtions in // sequential order through the states. enum class RecordState { kNotStarted, kStarting, kRunning, kStopping }; // Handler for video recording via the camera. // // Handles record sink initialization and manages the state of video recording. class RecordHandler { public: RecordHandler(bool record_audio) : record_audio_(record_audio) {} virtual ~RecordHandler() = default; // Prevent copying. RecordHandler(RecordHandler const&) = delete; RecordHandler& operator=(RecordHandler const&) = delete; // Initializes record sink and requests capture engine to start recording. // // Sets record state to: starting. // // file_path: A string that hold file path for video capture. // max_duration: A int64 value of maximun recording duration. // If value is -1 video recording is considered as // a continuous recording. // capture_engine: A pointer to capture engine instance. Used to start // the actual recording. // base_media_type: A pointer to base media type used as a base // for the actual video capture media type. HRESULT StartRecord(const std::string& file_path, int64_t max_duration, IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type); // Stops existing recording. // // capture_engine: A pointer to capture engine instance. Used to stop // the ongoing recording. HRESULT StopRecord(IMFCaptureEngine* capture_engine); // Set the record handler recording state to: running. void OnRecordStarted(); // Resets the record handler state and // sets recording state to: not started. void OnRecordStopped(); // Returns true if recording type is continuous recording. bool IsContinuousRecording() const { return type_ == RecordingType::kContinuous; } // Returns true if recording type is timed recording. bool IsTimedRecording() const { return type_ == RecordingType::kTimed; } // Returns true if new recording can be started. bool CanStart() const { return recording_state_ == RecordState::kNotStarted; } // Returns true if recording can be stopped. bool CanStop() const { return recording_state_ == RecordState::kRunning; } // Returns the filesystem path of the video recording. std::string GetRecordPath() const { return file_path_; } // Returns the duration of the video recording in microseconds. uint64_t GetRecordedDuration() const { return recording_duration_us_; } // Calculates new recording time from capture timestamp. void UpdateRecordingTime(uint64_t timestamp); // Returns true if recording time has exceeded the maximum duration for timed // recordings. bool ShouldStopTimedRecording() const; private: // Initializes record sink for video file capture. HRESULT InitRecordSink(IMFCaptureEngine* capture_engine, IMFMediaType* base_media_type); bool record_audio_ = false; int64_t max_video_duration_ms_ = -1; int64_t recording_start_timestamp_us_ = -1; uint64_t recording_duration_us_ = 0; std::string file_path_; RecordState recording_state_ = RecordState::kNotStarted; RecordingType type_ = RecordingType::kNone; ComPtr record_sink_; }; } // namespace camera_windows #endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_RECORD_HANDLER_H_ ================================================ FILE: packages/camera/camera_windows/windows/string_utils.cpp ================================================ // Copyright 2013 The Flutter 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 "string_utils.h" #include #include #include namespace camera_windows { // Converts the given UTF-16 string to UTF-8. std::string Utf8FromUtf16(const std::wstring& utf16_string) { if (utf16_string.empty()) { return std::string(); } int target_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string.data(), static_cast(utf16_string.length()), nullptr, 0, nullptr, nullptr); std::string utf8_string; if (target_length == 0 || target_length > utf8_string.max_size()) { return utf8_string; } utf8_string.resize(target_length); int converted_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string.data(), static_cast(utf16_string.length()), utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); } return utf8_string; } // Converts the given UTF-8 string to UTF-16. std::wstring Utf16FromUtf8(const std::string& utf8_string) { if (utf8_string.empty()) { return std::wstring(); } int target_length = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), static_cast(utf8_string.length()), nullptr, 0); std::wstring utf16_string; if (target_length == 0 || target_length > utf16_string.max_size()) { return utf16_string; } utf16_string.resize(target_length); int converted_length = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), static_cast(utf8_string.length()), utf16_string.data(), target_length); if (converted_length == 0) { return std::wstring(); } return utf16_string; } } // namespace camera_windows ================================================ FILE: packages/camera/camera_windows/windows/string_utils.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_STRING_UTILS_H_ #define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_STRING_UTILS_H_ #include #include namespace camera_windows { // Converts the given UTF-16 string to UTF-8. std::string Utf8FromUtf16(const std::wstring& utf16_string); // Converts the given UTF-8 string to UTF-16. std::wstring Utf16FromUtf8(const std::string& utf8_string); } // namespace camera_windows #endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_STRING_UTILS_H_ ================================================ FILE: packages/camera/camera_windows/windows/test/camera_plugin_test.cpp ================================================ // Copyright 2013 The Flutter 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 "camera_plugin.h" #include #include #include #include #include #include #include #include #include #include #include "mocks.h" namespace camera_windows { namespace test { using flutter::EncodableMap; using flutter::EncodableValue; using ::testing::_; using ::testing::DoAll; using ::testing::EndsWith; using ::testing::Eq; using ::testing::Pointee; using ::testing::Return; void MockInitCamera(MockCamera* camera, bool success) { EXPECT_CALL(*camera, HasPendingResultByType(Eq(PendingResultType::kCreateCamera))) .Times(1) .WillOnce(Return(false)); EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::kCreateCamera), _)) .Times(1) .WillOnce([camera](PendingResultType type, std::unique_ptr> result) { camera->pending_result_ = std::move(result); return true; }); EXPECT_CALL(*camera, HasDeviceId(Eq(camera->device_id_))) .WillRepeatedly(Return(true)); EXPECT_CALL(*camera, InitCamera) .Times(1) .WillOnce([camera, success](flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, bool record_audio, ResolutionPreset resolution_preset) { assert(camera->pending_result_); if (success) { camera->pending_result_->Success(EncodableValue(1)); return true; } else { camera->pending_result_->Error("camera_error", "InitCamera failed."); return false; } }); } TEST(CameraPlugin, AvailableCamerasHandlerSuccessIfNoCameras) { std::unique_ptr texture_registrar_ = std::make_unique(); std::unique_ptr messenger_ = std::make_unique(); std::unique_ptr camera_factory_ = std::make_unique(); std::unique_ptr result = std::make_unique(); MockCameraPlugin plugin(texture_registrar_.get(), messenger_.get(), std::move(camera_factory_)); EXPECT_CALL(plugin, EnumerateVideoCaptureDeviceSources) .Times(1) .WillOnce([](IMFActivate*** devices, UINT32* count) { *count = 0U; *devices = static_cast( CoTaskMemAlloc(sizeof(IMFActivate*) * (*count))); return true; }); EXPECT_CALL(*result, ErrorInternal).Times(0); EXPECT_CALL(*result, SuccessInternal).Times(1); plugin.HandleMethodCall( flutter::MethodCall("availableCameras", std::make_unique()), std::move(result)); } TEST(CameraPlugin, AvailableCamerasHandlerErrorIfFailsToEnumerateDevices) { std::unique_ptr texture_registrar_ = std::make_unique(); std::unique_ptr messenger_ = std::make_unique(); std::unique_ptr camera_factory_ = std::make_unique(); std::unique_ptr result = std::make_unique(); MockCameraPlugin plugin(texture_registrar_.get(), messenger_.get(), std::move(camera_factory_)); EXPECT_CALL(plugin, EnumerateVideoCaptureDeviceSources) .Times(1) .WillOnce([](IMFActivate*** devices, UINT32* count) { return false; }); EXPECT_CALL(*result, ErrorInternal).Times(1); EXPECT_CALL(*result, SuccessInternal).Times(0); plugin.HandleMethodCall( flutter::MethodCall("availableCameras", std::make_unique()), std::move(result)); } TEST(CameraPlugin, CreateHandlerCallsInitCamera) { std::unique_ptr result = std::make_unique(); std::unique_ptr texture_registrar_ = std::make_unique(); std::unique_ptr messenger_ = std::make_unique(); std::unique_ptr camera_factory_ = std::make_unique(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); MockInitCamera(camera.get(), true); // Move mocked camera to the factory to be passed // for plugin with CreateCamera function. camera_factory_->pending_camera_ = std::move(camera); EXPECT_CALL(*camera_factory_, CreateCamera(MOCK_DEVICE_ID)); EXPECT_CALL(*result, ErrorInternal).Times(0); EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(1)))); CameraPlugin plugin(texture_registrar_.get(), messenger_.get(), std::move(camera_factory_)); EncodableMap args = { {EncodableValue("cameraName"), EncodableValue(MOCK_CAMERA_NAME)}, {EncodableValue("resolutionPreset"), EncodableValue(nullptr)}, {EncodableValue("enableAudio"), EncodableValue(true)}, }; plugin.HandleMethodCall( flutter::MethodCall("create", std::make_unique(EncodableMap(args))), std::move(result)); } TEST(CameraPlugin, CreateHandlerErrorOnInvalidDeviceId) { std::unique_ptr result = std::make_unique(); std::unique_ptr texture_registrar_ = std::make_unique(); std::unique_ptr messenger_ = std::make_unique(); std::unique_ptr camera_factory_ = std::make_unique(); CameraPlugin plugin(texture_registrar_.get(), messenger_.get(), std::move(camera_factory_)); EncodableMap args = { {EncodableValue("cameraName"), EncodableValue(MOCK_INVALID_CAMERA_NAME)}, {EncodableValue("resolutionPreset"), EncodableValue(nullptr)}, {EncodableValue("enableAudio"), EncodableValue(true)}, }; EXPECT_CALL(*result, ErrorInternal).Times(1); plugin.HandleMethodCall( flutter::MethodCall("create", std::make_unique(EncodableMap(args))), std::move(result)); } TEST(CameraPlugin, CreateHandlerErrorOnExistingDeviceId) { std::unique_ptr first_create_result = std::make_unique(); std::unique_ptr second_create_result = std::make_unique(); std::unique_ptr texture_registrar_ = std::make_unique(); std::unique_ptr messenger_ = std::make_unique(); std::unique_ptr camera_factory_ = std::make_unique(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); MockInitCamera(camera.get(), true); // Move mocked camera to the factory to be passed // for plugin with CreateCamera function. camera_factory_->pending_camera_ = std::move(camera); EXPECT_CALL(*camera_factory_, CreateCamera(MOCK_DEVICE_ID)); EXPECT_CALL(*first_create_result, ErrorInternal).Times(0); EXPECT_CALL(*first_create_result, SuccessInternal(Pointee(EncodableValue(1)))); CameraPlugin plugin(texture_registrar_.get(), messenger_.get(), std::move(camera_factory_)); EncodableMap args = { {EncodableValue("cameraName"), EncodableValue(MOCK_CAMERA_NAME)}, {EncodableValue("resolutionPreset"), EncodableValue(nullptr)}, {EncodableValue("enableAudio"), EncodableValue(true)}, }; plugin.HandleMethodCall( flutter::MethodCall("create", std::make_unique(EncodableMap(args))), std::move(first_create_result)); EXPECT_CALL(*second_create_result, ErrorInternal).Times(1); EXPECT_CALL(*second_create_result, SuccessInternal).Times(0); plugin.HandleMethodCall( flutter::MethodCall("create", std::make_unique(EncodableMap(args))), std::move(second_create_result)); } TEST(CameraPlugin, CreateHandlerAllowsRetry) { std::unique_ptr first_create_result = std::make_unique(); std::unique_ptr second_create_result = std::make_unique(); std::unique_ptr texture_registrar_ = std::make_unique(); std::unique_ptr messenger_ = std::make_unique(); std::unique_ptr camera_factory_ = std::make_unique(); // The camera will fail initialization once and then succeed. EXPECT_CALL(*camera_factory_, CreateCamera(MOCK_DEVICE_ID)) .Times(2) .WillOnce([](const std::string& device_id) { std::unique_ptr first_camera = std::make_unique(MOCK_DEVICE_ID); MockInitCamera(first_camera.get(), false); return first_camera; }) .WillOnce([](const std::string& device_id) { std::unique_ptr second_camera = std::make_unique(MOCK_DEVICE_ID); MockInitCamera(second_camera.get(), true); return second_camera; }); EXPECT_CALL(*first_create_result, ErrorInternal).Times(1); EXPECT_CALL(*first_create_result, SuccessInternal).Times(0); CameraPlugin plugin(texture_registrar_.get(), messenger_.get(), std::move(camera_factory_)); EncodableMap args = { {EncodableValue("cameraName"), EncodableValue(MOCK_CAMERA_NAME)}, {EncodableValue("resolutionPreset"), EncodableValue(nullptr)}, {EncodableValue("enableAudio"), EncodableValue(true)}, }; plugin.HandleMethodCall( flutter::MethodCall("create", std::make_unique(EncodableMap(args))), std::move(first_create_result)); EXPECT_CALL(*second_create_result, ErrorInternal).Times(0); EXPECT_CALL(*second_create_result, SuccessInternal(Pointee(EncodableValue(1)))); plugin.HandleMethodCall( flutter::MethodCall("create", std::make_unique(EncodableMap(args))), std::move(second_create_result)); } TEST(CameraPlugin, InitializeHandlerCallStartPreview) { int64_t mock_camera_id = 1234; std::unique_ptr initialize_result = std::make_unique(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(); EXPECT_CALL(*camera, HasCameraId(Eq(mock_camera_id))) .Times(1) .WillOnce([cam = camera.get()](int64_t camera_id) { return cam->camera_id_ == camera_id; }); EXPECT_CALL(*camera, HasPendingResultByType(Eq(PendingResultType::kInitialize))) .Times(1) .WillOnce(Return(false)); EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::kInitialize), _)) .Times(1) .WillOnce([cam = camera.get()](PendingResultType type, std::unique_ptr> result) { cam->pending_result_ = std::move(result); return true; }); EXPECT_CALL(*camera, GetCaptureController) .Times(1) .WillOnce([cam = camera.get()]() { assert(cam->pending_result_); return cam->capture_controller_.get(); }); EXPECT_CALL(*capture_controller, StartPreview()) .Times(1) .WillOnce([cam = camera.get()]() { assert(cam->pending_result_); return cam->pending_result_->Success(); }); camera->camera_id_ = mock_camera_id; camera->capture_controller_ = std::move(capture_controller); MockCameraPlugin plugin(std::make_unique().get(), std::make_unique().get(), std::make_unique()); // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(0); EXPECT_CALL(*initialize_result, SuccessInternal).Times(1); EncodableMap args = { {EncodableValue("cameraId"), EncodableValue(mock_camera_id)}, }; plugin.HandleMethodCall( flutter::MethodCall("initialize", std::make_unique(EncodableMap(args))), std::move(initialize_result)); } TEST(CameraPlugin, InitializeHandlerErrorOnInvalidCameraId) { int64_t mock_camera_id = 1234; int64_t missing_camera_id = 5678; std::unique_ptr initialize_result = std::make_unique(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(); EXPECT_CALL(*camera, HasCameraId) .Times(1) .WillOnce([cam = camera.get()](int64_t camera_id) { return cam->camera_id_ == camera_id; }); EXPECT_CALL(*camera, HasPendingResultByType).Times(0); EXPECT_CALL(*camera, AddPendingResult).Times(0); EXPECT_CALL(*camera, GetCaptureController).Times(0); EXPECT_CALL(*capture_controller, StartPreview).Times(0); camera->camera_id_ = mock_camera_id; MockCameraPlugin plugin(std::make_unique().get(), std::make_unique().get(), std::make_unique()); // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(1); EXPECT_CALL(*initialize_result, SuccessInternal).Times(0); EncodableMap args = { {EncodableValue("cameraId"), EncodableValue(missing_camera_id)}, }; plugin.HandleMethodCall( flutter::MethodCall("initialize", std::make_unique(EncodableMap(args))), std::move(initialize_result)); } TEST(CameraPlugin, TakePictureHandlerCallsTakePictureWithPath) { int64_t mock_camera_id = 1234; std::unique_ptr initialize_result = std::make_unique(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(); EXPECT_CALL(*camera, HasCameraId(Eq(mock_camera_id))) .Times(1) .WillOnce([cam = camera.get()](int64_t camera_id) { return cam->camera_id_ == camera_id; }); EXPECT_CALL(*camera, HasPendingResultByType(Eq(PendingResultType::kTakePicture))) .Times(1) .WillOnce(Return(false)); EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::kTakePicture), _)) .Times(1) .WillOnce([cam = camera.get()](PendingResultType type, std::unique_ptr> result) { cam->pending_result_ = std::move(result); return true; }); EXPECT_CALL(*camera, GetCaptureController) .Times(1) .WillOnce([cam = camera.get()]() { assert(cam->pending_result_); return cam->capture_controller_.get(); }); EXPECT_CALL(*capture_controller, TakePicture(EndsWith(".jpeg"))) .Times(1) .WillOnce([cam = camera.get()](const std::string& file_path) { assert(cam->pending_result_); return cam->pending_result_->Success(); }); camera->camera_id_ = mock_camera_id; camera->capture_controller_ = std::move(capture_controller); MockCameraPlugin plugin(std::make_unique().get(), std::make_unique().get(), std::make_unique()); // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(0); EXPECT_CALL(*initialize_result, SuccessInternal).Times(1); EncodableMap args = { {EncodableValue("cameraId"), EncodableValue(mock_camera_id)}, }; plugin.HandleMethodCall( flutter::MethodCall("takePicture", std::make_unique(EncodableMap(args))), std::move(initialize_result)); } TEST(CameraPlugin, TakePictureHandlerErrorOnInvalidCameraId) { int64_t mock_camera_id = 1234; int64_t missing_camera_id = 5678; std::unique_ptr initialize_result = std::make_unique(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(); EXPECT_CALL(*camera, HasCameraId) .Times(1) .WillOnce([cam = camera.get()](int64_t camera_id) { return cam->camera_id_ == camera_id; }); EXPECT_CALL(*camera, HasPendingResultByType).Times(0); EXPECT_CALL(*camera, AddPendingResult).Times(0); EXPECT_CALL(*camera, GetCaptureController).Times(0); EXPECT_CALL(*capture_controller, TakePicture).Times(0); camera->camera_id_ = mock_camera_id; MockCameraPlugin plugin(std::make_unique().get(), std::make_unique().get(), std::make_unique()); // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(1); EXPECT_CALL(*initialize_result, SuccessInternal).Times(0); EncodableMap args = { {EncodableValue("cameraId"), EncodableValue(missing_camera_id)}, }; plugin.HandleMethodCall( flutter::MethodCall("takePicture", std::make_unique(EncodableMap(args))), std::move(initialize_result)); } TEST(CameraPlugin, StartVideoRecordingHandlerCallsStartRecordWithPath) { int64_t mock_camera_id = 1234; std::unique_ptr initialize_result = std::make_unique(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(); EXPECT_CALL(*camera, HasCameraId(Eq(mock_camera_id))) .Times(1) .WillOnce([cam = camera.get()](int64_t camera_id) { return cam->camera_id_ == camera_id; }); EXPECT_CALL(*camera, HasPendingResultByType(Eq(PendingResultType::kStartRecord))) .Times(1) .WillOnce(Return(false)); EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::kStartRecord), _)) .Times(1) .WillOnce([cam = camera.get()](PendingResultType type, std::unique_ptr> result) { cam->pending_result_ = std::move(result); return true; }); EXPECT_CALL(*camera, GetCaptureController) .Times(1) .WillOnce([cam = camera.get()]() { assert(cam->pending_result_); return cam->capture_controller_.get(); }); EXPECT_CALL(*capture_controller, StartRecord(EndsWith(".mp4"), -1)) .Times(1) .WillOnce([cam = camera.get()](const std::string& file_path, int64_t max_video_duration_ms) { assert(cam->pending_result_); return cam->pending_result_->Success(); }); camera->camera_id_ = mock_camera_id; camera->capture_controller_ = std::move(capture_controller); MockCameraPlugin plugin(std::make_unique().get(), std::make_unique().get(), std::make_unique()); // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(0); EXPECT_CALL(*initialize_result, SuccessInternal).Times(1); EncodableMap args = { {EncodableValue("cameraId"), EncodableValue(mock_camera_id)}, }; plugin.HandleMethodCall( flutter::MethodCall("startVideoRecording", std::make_unique(EncodableMap(args))), std::move(initialize_result)); } TEST(CameraPlugin, StartVideoRecordingHandlerCallsStartRecordWithPathAndCaptureDuration) { int64_t mock_camera_id = 1234; int32_t mock_video_duration = 100000; std::unique_ptr initialize_result = std::make_unique(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(); EXPECT_CALL(*camera, HasCameraId(Eq(mock_camera_id))) .Times(1) .WillOnce([cam = camera.get()](int64_t camera_id) { return cam->camera_id_ == camera_id; }); EXPECT_CALL(*camera, HasPendingResultByType(Eq(PendingResultType::kStartRecord))) .Times(1) .WillOnce(Return(false)); EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::kStartRecord), _)) .Times(1) .WillOnce([cam = camera.get()](PendingResultType type, std::unique_ptr> result) { cam->pending_result_ = std::move(result); return true; }); EXPECT_CALL(*camera, GetCaptureController) .Times(1) .WillOnce([cam = camera.get()]() { assert(cam->pending_result_); return cam->capture_controller_.get(); }); EXPECT_CALL(*capture_controller, StartRecord(EndsWith(".mp4"), Eq(mock_video_duration))) .Times(1) .WillOnce([cam = camera.get()](const std::string& file_path, int64_t max_video_duration_ms) { assert(cam->pending_result_); return cam->pending_result_->Success(); }); camera->camera_id_ = mock_camera_id; camera->capture_controller_ = std::move(capture_controller); MockCameraPlugin plugin(std::make_unique().get(), std::make_unique().get(), std::make_unique()); // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(0); EXPECT_CALL(*initialize_result, SuccessInternal).Times(1); EncodableMap args = { {EncodableValue("cameraId"), EncodableValue(mock_camera_id)}, {EncodableValue("maxVideoDuration"), EncodableValue(mock_video_duration)}, }; plugin.HandleMethodCall( flutter::MethodCall("startVideoRecording", std::make_unique(EncodableMap(args))), std::move(initialize_result)); } TEST(CameraPlugin, StartVideoRecordingHandlerErrorOnInvalidCameraId) { int64_t mock_camera_id = 1234; int64_t missing_camera_id = 5678; std::unique_ptr initialize_result = std::make_unique(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(); EXPECT_CALL(*camera, HasCameraId) .Times(1) .WillOnce([cam = camera.get()](int64_t camera_id) { return cam->camera_id_ == camera_id; }); EXPECT_CALL(*camera, HasPendingResultByType).Times(0); EXPECT_CALL(*camera, AddPendingResult).Times(0); EXPECT_CALL(*camera, GetCaptureController).Times(0); EXPECT_CALL(*capture_controller, StartRecord(_, -1)).Times(0); camera->camera_id_ = mock_camera_id; MockCameraPlugin plugin(std::make_unique().get(), std::make_unique().get(), std::make_unique()); // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(1); EXPECT_CALL(*initialize_result, SuccessInternal).Times(0); EncodableMap args = { {EncodableValue("cameraId"), EncodableValue(missing_camera_id)}, }; plugin.HandleMethodCall( flutter::MethodCall("startVideoRecording", std::make_unique(EncodableMap(args))), std::move(initialize_result)); } TEST(CameraPlugin, StopVideoRecordingHandlerCallsStopRecord) { int64_t mock_camera_id = 1234; std::unique_ptr initialize_result = std::make_unique(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(); EXPECT_CALL(*camera, HasCameraId(Eq(mock_camera_id))) .Times(1) .WillOnce([cam = camera.get()](int64_t camera_id) { return cam->camera_id_ == camera_id; }); EXPECT_CALL(*camera, HasPendingResultByType(Eq(PendingResultType::kStopRecord))) .Times(1) .WillOnce(Return(false)); EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::kStopRecord), _)) .Times(1) .WillOnce([cam = camera.get()](PendingResultType type, std::unique_ptr> result) { cam->pending_result_ = std::move(result); return true; }); EXPECT_CALL(*camera, GetCaptureController) .Times(1) .WillOnce([cam = camera.get()]() { assert(cam->pending_result_); return cam->capture_controller_.get(); }); EXPECT_CALL(*capture_controller, StopRecord) .Times(1) .WillOnce([cam = camera.get()]() { assert(cam->pending_result_); return cam->pending_result_->Success(); }); camera->camera_id_ = mock_camera_id; camera->capture_controller_ = std::move(capture_controller); MockCameraPlugin plugin(std::make_unique().get(), std::make_unique().get(), std::make_unique()); // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(0); EXPECT_CALL(*initialize_result, SuccessInternal).Times(1); EncodableMap args = { {EncodableValue("cameraId"), EncodableValue(mock_camera_id)}, }; plugin.HandleMethodCall( flutter::MethodCall("stopVideoRecording", std::make_unique(EncodableMap(args))), std::move(initialize_result)); } TEST(CameraPlugin, StopVideoRecordingHandlerErrorOnInvalidCameraId) { int64_t mock_camera_id = 1234; int64_t missing_camera_id = 5678; std::unique_ptr initialize_result = std::make_unique(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(); EXPECT_CALL(*camera, HasCameraId) .Times(1) .WillOnce([cam = camera.get()](int64_t camera_id) { return cam->camera_id_ == camera_id; }); EXPECT_CALL(*camera, HasPendingResultByType).Times(0); EXPECT_CALL(*camera, AddPendingResult).Times(0); EXPECT_CALL(*camera, GetCaptureController).Times(0); EXPECT_CALL(*capture_controller, StopRecord).Times(0); camera->camera_id_ = mock_camera_id; MockCameraPlugin plugin(std::make_unique().get(), std::make_unique().get(), std::make_unique()); // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(1); EXPECT_CALL(*initialize_result, SuccessInternal).Times(0); EncodableMap args = { {EncodableValue("cameraId"), EncodableValue(missing_camera_id)}, }; plugin.HandleMethodCall( flutter::MethodCall("stopVideoRecording", std::make_unique(EncodableMap(args))), std::move(initialize_result)); } TEST(CameraPlugin, ResumePreviewHandlerCallsResumePreview) { int64_t mock_camera_id = 1234; std::unique_ptr initialize_result = std::make_unique(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(); EXPECT_CALL(*camera, HasCameraId(Eq(mock_camera_id))) .Times(1) .WillOnce([cam = camera.get()](int64_t camera_id) { return cam->camera_id_ == camera_id; }); EXPECT_CALL(*camera, HasPendingResultByType(Eq(PendingResultType::kResumePreview))) .Times(1) .WillOnce(Return(false)); EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::kResumePreview), _)) .Times(1) .WillOnce([cam = camera.get()](PendingResultType type, std::unique_ptr> result) { cam->pending_result_ = std::move(result); return true; }); EXPECT_CALL(*camera, GetCaptureController) .Times(1) .WillOnce([cam = camera.get()]() { assert(cam->pending_result_); return cam->capture_controller_.get(); }); EXPECT_CALL(*capture_controller, ResumePreview) .Times(1) .WillOnce([cam = camera.get()]() { assert(cam->pending_result_); return cam->pending_result_->Success(); }); camera->camera_id_ = mock_camera_id; camera->capture_controller_ = std::move(capture_controller); MockCameraPlugin plugin(std::make_unique().get(), std::make_unique().get(), std::make_unique()); // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(0); EXPECT_CALL(*initialize_result, SuccessInternal).Times(1); EncodableMap args = { {EncodableValue("cameraId"), EncodableValue(mock_camera_id)}, }; plugin.HandleMethodCall( flutter::MethodCall("resumePreview", std::make_unique(EncodableMap(args))), std::move(initialize_result)); } TEST(CameraPlugin, ResumePreviewHandlerErrorOnInvalidCameraId) { int64_t mock_camera_id = 1234; int64_t missing_camera_id = 5678; std::unique_ptr initialize_result = std::make_unique(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(); EXPECT_CALL(*camera, HasCameraId) .Times(1) .WillOnce([cam = camera.get()](int64_t camera_id) { return cam->camera_id_ == camera_id; }); EXPECT_CALL(*camera, HasPendingResultByType).Times(0); EXPECT_CALL(*camera, AddPendingResult).Times(0); EXPECT_CALL(*camera, GetCaptureController).Times(0); EXPECT_CALL(*capture_controller, ResumePreview).Times(0); camera->camera_id_ = mock_camera_id; MockCameraPlugin plugin(std::make_unique().get(), std::make_unique().get(), std::make_unique()); // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(1); EXPECT_CALL(*initialize_result, SuccessInternal).Times(0); EncodableMap args = { {EncodableValue("cameraId"), EncodableValue(missing_camera_id)}, }; plugin.HandleMethodCall( flutter::MethodCall("resumePreview", std::make_unique(EncodableMap(args))), std::move(initialize_result)); } TEST(CameraPlugin, PausePreviewHandlerCallsPausePreview) { int64_t mock_camera_id = 1234; std::unique_ptr initialize_result = std::make_unique(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(); EXPECT_CALL(*camera, HasCameraId(Eq(mock_camera_id))) .Times(1) .WillOnce([cam = camera.get()](int64_t camera_id) { return cam->camera_id_ == camera_id; }); EXPECT_CALL(*camera, HasPendingResultByType(Eq(PendingResultType::kPausePreview))) .Times(1) .WillOnce(Return(false)); EXPECT_CALL(*camera, AddPendingResult(Eq(PendingResultType::kPausePreview), _)) .Times(1) .WillOnce([cam = camera.get()](PendingResultType type, std::unique_ptr> result) { cam->pending_result_ = std::move(result); return true; }); EXPECT_CALL(*camera, GetCaptureController) .Times(1) .WillOnce([cam = camera.get()]() { assert(cam->pending_result_); return cam->capture_controller_.get(); }); EXPECT_CALL(*capture_controller, PausePreview) .Times(1) .WillOnce([cam = camera.get()]() { assert(cam->pending_result_); return cam->pending_result_->Success(); }); camera->camera_id_ = mock_camera_id; camera->capture_controller_ = std::move(capture_controller); MockCameraPlugin plugin(std::make_unique().get(), std::make_unique().get(), std::make_unique()); // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(0); EXPECT_CALL(*initialize_result, SuccessInternal).Times(1); EncodableMap args = { {EncodableValue("cameraId"), EncodableValue(mock_camera_id)}, }; plugin.HandleMethodCall( flutter::MethodCall("pausePreview", std::make_unique(EncodableMap(args))), std::move(initialize_result)); } TEST(CameraPlugin, PausePreviewHandlerErrorOnInvalidCameraId) { int64_t mock_camera_id = 1234; int64_t missing_camera_id = 5678; std::unique_ptr initialize_result = std::make_unique(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(); EXPECT_CALL(*camera, HasCameraId) .Times(1) .WillOnce([cam = camera.get()](int64_t camera_id) { return cam->camera_id_ == camera_id; }); EXPECT_CALL(*camera, HasPendingResultByType).Times(0); EXPECT_CALL(*camera, AddPendingResult).Times(0); EXPECT_CALL(*camera, GetCaptureController).Times(0); EXPECT_CALL(*capture_controller, PausePreview).Times(0); camera->camera_id_ = mock_camera_id; MockCameraPlugin plugin(std::make_unique().get(), std::make_unique().get(), std::make_unique()); // Add mocked camera to plugins camera list. plugin.AddCamera(std::move(camera)); EXPECT_CALL(*initialize_result, ErrorInternal).Times(1); EXPECT_CALL(*initialize_result, SuccessInternal).Times(0); EncodableMap args = { {EncodableValue("cameraId"), EncodableValue(missing_camera_id)}, }; plugin.HandleMethodCall( flutter::MethodCall("pausePreview", std::make_unique(EncodableMap(args))), std::move(initialize_result)); } } // namespace test } // namespace camera_windows ================================================ FILE: packages/camera/camera_windows/windows/test/camera_test.cpp ================================================ // Copyright 2013 The Flutter 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 "camera.h" #include #include #include #include #include #include #include #include #include #include #include "mocks.h" namespace camera_windows { using ::testing::_; using ::testing::Eq; using ::testing::NiceMock; using ::testing::Pointee; using ::testing::Return; namespace test { TEST(Camera, InitCameraCreatesCaptureController) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller_factory = std::make_unique(); EXPECT_CALL(*capture_controller_factory, CreateCaptureController) .Times(1) .WillOnce([]() { std::unique_ptr> capture_controller = std::make_unique>(); EXPECT_CALL(*capture_controller, InitCaptureDevice) .Times(1) .WillOnce(Return(true)); return capture_controller; }); EXPECT_TRUE(camera->GetCaptureController() == nullptr); // Init camera with mock capture controller factory bool result = camera->InitCamera(std::move(capture_controller_factory), std::make_unique().get(), std::make_unique().get(), false, ResolutionPreset::kAuto); EXPECT_TRUE(result); EXPECT_TRUE(camera->GetCaptureController() != nullptr); } TEST(Camera, InitCameraReportsFailure) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller_factory = std::make_unique(); EXPECT_CALL(*capture_controller_factory, CreateCaptureController) .Times(1) .WillOnce([]() { std::unique_ptr> capture_controller = std::make_unique>(); EXPECT_CALL(*capture_controller, InitCaptureDevice) .Times(1) .WillOnce(Return(false)); return capture_controller; }); EXPECT_TRUE(camera->GetCaptureController() == nullptr); // Init camera with mock capture controller factory bool result = camera->InitCamera(std::move(capture_controller_factory), std::make_unique().get(), std::make_unique().get(), false, ResolutionPreset::kAuto); EXPECT_FALSE(result); EXPECT_TRUE(camera->GetCaptureController() != nullptr); } TEST(Camera, AddPendingResultReturnsErrorForDuplicates) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr first_pending_result = std::make_unique(); std::unique_ptr second_pending_result = std::make_unique(); EXPECT_CALL(*first_pending_result, ErrorInternal).Times(0); EXPECT_CALL(*first_pending_result, SuccessInternal); EXPECT_CALL(*second_pending_result, ErrorInternal).Times(1); camera->AddPendingResult(PendingResultType::kCreateCamera, std::move(first_pending_result)); // This should fail camera->AddPendingResult(PendingResultType::kCreateCamera, std::move(second_pending_result)); // Mark pending result as succeeded camera->OnCreateCaptureEngineSucceeded(0); } TEST(Camera, OnCreateCaptureEngineSucceededReturnsCameraId) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); const int64_t texture_id = 12345; EXPECT_CALL(*result, ErrorInternal).Times(0); EXPECT_CALL( *result, SuccessInternal(Pointee(EncodableValue(EncodableMap( {{EncodableValue("cameraId"), EncodableValue(texture_id)}}))))); camera->AddPendingResult(PendingResultType::kCreateCamera, std::move(result)); camera->OnCreateCaptureEngineSucceeded(texture_id); } TEST(Camera, CreateCaptureEngineReportsError) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); const std::string error_text = "error_text"; EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(Eq("camera_error"), Eq(error_text), _)); camera->AddPendingResult(PendingResultType::kCreateCamera, std::move(result)); camera->OnCreateCaptureEngineFailed(CameraResult::kError, error_text); } TEST(Camera, CreateCaptureEngineReportsAccessDenied) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); const std::string error_text = "error_text"; EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(Eq("CameraAccessDenied"), Eq(error_text), _)); camera->AddPendingResult(PendingResultType::kCreateCamera, std::move(result)); camera->OnCreateCaptureEngineFailed(CameraResult::kAccessDenied, error_text); } TEST(Camera, OnStartPreviewSucceededReturnsFrameSize) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); const int32_t width = 123; const int32_t height = 456; EXPECT_CALL(*result, ErrorInternal).Times(0); EXPECT_CALL( *result, SuccessInternal(Pointee(EncodableValue(EncodableMap({ {EncodableValue("previewWidth"), EncodableValue((float)width)}, {EncodableValue("previewHeight"), EncodableValue((float)height)}, }))))); camera->AddPendingResult(PendingResultType::kInitialize, std::move(result)); camera->OnStartPreviewSucceeded(width, height); } TEST(Camera, StartPreviewReportsError) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); const std::string error_text = "error_text"; EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(Eq("camera_error"), Eq(error_text), _)); camera->AddPendingResult(PendingResultType::kInitialize, std::move(result)); camera->OnStartPreviewFailed(CameraResult::kError, error_text); } TEST(Camera, StartPreviewReportsAccessDenied) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); const std::string error_text = "error_text"; EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(Eq("CameraAccessDenied"), Eq(error_text), _)); camera->AddPendingResult(PendingResultType::kInitialize, std::move(result)); camera->OnStartPreviewFailed(CameraResult::kAccessDenied, error_text); } TEST(Camera, OnPausePreviewSucceededReturnsSuccess) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); EXPECT_CALL(*result, ErrorInternal).Times(0); EXPECT_CALL(*result, SuccessInternal(nullptr)); camera->AddPendingResult(PendingResultType::kPausePreview, std::move(result)); camera->OnPausePreviewSucceeded(); } TEST(Camera, PausePreviewReportsError) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); const std::string error_text = "error_text"; EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(Eq("camera_error"), Eq(error_text), _)); camera->AddPendingResult(PendingResultType::kPausePreview, std::move(result)); camera->OnPausePreviewFailed(CameraResult::kError, error_text); } TEST(Camera, PausePreviewReportsAccessDenied) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); const std::string error_text = "error_text"; EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(Eq("CameraAccessDenied"), Eq(error_text), _)); camera->AddPendingResult(PendingResultType::kPausePreview, std::move(result)); camera->OnPausePreviewFailed(CameraResult::kAccessDenied, error_text); } TEST(Camera, OnResumePreviewSucceededReturnsSuccess) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); EXPECT_CALL(*result, ErrorInternal).Times(0); EXPECT_CALL(*result, SuccessInternal(nullptr)); camera->AddPendingResult(PendingResultType::kResumePreview, std::move(result)); camera->OnResumePreviewSucceeded(); } TEST(Camera, ResumePreviewReportsError) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); const std::string error_text = "error_text"; EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(Eq("camera_error"), Eq(error_text), _)); camera->AddPendingResult(PendingResultType::kResumePreview, std::move(result)); camera->OnResumePreviewFailed(CameraResult::kError, error_text); } TEST(Camera, OnResumePreviewPermissionFailureReturnsError) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); const std::string error_text = "error_text"; EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(Eq("CameraAccessDenied"), Eq(error_text), _)); camera->AddPendingResult(PendingResultType::kResumePreview, std::move(result)); camera->OnResumePreviewFailed(CameraResult::kAccessDenied, error_text); } TEST(Camera, OnStartRecordSucceededReturnsSuccess) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); EXPECT_CALL(*result, ErrorInternal).Times(0); EXPECT_CALL(*result, SuccessInternal(nullptr)); camera->AddPendingResult(PendingResultType::kStartRecord, std::move(result)); camera->OnStartRecordSucceeded(); } TEST(Camera, StartRecordReportsError) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); const std::string error_text = "error_text"; EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(Eq("camera_error"), Eq(error_text), _)); camera->AddPendingResult(PendingResultType::kStartRecord, std::move(result)); camera->OnStartRecordFailed(CameraResult::kError, error_text); } TEST(Camera, StartRecordReportsAccessDenied) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); const std::string error_text = "error_text"; EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(Eq("CameraAccessDenied"), Eq(error_text), _)); camera->AddPendingResult(PendingResultType::kStartRecord, std::move(result)); camera->OnStartRecordFailed(CameraResult::kAccessDenied, error_text); } TEST(Camera, OnStopRecordSucceededReturnsSuccess) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); const std::string file_path = "C:\temp\filename.mp4"; EXPECT_CALL(*result, ErrorInternal).Times(0); EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(file_path)))); camera->AddPendingResult(PendingResultType::kStopRecord, std::move(result)); camera->OnStopRecordSucceeded(file_path); } TEST(Camera, StopRecordReportsError) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); const std::string error_text = "error_text"; EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(Eq("camera_error"), Eq(error_text), _)); camera->AddPendingResult(PendingResultType::kStopRecord, std::move(result)); camera->OnStopRecordFailed(CameraResult::kError, error_text); } TEST(Camera, StopRecordReportsAccessDenied) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); const std::string error_text = "error_text"; EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(Eq("CameraAccessDenied"), Eq(error_text), _)); camera->AddPendingResult(PendingResultType::kStopRecord, std::move(result)); camera->OnStopRecordFailed(CameraResult::kAccessDenied, error_text); } TEST(Camera, OnTakePictureSucceededReturnsSuccess) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); const std::string file_path = "C:\\temp\\filename.jpeg"; EXPECT_CALL(*result, ErrorInternal).Times(0); EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(file_path)))); camera->AddPendingResult(PendingResultType::kTakePicture, std::move(result)); camera->OnTakePictureSucceeded(file_path); } TEST(Camera, TakePictureReportsError) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); const std::string error_text = "error_text"; EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(Eq("camera_error"), Eq(error_text), _)); camera->AddPendingResult(PendingResultType::kTakePicture, std::move(result)); camera->OnTakePictureFailed(CameraResult::kError, error_text); } TEST(Camera, TakePictureReportsAccessDenied) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr result = std::make_unique(); const std::string error_text = "error_text"; EXPECT_CALL(*result, SuccessInternal).Times(0); EXPECT_CALL(*result, ErrorInternal(Eq("CameraAccessDenied"), Eq(error_text), _)); camera->AddPendingResult(PendingResultType::kTakePicture, std::move(result)); camera->OnTakePictureFailed(CameraResult::kAccessDenied, error_text); } TEST(Camera, OnVideoRecordSucceededInvokesCameraChannelEvent) { std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller_factory = std::make_unique(); std::unique_ptr binary_messenger = std::make_unique(); const std::string file_path = "C:\\temp\\filename.mp4"; const int64_t camera_id = 12345; std::string camera_channel = std::string("plugins.flutter.io/camera_windows/camera") + std::to_string(camera_id); const int64_t video_duration = 1000000; EXPECT_CALL(*capture_controller_factory, CreateCaptureController) .Times(1) .WillOnce( []() { return std::make_unique>(); }); // TODO: test binary content. // First time is video record success message, // and second is camera closing message. EXPECT_CALL(*binary_messenger, Send(Eq(camera_channel), _, _, _)).Times(2); // Init camera with mock capture controller factory camera->InitCamera(std::move(capture_controller_factory), std::make_unique().get(), binary_messenger.get(), false, ResolutionPreset::kAuto); // Pass camera id for camera camera->OnCreateCaptureEngineSucceeded(camera_id); camera->OnVideoRecordSucceeded(file_path, video_duration); // Dispose camera before message channel. camera = nullptr; } } // namespace test } // namespace camera_windows ================================================ FILE: packages/camera/camera_windows/windows/test/capture_controller_test.cpp ================================================ // Copyright 2013 The Flutter 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 "capture_controller.h" #include #include #include #include #include #include #include #include #include #include #include #include "mocks.h" #include "string_utils.h" namespace camera_windows { namespace test { using Microsoft::WRL::ComPtr; using ::testing::_; using ::testing::Eq; using ::testing::Return; void MockInitCaptureController(CaptureControllerImpl* capture_controller, MockTextureRegistrar* texture_registrar, MockCaptureEngine* engine, MockCamera* camera, int64_t mock_texture_id) { ComPtr video_source = new MockMediaSource(); ComPtr audio_source = new MockMediaSource(); capture_controller->SetCaptureEngine( reinterpret_cast(engine)); capture_controller->SetVideoSource( reinterpret_cast(video_source.Get())); capture_controller->SetAudioSource( reinterpret_cast(audio_source.Get())); EXPECT_CALL(*texture_registrar, RegisterTexture) .Times(1) .WillOnce([reg = texture_registrar, mock_texture_id](flutter::TextureVariant* texture) -> int64_t { EXPECT_TRUE(texture); reg->texture_ = texture; reg->texture_id_ = mock_texture_id; return reg->texture_id_; }); EXPECT_CALL(*texture_registrar, UnregisterTexture(Eq(mock_texture_id))) .Times(1); EXPECT_CALL(*camera, OnCreateCaptureEngineFailed).Times(0); EXPECT_CALL(*camera, OnCreateCaptureEngineSucceeded(Eq(mock_texture_id))) .Times(1); EXPECT_CALL(*engine, Initialize).Times(1); bool result = capture_controller->InitCaptureDevice( texture_registrar, MOCK_DEVICE_ID, true, ResolutionPreset::kAuto); EXPECT_TRUE(result); // MockCaptureEngine::Initialize is called EXPECT_TRUE(engine->initialized_); engine->CreateFakeEvent(S_OK, MF_CAPTURE_ENGINE_INITIALIZED); } void MockAvailableMediaTypes(MockCaptureEngine* engine, MockCaptureSource* capture_source, uint32_t mock_preview_width, uint32_t mock_preview_height) { EXPECT_CALL(*engine, GetSource) .Times(1) .WillOnce( [src_source = capture_source](IMFCaptureSource** target_source) { *target_source = src_source; src_source->AddRef(); return S_OK; }); EXPECT_CALL( *capture_source, GetAvailableDeviceMediaType( Eq((DWORD) MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_PREVIEW), _, _)) .WillRepeatedly([mock_preview_width, mock_preview_height]( DWORD stream_index, DWORD media_type_index, IMFMediaType** media_type) { // We give only one media type to loop through if (media_type_index != 0) return MF_E_NO_MORE_TYPES; *media_type = new FakeMediaType(MFMediaType_Video, MFVideoFormat_RGB32, mock_preview_width, mock_preview_height); (*media_type)->AddRef(); return S_OK; }); EXPECT_CALL( *capture_source, GetAvailableDeviceMediaType( Eq((DWORD)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM_FOR_VIDEO_RECORD), _, _)) .WillRepeatedly([mock_preview_width, mock_preview_height]( DWORD stream_index, DWORD media_type_index, IMFMediaType** media_type) { // We give only one media type to loop through if (media_type_index != 0) return MF_E_NO_MORE_TYPES; *media_type = new FakeMediaType(MFMediaType_Video, MFVideoFormat_RGB32, mock_preview_width, mock_preview_height); (*media_type)->AddRef(); return S_OK; }); } void MockStartPreview(CaptureControllerImpl* capture_controller, MockCapturePreviewSink* preview_sink, MockTextureRegistrar* texture_registrar, MockCaptureEngine* engine, MockCamera* camera, std::unique_ptr mock_source_buffer, uint32_t mock_source_buffer_size, uint32_t mock_preview_width, uint32_t mock_preview_height, int64_t mock_texture_id) { EXPECT_CALL(*engine, GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, _)) .Times(1) .WillOnce([src_sink = preview_sink](MF_CAPTURE_ENGINE_SINK_TYPE sink_type, IMFCaptureSink** target_sink) { *target_sink = src_sink; src_sink->AddRef(); return S_OK; }); EXPECT_CALL(*preview_sink, RemoveAllStreams).Times(1).WillOnce(Return(S_OK)); EXPECT_CALL(*preview_sink, AddStream).Times(1).WillOnce(Return(S_OK)); EXPECT_CALL(*preview_sink, SetSampleCallback) .Times(1) .WillOnce([sink = preview_sink]( DWORD dwStreamSinkIndex, IMFCaptureEngineOnSampleCallback* pCallback) -> HRESULT { sink->sample_callback_ = pCallback; return S_OK; }); ComPtr capture_source = new MockCaptureSource(); MockAvailableMediaTypes(engine, capture_source.Get(), mock_preview_width, mock_preview_height); EXPECT_CALL(*engine, StartPreview()).Times(1).WillOnce(Return(S_OK)); // Called by destructor EXPECT_CALL(*engine, StopPreview()).Times(1).WillOnce(Return(S_OK)); // Called after first processed sample EXPECT_CALL(*camera, OnStartPreviewSucceeded(mock_preview_width, mock_preview_height)) .Times(1); EXPECT_CALL(*camera, OnStartPreviewFailed).Times(0); EXPECT_CALL(*texture_registrar, MarkTextureFrameAvailable(mock_texture_id)) .Times(1); capture_controller->StartPreview(); EXPECT_EQ(capture_controller->GetPreviewHeight(), mock_preview_height); EXPECT_EQ(capture_controller->GetPreviewWidth(), mock_preview_width); // Capture engine is now started and will first send event of started preview engine->CreateFakeEvent(S_OK, MF_CAPTURE_ENGINE_PREVIEW_STARTED); // SendFake sample preview_sink->SendFakeSample(mock_source_buffer.get(), mock_source_buffer_size); } void MockPhotoSink(MockCaptureEngine* engine, MockCapturePhotoSink* photo_sink) { EXPECT_CALL(*engine, GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PHOTO, _)) .Times(1) .WillOnce([src_sink = photo_sink](MF_CAPTURE_ENGINE_SINK_TYPE sink_type, IMFCaptureSink** target_sink) { *target_sink = src_sink; src_sink->AddRef(); return S_OK; }); EXPECT_CALL(*photo_sink, RemoveAllStreams).Times(1).WillOnce(Return(S_OK)); EXPECT_CALL(*photo_sink, AddStream).Times(1).WillOnce(Return(S_OK)); EXPECT_CALL(*photo_sink, SetOutputFileName).Times(1).WillOnce(Return(S_OK)); } void MockRecordStart(CaptureControllerImpl* capture_controller, MockCaptureEngine* engine, MockCaptureRecordSink* record_sink, MockCamera* camera, const std::string& mock_path_to_video) { EXPECT_CALL(*engine, StartRecord()).Times(1).WillOnce(Return(S_OK)); EXPECT_CALL(*engine, GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_RECORD, _)) .Times(1) .WillOnce([src_sink = record_sink](MF_CAPTURE_ENGINE_SINK_TYPE sink_type, IMFCaptureSink** target_sink) { *target_sink = src_sink; src_sink->AddRef(); return S_OK; }); EXPECT_CALL(*record_sink, RemoveAllStreams).Times(1).WillOnce(Return(S_OK)); EXPECT_CALL(*record_sink, AddStream).Times(2).WillRepeatedly(Return(S_OK)); EXPECT_CALL(*record_sink, SetOutputFileName).Times(1).WillOnce(Return(S_OK)); capture_controller->StartRecord(mock_path_to_video, -1); EXPECT_CALL(*camera, OnStartRecordSucceeded()).Times(1); engine->CreateFakeEvent(S_OK, MF_CAPTURE_ENGINE_RECORD_STARTED); } TEST(CaptureController, InitCaptureEngineCallsOnCreateCaptureEngineSucceededWithTextureId) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Init capture controller with mocks and tests MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); capture_controller = nullptr; camera = nullptr; texture_registrar = nullptr; engine = nullptr; } TEST(CaptureController, InitCaptureEngineCanOnlyBeCalledOnce) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Init capture controller once with mocks and tests MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); // Init capture controller a second time. EXPECT_CALL(*camera, OnCreateCaptureEngineFailed).Times(1); bool result = capture_controller->InitCaptureDevice( texture_registrar.get(), MOCK_DEVICE_ID, true, ResolutionPreset::kAuto); EXPECT_FALSE(result); capture_controller = nullptr; camera = nullptr; texture_registrar = nullptr; engine = nullptr; } TEST(CaptureController, InitCaptureEngineReportsFailure) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); ComPtr video_source = new MockMediaSource(); ComPtr audio_source = new MockMediaSource(); capture_controller->SetCaptureEngine( reinterpret_cast(engine.Get())); capture_controller->SetVideoSource( reinterpret_cast(video_source.Get())); capture_controller->SetAudioSource( reinterpret_cast(audio_source.Get())); // Cause initialization to fail EXPECT_CALL(*engine.Get(), Initialize).Times(1).WillOnce(Return(E_FAIL)); EXPECT_CALL(*texture_registrar, RegisterTexture).Times(0); EXPECT_CALL(*texture_registrar, UnregisterTexture(_)).Times(0); EXPECT_CALL(*camera, OnCreateCaptureEngineSucceeded).Times(0); EXPECT_CALL(*camera, OnCreateCaptureEngineFailed(Eq(CameraResult::kError), Eq("Failed to create camera"))) .Times(1); bool result = capture_controller->InitCaptureDevice( texture_registrar.get(), MOCK_DEVICE_ID, true, ResolutionPreset::kAuto); EXPECT_FALSE(result); EXPECT_FALSE(engine->initialized_); capture_controller = nullptr; camera = nullptr; texture_registrar = nullptr; engine = nullptr; } TEST(CaptureController, InitCaptureEngineReportsAccessDenied) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); ComPtr video_source = new MockMediaSource(); ComPtr audio_source = new MockMediaSource(); capture_controller->SetCaptureEngine( reinterpret_cast(engine.Get())); capture_controller->SetVideoSource( reinterpret_cast(video_source.Get())); capture_controller->SetAudioSource( reinterpret_cast(audio_source.Get())); // Cause initialization to fail EXPECT_CALL(*engine.Get(), Initialize) .Times(1) .WillOnce(Return(E_ACCESSDENIED)); EXPECT_CALL(*texture_registrar, RegisterTexture).Times(0); EXPECT_CALL(*texture_registrar, UnregisterTexture(_)).Times(0); EXPECT_CALL(*camera, OnCreateCaptureEngineSucceeded).Times(0); EXPECT_CALL(*camera, OnCreateCaptureEngineFailed(Eq(CameraResult::kAccessDenied), Eq("Failed to create camera"))) .Times(1); bool result = capture_controller->InitCaptureDevice( texture_registrar.get(), MOCK_DEVICE_ID, true, ResolutionPreset::kAuto); EXPECT_FALSE(result); EXPECT_FALSE(engine->initialized_); capture_controller = nullptr; camera = nullptr; texture_registrar = nullptr; engine = nullptr; } TEST(CaptureController, ReportsInitializedErrorEvent) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); EXPECT_CALL(*camera, OnCreateCaptureEngineFailed( Eq(CameraResult::kError), Eq("Failed to initialize capture engine"))) .Times(1); EXPECT_CALL(*camera, OnCreateCaptureEngineSucceeded).Times(0); // Send initialization failed event engine->CreateFakeEvent(E_FAIL, MF_CAPTURE_ENGINE_INITIALIZED); capture_controller = nullptr; camera = nullptr; texture_registrar = nullptr; engine = nullptr; } TEST(CaptureController, ReportsInitializedAccessDeniedEvent) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); EXPECT_CALL(*camera, OnCreateCaptureEngineFailed( Eq(CameraResult::kAccessDenied), Eq("Failed to initialize capture engine"))) .Times(1); EXPECT_CALL(*camera, OnCreateCaptureEngineSucceeded).Times(0); // Send initialization failed event engine->CreateFakeEvent(E_ACCESSDENIED, MF_CAPTURE_ENGINE_INITIALIZED); capture_controller = nullptr; camera = nullptr; texture_registrar = nullptr; engine = nullptr; } TEST(CaptureController, ReportsCaptureEngineErrorEvent) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); EXPECT_CALL(*(camera.get()), OnCaptureError(Eq(CameraResult::kError), Eq("Unspecified error"))) .Times(1); // Send error event. engine->CreateFakeEvent(E_FAIL, MF_CAPTURE_ENGINE_ERROR); capture_controller = nullptr; camera = nullptr; texture_registrar = nullptr; engine = nullptr; } TEST(CaptureController, ReportsCaptureEngineAccessDeniedEvent) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); EXPECT_CALL(*(camera.get()), OnCaptureError(Eq(CameraResult::kAccessDenied), Eq("Access is denied."))) .Times(1); // Send error event. engine->CreateFakeEvent(E_ACCESSDENIED, MF_CAPTURE_ENGINE_ERROR); capture_controller = nullptr; camera = nullptr; texture_registrar = nullptr; engine = nullptr; } TEST(CaptureController, StartPreviewStartsProcessingSamples) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr preview_sink = new MockCapturePreviewSink(); // Let's keep these small for mock texture data. Two pixels should be // enough. uint32_t mock_preview_width = 2; uint32_t mock_preview_height = 1; uint32_t pixels_total = mock_preview_width * mock_preview_height; uint32_t pixel_size = 4; // Build mock texture uint32_t mock_texture_data_size = pixels_total * pixel_size; std::unique_ptr mock_source_buffer = std::make_unique(mock_texture_data_size); uint8_t mock_red_pixel = 0x11; uint8_t mock_green_pixel = 0x22; uint8_t mock_blue_pixel = 0x33; MFVideoFormatRGB32Pixel* mock_source_buffer_data = (MFVideoFormatRGB32Pixel*)mock_source_buffer.get(); for (uint32_t i = 0; i < pixels_total; i++) { mock_source_buffer_data[i].r = mock_red_pixel; mock_source_buffer_data[i].g = mock_green_pixel; mock_source_buffer_data[i].b = mock_blue_pixel; } // Start preview and run preview tests MockStartPreview(capture_controller.get(), preview_sink.Get(), texture_registrar.get(), engine.Get(), camera.get(), std::move(mock_source_buffer), mock_texture_data_size, mock_preview_width, mock_preview_height, mock_texture_id); // Test texture processing EXPECT_TRUE(texture_registrar->texture_); if (texture_registrar->texture_) { auto pixel_buffer_texture = std::get_if(texture_registrar->texture_); EXPECT_TRUE(pixel_buffer_texture); if (pixel_buffer_texture) { auto converted_buffer = pixel_buffer_texture->CopyPixelBuffer((size_t)100, (size_t)100); EXPECT_TRUE(converted_buffer); if (converted_buffer) { EXPECT_EQ(converted_buffer->height, mock_preview_height); EXPECT_EQ(converted_buffer->width, mock_preview_width); FlutterDesktopPixel* converted_buffer_data = (FlutterDesktopPixel*)(converted_buffer->buffer); for (uint32_t i = 0; i < pixels_total; i++) { EXPECT_EQ(converted_buffer_data[i].r, mock_red_pixel); EXPECT_EQ(converted_buffer_data[i].g, mock_green_pixel); EXPECT_EQ(converted_buffer_data[i].b, mock_blue_pixel); } // Call release callback to get mutex lock unlocked. converted_buffer->release_callback(converted_buffer->release_context); } converted_buffer = nullptr; } pixel_buffer_texture = nullptr; } capture_controller = nullptr; engine = nullptr; camera = nullptr; texture_registrar = nullptr; } TEST(CaptureController, ReportsStartPreviewError) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr capture_source = new MockCaptureSource(); MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); // Cause start preview to fail EXPECT_CALL(*engine.Get(), GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, _)) .Times(1) .WillOnce(Return(E_FAIL)); EXPECT_CALL(*engine.Get(), StartPreview).Times(0); EXPECT_CALL(*engine.Get(), StopPreview).Times(0); EXPECT_CALL(*camera, OnStartPreviewSucceeded).Times(0); EXPECT_CALL(*camera, OnStartPreviewFailed(Eq(CameraResult::kError), Eq("Failed to start video preview"))) .Times(1); capture_controller->StartPreview(); capture_controller = nullptr; engine = nullptr; camera = nullptr; texture_registrar = nullptr; } // TODO(loic-sharma): Test duplicate calls to start preview. TEST(CaptureController, IgnoresStartPreviewErrorEvent) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); EXPECT_CALL(*camera, OnStartPreviewFailed).Times(0); EXPECT_CALL(*camera, OnCreateCaptureEngineSucceeded).Times(0); // Send a start preview error event engine->CreateFakeEvent(E_FAIL, MF_CAPTURE_ENGINE_PREVIEW_STARTED); capture_controller = nullptr; camera = nullptr; texture_registrar = nullptr; engine = nullptr; } TEST(CaptureController, ReportsStartPreviewAccessDenied) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr capture_source = new MockCaptureSource(); MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); // Cause start preview to fail EXPECT_CALL(*engine.Get(), GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, _)) .Times(1) .WillOnce(Return(E_ACCESSDENIED)); EXPECT_CALL(*engine.Get(), StartPreview).Times(0); EXPECT_CALL(*engine.Get(), StopPreview).Times(0); EXPECT_CALL(*camera, OnStartPreviewSucceeded).Times(0); EXPECT_CALL(*camera, OnStartPreviewFailed(Eq(CameraResult::kAccessDenied), Eq("Failed to start video preview"))) .Times(1); capture_controller->StartPreview(); capture_controller = nullptr; engine = nullptr; camera = nullptr; texture_registrar = nullptr; } TEST(CaptureController, StartRecordSuccess) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr capture_source = new MockCaptureSource(); // Prepare fake media types MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); // Start record ComPtr record_sink = new MockCaptureRecordSink(); std::string mock_path_to_video = "mock_path_to_video"; MockRecordStart(capture_controller.get(), engine.Get(), record_sink.Get(), camera.get(), mock_path_to_video); // Called by destructor EXPECT_CALL(*(engine.Get()), StopRecord(true, false)) .Times(1) .WillOnce(Return(S_OK)); capture_controller = nullptr; texture_registrar = nullptr; engine = nullptr; camera = nullptr; record_sink = nullptr; } TEST(CaptureController, ReportsStartRecordError) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr capture_source = new MockCaptureSource(); // Prepare fake media types MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); // Cause start record to fail EXPECT_CALL(*engine.Get(), GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_RECORD, _)) .Times(1) .WillOnce(Return(E_FAIL)); EXPECT_CALL(*engine.Get(), StartRecord).Times(0); EXPECT_CALL(*engine.Get(), StopRecord).Times(0); EXPECT_CALL(*camera, OnStartRecordSucceeded).Times(0); EXPECT_CALL(*camera, OnStartRecordFailed(Eq(CameraResult::kError), Eq("Failed to start video recording"))) .Times(1); capture_controller->StartRecord("mock_path", -1); capture_controller = nullptr; texture_registrar = nullptr; engine = nullptr; camera = nullptr; } TEST(CaptureController, ReportsStartRecordAccessDenied) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr capture_source = new MockCaptureSource(); // Prepare fake media types MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); // Cause start record to fail EXPECT_CALL(*engine.Get(), GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_RECORD, _)) .Times(1) .WillOnce(Return(E_ACCESSDENIED)); EXPECT_CALL(*engine.Get(), StartRecord).Times(0); EXPECT_CALL(*engine.Get(), StopRecord).Times(0); EXPECT_CALL(*camera, OnStartRecordSucceeded).Times(0); EXPECT_CALL(*camera, OnStartRecordFailed(Eq(CameraResult::kAccessDenied), Eq("Failed to start video recording"))) .Times(1); capture_controller->StartRecord("mock_path", -1); capture_controller = nullptr; texture_registrar = nullptr; engine = nullptr; camera = nullptr; } TEST(CaptureController, ReportsStartRecordErrorEvent) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr capture_source = new MockCaptureSource(); // Prepare fake media types MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); // Start record ComPtr record_sink = new MockCaptureRecordSink(); std::string mock_path_to_video = "mock_path_to_video"; EXPECT_CALL(*engine.Get(), StartRecord()).Times(1).WillOnce(Return(S_OK)); EXPECT_CALL(*engine.Get(), GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_RECORD, _)) .Times(1) .WillOnce([src_sink = record_sink](MF_CAPTURE_ENGINE_SINK_TYPE sink_type, IMFCaptureSink** target_sink) { *target_sink = src_sink.Get(); src_sink->AddRef(); return S_OK; }); EXPECT_CALL(*record_sink.Get(), RemoveAllStreams) .Times(1) .WillOnce(Return(S_OK)); EXPECT_CALL(*record_sink.Get(), AddStream) .Times(2) .WillRepeatedly(Return(S_OK)); EXPECT_CALL(*record_sink.Get(), SetOutputFileName) .Times(1) .WillOnce(Return(S_OK)); capture_controller->StartRecord(mock_path_to_video, -1); // Send a start record failed event EXPECT_CALL(*camera, OnStartRecordSucceeded).Times(0); EXPECT_CALL(*camera, OnStartRecordFailed(Eq(CameraResult::kError), Eq("Unspecified error"))) .Times(1); engine->CreateFakeEvent(E_FAIL, MF_CAPTURE_ENGINE_RECORD_STARTED); // Destructor shouldn't attempt to stop the recording that failed to start. EXPECT_CALL(*engine.Get(), StopRecord).Times(0); capture_controller = nullptr; texture_registrar = nullptr; engine = nullptr; camera = nullptr; record_sink = nullptr; } TEST(CaptureController, ReportsStartRecordAccessDeniedEvent) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr capture_source = new MockCaptureSource(); // Prepare fake media types MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); // Start record ComPtr record_sink = new MockCaptureRecordSink(); std::string mock_path_to_video = "mock_path_to_video"; EXPECT_CALL(*engine.Get(), StartRecord()).Times(1).WillOnce(Return(S_OK)); EXPECT_CALL(*engine.Get(), GetSink(MF_CAPTURE_ENGINE_SINK_TYPE_RECORD, _)) .Times(1) .WillOnce([src_sink = record_sink](MF_CAPTURE_ENGINE_SINK_TYPE sink_type, IMFCaptureSink** target_sink) { *target_sink = src_sink.Get(); src_sink->AddRef(); return S_OK; }); EXPECT_CALL(*record_sink.Get(), RemoveAllStreams) .Times(1) .WillOnce(Return(S_OK)); EXPECT_CALL(*record_sink.Get(), AddStream) .Times(2) .WillRepeatedly(Return(S_OK)); EXPECT_CALL(*record_sink.Get(), SetOutputFileName) .Times(1) .WillOnce(Return(S_OK)); // Send a start record failed event capture_controller->StartRecord(mock_path_to_video, -1); EXPECT_CALL(*camera, OnStartRecordSucceeded).Times(0); EXPECT_CALL(*camera, OnStartRecordFailed(Eq(CameraResult::kAccessDenied), Eq("Access is denied."))) .Times(1); engine->CreateFakeEvent(E_ACCESSDENIED, MF_CAPTURE_ENGINE_RECORD_STARTED); // Destructor shouldn't attempt to stop the recording that failed to start. EXPECT_CALL(*engine.Get(), StopRecord).Times(0); capture_controller = nullptr; texture_registrar = nullptr; engine = nullptr; camera = nullptr; record_sink = nullptr; } TEST(CaptureController, StopRecordSuccess) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr capture_source = new MockCaptureSource(); // Prepare fake media types MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); // Start record ComPtr record_sink = new MockCaptureRecordSink(); std::string mock_path_to_video = "mock_path_to_video"; MockRecordStart(capture_controller.get(), engine.Get(), record_sink.Get(), camera.get(), mock_path_to_video); // Request to stop record EXPECT_CALL(*(engine.Get()), StopRecord(true, false)) .Times(1) .WillOnce(Return(S_OK)); capture_controller->StopRecord(); // OnStopRecordSucceeded should be called with mocked file path EXPECT_CALL(*camera, OnStopRecordSucceeded(Eq(mock_path_to_video))).Times(1); EXPECT_CALL(*camera, OnStopRecordFailed).Times(0); engine->CreateFakeEvent(S_OK, MF_CAPTURE_ENGINE_RECORD_STOPPED); capture_controller = nullptr; texture_registrar = nullptr; engine = nullptr; camera = nullptr; record_sink = nullptr; } TEST(CaptureController, ReportsStopRecordError) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr capture_source = new MockCaptureSource(); // Prepare fake media types MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); // Start record ComPtr record_sink = new MockCaptureRecordSink(); MockRecordStart(capture_controller.get(), engine.Get(), record_sink.Get(), camera.get(), "mock_path_to_video"); // Cause stop record to fail EXPECT_CALL(*(engine.Get()), StopRecord(true, false)) .Times(1) .WillOnce(Return(E_FAIL)); EXPECT_CALL(*camera, OnStopRecordSucceeded).Times(0); EXPECT_CALL(*camera, OnStopRecordFailed(Eq(CameraResult::kError), Eq("Failed to stop video recording"))) .Times(1); capture_controller->StopRecord(); capture_controller = nullptr; texture_registrar = nullptr; engine = nullptr; camera = nullptr; record_sink = nullptr; } TEST(CaptureController, ReportsStopRecordAccessDenied) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr capture_source = new MockCaptureSource(); // Prepare fake media types MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); // Start record ComPtr record_sink = new MockCaptureRecordSink(); MockRecordStart(capture_controller.get(), engine.Get(), record_sink.Get(), camera.get(), "mock_path_to_video"); // Cause stop record to fail EXPECT_CALL(*(engine.Get()), StopRecord(true, false)) .Times(1) .WillOnce(Return(E_ACCESSDENIED)); EXPECT_CALL(*camera, OnStopRecordSucceeded).Times(0); EXPECT_CALL(*camera, OnStopRecordFailed(Eq(CameraResult::kAccessDenied), Eq("Failed to stop video recording"))) .Times(1); capture_controller->StopRecord(); capture_controller = nullptr; texture_registrar = nullptr; engine = nullptr; camera = nullptr; record_sink = nullptr; } TEST(CaptureController, ReportsStopRecordErrorEvent) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr capture_source = new MockCaptureSource(); // Prepare fake media types MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); // Start record ComPtr record_sink = new MockCaptureRecordSink(); std::string mock_path_to_video = "mock_path_to_video"; MockRecordStart(capture_controller.get(), engine.Get(), record_sink.Get(), camera.get(), mock_path_to_video); // Send a stop record failure event EXPECT_CALL(*camera, OnStopRecordSucceeded).Times(0); EXPECT_CALL(*camera, OnStopRecordFailed(Eq(CameraResult::kError), Eq("Unspecified error"))) .Times(1); engine->CreateFakeEvent(E_FAIL, MF_CAPTURE_ENGINE_RECORD_STOPPED); capture_controller = nullptr; texture_registrar = nullptr; engine = nullptr; camera = nullptr; record_sink = nullptr; } TEST(CaptureController, ReportsStopRecordAccessDeniedEvent) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr capture_source = new MockCaptureSource(); // Prepare fake media types MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); // Start record ComPtr record_sink = new MockCaptureRecordSink(); std::string mock_path_to_video = "mock_path_to_video"; MockRecordStart(capture_controller.get(), engine.Get(), record_sink.Get(), camera.get(), mock_path_to_video); // Send a stop record failure event EXPECT_CALL(*camera, OnStopRecordSucceeded).Times(0); EXPECT_CALL(*camera, OnStopRecordFailed(Eq(CameraResult::kAccessDenied), Eq("Access is denied."))) .Times(1); engine->CreateFakeEvent(E_ACCESSDENIED, MF_CAPTURE_ENGINE_RECORD_STOPPED); capture_controller = nullptr; texture_registrar = nullptr; engine = nullptr; camera = nullptr; record_sink = nullptr; } TEST(CaptureController, TakePictureSuccess) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to take picture MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr capture_source = new MockCaptureSource(); // Prepare fake media types MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); ComPtr photo_sink = new MockCapturePhotoSink(); // Initialize photo sink MockPhotoSink(engine.Get(), photo_sink.Get()); // Request photo std::string mock_path_to_photo = "mock_path_to_photo"; EXPECT_CALL(*(engine.Get()), TakePhoto()).Times(1).WillOnce(Return(S_OK)); capture_controller->TakePicture(mock_path_to_photo); // OnTakePictureSucceeded should be called with mocked file path EXPECT_CALL(*camera, OnTakePictureSucceeded(Eq(mock_path_to_photo))).Times(1); EXPECT_CALL(*camera, OnTakePictureFailed).Times(0); engine->CreateFakeEvent(S_OK, MF_CAPTURE_ENGINE_PHOTO_TAKEN); capture_controller = nullptr; texture_registrar = nullptr; engine = nullptr; camera = nullptr; photo_sink = nullptr; } TEST(CaptureController, ReportsTakePictureError) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to take picture MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr capture_source = new MockCaptureSource(); // Prepare fake media types MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); ComPtr photo_sink = new MockCapturePhotoSink(); // Initialize photo sink MockPhotoSink(engine.Get(), photo_sink.Get()); // Cause take picture to fail EXPECT_CALL(*(engine.Get()), TakePhoto).Times(1).WillOnce(Return(E_FAIL)); EXPECT_CALL(*camera, OnTakePictureSucceeded).Times(0); EXPECT_CALL(*camera, OnTakePictureFailed(Eq(CameraResult::kError), Eq("Failed to take photo"))) .Times(1); capture_controller->TakePicture("mock_path_to_photo"); capture_controller = nullptr; texture_registrar = nullptr; engine = nullptr; camera = nullptr; photo_sink = nullptr; } TEST(CaptureController, ReportsTakePictureAccessDenied) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to take picture MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr capture_source = new MockCaptureSource(); // Prepare fake media types MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); ComPtr photo_sink = new MockCapturePhotoSink(); // Initialize photo sink MockPhotoSink(engine.Get(), photo_sink.Get()); // Cause take picture to fail. EXPECT_CALL(*(engine.Get()), TakePhoto) .Times(1) .WillOnce(Return(E_ACCESSDENIED)); EXPECT_CALL(*camera, OnTakePictureSucceeded).Times(0); EXPECT_CALL(*camera, OnTakePictureFailed(Eq(CameraResult::kAccessDenied), Eq("Failed to take photo"))) .Times(1); capture_controller->TakePicture("mock_path_to_photo"); capture_controller = nullptr; texture_registrar = nullptr; engine = nullptr; camera = nullptr; photo_sink = nullptr; } TEST(CaptureController, ReportsPhotoTakenErrorEvent) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to take picture MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr capture_source = new MockCaptureSource(); // Prepare fake media types MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); ComPtr photo_sink = new MockCapturePhotoSink(); // Initialize photo sink MockPhotoSink(engine.Get(), photo_sink.Get()); // Request photo std::string mock_path_to_photo = "mock_path_to_photo"; EXPECT_CALL(*(engine.Get()), TakePhoto()).Times(1).WillOnce(Return(S_OK)); capture_controller->TakePicture(mock_path_to_photo); // Send take picture failed event EXPECT_CALL(*camera, OnTakePictureSucceeded).Times(0); EXPECT_CALL(*camera, OnTakePictureFailed(Eq(CameraResult::kError), Eq("Unspecified error"))) .Times(1); engine->CreateFakeEvent(E_FAIL, MF_CAPTURE_ENGINE_PHOTO_TAKEN); capture_controller = nullptr; texture_registrar = nullptr; engine = nullptr; camera = nullptr; photo_sink = nullptr; } TEST(CaptureController, ReportsPhotoTakenAccessDeniedEvent) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to take picture MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr capture_source = new MockCaptureSource(); // Prepare fake media types MockAvailableMediaTypes(engine.Get(), capture_source.Get(), 1, 1); ComPtr photo_sink = new MockCapturePhotoSink(); // Initialize photo sink MockPhotoSink(engine.Get(), photo_sink.Get()); // Request photo std::string mock_path_to_photo = "mock_path_to_photo"; EXPECT_CALL(*(engine.Get()), TakePhoto()).Times(1).WillOnce(Return(S_OK)); capture_controller->TakePicture(mock_path_to_photo); // Send take picture failed event EXPECT_CALL(*camera, OnTakePictureSucceeded).Times(0); EXPECT_CALL(*camera, OnTakePictureFailed(Eq(CameraResult::kAccessDenied), Eq("Access is denied."))) .Times(1); engine->CreateFakeEvent(E_ACCESSDENIED, MF_CAPTURE_ENGINE_PHOTO_TAKEN); capture_controller = nullptr; texture_registrar = nullptr; engine = nullptr; camera = nullptr; photo_sink = nullptr; } TEST(CaptureController, PauseResumePreviewSuccess) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); ComPtr preview_sink = new MockCapturePreviewSink(); std::unique_ptr mock_source_buffer = std::make_unique(0); // Start preview to be able to start record MockStartPreview(capture_controller.get(), preview_sink.Get(), texture_registrar.get(), engine.Get(), camera.get(), std::move(mock_source_buffer), 0, 1, 1, mock_texture_id); EXPECT_CALL(*camera, OnPausePreviewSucceeded()).Times(1); capture_controller->PausePreview(); EXPECT_CALL(*camera, OnResumePreviewSucceeded()).Times(1); capture_controller->ResumePreview(); capture_controller = nullptr; texture_registrar = nullptr; engine = nullptr; camera = nullptr; } TEST(CaptureController, PausePreviewFailsIfPreviewNotStarted) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); // Pause preview fails if not started EXPECT_CALL(*camera, OnPausePreviewFailed(Eq(CameraResult::kError), Eq("Preview not started"))) .Times(1); capture_controller->PausePreview(); capture_controller = nullptr; texture_registrar = nullptr; engine = nullptr; camera = nullptr; } TEST(CaptureController, ResumePreviewFailsIfPreviewNotStarted) { ComPtr engine = new MockCaptureEngine(); std::unique_ptr camera = std::make_unique(MOCK_DEVICE_ID); std::unique_ptr capture_controller = std::make_unique(camera.get()); std::unique_ptr texture_registrar = std::make_unique(); int64_t mock_texture_id = 1234; // Initialize capture controller to be able to start preview MockInitCaptureController(capture_controller.get(), texture_registrar.get(), engine.Get(), camera.get(), mock_texture_id); // Resume preview fails if not started. EXPECT_CALL(*camera, OnResumePreviewFailed(Eq(CameraResult::kError), Eq("Preview not started"))) .Times(1); capture_controller->ResumePreview(); capture_controller = nullptr; texture_registrar = nullptr; engine = nullptr; camera = nullptr; } } // namespace test } // namespace camera_windows ================================================ FILE: packages/camera/camera_windows/windows/test/mocks.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_TEST_MOCKS_H_ #define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_TEST_MOCKS_H_ #include #include #include #include #include #include #include #include "camera.h" #include "camera_plugin.h" #include "capture_controller.h" #include "capture_controller_listener.h" #include "capture_engine_listener.h" namespace camera_windows { 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 MockBinaryMessenger : public flutter::BinaryMessenger { public: ~MockBinaryMessenger() = default; MOCK_METHOD(void, Send, (const std::string& channel, const uint8_t* message, size_t message_size, flutter::BinaryReply reply), (const)); MOCK_METHOD(void, SetMessageHandler, (const std::string& channel, flutter::BinaryMessageHandler handler), ()); }; class MockTextureRegistrar : public flutter::TextureRegistrar { public: MockTextureRegistrar() { ON_CALL(*this, RegisterTexture) .WillByDefault([this](flutter::TextureVariant* texture) -> int64_t { EXPECT_TRUE(texture); this->texture_ = texture; this->texture_id_ = 1000; return this->texture_id_; }); // Deprecated pre-Flutter-3.4 version. ON_CALL(*this, UnregisterTexture(_)) .WillByDefault([this](int64_t tid) -> bool { if (tid == this->texture_id_) { texture_ = nullptr; this->texture_id_ = -1; return true; } return false; }); // Flutter 3.4+ version. ON_CALL(*this, UnregisterTexture(_, _)) .WillByDefault( [this](int64_t tid, std::function callback) -> void { // Forward to the pre-3.4 implementation so that expectations can // be the same for all versions. this->UnregisterTexture(tid); if (callback) { callback(); } }); ON_CALL(*this, MarkTextureFrameAvailable) .WillByDefault([this](int64_t tid) -> bool { if (tid == this->texture_id_) { return true; } return false; }); } ~MockTextureRegistrar() { texture_ = nullptr; } MOCK_METHOD(int64_t, RegisterTexture, (flutter::TextureVariant * texture), (override)); // Pre-Flutter-3.4 version. MOCK_METHOD(bool, UnregisterTexture, (int64_t), (override)); // Flutter 3.4+ version. // TODO(cbracken): Add an override annotation to this once 3.4+ is the // minimum version tested in CI. MOCK_METHOD(void, UnregisterTexture, (int64_t, std::function callback), ()); MOCK_METHOD(bool, MarkTextureFrameAvailable, (int64_t), (override)); int64_t texture_id_ = -1; flutter::TextureVariant* texture_ = nullptr; }; class MockCameraFactory : public CameraFactory { public: MockCameraFactory() { ON_CALL(*this, CreateCamera).WillByDefault([this]() { assert(this->pending_camera_); return std::move(this->pending_camera_); }); } ~MockCameraFactory() = default; // Disallow copy and move. MockCameraFactory(const MockCameraFactory&) = delete; MockCameraFactory& operator=(const MockCameraFactory&) = delete; MOCK_METHOD(std::unique_ptr, CreateCamera, (const std::string& device_id), (override)); std::unique_ptr pending_camera_; }; class MockCamera : public Camera { public: MockCamera(const std::string& device_id) : device_id_(device_id), Camera(device_id){}; ~MockCamera() = default; // Disallow copy and move. MockCamera(const MockCamera&) = delete; MockCamera& operator=(const MockCamera&) = delete; MOCK_METHOD(void, OnCreateCaptureEngineSucceeded, (int64_t texture_id), (override)); MOCK_METHOD(std::unique_ptr>, GetPendingResultByType, (PendingResultType type)); MOCK_METHOD(void, OnCreateCaptureEngineFailed, (CameraResult result, const std::string& error), (override)); MOCK_METHOD(void, OnStartPreviewSucceeded, (int32_t width, int32_t height), (override)); MOCK_METHOD(void, OnStartPreviewFailed, (CameraResult result, const std::string& error), (override)); MOCK_METHOD(void, OnResumePreviewSucceeded, (), (override)); MOCK_METHOD(void, OnResumePreviewFailed, (CameraResult result, const std::string& error), (override)); MOCK_METHOD(void, OnPausePreviewSucceeded, (), (override)); MOCK_METHOD(void, OnPausePreviewFailed, (CameraResult result, const std::string& error), (override)); MOCK_METHOD(void, OnStartRecordSucceeded, (), (override)); MOCK_METHOD(void, OnStartRecordFailed, (CameraResult result, const std::string& error), (override)); MOCK_METHOD(void, OnStopRecordSucceeded, (const std::string& file_path), (override)); MOCK_METHOD(void, OnStopRecordFailed, (CameraResult result, const std::string& error), (override)); MOCK_METHOD(void, OnTakePictureSucceeded, (const std::string& file_path), (override)); MOCK_METHOD(void, OnTakePictureFailed, (CameraResult result, const std::string& error), (override)); MOCK_METHOD(void, OnVideoRecordSucceeded, (const std::string& file_path, int64_t video_duration), (override)); MOCK_METHOD(void, OnVideoRecordFailed, (CameraResult result, const std::string& error), (override)); MOCK_METHOD(void, OnCaptureError, (CameraResult result, const std::string& error), (override)); MOCK_METHOD(bool, HasDeviceId, (std::string & device_id), (const override)); MOCK_METHOD(bool, HasCameraId, (int64_t camera_id), (const override)); MOCK_METHOD(bool, AddPendingResult, (PendingResultType type, std::unique_ptr> result), (override)); MOCK_METHOD(bool, HasPendingResultByType, (PendingResultType type), (const override)); MOCK_METHOD(camera_windows::CaptureController*, GetCaptureController, (), (override)); MOCK_METHOD(bool, InitCamera, (flutter::TextureRegistrar * texture_registrar, flutter::BinaryMessenger* messenger, bool record_audio, ResolutionPreset resolution_preset), (override)); std::unique_ptr capture_controller_; std::unique_ptr> pending_result_; std::string device_id_; int64_t camera_id_ = -1; }; class MockCaptureControllerFactory : public CaptureControllerFactory { public: MockCaptureControllerFactory(){}; virtual ~MockCaptureControllerFactory() = default; // Disallow copy and move. MockCaptureControllerFactory(const MockCaptureControllerFactory&) = delete; MockCaptureControllerFactory& operator=(const MockCaptureControllerFactory&) = delete; MOCK_METHOD(std::unique_ptr, CreateCaptureController, (CaptureControllerListener * listener), (override)); }; class MockCaptureController : public CaptureController { public: ~MockCaptureController() = default; MOCK_METHOD(bool, InitCaptureDevice, (flutter::TextureRegistrar * texture_registrar, const std::string& device_id, bool record_audio, ResolutionPreset resolution_preset), (override)); MOCK_METHOD(uint32_t, GetPreviewWidth, (), (const override)); MOCK_METHOD(uint32_t, GetPreviewHeight, (), (const override)); // Actions MOCK_METHOD(void, StartPreview, (), (override)); MOCK_METHOD(void, ResumePreview, (), (override)); MOCK_METHOD(void, PausePreview, (), (override)); MOCK_METHOD(void, StartRecord, (const std::string& file_path, int64_t max_video_duration_ms), (override)); MOCK_METHOD(void, StopRecord, (), (override)); MOCK_METHOD(void, TakePicture, (const std::string& file_path), (override)); }; // MockCameraPlugin extends CameraPlugin behaviour a bit to allow adding cameras // without creating them first with create message handler and mocking static // system calls class MockCameraPlugin : public CameraPlugin { public: MockCameraPlugin(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger) : CameraPlugin(texture_registrar, messenger){}; // Creates a plugin instance with the given CameraFactory instance. // Exists for unit testing with mock implementations. MockCameraPlugin(flutter::TextureRegistrar* texture_registrar, flutter::BinaryMessenger* messenger, std::unique_ptr camera_factory) : CameraPlugin(texture_registrar, messenger, std::move(camera_factory)){}; ~MockCameraPlugin() = default; // Disallow copy and move. MockCameraPlugin(const MockCameraPlugin&) = delete; MockCameraPlugin& operator=(const MockCameraPlugin&) = delete; MOCK_METHOD(bool, EnumerateVideoCaptureDeviceSources, (IMFActivate * **devices, UINT32* count), (override)); // Helper to add camera without creating it via CameraFactory for testing // purposes void AddCamera(std::unique_ptr camera) { cameras_.push_back(std::move(camera)); } }; class MockCaptureSource : public IMFCaptureSource { public: MockCaptureSource(){}; ~MockCaptureSource() = default; // IUnknown STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_); } // IUnknown STDMETHODIMP_(ULONG) Release() { LONG ref = InterlockedDecrement(&ref_); if (ref == 0) { delete this; } return ref; } // IUnknown STDMETHODIMP_(HRESULT) QueryInterface(const IID& riid, void** ppv) { *ppv = nullptr; if (riid == IID_IMFCaptureSource) { *ppv = static_cast(this); ((IUnknown*)*ppv)->AddRef(); return S_OK; } return E_NOINTERFACE; } MOCK_METHOD(HRESULT, GetCaptureDeviceSource, (MF_CAPTURE_ENGINE_DEVICE_TYPE mfCaptureEngineDeviceType, IMFMediaSource** ppMediaSource)); MOCK_METHOD(HRESULT, GetCaptureDeviceActivate, (MF_CAPTURE_ENGINE_DEVICE_TYPE mfCaptureEngineDeviceType, IMFActivate** ppActivate)); MOCK_METHOD(HRESULT, GetService, (REFIID rguidService, REFIID riid, IUnknown** ppUnknown)); MOCK_METHOD(HRESULT, AddEffect, (DWORD dwSourceStreamIndex, IUnknown* pUnknown)); MOCK_METHOD(HRESULT, RemoveEffect, (DWORD dwSourceStreamIndex, IUnknown* pUnknown)); MOCK_METHOD(HRESULT, RemoveAllEffects, (DWORD dwSourceStreamIndex)); MOCK_METHOD(HRESULT, GetAvailableDeviceMediaType, (DWORD dwSourceStreamIndex, DWORD dwMediaTypeIndex, IMFMediaType** ppMediaType)); MOCK_METHOD(HRESULT, SetCurrentDeviceMediaType, (DWORD dwSourceStreamIndex, IMFMediaType* pMediaType)); MOCK_METHOD(HRESULT, GetCurrentDeviceMediaType, (DWORD dwSourceStreamIndex, IMFMediaType** ppMediaType)); MOCK_METHOD(HRESULT, GetDeviceStreamCount, (DWORD * pdwStreamCount)); MOCK_METHOD(HRESULT, GetDeviceStreamCategory, (DWORD dwSourceStreamIndex, MF_CAPTURE_ENGINE_STREAM_CATEGORY* pStreamCategory)); MOCK_METHOD(HRESULT, GetMirrorState, (DWORD dwStreamIndex, BOOL* pfMirrorState)); MOCK_METHOD(HRESULT, SetMirrorState, (DWORD dwStreamIndex, BOOL fMirrorState)); MOCK_METHOD(HRESULT, GetStreamIndexFromFriendlyName, (UINT32 uifriendlyName, DWORD* pdwActualStreamIndex)); private: volatile ULONG ref_ = 0; }; // Uses IMFMediaSourceEx which has SetD3DManager method. class MockMediaSource : public IMFMediaSourceEx { public: MockMediaSource(){}; ~MockMediaSource() = default; // IUnknown STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_); } // IUnknown STDMETHODIMP_(ULONG) Release() { LONG ref = InterlockedDecrement(&ref_); if (ref == 0) { delete this; } return ref; } // IUnknown STDMETHODIMP_(HRESULT) QueryInterface(const IID& riid, void** ppv) { *ppv = nullptr; if (riid == IID_IMFMediaSource) { *ppv = static_cast(this); ((IUnknown*)*ppv)->AddRef(); return S_OK; } return E_NOINTERFACE; } // IMFMediaSource HRESULT GetCharacteristics(DWORD* dwCharacteristics) override { return E_NOTIMPL; } // IMFMediaSource HRESULT CreatePresentationDescriptor( IMFPresentationDescriptor** presentationDescriptor) override { return E_NOTIMPL; } // IMFMediaSource HRESULT Start(IMFPresentationDescriptor* presentationDescriptor, const GUID* guidTimeFormat, const PROPVARIANT* varStartPosition) override { return E_NOTIMPL; } // IMFMediaSource HRESULT Stop(void) override { return E_NOTIMPL; } // IMFMediaSource HRESULT Pause(void) override { return E_NOTIMPL; } // IMFMediaSource HRESULT Shutdown(void) override { return E_NOTIMPL; } // IMFMediaEventGenerator HRESULT GetEvent(DWORD dwFlags, IMFMediaEvent** event) override { return E_NOTIMPL; } // IMFMediaEventGenerator HRESULT BeginGetEvent(IMFAsyncCallback* callback, IUnknown* unkState) override { return E_NOTIMPL; } // IMFMediaEventGenerator HRESULT EndGetEvent(IMFAsyncResult* result, IMFMediaEvent** event) override { return E_NOTIMPL; } // IMFMediaEventGenerator HRESULT QueueEvent(MediaEventType met, REFGUID guidExtendedType, HRESULT hrStatus, const PROPVARIANT* value) override { return E_NOTIMPL; } // IMFMediaSourceEx HRESULT GetSourceAttributes(IMFAttributes** attributes) { return E_NOTIMPL; } // IMFMediaSourceEx HRESULT GetStreamAttributes(DWORD stream_id, IMFAttributes** attributes) { return E_NOTIMPL; } // IMFMediaSourceEx HRESULT SetD3DManager(IUnknown* manager) { return S_OK; } private: volatile ULONG ref_ = 0; }; class MockCapturePreviewSink : public IMFCapturePreviewSink { public: // IMFCaptureSink MOCK_METHOD(HRESULT, GetOutputMediaType, (DWORD dwSinkStreamIndex, IMFMediaType** ppMediaType)); // IMFCaptureSink MOCK_METHOD(HRESULT, GetService, (DWORD dwSinkStreamIndex, REFGUID rguidService, REFIID riid, IUnknown** ppUnknown)); // IMFCaptureSink MOCK_METHOD(HRESULT, AddStream, (DWORD dwSourceStreamIndex, IMFMediaType* pMediaType, IMFAttributes* pAttributes, DWORD* pdwSinkStreamIndex)); // IMFCaptureSink MOCK_METHOD(HRESULT, Prepare, ()); // IMFCaptureSink MOCK_METHOD(HRESULT, RemoveAllStreams, ()); // IMFCapturePreviewSink MOCK_METHOD(HRESULT, SetRenderHandle, (HANDLE handle)); // IMFCapturePreviewSink MOCK_METHOD(HRESULT, SetRenderSurface, (IUnknown * pSurface)); // IMFCapturePreviewSink MOCK_METHOD(HRESULT, UpdateVideo, (const MFVideoNormalizedRect* pSrc, const RECT* pDst, const COLORREF* pBorderClr)); // IMFCapturePreviewSink MOCK_METHOD(HRESULT, SetSampleCallback, (DWORD dwStreamSinkIndex, IMFCaptureEngineOnSampleCallback* pCallback)); // IMFCapturePreviewSink MOCK_METHOD(HRESULT, GetMirrorState, (BOOL * pfMirrorState)); // IMFCapturePreviewSink MOCK_METHOD(HRESULT, SetMirrorState, (BOOL fMirrorState)); // IMFCapturePreviewSink MOCK_METHOD(HRESULT, GetRotation, (DWORD dwStreamIndex, DWORD* pdwRotationValue)); // IMFCapturePreviewSink MOCK_METHOD(HRESULT, SetRotation, (DWORD dwStreamIndex, DWORD dwRotationValue)); // IMFCapturePreviewSink MOCK_METHOD(HRESULT, SetCustomSink, (IMFMediaSink * pMediaSink)); // IUnknown STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_); } // IUnknown STDMETHODIMP_(ULONG) Release() { LONG ref = InterlockedDecrement(&ref_); if (ref == 0) { delete this; } return ref; } // IUnknown STDMETHODIMP_(HRESULT) QueryInterface(const IID& riid, void** ppv) { *ppv = nullptr; if (riid == IID_IMFCapturePreviewSink) { *ppv = static_cast(this); ((IUnknown*)*ppv)->AddRef(); return S_OK; } return E_NOINTERFACE; } void SendFakeSample(uint8_t* src_buffer, uint32_t size) { assert(sample_callback_); ComPtr sample; ComPtr buffer; HRESULT hr = MFCreateSample(&sample); if (SUCCEEDED(hr)) { hr = MFCreateMemoryBuffer(size, &buffer); } if (SUCCEEDED(hr)) { uint8_t* target_data; if (SUCCEEDED(buffer->Lock(&target_data, nullptr, nullptr))) { std::copy(src_buffer, src_buffer + size, target_data); } hr = buffer->Unlock(); } if (SUCCEEDED(hr)) { hr = buffer->SetCurrentLength(size); } if (SUCCEEDED(hr)) { hr = sample->AddBuffer(buffer.Get()); } if (SUCCEEDED(hr)) { sample_callback_->OnSample(sample.Get()); } } ComPtr sample_callback_; private: ~MockCapturePreviewSink() = default; volatile ULONG ref_ = 0; }; class MockCaptureRecordSink : public IMFCaptureRecordSink { public: // IMFCaptureSink MOCK_METHOD(HRESULT, GetOutputMediaType, (DWORD dwSinkStreamIndex, IMFMediaType** ppMediaType)); // IMFCaptureSink MOCK_METHOD(HRESULT, GetService, (DWORD dwSinkStreamIndex, REFGUID rguidService, REFIID riid, IUnknown** ppUnknown)); // IMFCaptureSink MOCK_METHOD(HRESULT, AddStream, (DWORD dwSourceStreamIndex, IMFMediaType* pMediaType, IMFAttributes* pAttributes, DWORD* pdwSinkStreamIndex)); // IMFCaptureSink MOCK_METHOD(HRESULT, Prepare, ()); // IMFCaptureSink MOCK_METHOD(HRESULT, RemoveAllStreams, ()); // IMFCaptureRecordSink MOCK_METHOD(HRESULT, SetOutputByteStream, (IMFByteStream * pByteStream, REFGUID guidContainerType)); // IMFCaptureRecordSink MOCK_METHOD(HRESULT, SetOutputFileName, (LPCWSTR fileName)); // IMFCaptureRecordSink MOCK_METHOD(HRESULT, SetSampleCallback, (DWORD dwStreamSinkIndex, IMFCaptureEngineOnSampleCallback* pCallback)); // IMFCaptureRecordSink MOCK_METHOD(HRESULT, SetCustomSink, (IMFMediaSink * pMediaSink)); // IMFCaptureRecordSink MOCK_METHOD(HRESULT, GetRotation, (DWORD dwStreamIndex, DWORD* pdwRotationValue)); // IMFCaptureRecordSink MOCK_METHOD(HRESULT, SetRotation, (DWORD dwStreamIndex, DWORD dwRotationValue)); // IUnknown STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_); } // IUnknown STDMETHODIMP_(ULONG) Release() { LONG ref = InterlockedDecrement(&ref_); if (ref == 0) { delete this; } return ref; } // IUnknown STDMETHODIMP_(HRESULT) QueryInterface(const IID& riid, void** ppv) { *ppv = nullptr; if (riid == IID_IMFCaptureRecordSink) { *ppv = static_cast(this); ((IUnknown*)*ppv)->AddRef(); return S_OK; } return E_NOINTERFACE; } private: ~MockCaptureRecordSink() = default; volatile ULONG ref_ = 0; }; class MockCapturePhotoSink : public IMFCapturePhotoSink { public: // IMFCaptureSink MOCK_METHOD(HRESULT, GetOutputMediaType, (DWORD dwSinkStreamIndex, IMFMediaType** ppMediaType)); // IMFCaptureSink MOCK_METHOD(HRESULT, GetService, (DWORD dwSinkStreamIndex, REFGUID rguidService, REFIID riid, IUnknown** ppUnknown)); // IMFCaptureSink MOCK_METHOD(HRESULT, AddStream, (DWORD dwSourceStreamIndex, IMFMediaType* pMediaType, IMFAttributes* pAttributes, DWORD* pdwSinkStreamIndex)); // IMFCaptureSink MOCK_METHOD(HRESULT, Prepare, ()); // IMFCaptureSink MOCK_METHOD(HRESULT, RemoveAllStreams, ()); // IMFCapturePhotoSink MOCK_METHOD(HRESULT, SetOutputFileName, (LPCWSTR fileName)); // IMFCapturePhotoSink MOCK_METHOD(HRESULT, SetSampleCallback, (IMFCaptureEngineOnSampleCallback * pCallback)); // IMFCapturePhotoSink MOCK_METHOD(HRESULT, SetOutputByteStream, (IMFByteStream * pByteStream)); // IUnknown STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_); } // IUnknown STDMETHODIMP_(ULONG) Release() { LONG ref = InterlockedDecrement(&ref_); if (ref == 0) { delete this; } return ref; } // IUnknown STDMETHODIMP_(HRESULT) QueryInterface(const IID& riid, void** ppv) { *ppv = nullptr; if (riid == IID_IMFCapturePhotoSink) { *ppv = static_cast(this); ((IUnknown*)*ppv)->AddRef(); return S_OK; } return E_NOINTERFACE; } private: ~MockCapturePhotoSink() = default; volatile ULONG ref_ = 0; }; template class FakeIMFAttributesBase : public T { static_assert(std::is_base_of::value, "I must inherit from IMFAttributes"); // IIMFAttributes HRESULT GetItem(REFGUID guidKey, PROPVARIANT* pValue) override { return E_NOTIMPL; } // IIMFAttributes HRESULT GetItemType(REFGUID guidKey, MF_ATTRIBUTE_TYPE* pType) override { return E_NOTIMPL; } // IIMFAttributes HRESULT CompareItem(REFGUID guidKey, REFPROPVARIANT Value, BOOL* pbResult) override { return E_NOTIMPL; } // IIMFAttributes HRESULT Compare(IMFAttributes* pTheirs, MF_ATTRIBUTES_MATCH_TYPE MatchType, BOOL* pbResult) override { return E_NOTIMPL; } // IIMFAttributes HRESULT GetUINT32(REFGUID guidKey, UINT32* punValue) override { return E_NOTIMPL; } // IIMFAttributes HRESULT GetUINT64(REFGUID guidKey, UINT64* punValue) override { return E_NOTIMPL; } // IIMFAttributes HRESULT GetDouble(REFGUID guidKey, double* pfValue) override { return E_NOTIMPL; } // IIMFAttributes HRESULT GetGUID(REFGUID guidKey, GUID* pguidValue) override { return E_NOTIMPL; } // IIMFAttributes HRESULT GetStringLength(REFGUID guidKey, UINT32* pcchLength) override { return E_NOTIMPL; } // IIMFAttributes HRESULT GetString(REFGUID guidKey, LPWSTR pwszValue, UINT32 cchBufSize, UINT32* pcchLength) override { return E_NOTIMPL; } // IIMFAttributes HRESULT GetAllocatedString(REFGUID guidKey, LPWSTR* ppwszValue, UINT32* pcchLength) override { return E_NOTIMPL; } // IIMFAttributes HRESULT GetBlobSize(REFGUID guidKey, UINT32* pcbBlobSize) override { return E_NOTIMPL; } // IIMFAttributes HRESULT GetBlob(REFGUID guidKey, UINT8* pBuf, UINT32 cbBufSize, UINT32* pcbBlobSize) override { return E_NOTIMPL; } // IIMFAttributes HRESULT GetAllocatedBlob(REFGUID guidKey, UINT8** ppBuf, UINT32* pcbSize) override { return E_NOTIMPL; } // IIMFAttributes HRESULT GetUnknown(REFGUID guidKey, REFIID riid, __RPC__deref_out_opt LPVOID* ppv) override { return E_NOTIMPL; } // IIMFAttributes HRESULT SetItem(REFGUID guidKey, REFPROPVARIANT Value) override { return E_NOTIMPL; } // IIMFAttributes HRESULT DeleteItem(REFGUID guidKey) override { return E_NOTIMPL; } // IIMFAttributes HRESULT DeleteAllItems(void) override { return E_NOTIMPL; } // IIMFAttributes HRESULT SetUINT32(REFGUID guidKey, UINT32 unValue) override { return E_NOTIMPL; } // IIMFAttributes HRESULT SetUINT64(REFGUID guidKey, UINT64 unValue) override { return E_NOTIMPL; } // IIMFAttributes HRESULT SetDouble(REFGUID guidKey, double fValue) override { return E_NOTIMPL; } // IIMFAttributes HRESULT SetGUID(REFGUID guidKey, REFGUID guidValue) override { return E_NOTIMPL; } // IIMFAttributes HRESULT SetString(REFGUID guidKey, LPCWSTR wszValue) override { return E_NOTIMPL; } // IIMFAttributes HRESULT SetBlob(REFGUID guidKey, const UINT8* pBuf, UINT32 cbBufSize) override { return E_NOTIMPL; } // IIMFAttributes HRESULT SetUnknown(REFGUID guidKey, IUnknown* pUnknown) override { return E_NOTIMPL; } // IIMFAttributes HRESULT LockStore(void) override { return E_NOTIMPL; } // IIMFAttributes HRESULT UnlockStore(void) override { return E_NOTIMPL; } // IIMFAttributes HRESULT GetCount(UINT32* pcItems) override { return E_NOTIMPL; } // IIMFAttributes HRESULT GetItemByIndex(UINT32 unIndex, GUID* pguidKey, PROPVARIANT* pValue) override { return E_NOTIMPL; } // IIMFAttributes HRESULT CopyAllItems(IMFAttributes* pDest) override { return E_NOTIMPL; } }; class FakeMediaType : public FakeIMFAttributesBase { public: FakeMediaType(GUID major_type, GUID sub_type, int width, int height) : major_type_(major_type), sub_type_(sub_type), width_(width), height_(height){}; // IMFAttributes HRESULT GetUINT64(REFGUID key, UINT64* value) override { if (key == MF_MT_FRAME_SIZE) { *value = (int64_t)width_ << 32 | (int64_t)height_; return S_OK; } else if (key == MF_MT_FRAME_RATE) { *value = (int64_t)frame_rate_ << 32 | 1; return S_OK; } return E_FAIL; }; // IMFAttributes HRESULT GetGUID(REFGUID key, GUID* value) override { if (key == MF_MT_MAJOR_TYPE) { *value = major_type_; return S_OK; } else if (key == MF_MT_SUBTYPE) { *value = sub_type_; return S_OK; } return E_FAIL; } // IIMFAttributes HRESULT CopyAllItems(IMFAttributes* pDest) override { pDest->SetUINT64(MF_MT_FRAME_SIZE, (int64_t)width_ << 32 | (int64_t)height_); pDest->SetUINT64(MF_MT_FRAME_RATE, (int64_t)frame_rate_ << 32 | 1); pDest->SetGUID(MF_MT_MAJOR_TYPE, major_type_); pDest->SetGUID(MF_MT_SUBTYPE, sub_type_); return S_OK; } // IMFMediaType HRESULT STDMETHODCALLTYPE GetMajorType(GUID* pguidMajorType) override { return E_NOTIMPL; }; // IMFMediaType HRESULT STDMETHODCALLTYPE IsCompressedFormat(BOOL* pfCompressed) override { return E_NOTIMPL; } // IMFMediaType HRESULT STDMETHODCALLTYPE IsEqual(IMFMediaType* pIMediaType, DWORD* pdwFlags) override { return E_NOTIMPL; } // IMFMediaType HRESULT STDMETHODCALLTYPE GetRepresentation( GUID guidRepresentation, LPVOID* ppvRepresentation) override { return E_NOTIMPL; } // IMFMediaType HRESULT STDMETHODCALLTYPE FreeRepresentation( GUID guidRepresentation, LPVOID pvRepresentation) override { return E_NOTIMPL; } // IUnknown STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_); } // IUnknown STDMETHODIMP_(ULONG) Release() { LONG ref = InterlockedDecrement(&ref_); if (ref == 0) { delete this; } return ref; } // IUnknown STDMETHODIMP_(HRESULT) QueryInterface(const IID& riid, void** ppv) { *ppv = nullptr; if (riid == IID_IMFMediaType) { *ppv = static_cast(this); ((IUnknown*)*ppv)->AddRef(); return S_OK; } return E_NOINTERFACE; } private: ~FakeMediaType() = default; volatile ULONG ref_ = 0; const GUID major_type_; const GUID sub_type_; const int width_; const int height_; const int frame_rate_ = 30; }; class MockCaptureEngine : public IMFCaptureEngine { public: MockCaptureEngine() { ON_CALL(*this, Initialize) .WillByDefault([this](IMFCaptureEngineOnEventCallback* callback, IMFAttributes* attributes, IUnknown* audioSource, IUnknown* videoSource) -> HRESULT { EXPECT_TRUE(callback); EXPECT_TRUE(attributes); EXPECT_TRUE(videoSource); // audioSource is allowed to be nullptr; callback_ = callback; videoSource_ = reinterpret_cast(videoSource); audioSource_ = reinterpret_cast(audioSource); initialized_ = true; return S_OK; }); }; virtual ~MockCaptureEngine() = default; MOCK_METHOD(HRESULT, Initialize, (IMFCaptureEngineOnEventCallback * callback, IMFAttributes* attributes, IUnknown* audioSource, IUnknown* videoSource)); MOCK_METHOD(HRESULT, StartPreview, ()); MOCK_METHOD(HRESULT, StopPreview, ()); MOCK_METHOD(HRESULT, StartRecord, ()); MOCK_METHOD(HRESULT, StopRecord, (BOOL finalize, BOOL flushUnprocessedSamples)); MOCK_METHOD(HRESULT, TakePhoto, ()); MOCK_METHOD(HRESULT, GetSink, (MF_CAPTURE_ENGINE_SINK_TYPE type, IMFCaptureSink** sink)); MOCK_METHOD(HRESULT, GetSource, (IMFCaptureSource * *ppSource)); // IUnknown STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_); } // IUnknown STDMETHODIMP_(ULONG) Release() { LONG ref = InterlockedDecrement(&ref_); if (ref == 0) { delete this; } return ref; } // IUnknown STDMETHODIMP_(HRESULT) QueryInterface(const IID& riid, void** ppv) { *ppv = nullptr; if (riid == IID_IMFCaptureEngine) { *ppv = static_cast(this); ((IUnknown*)*ppv)->AddRef(); return S_OK; } return E_NOINTERFACE; } void CreateFakeEvent(HRESULT hrStatus, GUID event_type) { EXPECT_TRUE(initialized_); ComPtr event; MFCreateMediaEvent(MEExtendedType, event_type, hrStatus, nullptr, &event); if (callback_) { callback_->OnEvent(event.Get()); } } ComPtr callback_; ComPtr videoSource_; ComPtr audioSource_; volatile ULONG ref_ = 0; bool initialized_ = false; }; #define MOCK_DEVICE_ID "mock_device_id" #define MOCK_CAMERA_NAME "mock_camera_name <" MOCK_DEVICE_ID ">" #define MOCK_INVALID_CAMERA_NAME "invalid_camera_name" } // namespace } // namespace test } // namespace camera_windows #endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_TEST_MOCKS_H_ ================================================ FILE: packages/camera/camera_windows/windows/texture_handler.cpp ================================================ // Copyright 2013 The Flutter 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 "texture_handler.h" #include namespace camera_windows { TextureHandler::~TextureHandler() { // Texture might still be processed while destructor is called. // Lock mutex for safe destruction const std::lock_guard lock(buffer_mutex_); if (texture_registrar_ && texture_id_ > 0) { texture_registrar_->UnregisterTexture(texture_id_); } texture_id_ = -1; texture_ = nullptr; texture_registrar_ = nullptr; } int64_t TextureHandler::RegisterTexture() { if (!texture_registrar_) { return -1; } // Create flutter desktop pixelbuffer texture; texture_ = std::make_unique(flutter::PixelBufferTexture( [this](size_t width, size_t height) -> const FlutterDesktopPixelBuffer* { return this->ConvertPixelBufferForFlutter(width, height); })); texture_id_ = texture_registrar_->RegisterTexture(texture_.get()); return texture_id_; } bool TextureHandler::UpdateBuffer(uint8_t* data, uint32_t data_length) { // Scoped lock guard. { const std::lock_guard lock(buffer_mutex_); if (!TextureRegistered()) { return false; } if (source_buffer_.size() != data_length) { // Update source buffer size. source_buffer_.resize(data_length); } std::copy(data, data + data_length, source_buffer_.data()); } OnBufferUpdated(); return true; }; // Marks texture frame available after buffer is updated. void TextureHandler::OnBufferUpdated() { if (TextureRegistered()) { texture_registrar_->MarkTextureFrameAvailable(texture_id_); } } const FlutterDesktopPixelBuffer* TextureHandler::ConvertPixelBufferForFlutter( size_t target_width, size_t target_height) { // TODO: optimize image processing size by adjusting capture size // dynamically to match target_width and target_height. // If target size changes, create new media type for preview and set new // target framesize to MF_MT_FRAME_SIZE attribute. // Size should be kept inside requested resolution preset. // Update output media type with IMFCaptureSink2::SetOutputMediaType method // call and implement IMFCaptureEngineOnSampleCallback2::OnSynchronizedEvent // to detect size changes. // Lock buffer mutex to protect texture processing std::unique_lock buffer_lock(buffer_mutex_); if (!TextureRegistered()) { return nullptr; } const uint32_t bytes_per_pixel = 4; const uint32_t pixels_total = preview_frame_width_ * preview_frame_height_; const uint32_t data_size = pixels_total * bytes_per_pixel; if (data_size > 0 && source_buffer_.size() == data_size) { if (dest_buffer_.size() != data_size) { dest_buffer_.resize(data_size); } // Map buffers to structs for easier conversion. MFVideoFormatRGB32Pixel* src = reinterpret_cast(source_buffer_.data()); FlutterDesktopPixel* dst = reinterpret_cast(dest_buffer_.data()); for (uint32_t y = 0; y < preview_frame_height_; y++) { for (uint32_t x = 0; x < preview_frame_width_; x++) { uint32_t sp = (y * preview_frame_width_) + x; if (mirror_preview_) { // Software mirror mode. // IMFCapturePreviewSink also has the SetMirrorState setting, // but if enabled, samples will not be processed. // Calculates mirrored pixel position. uint32_t tp = (y * preview_frame_width_) + ((preview_frame_width_ - 1) - x); dst[tp].r = src[sp].r; dst[tp].g = src[sp].g; dst[tp].b = src[sp].b; dst[tp].a = 255; } else { dst[sp].r = src[sp].r; dst[sp].g = src[sp].g; dst[sp].b = src[sp].b; dst[sp].a = 255; } } } if (!flutter_desktop_pixel_buffer_) { flutter_desktop_pixel_buffer_ = std::make_unique(); // Unlocks mutex after texture is processed. flutter_desktop_pixel_buffer_->release_callback = [](void* release_context) { auto mutex = reinterpret_cast(release_context); mutex->unlock(); }; } flutter_desktop_pixel_buffer_->buffer = dest_buffer_.data(); flutter_desktop_pixel_buffer_->width = preview_frame_width_; flutter_desktop_pixel_buffer_->height = preview_frame_height_; // Releases unique_lock and set mutex pointer for release context. flutter_desktop_pixel_buffer_->release_context = buffer_lock.release(); return flutter_desktop_pixel_buffer_.get(); } return nullptr; } } // namespace camera_windows ================================================ FILE: packages/camera/camera_windows/windows/texture_handler.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_TEXTURE_HANDLER_H_ #define PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_TEXTURE_HANDLER_H_ #include #include #include #include namespace camera_windows { // Describes flutter desktop pixelbuffers pixel data order. struct FlutterDesktopPixel { uint8_t r = 0; uint8_t g = 0; uint8_t b = 0; uint8_t a = 0; }; // Describes MFVideoFormat_RGB32 data order. struct MFVideoFormatRGB32Pixel { uint8_t b = 0; uint8_t g = 0; uint8_t r = 0; uint8_t x = 0; }; // Handles the registration of Flutter textures, pixel buffers, and the // conversion of texture formats. class TextureHandler { public: TextureHandler(flutter::TextureRegistrar* texture_registrar) : texture_registrar_(texture_registrar) {} virtual ~TextureHandler(); // Prevent copying. TextureHandler(TextureHandler const&) = delete; TextureHandler& operator=(TextureHandler const&) = delete; // Updates source data buffer with given data. bool UpdateBuffer(uint8_t* data, uint32_t data_length); // Registers texture and updates given texture_id pointer value. int64_t RegisterTexture(); // Updates current preview texture size. void UpdateTextureSize(uint32_t width, uint32_t height) { preview_frame_width_ = width; preview_frame_height_ = height; } // Sets software mirror state. void SetMirrorPreviewState(bool mirror) { mirror_preview_ = mirror; } private: // Informs flutter texture registrar of updated texture. void OnBufferUpdated(); // Converts local pixel buffer to flutter pixel buffer. const FlutterDesktopPixelBuffer* ConvertPixelBufferForFlutter(size_t width, size_t height); // Checks if texture registrar, texture id and texture are available. bool TextureRegistered() { return texture_registrar_ && texture_ && texture_id_ > -1; } bool mirror_preview_ = true; int64_t texture_id_ = -1; uint32_t bytes_per_pixel_ = 4; uint32_t source_buffer_size_ = 0; uint32_t preview_frame_width_ = 0; uint32_t preview_frame_height_ = 0; std::vector source_buffer_; std::vector dest_buffer_; std::unique_ptr texture_; std::unique_ptr flutter_desktop_pixel_buffer_ = nullptr; flutter::TextureRegistrar* texture_registrar_ = nullptr; std::mutex buffer_mutex_; }; } // namespace camera_windows #endif // PACKAGES_CAMERA_CAMERA_WINDOWS_WINDOWS_TEXTURE_HANDLER_H_ ================================================ FILE: packages/e2e/README.md ================================================ # e2e (deprecated) This package has been moved to [`integration_test` in the Flutter SDK](https://github.com/flutter/flutter/tree/master/packages/integration_test). ================================================ FILE: packages/espresso/.gitignore ================================================ .DS_Store .dart_tool/ .packages .pub/ build/ ================================================ FILE: packages/espresso/.metadata ================================================ # 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: 0190e40457d43e17bdfaf046dfa634cbc5bf28b9 channel: unknown project_type: plugin ================================================ FILE: packages/espresso/AUTHORS ================================================ # 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> ================================================ FILE: packages/espresso/CHANGELOG.md ================================================ ## 0.2.0+8 * Updates espresso and junit dependencies. ## 0.2.0+7 * Updates espresso gradle and gson dependencies. * 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. ## 0.2.0+4 * Updates minimum Flutter version to 2.10. * Bumps gson to 2.9.1 ## 0.2.0+3 * Bumps okhttp to 4.10.0. ## 0.2.0+2 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.2.0+1 * Adds OS version support information to README. * Updates `androidx.test.ext:junit` and `androidx.test.ext:truth` for compatibility with updated Flutter template. ## 0.2.0 * Updates compileSdkVersion to 31. * **Breaking Change** Update guava version to latest stable: `com.google.guava:guava:31.1-android`. ## 0.1.0+4 * Updated Android lint settings. * Updated package description. ## 0.1.0+3 * Remove references to the Android v1 embedding. ## 0.1.0+2 * Migrate maven repo from jcenter to mavenCentral ## 0.1.0+1 * Minor code cleanup * Package metadata updates ## 0.1.0 * Update SDK requirement for null-safety compatibility. ## 0.0.1+9 * Update Flutter SDK constraint. ## 0.0.1+8 * Android: Handle deprecation & unchecked warning as error. ## 0.0.1+7 * Update android compileSdkVersion to 29. ## 0.0.1+6 * Keep handling deprecated Android v1 classes for backward compatibility. ## 0.0.1+5 * Replace deprecated `getFlutterEngine` call on Android. * Fix CocoaPods podspec lint warnings. ## 0.0.1+4 * Remove Swift dependency. ## 0.0.1+3 * Make the pedantic dev_dependency explicit. ## 0.0.1+2 * Update te example app to avoid using deprecated api. ## 0.0.1+1 * Updates to README to avoid unnecessary imports and warnings. ## 0.0.1 * Initial open-source release of Espresso bindings for Flutter. ================================================ FILE: packages/espresso/LICENSE ================================================ 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. ================================================ FILE: packages/espresso/README.md ================================================ # espresso Provides bindings for Espresso tests of Flutter Android apps. | | Android | |-------------|---------| | **Support** | SDK 16+ | ## Installation Add the `espresso` package as a `dev_dependency` in your app's pubspec.yaml. If you're testing the example app of a package, add it as a dev_dependency of the main package as well. Add ```android:usesCleartextTraffic="true"``` in the `````` in the AndroidManifest.xml of the Android app used for testing. It's best to put this in a debug or androidTest AndroidManifest.xml so that you don't ship it to end users. (See the example app of this package.) Add the following dependencies in android/app/build.gradle: ```groovy dependencies { testImplementation 'junit:junit:4.13.2' testImplementation "com.google.truth:truth:1.0" androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' api 'androidx.test:core:1.2.0' } ``` Create an `android/app/src/androidTest` folder and put a test file in a package-appropriate subfolder, e.g. `android/app/src/androidTest/java/com/example/MainActivityTest.java`: ```java package com.example.espresso_example; import static androidx.test.espresso.flutter.EspressoFlutter.onFlutterWidget; import static androidx.test.espresso.flutter.action.FlutterActions.click; import static androidx.test.espresso.flutter.action.FlutterActions.syntheticClick; import static androidx.test.espresso.flutter.assertion.FlutterAssertions.matches; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.isDescendantOf; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withText; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withTooltip; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withType; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withValueKey; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import androidx.test.core.app.ActivityScenario; import androidx.test.espresso.flutter.EspressoFlutter.WidgetInteraction; import androidx.test.espresso.flutter.assertion.FlutterAssertions; import androidx.test.espresso.flutter.matcher.FlutterMatchers; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; /** Unit tests for {@link EspressoFlutter}. */ @RunWith(AndroidJUnit4.class) public class MainActivityTest { @Before public void setUp() throws Exception { ActivityScenario.launch(MainActivity.class); } @Test public void performClick() { onFlutterWidget(withTooltip("Increment")).perform(click()); onFlutterWidget(withValueKey("CountText")).check(matches(withText("Button tapped 1 time."))); } ``` You'll need to create a test app that enables the Flutter driver extension. You can put this in your test_driver/ folder, e.g. test_driver/example.dart. Replace `` with the package name of your app. If you're developing a plugin, this will be the package name of the example app. ```dart import 'package:flutter_driver/driver_extension.dart'; import 'package:/main.dart' as app; void main() { enableFlutterDriverExtension(); app.main(); } ``` The following command line command runs the test locally: ```sh ./gradlew app:connectedAndroidTest -Ptarget=`pwd`/../test_driver/example.dart ``` Espresso tests can also be run on [Firebase Test Lab](https://firebase.google.com/docs/test-lab): ```sh ./gradlew app:assembleAndroidTest ./gradlew app:assembleDebug -Ptarget=.dart gcloud auth activate-service-account --key-file= gcloud --quiet config set project gcloud firebase test android run --type instrumentation \ --app build/app/outputs/apk/debug/app-debug.apk \ --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk\ --timeout 2m \ --results-bucket= \ --results-dir= ``` ================================================ FILE: packages/espresso/android/.gitignore ================================================ *.iml .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures ================================================ FILE: packages/espresso/android/build.gradle ================================================ group 'com.example.espresso' version '1.0' buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.4.1' } } rootProject.allprojects { repositories { google() mavenCentral() } } apply plugin: 'com.android.library' android { compileSdkVersion 31 defaultConfig { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' baseline file("lint-baseline.xml") } testOptions { unitTests.includeAndroidResources = true unitTests.returnDefaultValues = true unitTests.all { testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" outputs.upToDateWhen {false} showStandardStreams = true } } } } dependencies { implementation 'com.google.guava:guava:31.1-android' implementation 'com.squareup.okhttp3:okhttp:4.10.0' implementation 'com.google.code.gson:gson:2.10.1' androidTestImplementation 'org.hamcrest:hamcrest:2.2' testImplementation 'junit:junit:4.13.2' testImplementation "com.google.truth:truth:1.0" api 'androidx.test:runner:1.1.1' api 'androidx.test.espresso:espresso-core:3.1.1' // Core library api 'androidx.test:core:1.0.0' // AndroidJUnitRunner and JUnit Rules api 'androidx.test:runner:1.1.0' api 'androidx.test:rules:1.1.0' // Assertions api 'androidx.test.ext:junit:1.1.5' api 'androidx.test.ext:truth:1.5.0' api 'com.google.truth:truth:0.42' // Espresso dependencies api 'androidx.test.espresso:espresso-core:3.5.1' api 'androidx.test.espresso:espresso-contrib:3.5.1' api 'androidx.test.espresso:espresso-intents:3.5.1' api 'androidx.test.espresso:espresso-accessibility:3.5.1' api 'androidx.test.espresso:espresso-web:3.5.1' api 'androidx.test.espresso.idling:idling-concurrent:3.5.1' // The following Espresso dependency can be either "implementation" // or "androidTestImplementation", depending on whether you want the // dependency to appear on your APK's compile classpath or the test APK // classpath. api 'androidx.test.espresso:espresso-idling-resource:3.5.1' } ================================================ FILE: packages/espresso/android/lint-baseline.xml ================================================ ================================================ FILE: packages/espresso/android/settings.gradle ================================================ rootProject.name = 'espresso' ================================================ FILE: packages/espresso/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/EspressoFlutter.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.flutter.common.Constants.DEFAULT_INTERACTION_TIMEOUT; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.isFlutterView; import static com.google.common.base.Preconditions.checkNotNull; import static org.hamcrest.Matchers.any; import android.util.Log; import android.view.View; import androidx.test.espresso.UiController; import androidx.test.espresso.ViewAction; import androidx.test.espresso.flutter.action.FlutterViewAction; import androidx.test.espresso.flutter.action.WidgetInfoFetcher; import androidx.test.espresso.flutter.api.FlutterAction; import androidx.test.espresso.flutter.api.WidgetAction; import androidx.test.espresso.flutter.api.WidgetAssertion; import androidx.test.espresso.flutter.api.WidgetMatcher; import androidx.test.espresso.flutter.assertion.FlutterViewAssertion; import androidx.test.espresso.flutter.common.Duration; import androidx.test.espresso.flutter.exception.NoMatchingWidgetException; import androidx.test.espresso.flutter.internal.idgenerator.IdGenerator; import androidx.test.espresso.flutter.internal.idgenerator.IdGenerators; import androidx.test.espresso.flutter.model.WidgetInfo; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import javax.annotation.Nonnull; import okhttp3.OkHttpClient; import org.hamcrest.Matcher; /** Entry point to the Espresso testing APIs on Flutter. */ public final class EspressoFlutter { private static final String TAG = EspressoFlutter.class.getSimpleName(); private static final OkHttpClient okHttpClient; private static final IdGenerator idGenerator; private static final ExecutorService taskExecutor; static { okHttpClient = new OkHttpClient(); idGenerator = IdGenerators.newIntegerIdGenerator(); taskExecutor = Executors.newCachedThreadPool(); } /** * Creates a {@link WidgetInteraction} for the Flutter widget matched by the given {@code * widgetMatcher}, which is an entry point to perform actions or asserts. * * @param widgetMatcher the matcher used to uniquely match a Flutter widget on the screen. */ public static WidgetInteraction onFlutterWidget(@Nonnull WidgetMatcher widgetMatcher) { return new WidgetInteraction(isFlutterView(), widgetMatcher); } /** * Provides fluent testing APIs for test authors to perform actions or asserts on Flutter widgets, * similar to {@code ViewInteraction} and {@code WebInteraction}. */ public static final class WidgetInteraction { /** * Adds a little delay to the interaction timeout so that we make sure not to time out before * the action or assert does. */ private static final Duration INTERACTION_TIMEOUT_DELAY = new Duration(1, TimeUnit.SECONDS); private final Matcher flutterViewMatcher; private final WidgetMatcher widgetMatcher; private final Duration timeout; private WidgetInteraction(Matcher flutterViewMatcher, WidgetMatcher widgetMatcher) { this( flutterViewMatcher, widgetMatcher, DEFAULT_INTERACTION_TIMEOUT.plus(INTERACTION_TIMEOUT_DELAY)); } private WidgetInteraction( Matcher flutterViewMatcher, WidgetMatcher widgetMatcher, Duration timeout) { this.flutterViewMatcher = checkNotNull(flutterViewMatcher); this.widgetMatcher = checkNotNull(widgetMatcher); this.timeout = checkNotNull(timeout); } /** * Executes the given action(s) with synchronization guarantees: Espresso ensures Flutter's in * an idle state before interacting with the Flutter UI. * *

If more than one action is provided, actions are executed in the order provided. * * @param widgetActions one or more actions that shall be performed. Cannot be {@code null}. * @return this interaction for further perform/verification calls. */ public WidgetInteraction perform(@Nonnull final WidgetAction... widgetActions) { checkNotNull(widgetActions); for (WidgetAction widgetAction : widgetActions) { // If any error occurred, an unchecked exception will be thrown that stops execution of // following actions. performInternal(widgetAction); } return this; } /** * Evaluates the given widget assertion. * * @param assertion a widget assertion that shall be made on the matched Flutter widget. Cannot * be {@code null}. */ public WidgetInteraction check(@Nonnull WidgetAssertion assertion) { checkNotNull( assertion, "Assertion cannot be null. You must specify an assertion on the matched Flutter widget."); WidgetInfo widgetInfo = performInternal(new WidgetInfoFetcher()); if (widgetInfo == null) { Log.w(TAG, String.format("Widget info that matches %s is null.", widgetMatcher)); throw new NoMatchingWidgetException( String.format("Widget info that matches %s is null.", widgetMatcher)); } FlutterViewAssertion flutterViewAssertion = new FlutterViewAssertion(assertion, widgetInfo); onView(flutterViewMatcher).check(flutterViewAssertion); return this; } @SuppressWarnings("unchecked") private T performInternal(FlutterAction flutterAction) { checkNotNull( flutterAction, "The action cannot be null. You must specify an action to perform on the matched" + " Flutter widget."); FlutterViewAction flutterViewAction = new FlutterViewAction( widgetMatcher, flutterAction, okHttpClient, idGenerator, taskExecutor); onView(flutterViewMatcher).perform(flutterViewAction); T result; try { if (timeout != null && timeout.getQuantity() > 0) { result = flutterViewAction.waitUntilCompleted(timeout.getQuantity(), timeout.getUnit()); } else { result = flutterViewAction.waitUntilCompleted(); } return result; } catch (ExecutionException e) { propagateException(e.getCause()); } catch (InterruptedException | TimeoutException | RuntimeException e) { propagateException(e); } return null; } /** * Propagates exception through #onView so that it get a chance to be handled by the registered * {@code FailureHandler}. */ private void propagateException(Throwable t) { onView(flutterViewMatcher).perform(new ExceptionPropagator(t)); } /** * An exception wrapper that propagates an exception through {@code #onView}, so that it can be * handled by the registered {@code FailureHandler} for the underlying {@code ViewInteraction}. */ static class ExceptionPropagator implements ViewAction { private final RuntimeException exception; public ExceptionPropagator(RuntimeException exception) { this.exception = checkNotNull(exception); } public ExceptionPropagator(Throwable t) { this(new RuntimeException(t)); } @Override public String getDescription() { return "Propagate: " + exception; } @Override public void perform(UiController uiController, View view) { throw exception; } @SuppressWarnings("unchecked") @Override public Matcher getConstraints() { return any(View.class); } } } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/action/ActionUtil.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.action; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import android.os.Looper; import androidx.test.espresso.IdlingRegistry; import androidx.test.espresso.IdlingResource; import androidx.test.espresso.UiController; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; /** Utils for the Flutter actions. */ final class ActionUtil { /** * Loops the main thread until the given future task has been done. Users could use this method to * "synchronize" between the main thread and {@code Future} instances running on its own thread * (e.g. methods of the {@code FlutterTestingProtocol}), without blocking the main thread. * *

Usage: * *

{@code
   * Future fooFuture = flutterTestingProtocol.callFoo();
   * T fooResult = loopUntilCompletion("fooTask", androidUiController, fooFuture, executor);
   * // Then consumes the fooResult on main thread.
   * }
* * @param taskName the name that shall be used when registering the task as an {@link * IdlingResource}. Espresso ignores {@link IdlingResource} with the same name, so always uses * a unique name if you don't want Espresso to ignore your task. * @param androidUiController the controller to use to interact with the Android UI. * @param futureTask the future task that main thread should wait for a completion signal. * @param executor the executor to use for running async tasks within the method. * @param the return value type. * @return the result of the future task. * @throws ExecutionException if any error occurs during executing the future task. * @throws InterruptedException when any internal thread is interrupted. */ public static T loopUntilCompletion( String taskName, UiController androidUiController, Future futureTask, ExecutorService executor) throws ExecutionException, InterruptedException { checkState(Looper.myLooper() == Looper.getMainLooper(), "Expecting to be on main thread!"); FutureIdlingResource idlingResourceFuture = new FutureIdlingResource<>(taskName, futureTask); IdlingRegistry.getInstance().register(idlingResourceFuture); try { // It's fine to ignore this {@code Future} handler, since {@code idlingResourceFuture} should // give us the result/error any way. @SuppressWarnings("unused") Future possiblyIgnoredError = executor.submit(idlingResourceFuture); androidUiController.loopMainThreadUntilIdle(); checkState(idlingResourceFuture.isDone(), "Future task signaled - but it wasn't done."); return idlingResourceFuture.get(); } finally { IdlingRegistry.getInstance().unregister(idlingResourceFuture); } } /** * An {@code IdlingResource} implementation that takes in a {@code Future}, and sends the idle * signal to the main thread when the given {@code Future} is done. * * @param the return value type of this {@code FutureTask}. */ private static class FutureIdlingResource extends FutureTask implements IdlingResource { private final String taskName; // Written from main thread, read from any thread. private volatile ResourceCallback resourceCallback; public FutureIdlingResource(String taskName, final Future future) { super( new Callable() { @Override public T call() throws Exception { return future.get(); } }); this.taskName = checkNotNull(taskName); } @Override public String getName() { return taskName; } @Override public void done() { resourceCallback.onTransitionToIdle(); } @Override public boolean isIdleNow() { return isDone(); } @Override public void registerIdleTransitionCallback(ResourceCallback callback) { this.resourceCallback = callback; } } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/action/ClickAction.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.action; import static androidx.test.espresso.flutter.action.ActionUtil.loopUntilCompletion; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.util.concurrent.Futures.immediateFailedFuture; import static com.google.common.util.concurrent.Futures.immediateFuture; import android.graphics.Rect; import android.view.InputDevice; import android.view.MotionEvent; import android.view.View; import androidx.test.espresso.UiController; import androidx.test.espresso.ViewAction; import androidx.test.espresso.action.GeneralClickAction; import androidx.test.espresso.action.Press; import androidx.test.espresso.action.Tap; import androidx.test.espresso.flutter.api.FlutterTestingProtocol; import androidx.test.espresso.flutter.api.WidgetAction; import androidx.test.espresso.flutter.api.WidgetMatcher; import com.google.common.util.concurrent.ListenableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** A click on the given Flutter widget by issuing gesture events to the Android system. */ public final class ClickAction implements WidgetAction { private static final String GET_LOCAL_RECT_TASK_NAME = "ClickAction#getLocalRect"; private final ExecutorService executor; public ClickAction(@Nonnull ExecutorService executor) { this.executor = checkNotNull(executor); } @Override public ListenableFuture perform( @Nullable WidgetMatcher targetWidget, @Nonnull View flutterView, @Nonnull FlutterTestingProtocol flutterTestingProtocol, @Nonnull UiController androidUiController) { try { Future widgetRectFuture = flutterTestingProtocol.getLocalRect(targetWidget); Rect widgetRectInDp = loopUntilCompletion( GET_LOCAL_RECT_TASK_NAME, androidUiController, widgetRectFuture, executor); WidgetCoordinatesCalculator coordinatesCalculator = new WidgetCoordinatesCalculator(widgetRectInDp); // Clicks at the center of the Flutter widget (with no visibility check), with all the default // settings of a native View's click action. ViewAction clickAction = new GeneralClickAction( Tap.SINGLE, coordinatesCalculator, Press.FINGER, InputDevice.SOURCE_UNKNOWN, MotionEvent.BUTTON_PRIMARY); clickAction.perform(androidUiController, flutterView); // Espresso will wait for the main thread to finish, so nothing else to wait for in the // testing thread. return immediateFuture(null); } catch (InterruptedException ie) { return immediateFailedFuture(ie); } catch (ExecutionException ee) { return immediateFailedFuture(ee.getCause()); } finally { androidUiController.loopMainThreadUntilIdle(); } } @Override public String toString() { return "click"; } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/action/FlutterActions.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.action; import androidx.test.espresso.flutter.api.WidgetAction; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.annotation.Nonnull; /** A collection of actions that can be performed on {@code FlutterView}s or Flutter widgets. */ public final class FlutterActions { private static final ExecutorService taskExecutor = Executors.newCachedThreadPool(); // Do not initialize. private FlutterActions() {} /** * Returns a click action that can be performed on a Flutter widget. * *

The current implementation simply clicks at the center of the widget (with no visibility * checks yet). Internally, it calculates the coordinates to click on screen based on the position * of the matched Flutter widget and also its outer Flutter view, and injects gesture events to * the Android system to mimic a human's click. * *

Try {@link #syntheticClick()} only when this action cannot handle your case properly, e.g. * Flutter's internal state (only accessible within Flutter) affects how the action should * performed. */ public static WidgetAction click() { return new ClickAction(taskExecutor); } /** * Returns a synthetic click action that can be performed on a Flutter widget. * *

Note, this is not a real click gesture event issued from Android system. Espresso delegates * to Flutter engine to perform the action. * *

Always prefer {@link #click()} as it exercises the entire Flutter stack and your Flutter app * by directly injecting key events to the Android system. Uses this {@link #syntheticClick()} * only when there are special cases that {@link #click()} cannot handle properly. */ public static WidgetAction syntheticClick() { return new SyntheticClickAction(); } /** * Returns an action that focuses on the widget (by clicking on it) and types the provided string * into the widget. Appending a \n to the end of the string translates to a ENTER key event. Note: * this method performs a tap on the widget before typing to force the widget into focus, if the * widget already contains text this tap may place the cursor at an arbitrary position within the * text. * *

The Flutter widget must support input methods. * * @param stringToBeTyped the text String that shall be input to the matched widget. Cannot be * {@code null}. */ public static WidgetAction typeText(@Nonnull String stringToBeTyped) { return new FlutterTypeTextAction(stringToBeTyped, taskExecutor); } /** * Returns an action that scrolls to the widget. * *

The widget must be a descendant of a scrollable widget like SingleChildScrollView. */ public static WidgetAction scrollTo() { return new FlutterScrollToAction(); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/action/FlutterScrollToAction.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.action; import android.view.View; import androidx.test.espresso.UiController; import androidx.test.espresso.flutter.api.FlutterTestingProtocol; import androidx.test.espresso.flutter.api.SyntheticAction; import androidx.test.espresso.flutter.api.WidgetAction; import androidx.test.espresso.flutter.api.WidgetMatcher; import com.google.gson.annotations.Expose; import java.util.concurrent.Future; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * An action that scrolls the Scrollable ancestor of the widget until the widget is completely * visible. */ public final class FlutterScrollToAction implements WidgetAction { @Override public Future perform( @Nullable WidgetMatcher targetWidget, @Nonnull View flutterView, @Nonnull FlutterTestingProtocol flutterTestingProtocol, @Nonnull UiController androidUiController) { return flutterTestingProtocol.perform(targetWidget, new ScrollIntoViewAction()); } @Override public String toString() { return "scrollTo"; } static class ScrollIntoViewAction extends SyntheticAction { @Expose private final double alignment; public ScrollIntoViewAction() { this(0.0); } public ScrollIntoViewAction(double alignment) { super("scrollIntoView"); this.alignment = alignment; } } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/action/FlutterTypeTextAction.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.action; import static androidx.test.espresso.flutter.action.ActionUtil.loopUntilCompletion; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.util.concurrent.Futures.allAsList; import static com.google.common.util.concurrent.Futures.immediateFailedFuture; import static com.google.common.util.concurrent.Futures.immediateFuture; import android.graphics.Rect; import android.util.Log; import android.view.InputDevice; import android.view.MotionEvent; import android.view.View; import androidx.test.espresso.UiController; import androidx.test.espresso.ViewAction; import androidx.test.espresso.action.GeneralClickAction; import androidx.test.espresso.action.Press; import androidx.test.espresso.action.Tap; import androidx.test.espresso.action.TypeTextAction; import androidx.test.espresso.flutter.api.FlutterTestingProtocol; import androidx.test.espresso.flutter.api.SyntheticAction; import androidx.test.espresso.flutter.api.WidgetAction; import androidx.test.espresso.flutter.api.WidgetMatcher; import com.google.common.util.concurrent.JdkFutureAdapters; import com.google.common.util.concurrent.ListenableFuture; import com.google.gson.annotations.Expose; import java.util.Locale; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** An action that types text on a Flutter widget. */ public final class FlutterTypeTextAction implements WidgetAction { private static final String TAG = FlutterTypeTextAction.class.getSimpleName(); private static final String GET_LOCAL_RECT_TASK_NAME = "FlutterTypeTextAction#getLocalRect"; private static final String FLUTTER_IDLE_TASK_NAME = "FlutterTypeTextAction#flutterIsIdle"; private final String stringToBeTyped; private final boolean tapToFocus; private final ExecutorService executor; /** * Constructs with the given input string. If the string is empty it results in no-op (nothing is * typed). By default this action sends a tap event to the center of the widget to attain focus * before typing. * * @param stringToBeTyped String To be typed in. */ FlutterTypeTextAction(@Nonnull String stringToBeTyped, @Nonnull ExecutorService executor) { this(stringToBeTyped, executor, true); } /** * Constructs with the given input string. If the string is empty it results in no-op (nothing is * typed). By default this action sends a tap event to the center of the widget to attain focus * before typing. * * @param stringToBeTyped String To be typed in. * @param tapToFocus indicates whether a tap should be sent to the underlying widget before * typing. */ FlutterTypeTextAction( @Nonnull String stringToBeTyped, @Nonnull ExecutorService executor, boolean tapToFocus) { this.stringToBeTyped = checkNotNull(stringToBeTyped, "The text to type in cannot be null."); this.executor = checkNotNull(executor); this.tapToFocus = tapToFocus; } @Override public ListenableFuture perform( @Nullable WidgetMatcher targetWidget, @Nonnull View flutterView, @Nonnull FlutterTestingProtocol flutterTestingProtocol, @Nonnull UiController androidUiController) { // No-op if string is empty. if (stringToBeTyped.length() == 0) { Log.w(TAG, "Text string is empty resulting in no-op (nothing is typed)."); return immediateFuture(null); } try { ListenableFuture setTextEntryEmulationFuture = JdkFutureAdapters.listenInPoolThread( flutterTestingProtocol.perform(null, new SetTextEntryEmulationAction(false))); ListenableFuture widgetRectFuture = JdkFutureAdapters.listenInPoolThread(flutterTestingProtocol.getLocalRect(targetWidget)); // Waits until both Futures return and then proceeds. Rect widgetRectInDp = (Rect) loopUntilCompletion( GET_LOCAL_RECT_TASK_NAME, androidUiController, allAsList(widgetRectFuture, setTextEntryEmulationFuture), executor) .get(0); // Clicks at the center of the Flutter widget (with no visibility check). // // Calls the click action separately so we get a chance to ensure Flutter is idle before // typing text. WidgetCoordinatesCalculator coordinatesCalculator = new WidgetCoordinatesCalculator(widgetRectInDp); if (tapToFocus) { GeneralClickAction clickAction = new GeneralClickAction( Tap.SINGLE, coordinatesCalculator, Press.FINGER, InputDevice.SOURCE_UNKNOWN, MotionEvent.BUTTON_PRIMARY); clickAction.perform(androidUiController, flutterView); loopUntilCompletion( FLUTTER_IDLE_TASK_NAME, androidUiController, flutterTestingProtocol.waitUntilIdle(), executor); } // Then types in text. ViewAction typeTextAction = new TypeTextAction(stringToBeTyped, false); typeTextAction.perform(androidUiController, flutterView); // Espresso will wait for the main thread to finish, so nothing else to wait for in the // testing thread. return immediateFuture(null); } catch (InterruptedException ie) { return immediateFailedFuture(ie); } catch (ExecutionException ee) { return immediateFailedFuture(ee.getCause()); } finally { androidUiController.loopMainThreadUntilIdle(); } } @Override public String toString() { return String.format(Locale.ROOT, "type text(%s)", stringToBeTyped); } /** * The {@link SyntheticAction} that configures text entry emulation. * *

If the text entry emulation is enabled, the operating system's configured keyboard will not * be invoked when the widget is focused. Explicitly disables the text entry emulation when text * input is supposed to be sent using the system's keyboard. * *

By default, the text entry emulation is enabled in the Flutter testing protocol. */ private static final class SetTextEntryEmulationAction extends SyntheticAction { @Expose private final boolean enabled; /** * Constructs with the given text entry emulation setting. * * @param enabled whether the text entry emulation is enabled. When {@code enabled} is {@code * true}, the system's configured keyboard will not be invoked when the widget is focused. */ public SetTextEntryEmulationAction(boolean enabled) { super("set_text_entry_emulation"); this.enabled = enabled; } /** * Constructs with the given text entry emulation setting and also a timeout setting for this * action. * * @param enabled whether the text entry emulation is enabled. When {@code enabled} is {@code * true}, the system's configured keyboard will not be invoked when the widget is focused. * @param timeOutInMillis the timeout setting of this action. */ public SetTextEntryEmulationAction(boolean enabled, long timeOutInMillis) { super("set_text_entry_emulation", timeOutInMillis); this.enabled = enabled; } } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/action/FlutterViewAction.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.action; import static androidx.test.espresso.flutter.action.ActionUtil.loopUntilCompletion; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.isFlutterView; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.util.concurrent.Futures.transformAsync; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import android.os.Looper; import android.view.View; import androidx.test.annotation.Beta; import androidx.test.espresso.IdlingRegistry; import androidx.test.espresso.IdlingResource; import androidx.test.espresso.UiController; import androidx.test.espresso.ViewAction; import androidx.test.espresso.flutter.api.FlutterAction; import androidx.test.espresso.flutter.api.FlutterTestingProtocol; import androidx.test.espresso.flutter.api.WidgetMatcher; import androidx.test.espresso.flutter.internal.idgenerator.IdGenerator; import androidx.test.espresso.flutter.internal.jsonrpc.JsonRpcClient; import androidx.test.espresso.flutter.internal.protocol.impl.DartVmService; import androidx.test.espresso.flutter.internal.protocol.impl.DartVmServiceUtil; import androidx.test.espresso.flutter.internal.protocol.impl.FlutterProtocolException; import com.google.common.annotations.VisibleForTesting; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.JdkFutureAdapters; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import io.flutter.embedding.android.FlutterView; import io.flutter.embedding.engine.FlutterJNI; import java.net.URI; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import okhttp3.OkHttpClient; import org.hamcrest.Matcher; /** * A {@code ViewAction} which performs an action on the given {@code FlutterView}. * *

This class acts as a bridge to perform {@code WidgetAction} on a Flutter widget on the given * {@code FlutterView}. */ @Beta public final class FlutterViewAction implements ViewAction { private static final String FLUTTER_IDLE_TASK_NAME = "flutterIdlingResource"; private final SettableFuture resultFuture = SettableFuture.create(); private final WidgetMatcher widgetMatcher; private final FlutterAction widgetAction; private final OkHttpClient webSocketClient; private final IdGenerator messageIdGenerator; private final ExecutorService taskExecutor; /** * Constructs an instance based on the given params. * * @param widgetMatcher the matcher that uniquely matches a widget on the {@code FlutterView}. * Could be {@code null} if this is a universal action that doesn't apply to any specific * widget. * @param widgetAction the action to be performed on the matched Flutter widget. * @param webSocketClient the WebSocket client that shall be used in the {@code * FlutterTestingProtocol}. * @param messageIdGenerator an ID generator that shall be used in the {@code * FlutterTestingProtocol}. * @param taskExecutor the task executor that shall be used in the {@code WidgetAction}. */ public FlutterViewAction( WidgetMatcher widgetMatcher, FlutterAction widgetAction, OkHttpClient webSocketClient, IdGenerator messageIdGenerator, ExecutorService taskExecutor) { this.widgetMatcher = widgetMatcher; this.widgetAction = checkNotNull(widgetAction); this.webSocketClient = checkNotNull(webSocketClient); this.messageIdGenerator = checkNotNull(messageIdGenerator); this.taskExecutor = checkNotNull(taskExecutor); } @Override public Matcher getConstraints() { return isFlutterView(); } @Override public String getDescription() { return String.format( "Perform a %s action on the Flutter widget matched %s.", widgetAction, widgetMatcher); } @Override public void perform(UiController uiController, View flutterView) { // There could be a gap between when the Flutter view is available in the view hierarchy and the // engine & Dart isolates are actually up and running. Check whether the first frame has been // rendered before proceeding in an unblocking way. loopUntilFlutterViewRendered(flutterView, uiController); // The url {@code FlutterNativeView} returns is the http url that the Dart VM Observatory http // server serves at. Need to convert to the one that the WebSocket uses. URI dartVmServiceProtocolUrl = DartVmServiceUtil.getServiceProtocolUri(FlutterJNI.getObservatoryUri()); String isolateId = DartVmServiceUtil.getDartIsolateId(flutterView); final FlutterTestingProtocol flutterTestingProtocol = new DartVmService( isolateId, new JsonRpcClient(webSocketClient, dartVmServiceProtocolUrl), messageIdGenerator, taskExecutor); try { // First checks the testing protocol is ready for use and then waits until the Flutter app is // idle before executing the action. ListenableFuture testingProtocolReadyFuture = JdkFutureAdapters.listenInPoolThread(flutterTestingProtocol.connect()); AsyncFunction flutterIdleFunc = new AsyncFunction() { public ListenableFuture apply(Void readyResult) { return JdkFutureAdapters.listenInPoolThread(flutterTestingProtocol.waitUntilIdle()); } }; ListenableFuture flutterIdleFuture = transformAsync(testingProtocolReadyFuture, flutterIdleFunc, taskExecutor); loopUntilCompletion(FLUTTER_IDLE_TASK_NAME, uiController, flutterIdleFuture, taskExecutor); perform(flutterView, flutterTestingProtocol, uiController); } catch (ExecutionException ee) { resultFuture.setException(ee.getCause()); } catch (InterruptedException ie) { resultFuture.setException(ie); } } @VisibleForTesting void perform( View flutterView, FlutterTestingProtocol flutterTestingProtocol, UiController uiController) { final ListenableFuture actionResultFuture = JdkFutureAdapters.listenInPoolThread( widgetAction.perform(widgetMatcher, flutterView, flutterTestingProtocol, uiController)); actionResultFuture.addListener( new Runnable() { @Override public void run() { try { resultFuture.set(actionResultFuture.get()); } catch (ExecutionException | InterruptedException e) { resultFuture.setException(e); } } }, directExecutor()); } /** Blocks until this action has completed execution. */ public T waitUntilCompleted() throws ExecutionException, InterruptedException { checkState(Looper.myLooper() != Looper.getMainLooper(), "On main thread!"); return resultFuture.get(); } /** Blocks until this action has completed execution with a configurable timeout. */ public T waitUntilCompleted(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { checkState(Looper.myLooper() != Looper.getMainLooper(), "On main thread!"); return resultFuture.get(timeout, unit); } private static void loopUntilFlutterViewRendered(View flutterView, UiController uiController) { FlutterViewRenderedIdlingResource idlingResource = new FlutterViewRenderedIdlingResource(flutterView); try { IdlingRegistry.getInstance().register(idlingResource); uiController.loopMainThreadUntilIdle(); } finally { IdlingRegistry.getInstance().unregister(idlingResource); } } /** * An {@link IdlingResource} that checks whether the Flutter view's first frame has been rendered * in an unblocking way. */ static final class FlutterViewRenderedIdlingResource implements IdlingResource { private final View flutterView; // Written from main thread, read from any thread. private volatile ResourceCallback resourceCallback; FlutterViewRenderedIdlingResource(View flutterView) { this.flutterView = checkNotNull(flutterView); } @Override public String getName() { return FlutterViewRenderedIdlingResource.class.getSimpleName(); } @SuppressWarnings("deprecation") @Override public boolean isIdleNow() { boolean isIdle = false; if (flutterView instanceof FlutterView) { isIdle = ((FlutterView) flutterView).hasRenderedFirstFrame(); } else if (flutterView instanceof io.flutter.view.FlutterView) { isIdle = ((io.flutter.view.FlutterView) flutterView).hasRenderedFirstFrame(); } else { throw new FlutterProtocolException( String.format("This is not a Flutter View instance [id: %d].", flutterView.getId())); } if (isIdle) { resourceCallback.onTransitionToIdle(); } return isIdle; } @Override public void registerIdleTransitionCallback(ResourceCallback callback) { resourceCallback = callback; } } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/action/SyntheticClickAction.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.action; import android.view.View; import androidx.test.annotation.Beta; import androidx.test.espresso.UiController; import androidx.test.espresso.flutter.api.FlutterTestingProtocol; import androidx.test.espresso.flutter.api.SyntheticAction; import androidx.test.espresso.flutter.api.WidgetAction; import androidx.test.espresso.flutter.api.WidgetMatcher; import java.util.concurrent.Future; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * A synthetic click on a Flutter widget. * *

Note, this is not a real click gesture event issued from Android system. Espresso delegates to * Flutter engine to perform the {@link SyntheticClick} action. */ @Beta public final class SyntheticClickAction implements WidgetAction { @Override public Future perform( @Nullable WidgetMatcher targetWidget, @Nonnull View flutterView, @Nonnull FlutterTestingProtocol flutterTestingProtocol, @Nonnull UiController androidUiController) { return flutterTestingProtocol.perform(targetWidget, new SyntheticClick()); } @Override public String toString() { return "click"; } static class SyntheticClick extends SyntheticAction { public SyntheticClick() { super("tap"); } } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/action/WaitUntilIdleAction.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.action; import android.view.View; import androidx.test.espresso.UiController; import androidx.test.espresso.flutter.api.FlutterTestingProtocol; import androidx.test.espresso.flutter.api.WidgetAction; import androidx.test.espresso.flutter.api.WidgetMatcher; import java.util.concurrent.Future; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** An action that ensures Flutter is in an idle state. */ public final class WaitUntilIdleAction implements WidgetAction { @Override public Future perform( @Nullable WidgetMatcher targetWidget, @Nonnull View flutterView, @Nonnull FlutterTestingProtocol flutterTestingProtocol, @Nonnull UiController androidUiController) { return flutterTestingProtocol.waitUntilIdle(); } @Override public String toString() { return "action that waits until Flutter's idle."; } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/action/WidgetCoordinatesCalculator.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.action; import static com.google.common.base.Preconditions.checkNotNull; import android.graphics.Rect; import android.util.DisplayMetrics; import android.util.Log; import android.view.View; import androidx.test.espresso.action.CoordinatesProvider; import java.util.Arrays; /** Provides coordinates of a Flutter widget. */ final class WidgetCoordinatesCalculator implements CoordinatesProvider { private static final String TAG = WidgetCoordinatesCalculator.class.getSimpleName(); private final Rect widgetRectInDp; /** * Constructs with the local (as relative to the outer Flutter view) coordinates of a Flutter * widget in the unit of dp. * * @param widgetRectInDp the local widget coordinates in dp. */ public WidgetCoordinatesCalculator(Rect widgetRectInDp) { this.widgetRectInDp = checkNotNull(widgetRectInDp); } @Override public float[] calculateCoordinates(View flutterView) { int deviceDensityDpi = flutterView.getContext().getResources().getDisplayMetrics().densityDpi; Rect widgetRectInPixel = convertDpToPixel(widgetRectInDp, deviceDensityDpi); float widgetCenterX = (widgetRectInPixel.left + widgetRectInPixel.right) / 2; float widgetCenterY = (widgetRectInPixel.top + widgetRectInPixel.bottom) / 2; int[] viewCords = new int[] {0, 0}; flutterView.getLocationOnScreen(viewCords); float[] coords = new float[] {viewCords[0] + widgetCenterX, viewCords[1] + widgetCenterY}; Log.d( TAG, String.format( "Clicks on widget[%s] on Flutter View[%d, %d][width:%d, height:%d] at coordinates" + " [%s] on screen", widgetRectInPixel, viewCords[0], viewCords[1], flutterView.getWidth(), flutterView.getHeight(), Arrays.toString(coords))); return coords; } private static Rect convertDpToPixel(Rect rectInDp, int densityDpi) { checkNotNull(rectInDp); int left = (int) convertDpToPixel(rectInDp.left, densityDpi); int top = (int) convertDpToPixel(rectInDp.top, densityDpi); int right = (int) convertDpToPixel(rectInDp.right, densityDpi); int bottom = (int) convertDpToPixel(rectInDp.bottom, densityDpi); return new Rect(left, top, right, bottom); } private static float convertDpToPixel(float dp, int densityDpi) { return dp * ((float) densityDpi / DisplayMetrics.DENSITY_DEFAULT); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/action/WidgetInfoFetcher.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.action; import android.view.View; import androidx.test.espresso.UiController; import androidx.test.espresso.flutter.api.FlutterAction; import androidx.test.espresso.flutter.api.FlutterTestingProtocol; import androidx.test.espresso.flutter.api.WidgetMatcher; import androidx.test.espresso.flutter.model.WidgetInfo; import java.util.concurrent.Future; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** A {@link FlutterAction} that retrieves the {@code WidgetInfo} of the matched Flutter widget. */ public final class WidgetInfoFetcher implements FlutterAction { @Override public Future perform( @Nullable WidgetMatcher targetWidget, @Nonnull View flutterView, @Nonnull FlutterTestingProtocol flutterTestingProtocol, @Nonnull UiController androidUiController) { return flutterTestingProtocol.matchWidget(targetWidget); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/api/FlutterAction.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.api; import android.view.View; import androidx.test.espresso.UiController; import java.util.concurrent.Future; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * Represents a Flutter widget action. * *

This interface is part of Espresso-Flutter testing framework. Users should usually expect no * return value for an action and use the {@code WidgetAction} for customizing an action on a * Flutter widget. * * @param The type of the action result. */ public interface FlutterAction { /** Performs an action on the given Flutter widget and gets its return value. */ Future perform( @Nullable WidgetMatcher targetWidget, @Nonnull View flutterView, @Nonnull FlutterTestingProtocol flutterTestingProtocol, @Nonnull UiController androidUiController); } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/api/FlutterTestingProtocol.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.api; import android.graphics.Rect; import androidx.test.espresso.flutter.model.WidgetInfo; import com.google.common.annotations.Beta; import java.util.concurrent.Future; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** Defines the testing protocol/semantics between Espresso and Flutter. */ @Beta public interface FlutterTestingProtocol { /** Returns a future that waits until the Flutter testing protocol is in a usable state. */ public Future connect(); /** * Performs a synthetic action on the Flutter widget that matches the given {@code widgetMatcher}. * *

If failed to perform the given {@code action}, returns a {@code Future} containing an {@code * ExecutionException} that wraps the following exception: * *

    *
  • {@code AmbiguousWidgetMatcherException} if the given {@code widgetMatcher} matched * multiple widgets in the hierarchy when only one widget was expected. *
  • {@code NoMatchingWidgetException} if the given {@code widgetMatcher} did not match any * widget in the Flutter UI hierarchy. *
  • {@code ConnectException} if connection error occurred. *
* * @param widgetMatcher the matcher to match a Flutter widget. If {@code null}, {@code action} is * not performed on a specific widget. * @param action the action to be performed on the widget. * @return a {@code Future} representing pending completion of performing the action, or yields an * exception if the action was failed to perform. */ Future perform(@Nullable WidgetMatcher widgetMatcher, @Nonnull SyntheticAction action); /** * Returns a Java representation of the Flutter widget that matches the given widget matcher. * *

If failed to find a matching widget, returns a {@code Future} containing an {@code * ExecutionException} that wraps the following exception: * *

    *
  • {@code AmbiguousWidgetMatcherException} if the given {@code widgetMatcher} matched * multiple widgets in the hierarchy when only one widget was expected. *
  • {@code NoMatchingWidgetException} if the given {@code widgetMatcher} did not match any * widget in the Flutter UI hierarchy. *
  • {@code ConnectException} if connection error occurred. *
* * @param widgetMatcher the matcher to match a Flutter widget. Cannot be {@code null}. * @return a {@code Future} representing pending completion of the matching operation. */ Future matchWidget(@Nonnull WidgetMatcher widgetMatcher); /** * Returns the local (as relative to its outer Flutter View) rectangle area of a widget that * matches the given widget matcher. * * @param widgetMatcher the matcher to match a Flutter widget. Cannot be {@code null}. * @return a rectangle area where the matched widget lives, in the unit of dp (Density-independent * Pixel). */ Future getLocalRect(@Nonnull WidgetMatcher widgetMatcher); /** Waits until the Flutter frame is in a stable state. */ Future waitUntilIdle(); /** Releases all the resources associated with this testing protocol connection. */ void close(); } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/api/SyntheticAction.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.api; import static androidx.test.espresso.flutter.common.Constants.DEFAULT_INTERACTION_TIMEOUT; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.Beta; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import java.util.Objects; import javax.annotation.Nonnull; /** * Base Flutter synthetic action. * *

A synthetic action is not a real gesture event issued to the Android system, rather it's an * action that's performed via Flutter engine. It's supposed to be used for complex interactions or * those that are brittle if performed through Android system. Most of the actions should be * associated with a {@link WidgetMatcher}, but some may not, e.g. an action that checks the * rendering status of the entire {@link io.flutter.view.FlutterView}. */ @Beta public abstract class SyntheticAction { @Expose @SerializedName("command") protected String actionId; @Expose @SerializedName("timeout") protected long timeOutInMillis; protected SyntheticAction(@Nonnull String actionId) { this(actionId, DEFAULT_INTERACTION_TIMEOUT.toMillis()); } protected SyntheticAction(@Nonnull String actionId, long timeOutInMillis) { this.actionId = checkNotNull(actionId); this.timeOutInMillis = timeOutInMillis; } @Override public String toString() { return actionId; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } else if (obj instanceof SyntheticAction) { SyntheticAction otherAction = (SyntheticAction) obj; return Objects.equals(actionId, otherAction.actionId); } else { return false; } } @Override public int hashCode() { return Objects.hashCode(actionId); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/api/WidgetAction.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.api; import android.view.View; import androidx.test.espresso.UiController; import com.google.common.annotations.Beta; import java.util.concurrent.Future; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * Responsible for performing an interaction on the given Flutter widget. * *

This is part of the Espresso-Flutter test framework public API - developers are free to write * their own {@code WidgetAction} implementation when necessary. */ @Beta public interface WidgetAction extends FlutterAction { /** * Performs this action on the given Flutter widget. * *

If the given {@code targetWidget} is {@code null}, this action shall be performed on the * entire {@code FlutterView} in context. * * @param targetWidget the matcher that uniquely identifies a Flutter widget on the given {@code * FlutterView}. {@code Null} if it's a global action on the {@code FlutterView} in context. * @param flutterView the Flutter view that this widget lives in. * @param flutterTestingProtocol the channel for talking to Flutter app directly. * @param androidUiController the interface for issuing UI operations to the Android system. * @return a {@code Future} representing pending completion of performing the action, or yields an * exception if the action failed to perform. */ @Override Future perform( @Nullable WidgetMatcher targetWidget, @Nonnull View flutterView, @Nonnull FlutterTestingProtocol flutterTestingProtocol, @Nonnull UiController androidUiController); } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/api/WidgetAssertion.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.api; import android.view.View; import androidx.test.espresso.flutter.model.WidgetInfo; import com.google.common.annotations.Beta; /** * Similar to a {@code ViewAssertion}, a {@link WidgetAssertion} is responsible for performing an * assertion on a Flutter widget. */ @Beta public interface WidgetAssertion { /** * Checks the state of the Flutter widget. * * @param flutterView the Flutter view that this widget lives in. * @param widgetInfo the instance that represents a Flutter widget. */ void check(View flutterView, WidgetInfo widgetInfo); } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/api/WidgetMatcher.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.api; import static com.google.common.base.Preconditions.checkNotNull; import androidx.test.espresso.flutter.model.WidgetInfo; import com.google.common.annotations.Beta; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import javax.annotation.Nonnull; import org.hamcrest.TypeSafeMatcher; /** * Base matcher for Flutter widgets. * *

A widget matcher's function is two-fold: * *

    *
  • A matcher that can be passed into Flutter for selecting a Flutter widget. *
  • Works with the {@code MatchesWidgetAssertion} to assert on a widget's properties. *
*/ @Beta public abstract class WidgetMatcher extends TypeSafeMatcher { @Expose @SerializedName("finderType") protected String matcherId; /** * Constructs a {@code WidgetMatcher} instance with the given {@code matcherId}. * * @param matcherId the matcher id that represents this widget matcher. */ public WidgetMatcher(@Nonnull String matcherId) { this.matcherId = checkNotNull(matcherId); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/assertion/FlutterAssertions.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.assertion; import static com.google.common.base.Preconditions.checkNotNull; import static org.hamcrest.MatcherAssert.assertThat; import android.view.View; import androidx.test.espresso.flutter.api.WidgetAssertion; import androidx.test.espresso.flutter.model.WidgetInfo; import javax.annotation.Nonnull; import org.hamcrest.Matcher; /** Collection of common {@link WidgetAssertion} instances. */ public final class FlutterAssertions { /** * Returns a generic {@link WidgetAssertion} that asserts that a Flutter widget exists and is * matched by the given widget matcher. */ public static WidgetAssertion matches(@Nonnull Matcher widgetMatcher) { return new MatchesWidgetAssertion(checkNotNull(widgetMatcher, "Matcher cannot be null.")); } /** A widget assertion that checks whether a widget is matched by the given matcher. */ static class MatchesWidgetAssertion implements WidgetAssertion { private final Matcher widgetMatcher; private MatchesWidgetAssertion(Matcher widgetMatcher) { this.widgetMatcher = checkNotNull(widgetMatcher); } @Override public void check(View flutterView, WidgetInfo widgetInfo) { assertThat(widgetInfo, widgetMatcher); } } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/assertion/FlutterViewAssertion.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.assertion; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.isFlutterView; import static com.google.common.base.Preconditions.checkNotNull; import android.view.View; import androidx.test.espresso.NoMatchingViewException; import androidx.test.espresso.ViewAssertion; import androidx.test.espresso.flutter.api.WidgetAssertion; import androidx.test.espresso.flutter.exception.InvalidFlutterViewException; import androidx.test.espresso.flutter.model.WidgetInfo; import androidx.test.espresso.util.HumanReadables; /** * A {@code ViewAssertion} which performs an action on the given Flutter view. * *

This class acts as a bridge to perform {@code WidgetAssertion} on a Flutter widget on the * given Flutter view. */ public final class FlutterViewAssertion implements ViewAssertion { private final WidgetAssertion assertion; private final WidgetInfo widgetInfo; public FlutterViewAssertion(WidgetAssertion assertion, WidgetInfo widgetInfo) { this.assertion = checkNotNull(assertion, "Widget assertion cannot be null."); this.widgetInfo = checkNotNull(widgetInfo, "The widget info to be asserted on cannot be null."); } @Override public void check(View view, NoMatchingViewException noViewFoundException) { if (view == null) { throw noViewFoundException; } else if (!isFlutterView().matches(view)) { throw new InvalidFlutterViewException( String.format("Not a valid Flutter view:%s", HumanReadables.describe(view))); } else { assertion.check(view, widgetInfo); } } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/common/Constants.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.common; import java.util.concurrent.TimeUnit; /** A utility class to hold various constants used by the Espresso-Flutter library. */ public final class Constants { // Do not initialize. private Constants() {} /** Default timeout for actions and asserts like {@code WidgetAction}. */ public static final Duration DEFAULT_INTERACTION_TIMEOUT = new Duration(10, TimeUnit.SECONDS); } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/common/Duration.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.common; import static com.google.common.base.Preconditions.checkNotNull; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; /** * A simple implementation of a time duration, supposed to be used within the Espresso-Flutter * library. * *

This class is immutable. */ public final class Duration { private final long quantity; private final TimeUnit unit; /** * Initializes a Duration instance. * * @param quantity the amount of time in the given unit. * @param unit the time unit. Cannot be null. */ public Duration(long quantity, TimeUnit unit) { this.quantity = quantity; this.unit = checkNotNull(unit, "Time unit cannot be null."); } /** Returns the amount of time. */ public long getQuantity() { return quantity; } /** Returns the time unit. */ public TimeUnit getUnit() { return unit; } /** Returns the amount of time in milliseconds. */ public long toMillis() { return TimeUnit.MILLISECONDS.convert(quantity, unit); } /** * Returns a new Duration instance that adds this instance to the given {@code duration}. If the * given {@code duration} is null, this method simply returns this instance. */ public Duration plus(@Nullable Duration duration) { if (duration == null) { return this; } long add = unit.convert(duration.quantity, duration.unit); long newQuantity = quantity + add; return new Duration(newQuantity, unit); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/exception/AmbiguousWidgetMatcherException.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.exception; import androidx.test.espresso.EspressoException; /** * Indicates that a {@code WidgetMatcher} matched multiple widgets in the Flutter UI hierarchy when * only one widget was expected. */ public final class AmbiguousWidgetMatcherException extends RuntimeException implements EspressoException { public AmbiguousWidgetMatcherException(String message) { super(message); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/exception/InvalidFlutterViewException.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.exception; import androidx.test.espresso.EspressoException; /** Indicates that the {@code View} that Espresso operates on is not a valid Flutter View. */ public final class InvalidFlutterViewException extends RuntimeException implements EspressoException { /** Constructs with an error message. */ public InvalidFlutterViewException(String message) { super(message); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/exception/NoMatchingWidgetException.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.exception; import androidx.test.espresso.EspressoException; /** * Indicates that a given {@code WidgetMatcher} did not match any widgets in the Flutter UI * hierarchy. */ public final class NoMatchingWidgetException extends RuntimeException implements EspressoException { public NoMatchingWidgetException(String message) { super(message); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/idgenerator/IdException.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.idgenerator; /** Thrown if an ID cannot be generated. */ public final class IdException extends RuntimeException { private static final long serialVersionUID = 0L; public IdException() { super(); } public IdException(String message) { super(message); } public IdException(String message, Throwable throwable) { super(message, throwable); } public IdException(Throwable throwable) { super(throwable); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/idgenerator/IdGenerator.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.idgenerator; import com.google.errorprone.annotations.CanIgnoreReturnValue; /** Generates unique IDs of the parameterized type. */ public interface IdGenerator { /** * Returns a new, unique ID. * * @throws IdException if there were any errors in getting an ID. */ @CanIgnoreReturnValue T next(); } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/idgenerator/IdGenerators.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.idgenerator; import static com.google.common.base.Preconditions.checkArgument; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; /** Some simple in-memory ID generators. */ public final class IdGenerators { private IdGenerators() {} private static final IdGenerator UUID_STRING_GENERATOR = new IdGenerator() { @Override public String next() { return UUID.randomUUID().toString(); } }; /** * Returns a {@code Integer} ID generator whose next value is the value passed in. The value * returned increases by one each time until {@code Integer.MAX_VALUE}. After that an {@code * IdException} is thrown. This IdGenerator is threadsafe. */ public static IdGenerator newIntegerIdGenerator(int nextValue) { checkArgument(nextValue >= 0, "ID values must be non-negative"); final AtomicInteger nextInt = new AtomicInteger(nextValue); return new IdGenerator() { @Override public Integer next() { int value = nextInt.getAndIncrement(); if (value >= 0) { return value; } // Make sure that all subsequent calls throw by setting to the most // negative value possible. nextInt.set(Integer.MIN_VALUE); throw new IdException("Returned the last integer value available"); } }; } /** * Returns a {@code Integer} ID generator whose next value is one. The value returned increases by * one each time until {@code Integer.MAX_VALUE}. After that an {@code IdException} is thrown. * This IdGenerator is threadsafe. */ public static IdGenerator newIntegerIdGenerator() { return newIntegerIdGenerator(1); } /** * Returns a {@code String} ID generator that passes ID requests to {@link UUID#randomUUID()}, * thereby generating type-4 (pseudo-randomly generated) UUIDs. */ public static IdGenerator randomUuidStringGenerator() { return UUID_STRING_GENERATOR; } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/jsonrpc/JsonRpcClient.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.jsonrpc; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.util.concurrent.Futures.immediateFailedFuture; import static com.google.common.util.concurrent.Futures.immediateFuture; import android.util.Log; import androidx.test.espresso.flutter.internal.jsonrpc.message.JsonRpcRequest; import androidx.test.espresso.flutter.internal.jsonrpc.message.JsonRpcResponse; import com.google.common.collect.Maps; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import java.net.ConnectException; import java.net.URI; import java.util.concurrent.ConcurrentMap; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.WebSocket; import okhttp3.WebSocketListener; /** * A client that can be used to talk to a WebSocket-based JSON-RPC server. * *

One {@code JsonRpcClient} instance is not supposed to be shared between multiple threads. * Always create a new instance of {@code JsonRpcClient} for connecting to a new JSON-RPC URI, but * try to reuse the {@link OkHttpClient} instance, which is thread-safe and maintains a thread pool * in handling requests and responses. */ public class JsonRpcClient { private static final String TAG = JsonRpcClient.class.getSimpleName(); private static final int NORMAL_CLOSURE_STATUS = 1000; private final URI webSocketUri; private final ConcurrentMap> responseFutures; private WebSocket webSocketConn; /** {@code client} can be shared between multiple {@code JsonRpcClient}s. */ public JsonRpcClient(OkHttpClient client, URI webSocketUri) { this.webSocketUri = checkNotNull(webSocketUri, "WebSocket URL can't be null."); responseFutures = Maps.newConcurrentMap(); connect(checkNotNull(client, "OkHttpClient can't be null."), webSocketUri); } private void connect(OkHttpClient client, URI webSocketUri) { Request request = new Request.Builder().url(webSocketUri.toString()).build(); WebSocketListener webSocketListener = new WebSocketListenerImpl(); webSocketConn = client.newWebSocket(request, webSocketListener); } /** Closes the web socket connection. Non-blocking, and will return immediately. */ public void disconnect() { if (webSocketConn != null) { webSocketConn.close(NORMAL_CLOSURE_STATUS, "Client request closing. All requests handled."); } } /** * Sends a JSON-RPC request and returns a {@link ListenableFuture} with which the client could * wait on response. If the {@code request} is a JSON-RPC notification, this method returns * immediately with a {@code null} response. * * @param request the JSON-RPC request to be sent. * @return a {@code ListenableFuture} representing pending completion of the request, or yields an * {@code ExecutionException}, which wraps a {@code ConnectException} if failed to send the * request. */ public ListenableFuture request(JsonRpcRequest request) { checkNotNull(request, "JSON-RPC request shouldn't be null."); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d( TAG, String.format("JSON-RPC Request sent to uri %s: %s.", webSocketUri, request.toJson())); } if (webSocketConn == null) { ConnectException e = new ConnectException("WebSocket connection was not initiated correctly."); return immediateFailedFuture(e); } synchronized (responseFutures) { // Holding the lock of responseFutures for send-and-add operations, so that we could make sure // to add its ListenableFuture to the responseFutures map before the thread of // {@code WebSocketListenerImpl#onMessage} method queries the map. boolean succeeded = webSocketConn.send(request.toJson()); if (!succeeded) { ConnectException e = new ConnectException("Failed to send request: " + request); return immediateFailedFuture(e); } if (isNullOrEmpty(request.getId())) { // Request id is null or empty. This is a notification request, so returns immediately. return immediateFuture(null); } else { SettableFuture responseFuture = SettableFuture.create(); responseFutures.put(request.getId(), responseFuture); return responseFuture; } } } /** A callback listener that handles incoming web socket messages. */ private class WebSocketListenerImpl extends WebSocketListener { @Override public void onMessage(WebSocket webSocket, String response) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, String.format("JSON-RPC response received: %s.", response)); } JsonRpcResponse responseObj = JsonRpcResponse.fromJson(response); synchronized (responseFutures) { if (isNullOrEmpty(responseObj.getId()) || !responseFutures.containsKey(responseObj.getId())) { Log.w( TAG, String.format( "Received a message with empty or unknown ID: %s. Drop the message.", responseObj.getId())); return; } SettableFuture responseFuture = responseFutures.remove(responseObj.getId()); responseFuture.set(responseObj); } } @Override public void onClosing(WebSocket webSocket, int code, String reason) { Log.d( TAG, String.format( "Server requested connection close with code %d, reason: %s", code, reason)); webSocket.close(NORMAL_CLOSURE_STATUS, "Server requested closing connection."); } @Override public void onFailure(WebSocket webSocket, Throwable t, Response response) { Log.w(TAG, String.format("Failed to deliver message with error: %s.", t.getMessage())); throw new RuntimeException("WebSocket request failure.", t); } } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/jsonrpc/message/ErrorObject.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.jsonrpc.message; import com.google.gson.JsonObject; import java.util.Objects; /** * A class for holding the error object in {@code JsonRpcResponse}. * *

See https://www.jsonrpc.org/specification#error_object for detailed specification. */ public class ErrorObject { private final int code; private final String message; private final JsonObject data; public ErrorObject(int code, String message) { this(code, message, null); } public ErrorObject(int code, String message, JsonObject data) { this.code = code; this.message = message; this.data = data; } /** Gets the error code. */ public int getCode() { return code; } /** Gets the error message. */ public String getMessage() { return message; } /** Gets the additional information about the error. Could be null. */ public JsonObject getData() { return data; } @Override public boolean equals(Object obj) { if (obj instanceof ErrorObject) { ErrorObject errorObject = (ErrorObject) obj; return errorObject.code == this.code && Objects.equals(errorObject.message, this.message) && Objects.equals(errorObject.data, this.data); } else { return false; } } @Override public int hashCode() { int hash = code; hash = hash * 31 + Objects.hashCode(message); hash = hash * 31 + Objects.hashCode(data); return hash; } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/jsonrpc/message/JsonRpcRequest.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.jsonrpc.message; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Strings.isNullOrEmpty; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.annotations.SerializedName; import java.util.Objects; import javax.annotation.Nullable; /** * JSON-RPC 2.0 request object. * *

See https://www.jsonrpc.org/specification for detailed specification. */ public final class JsonRpcRequest { private static final Gson gson = new Gson(); private static final String JSON_RPC_VERSION = "2.0"; /** Specifying the version of the JSON-RPC protocol. Must be "2.0". */ @SerializedName("jsonrpc") private final String version; /** * An identifier of the request. Could be String, a number, or null. In this implementation, we * always use String as the type. If null, this is a notification and no response is required. */ @Nullable private final String id; /** A String containing the name of the method to be invoked. */ private final String method; /** Parameter values to be used during the invocation of the method. */ private JsonObject params; /** * Deserializes the given Json string to a {@code JsonRpcRequest} object. * * @param jsonString the string from which the object is to be deserialized. * @return the deserialized object. */ public static JsonRpcRequest fromJson(String jsonString) { checkArgument(!isNullOrEmpty(jsonString), "Json string cannot be null or empty."); JsonRpcRequest request = gson.fromJson(jsonString, JsonRpcRequest.class); checkState(JSON_RPC_VERSION.equals(request.getVersion()), "JSON-RPC version must be 2.0."); checkState( !isNullOrEmpty(request.getMethod()), "JSON-RPC request must contain the method field."); return request; } /** * Constructs with the given method name. The JSON-RPC version will be defaulted to "2.0". * * @param method the method name of this request. */ private JsonRpcRequest(String method) { this(null, method); } /** * Constructs with the given id and method name. The JSON-RPC version will be defaulted to "2.0". * * @param id the id of this request. * @param method the method name of this request. */ private JsonRpcRequest(@Nullable String id, String method) { this.version = JSON_RPC_VERSION; this.id = id; this.method = checkNotNull(method, "JSON-RPC request method cannot be null."); } /** * Gets the JSON-RPC version. * * @return the JSON-RPC version. Should always be "2.0". */ public String getVersion() { return version; } /** * Gets the id of this JSON-RPC request. * * @return the id of this request. Returns null if this is a notification request. */ public String getId() { return id; } /** * Gets the method name of this JSON-RPC request. * * @return the method name. */ public String getMethod() { return method; } /** Gets the params used in this request. */ public JsonObject getParams() { return params; } /** * Serializes this object to its equivalent Json representation. * * @return the Json representation of this object. */ public String toJson() { return gson.toJson(this); } /** * Equivalent to {@link #toJson()}. * * @return the Json representation of this object. */ @Override public String toString() { return toJson(); } @Override public boolean equals(Object obj) { if (obj instanceof JsonRpcRequest) { JsonRpcRequest objRequest = (JsonRpcRequest) obj; return Objects.equals(objRequest.id, this.id) && Objects.equals(objRequest.method, this.method) && Objects.equals(objRequest.params, this.params); } else { return false; } } @Override public int hashCode() { int hash = Objects.hashCode(id); hash = hash * 31 + Objects.hashCode(method); hash = hash * 31 + Objects.hashCode(params); return hash; } /** Builder for {@link JsonRpcRequest}. */ public static class Builder { /** The request id. Could be null if the request is a notification. */ @Nullable private String id; /** A String containing the name of the method to be invoked. */ private String method; /** Parameter values to be used during the invocation of the method. */ private JsonObject params = new JsonObject(); /** Empty constructor. */ public Builder() {} /** * Constructs an instance with the given method name. * * @param method the method name of this request builder. */ public Builder(String method) { this.method = method; } /** Sets the id of this request builder. */ public Builder setId(@Nullable String id) { this.id = id; return this; } /** Sets the method name of this request builder. */ public Builder setMethod(String method) { this.method = method; return this; } /** Sets the params of this request builder. */ public Builder setParams(JsonObject params) { this.params = params; return this; } /** Sugar method to add a {@code String} param to this request builder. */ public Builder addParam(String tag, String value) { params.addProperty(tag, value); return this; } /** Sugar method to add an integer param to this request builder. */ public Builder addParam(String tag, int value) { params.addProperty(tag, value); return this; } /** Sugar method to add a {@code boolean} param to this request builder. */ public Builder addParam(String tag, boolean value) { params.addProperty(tag, value); return this; } /** Builds and returns a {@code JsonRpcRequest} instance out of this builder. */ public JsonRpcRequest build() { JsonRpcRequest request = new JsonRpcRequest(id, method); if (params != null && params.size() != 0) { request.params = this.params; } return request; } } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/jsonrpc/message/JsonRpcResponse.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.jsonrpc.message; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Strings.isNullOrEmpty; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.annotations.SerializedName; import java.util.Objects; /** * JSON-RPC 2.0 response object. * *

See https://www.jsonrpc.org/specification for detailed specification. */ public final class JsonRpcResponse { private static final Gson gson = new Gson(); private static final String JSON_RPC_VERSION = "2.0"; /** Specifying the version of the JSON-RPC protocol. Must be "2.0". */ @SerializedName("jsonrpc") private final String version; /** * Required. Must be the same as the value of the id in the corresponding JsonRpcRequest object. */ private String id; /** The result of the JSON-RPC call. Required on success. */ private JsonObject result; /** Error occurred in the JSON-RPC call. Required on error. */ private ErrorObject error; /** * Deserializes the given Json string to a {@code JsonRpcResponse} object. * * @param jsonString the string from which the object is to be deserialized. * @return the deserialized object. */ public static JsonRpcResponse fromJson(String jsonString) { checkArgument(!isNullOrEmpty(jsonString), "Json string cannot be null or empty."); JsonRpcResponse response = gson.fromJson(jsonString, JsonRpcResponse.class); checkState(!isNullOrEmpty(response.getId())); checkState(JSON_RPC_VERSION.equals(response.getVersion()), "JSON-RPC version must be 2.0."); return response; } /** * Constructs with the given id and. The JSON-RPC version will be defaulted to "2.0". * * @param id the id of this response. Should be the same as the corresponding request. */ public JsonRpcResponse(String id) { this.version = JSON_RPC_VERSION; setId(id); } /** * Gets the JSON-RPC version. * * @return the JSON-RPC version. Should always be "2.0". */ public String getVersion() { return version; } /** Gets the id of this JSON-RPC response. */ public String getId() { return id; } /** * Sets the id of this JSON-RPC response. * * @param id the id to be set. Cannot be null. */ public void setId(String id) { this.id = checkNotNull(id); } /** Gets the result of this JSON-RPC response. Should be present on success. */ public JsonObject getResult() { return result; } /** * Sets the result of this JSON-RPC response. * * @param result */ public void setResult(JsonObject result) { this.result = result; } /** Gets the error object of this JSON-RPC response. Should be present on error. */ public ErrorObject getError() { return error; } /** * Sets the error object of this JSON-RPC response. * * @param error the error to be set. */ public void setError(ErrorObject error) { this.error = error; } /** * Serializes this object to its equivalent Json representation. * * @return the Json representation of this object. */ public String toJson() { return gson.toJson(this); } /** * Equivalent to {@link #toJson()}. * * @return the Json representation of this object. */ @Override public String toString() { return toJson(); } @Override public boolean equals(Object obj) { if (obj instanceof JsonRpcResponse) { JsonRpcResponse objResponse = (JsonRpcResponse) obj; return Objects.equals(objResponse.id, this.id) && Objects.equals(objResponse.result, this.result) && Objects.equals(objResponse.error, this.error); } else { return false; } } @Override public int hashCode() { int hash = Objects.hashCode(id); hash = hash * 31 + Objects.hashCode(result); hash = hash * 31 + Objects.hashCode(error); return hash; } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/protocol/impl/DartVmService.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.protocol.impl; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.util.concurrent.Futures.transform; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import android.graphics.Rect; import android.util.Log; import androidx.test.espresso.flutter.api.FlutterTestingProtocol; import androidx.test.espresso.flutter.api.SyntheticAction; import androidx.test.espresso.flutter.api.WidgetMatcher; import androidx.test.espresso.flutter.internal.idgenerator.IdGenerator; import androidx.test.espresso.flutter.internal.jsonrpc.JsonRpcClient; import androidx.test.espresso.flutter.internal.jsonrpc.message.JsonRpcRequest; import androidx.test.espresso.flutter.internal.jsonrpc.message.JsonRpcResponse; import androidx.test.espresso.flutter.internal.protocol.impl.GetOffsetAction.OffsetType; import androidx.test.espresso.flutter.model.WidgetInfo; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * An implementation of the Espresso-Flutter testing protocol by using the testing APIs exposed by * Dart VM service protocol. * * @see Dart VM * Service Protocol. */ public final class DartVmService implements FlutterTestingProtocol { private static final String TAG = DartVmService.class.getSimpleName(); private static final Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); /** Prefix to be attached to the JSON-RPC message id. */ private static final String MESSAGE_ID_PREFIX = "message-"; /** The JSON-RPC method for testing extension APIs. */ private static final String TESTING_EXTENSION_METHOD = "ext.flutter.driver"; /** The JSON-RPC method for retrieving Dart isolate info. */ private static final String GET_ISOLATE_METHOD = "getIsolate"; /** The JSON-RPC method for retrieving Dart VM info. */ private static final String GET_VM_METHOD = "getVM"; /** Json property name for the Dart VM isolate id. */ private static final String ISOLATE_ID_TAG = "isolateId"; private final JsonRpcClient client; private final IdGenerator messageIdGenerator; private final String isolateId; private final ListeningExecutorService taskExecutor; /** * Constructs a {@code DartVmService} instance that can be used to talk to the testing protocol * exposed by Dart VM service extension protocol. It uses the given {@code isolateId} in all the * JSON-RPC requests. It waits until the service extension protocol is in a usable state before * returning. * * @param isolateId the Dart isolate ID to be used in the JSON-RPC requests sent to Dart VM * service protocol. * @param jsonRpcClient a JSON-RPC web socket connection to send requests to the Dart VM service * protocol. * @param messageIdGenerator an ID generator for generating the JSON-RPC request IDs. * @param taskExecutor an executor for running async tasks. */ public DartVmService( String isolateId, JsonRpcClient jsonRpcClient, IdGenerator messageIdGenerator, ExecutorService taskExecutor) { this.isolateId = checkNotNull( isolateId, "The ID of the Dart isolate that draws the Flutter UI shouldn't be null."); this.client = checkNotNull( jsonRpcClient, "The JsonRpcClient used to talk to Dart VM service protocol shouldn't be null."); this.messageIdGenerator = checkNotNull( messageIdGenerator, "The id generator for generating request IDs shouldn't be null."); this.taskExecutor = MoreExecutors.listeningDecorator(checkNotNull(taskExecutor)); } /** * {@inheritDoc} * *

This method ensures the Dart VM service is ready for use by checking: * *

    *
  • Dart VM Observatory is up and running. *
  • The Flutter testing API is registered with the running Dart VM service protocol. *
*/ @Override @SuppressWarnings("unchecked") public Future connect() { return (Future) taskExecutor.submit(new IsDartVmServiceReady(isolateId, this)); } @Override public Future perform( @Nullable final WidgetMatcher widgetMatcher, final SyntheticAction action) { // Assumes all the actions require a response. ListenableFuture responseFuture = client.request(getActionRequest(widgetMatcher, action)); Function resultTransformFunc = new Function() { public Void apply(JsonRpcResponse response) { if (response.getError() == null) { return null; } else { // TODO(https://github.com/android/android-test/issues/251): Update error case handling // like // AmbiguousWidgetMatcherException, NoMatchingWidgetException after nailing down the // design with // Flutter team. throw new RuntimeException( String.format( "Error occurred when performing the given action %s on widget matched %s", action, widgetMatcher)); } } }; return transform(responseFuture, resultTransformFunc, directExecutor()); } @Override public Future matchWidget(@Nonnull WidgetMatcher widgetMatcher) { JsonRpcRequest request = getActionRequest(widgetMatcher, new GetWidgetDiagnosticsAction()); ListenableFuture jsonResponseFuture = client.request(request); Function widgetInfoTransformer = new Function() { public WidgetInfo apply(JsonRpcResponse jsonResponse) { GetWidgetDiagnosticsResponse widgetDiagnostics = GetWidgetDiagnosticsResponse.fromJsonRpcResponse(jsonResponse); return WidgetInfoFactory.createWidgetInfo(widgetDiagnostics); } }; return transform(jsonResponseFuture, widgetInfoTransformer, directExecutor()); } @Override public Future getLocalRect(@Nonnull WidgetMatcher widgetMatcher) { ListenableFuture topLeftFuture = client.request(getActionRequest(widgetMatcher, new GetOffsetAction(OffsetType.TOP_LEFT))); ListenableFuture bottomRightFuture = client.request( getActionRequest(widgetMatcher, new GetOffsetAction(OffsetType.BOTTOM_RIGHT))); ListenableFuture> responses = Futures.allAsList(topLeftFuture, bottomRightFuture); Function, Rect> rectTransformer = new Function, Rect>() { public Rect apply(List jsonResponses) { GetOffsetResponse topLeft = GetOffsetResponse.fromJsonRpcResponse(jsonResponses.get(0)); GetOffsetResponse bottomRight = GetOffsetResponse.fromJsonRpcResponse(jsonResponses.get(1)); checkState( topLeft.getX() >= 0 && topLeft.getY() >= 0, String.format( "The relative coordinates [%.1f, %.1f] of a widget's top left vertex cannot be" + " negative (negative means it's off the outer Flutter view)!", topLeft.getX(), topLeft.getY())); checkState( bottomRight.getX() >= 0 && bottomRight.getY() >= 0, String.format( "The relative coordinates [%.1f, %.1f] of a widget's bottom right vertex cannot" + " be negative (negative means it's off the outer Flutter view)!", bottomRight.getX(), bottomRight.getY())); checkState( topLeft.getX() <= bottomRight.getX() && topLeft.getY() <= bottomRight.getY(), String.format( "The coordinates of the bottom right vertex [%.1f, %.1f] are not actually to the" + " bottom right of the top left vertex [%.1f, %.1f]!", topLeft.getX(), topLeft.getY(), bottomRight.getX(), bottomRight.getY())); return new Rect( (int) topLeft.getX(), (int) topLeft.getY(), (int) bottomRight.getX(), (int) bottomRight.getY()); } }; return transform(responses, rectTransformer, directExecutor()); } @Override public Future waitUntilIdle() { return perform( null, new WaitForConditionAction( new NoPendingPlatformMessagesCondition(), new NoTransientCallbacksCondition(), new NoPendingFrameCondition())); } @Override public void close() { if (client != null) { client.disconnect(); } } /** Queries the Dart isolate information. */ public ListenableFuture getIsolateInfo() { JsonRpcRequest getIsolateReq = new JsonRpcRequest.Builder(GET_ISOLATE_METHOD) .setId(getNextMessageId()) .addParam(ISOLATE_ID_TAG, isolateId) .build(); return client.request(getIsolateReq); } /** Queries the Dart VM information. */ public ListenableFuture getVmInfo() { JsonRpcRequest getVmReq = new JsonRpcRequest.Builder(GET_VM_METHOD).setId(getNextMessageId()).build(); ListenableFuture jsonGetVmResp = client.request(getVmReq); Function jsonToResponse = new Function() { public GetVmResponse apply(JsonRpcResponse jsonResp) { return GetVmResponse.fromJsonRpcResponse(jsonResp); } }; return transform(jsonGetVmResp, jsonToResponse, directExecutor()); } /** Gets the next usable message id. */ private String getNextMessageId() { return MESSAGE_ID_PREFIX + messageIdGenerator.next(); } /** Constructs a {@code JsonRpcRequest} based on the given matcher and action. */ private JsonRpcRequest getActionRequest(WidgetMatcher widgetMatcher, SyntheticAction action) { checkNotNull(action, "Action cannot be null."); // Assumes all the actions require a response. return new JsonRpcRequest.Builder(TESTING_EXTENSION_METHOD) .setId(getNextMessageId()) .setParams(constructParams(isolateId, widgetMatcher, action)) .build(); } /** Constructs the JSON-RPC request params. */ private static JsonObject constructParams( String isolateId, WidgetMatcher widgetMatcher, SyntheticAction action) { JsonObject paramObject = new JsonObject(); paramObject.addProperty(ISOLATE_ID_TAG, isolateId); if (widgetMatcher != null) { paramObject = merge(paramObject, (JsonObject) gson.toJsonTree(widgetMatcher)); } paramObject = merge(paramObject, (JsonObject) gson.toJsonTree(action)); return paramObject; } /** * Returns a merged {@code JsonObject} of the two given {@code JsonObject}s, or an empty {@code * JsonObject} if both of the objects to be merged are null. */ private static JsonObject merge(@Nullable JsonObject obj1, @Nullable JsonObject obj2) { JsonObject result = new JsonObject(); mergeTo(result, obj1); mergeTo(result, obj2); return result; } private static void mergeTo(JsonObject obj, @Nullable JsonObject toBeMerged) { if (toBeMerged != null) { for (Map.Entry entry : toBeMerged.entrySet()) { obj.add(entry.getKey(), entry.getValue()); } } } /** A {@link Runnable} that waits until the Dart VM testing extension is ready for use. */ static class IsDartVmServiceReady implements Runnable { /** Maximum number of retries for checking extension APIs' availability. */ private static final int EXTENSION_API_CHECKING_RETRIES = 5; /** Json param name for retrieving all the available extension APIs. */ private static final String EXTENSION_RPCS_TAG = "extensionRPCs"; private final String isolateId; private final DartVmService dartVmService; IsDartVmServiceReady(String isolateId, DartVmService dartVmService) { this.isolateId = checkNotNull(isolateId); this.dartVmService = checkNotNull(dartVmService); } @Override public void run() { waitForTestingApiRegistered(); } /** * Blocks until the Flutter testing/driver API is registered with the running Dart VM service * protocol by querying whether it's listed in the isolate's 'extensionRPCs'. */ @VisibleForTesting void waitForTestingApiRegistered() { int retries = EXTENSION_API_CHECKING_RETRIES; boolean isApiRegistered = false; do { retries--; try { JsonRpcResponse isolateResp = dartVmService.getIsolateInfo().get(); isApiRegistered = isTestingApiRegistered(isolateResp); } catch (ExecutionException e) { Log.d( TAG, "Error occurred during retrieving Dart isolate information. Retry.", e.getCause()); continue; } catch (InterruptedException e) { Log.d( TAG, "InterruptedException occurred during retrieving Dart isolate information. Retry.", e); Thread.currentThread().interrupt(); // Restores the interrupted status. continue; } } while (!isApiRegistered && retries > 0); if (!isApiRegistered) { throw new FlutterProtocolException( String.format("Flutter testing APIs not registered with Dart isolate %s.", isolateId)); } } @VisibleForTesting boolean isTestingApiRegistered(JsonRpcResponse isolateInfoResp) { if (isolateInfoResp == null || isolateInfoResp.getError() != null || isolateInfoResp.getResult() == null) { Log.w( TAG, String.format( "Error occurred in JSON-RPC response when querying isolate info for %s: %s.", isolateId, isolateInfoResp.getError())); return false; } for (JsonElement jsonElement : isolateInfoResp.getResult().get(EXTENSION_RPCS_TAG).getAsJsonArray()) { String extensionApi = jsonElement.getAsString(); if (TESTING_EXTENSION_METHOD.equals(extensionApi)) { Log.d( TAG, String.format("Flutter testing API registered with Dart isolate %s.", isolateId)); return true; } } return false; } } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/protocol/impl/DartVmServiceUtil.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.protocol.impl; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.isNullOrEmpty; import android.util.Log; import android.view.View; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.dart.DartExecutor; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; /** Util class for dealing with Dart VM service protocols. */ public final class DartVmServiceUtil { private static final String TAG = DartVmServiceUtil.class.getSimpleName(); /** * Converts the Dart VM observatory http server URL to the service protocol WebSocket URL. * * @param observatoryUrl The Dart VM http server URL that can be converted to a service protocol * URI. */ public static URI getServiceProtocolUri(String observatoryUrl) { if (isNullOrEmpty(observatoryUrl)) { throw new RuntimeException( "Dart VM Observatory is not enabled. " + "Please make sure your Flutter app is running under debug mode."); } try { new URL(observatoryUrl); } catch (MalformedURLException e) { throw new RuntimeException( String.format("Dart VM Observatory url %s is malformed.", observatoryUrl), e); } // Constructs the service protocol URL based on the Observatory http url. // For example, http://127.0.0.1:39694/qsnVeidc78Y=/ -> ws://127.0.0.1:39694/qsnVeidc78Y=/ws. int schemaIndex = observatoryUrl.indexOf(":"); String serviceProtocolUri = "ws" + observatoryUrl.substring(schemaIndex); if (!observatoryUrl.endsWith("/")) { serviceProtocolUri += "/"; } serviceProtocolUri += "ws"; Log.i(TAG, "Dart VM service protocol runs at uri: " + serviceProtocolUri); try { return new URI(serviceProtocolUri); } catch (URISyntaxException e) { // Should never happen. throw new RuntimeException("Illegal Dart VM service protocol URI: " + serviceProtocolUri, e); } } /** Gets the Dart isolate ID for the given {@code flutterView}. */ public static String getDartIsolateId(View flutterView) { checkNotNull(flutterView, "The Flutter View instance cannot be null."); String uiIsolateId = getDartExecutor(flutterView).getIsolateServiceId(); Log.d( TAG, String.format( "Dart isolate ID for the Flutter View [id: %d]: %s.", flutterView.getId(), uiIsolateId)); return uiIsolateId; } /** Gets the Dart executor for the given {@code flutterView}. */ @SuppressWarnings("deprecation") public static DartExecutor getDartExecutor(View flutterView) { checkNotNull(flutterView, "The Flutter View instance cannot be null."); // Flutter's embedding is in the phase of rewriting/refactoring. Let's be compatible with both // the old and the new FlutterView classes. if (flutterView instanceof io.flutter.view.FlutterView) { return ((io.flutter.view.FlutterView) flutterView).getDartExecutor(); } else if (flutterView instanceof io.flutter.embedding.android.FlutterView) { FlutterEngine flutterEngine = ((io.flutter.embedding.android.FlutterView) flutterView).getAttachedFlutterEngine(); if (flutterEngine == null) { throw new FlutterProtocolException( String.format( "No Flutter engine attached to the Flutter view [id: %d].", flutterView.getId())); } return flutterEngine.getDartExecutor(); } else { throw new FlutterProtocolException( String.format("This is not a Flutter View instance [id: %d].", flutterView.getId())); } } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/protocol/impl/FlutterProtocolException.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.protocol.impl; /** Represents an exception/error relevant to Dart VM service. */ public final class FlutterProtocolException extends RuntimeException { public FlutterProtocolException(String message) { super(message); } public FlutterProtocolException(Throwable t) { super(t); } public FlutterProtocolException(String message, Throwable t) { super(message, t); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/protocol/impl/GetOffsetAction.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.protocol.impl; import static com.google.common.base.Preconditions.checkNotNull; import androidx.test.espresso.flutter.api.SyntheticAction; import com.google.common.base.Ascii; import com.google.gson.annotations.Expose; /** An action that retrieves the widget offset coordinates to the outer Flutter view. */ final class GetOffsetAction extends SyntheticAction { /** The position of the offset coordinates. */ public enum OffsetType { TOP_LEFT("topLeft"), TOP_RIGHT("topRight"), BOTTOM_LEFT("bottomLeft"), BOTTOM_RIGHT("bottomRight"); private OffsetType(String type) { this.type = type; } private final String type; @Override public String toString() { return type; } public static OffsetType fromString(String typeString) { if (typeString == null) { return null; } for (OffsetType offsetType : OffsetType.values()) { if (Ascii.equalsIgnoreCase(offsetType.type, typeString)) { return offsetType; } } return null; } } @Expose private final String offsetType; /** * Constructor. * * @param type the vertex position. */ public GetOffsetAction(OffsetType type) { super("get_offset"); this.offsetType = checkNotNull(type).toString(); } /** * Constructor. * * @param type the vertex position. * @param timeOutInMillis action's timeout setting in milliseconds. */ public GetOffsetAction(OffsetType type, long timeOutInMillis) { super("get_offset", timeOutInMillis); this.offsetType = checkNotNull(type).toString(); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/protocol/impl/GetOffsetResponse.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.protocol.impl; import static com.google.common.base.Preconditions.checkNotNull; import androidx.test.espresso.flutter.internal.jsonrpc.message.JsonRpcResponse; import androidx.test.espresso.flutter.internal.protocol.impl.GetOffsetAction.OffsetType; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.google.gson.annotations.Expose; /** * Represents the {@code result} section in a {@code JsonRpcResponse} that's the response of a * {@code GetOffsetAction}. */ final class GetOffsetResponse { private static final Gson gson = new Gson(); @Expose private boolean isError; @Expose private Coordinates response; @Expose private String type; private GetOffsetResponse() {} /** * Builds the {@code GetOffsetResponse} out of the JSON-RPC response. * * @param jsonRpcResponse the JSON-RPC response. Cannot be {@code null}. * @return a {@code GetOffsetResponse} instance that's parsed out from the JSON-RPC response. */ public static GetOffsetResponse fromJsonRpcResponse(JsonRpcResponse jsonRpcResponse) { checkNotNull(jsonRpcResponse, "The JSON-RPC response cannot be null."); if (jsonRpcResponse.getResult() == null) { throw new FlutterProtocolException( String.format( "Error occurred during retrieving a Flutter widget's geometry info. Response" + " received: %s.", jsonRpcResponse)); } try { return gson.fromJson(jsonRpcResponse.getResult(), GetOffsetResponse.class); } catch (JsonSyntaxException e) { throw new FlutterProtocolException( String.format( "Error occurred during retrieving a Flutter widget's geometry info. Response" + " received: %s.", jsonRpcResponse), e); } } /** Returns whether this is an error response. */ public boolean isError() { return isError; } /** Returns the vertex position. */ public OffsetType getType() { return OffsetType.fromString(type); } /** Returns the X-Coordinate. */ public float getX() { if (response == null) { throw new FlutterProtocolException( String.format( "Error occurred during retrieving a Flutter widget's geometry info. Response" + " received: %s", this)); } else { return response.dx; } } /** Returns the Y-Coordinate. */ public float getY() { if (response == null) { throw new FlutterProtocolException( String.format( "Error occurred during retrieving a Flutter widget's geometry info. Response" + " received: %s", this)); } else { return response.dy; } } @Override public String toString() { return gson.toJson(this); } static class Coordinates { @Expose private float dx; @Expose private float dy; Coordinates() {} Coordinates(float dx, float dy) { this.dx = dx; this.dy = dy; } } static class Builder { private boolean isError; private Coordinates coordinate; private OffsetType type; public Builder() {} public Builder setIsError(boolean isError) { this.isError = isError; return this; } public Builder setCoordinates(float dx, float dy) { this.coordinate = new Coordinates(dx, dy); return this; } public Builder setType(OffsetType type) { this.type = checkNotNull(type); return this; } public GetOffsetResponse build() { GetOffsetResponse response = new GetOffsetResponse(); response.isError = this.isError; response.response = this.coordinate; response.type = checkNotNull(type).toString(); return response; } } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/protocol/impl/GetVmResponse.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.protocol.impl; import static com.google.common.base.Preconditions.checkNotNull; import androidx.test.espresso.flutter.internal.jsonrpc.message.JsonRpcResponse; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.google.gson.annotations.Expose; import java.util.List; import java.util.Objects; /** * Represents a response of a getVM() * request. */ public class GetVmResponse { private static final Gson gson = new Gson(); @Expose private List isolates; private GetVmResponse() {} /** * Builds the {@code GetVmResponse} out of the JSON-RPC response. * * @param jsonRpcResponse the JSON-RPC response. Cannot be {@code null}. * @return a {@code GetVmResponse} instance that's parsed out from the JSON-RPC response. */ public static GetVmResponse fromJsonRpcResponse(JsonRpcResponse jsonRpcResponse) { checkNotNull(jsonRpcResponse, "The JSON-RPC response cannot be null."); if (jsonRpcResponse.getResult() == null) { throw new FlutterProtocolException( String.format( "Error occurred during retrieving Dart VM info. Response received: %s.", jsonRpcResponse)); } try { return gson.fromJson(jsonRpcResponse.getResult(), GetVmResponse.class); } catch (JsonSyntaxException e) { throw new FlutterProtocolException( String.format( "Error occurred during retrieving Dart VM info. Response received: %s.", jsonRpcResponse), e); } } /** Returns the number of isolates living in the Dart VM. */ public int getIsolateNum() { return isolates == null ? 0 : isolates.size(); } /** Returns the Dart isolate listed at the given index. */ public Isolate getIsolate(int index) { if (isolates == null) { return null; } else if (index < 0 || index >= isolates.size()) { throw new IllegalArgumentException( String.format( "Illegal Dart isolate index: %d. Should be in the range [%d, %d]", index, 0, isolates.size() - 1)); } else { return isolates.get(index); } } @Override public String toString() { return gson.toJson(this); } /** Represents a Dart isolate. */ static class Isolate { @Expose private String id; @Expose private boolean runnable; @Expose private List extensionRpcList; Isolate() {} Isolate(String id, boolean runnable) { this.id = id; this.runnable = runnable; } /** Gets the Dart isolate ID. */ public String getId() { return id; } /** * Checks whether the Dart isolate is in a runnable state. True if it's runnable, false * otherwise. */ public boolean isRunnable() { return runnable; } /** Gets the list of extension RPCs registered at this Dart isolate. Could be {@code null}. */ public List getExtensionRpcList() { return extensionRpcList; } @Override public boolean equals(Object obj) { if (obj instanceof Isolate) { Isolate isolate = (Isolate) obj; return Objects.equals(isolate.id, this.id) && Objects.equals(isolate.runnable, this.runnable) && Objects.equals(isolate.extensionRpcList, this.extensionRpcList); } else { return false; } } @Override public int hashCode() { return Objects.hash(id, runnable, extensionRpcList); } } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/protocol/impl/GetWidgetDiagnosticsAction.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.protocol.impl; import androidx.test.espresso.flutter.api.SyntheticAction; import com.google.gson.annotations.Expose; /** Represents an action that retrieves the Flutter widget's diagnostics information. */ final class GetWidgetDiagnosticsAction extends SyntheticAction { @Expose private final String diagnosticsType = "widget"; /** * Sets the depth of the retrieved diagnostics tree as 0. This means only the information of the * root widget will be retrieved. */ @Expose private final int subtreeDepth = 0; /** Always includes the diagnostics properties of this widget. */ @Expose private final boolean includeProperties = true; GetWidgetDiagnosticsAction() { super("get_diagnostics_tree"); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/protocol/impl/GetWidgetDiagnosticsResponse.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.protocol.impl; import static com.google.common.base.Preconditions.checkNotNull; import android.util.Log; import androidx.test.espresso.flutter.internal.jsonrpc.message.JsonRpcResponse; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Ascii; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import java.util.List; import java.util.Objects; /** Represents a response of the {@code GetWidgetDiagnosticsAction}. */ final class GetWidgetDiagnosticsResponse { private static final String TAG = GetWidgetDiagnosticsResponse.class.getSimpleName(); private static final Gson gson = new Gson(); @Expose private boolean isError; @Expose @SerializedName("response") private DiagnosticNodeInfo widgetInfo; private GetWidgetDiagnosticsResponse() {} /** * Builds the {@code GetWidgetDiagnosticsResponse} out of the JSON-RPC response. * * @param jsonRpcResponse the JSON-RPC response. Cannot be {@code null}. * @return a {@code GetWidgetDiagnosticsResponse} instance that's parsed out from the JSON-RPC * response. */ public static GetWidgetDiagnosticsResponse fromJsonRpcResponse(JsonRpcResponse jsonRpcResponse) { checkNotNull(jsonRpcResponse, "The JSON-RPC response cannot be null."); if (jsonRpcResponse.getResult() == null) { throw new FlutterProtocolException( String.format( "Error occurred during retrieving widget's diagnostics info. Response received: %s.", jsonRpcResponse)); } try { return gson.fromJson(jsonRpcResponse.getResult(), GetWidgetDiagnosticsResponse.class); } catch (JsonSyntaxException e) { throw new FlutterProtocolException( String.format( "Error occurred during retrieving widget's diagnostics info. Response received: %s.", jsonRpcResponse), e); } } /** Returns whether this is an error response. */ public boolean isError() { return isError; } /** Returns the runtime type of this widget, or {@code null} if the type info is not available. */ public String getRuntimeType() { if (widgetInfo == null) { Log.w(TAG, "Widget info is null."); return null; } else { return widgetInfo.runtimeType; } } /** * Gets the widget property by its name, or null if the property doesn't exist. * * @param propertyName the property name. Cannot be {@code null}. */ public WidgetProperty getPropertyByName(String propertyName) { checkNotNull(propertyName, "Widget property name cannot be null."); if (widgetInfo == null) { Log.w(TAG, "Widget info is null."); return null; } return widgetInfo.getPropertyByName(propertyName); } /** * Returns the description of this widget, or {@code null} if the diagnostics info is not * available. */ public String getDescription() { if (widgetInfo == null) { Log.w(TAG, "Widget info is null."); return null; } return widgetInfo.description; } /** * Returns whether this widget has children, or {@code false} if the diagnostics info is not * available. */ public boolean isHasChildren() { if (widgetInfo == null) { Log.w(TAG, "Widget info is null."); return false; } return widgetInfo.hasChildren; } @Override public String toString() { return gson.toJson(this); } /** A data structure that holds a widget's diagnostics info. */ static class DiagnosticNodeInfo { @Expose @SerializedName("widgetRuntimeType") private String runtimeType; @Expose private List properties; @Expose private String description; @Expose private boolean hasChildren; WidgetProperty getPropertyByName(String propertyName) { checkNotNull(propertyName, "Widget property name cannot be null."); if (properties == null) { Log.w(TAG, "Widget property list is null."); return null; } for (WidgetProperty property : properties) { if (Ascii.equalsIgnoreCase(propertyName, property.getName())) { return property; } } return null; } } /** Represents a widget property. */ static class WidgetProperty { @Expose private final String name; @Expose private final String value; @Expose private final String description; @VisibleForTesting WidgetProperty(String name, String value, String description) { this.name = name; this.value = value; this.description = description; } /** Returns the name of this widget property. */ public String getName() { return name; } /** Returns the value of this widget property. */ public String getValue() { return value; } /** Returns the description of this widget property. */ public String getDescription() { return description; } @Override public boolean equals(Object obj) { if (!(obj instanceof WidgetProperty)) { return false; } else { WidgetProperty widgetProperty = (WidgetProperty) obj; return Objects.equals(this.name, widgetProperty.name) && Objects.equals(this.value, widgetProperty.value) && Objects.equals(this.description, widgetProperty.description); } } @Override public int hashCode() { return Objects.hash(name, value, description); } } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/protocol/impl/NoPendingFrameCondition.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.protocol.impl; /** * Represents a condition that waits until no pending frame is scheduled in the Flutter framework. */ class NoPendingFrameCondition extends WaitCondition { public NoPendingFrameCondition() { super("NoPendingFrameCondition"); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/protocol/impl/NoPendingPlatformMessagesCondition.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.protocol.impl; /** * Represents a condition that waits until there are no pending platform messages in the Flutter's * platform channels. */ class NoPendingPlatformMessagesCondition extends WaitCondition { public NoPendingPlatformMessagesCondition() { super("NoPendingPlatformMessagesCondition"); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/protocol/impl/NoTransientCallbacksCondition.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.protocol.impl; /** Represents a condition that waits until no transient callbacks in the Flutter framework. */ class NoTransientCallbacksCondition extends WaitCondition { public NoTransientCallbacksCondition() { super("NoTransientCallbacksCondition"); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/protocol/impl/WaitCondition.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.protocol.impl; import static com.google.common.base.Preconditions.checkNotNull; /** The base class that represents a wait condition in the Flutter app. */ abstract class WaitCondition { // Used in JSON serialization. @SuppressWarnings("unused") private final String conditionName; public WaitCondition(String conditionName) { this.conditionName = checkNotNull(conditionName); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/protocol/impl/WaitForConditionAction.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.protocol.impl; import static com.google.common.base.Preconditions.checkNotNull; import androidx.test.espresso.flutter.api.SyntheticAction; import com.google.gson.Gson; import com.google.gson.annotations.Expose; /** * Represents an action that waits until the specified conditions have been met in the Flutter app. */ final class WaitForConditionAction extends SyntheticAction { private static final Gson gson = new Gson(); @Expose private final String conditionName = "CombinedCondition"; @Expose private final String conditions; /** * Creates with the given wait conditions. * * @param waitConditions the conditions that this action shall wait for. Cannot be null. */ public WaitForConditionAction(WaitCondition... waitConditions) { super("waitForCondition"); conditions = gson.toJson(checkNotNull(waitConditions)); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/internal/protocol/impl/WidgetInfoFactory.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.internal.protocol.impl; import static com.google.common.base.Preconditions.checkNotNull; import android.util.Log; import androidx.test.espresso.flutter.model.WidgetInfo; import androidx.test.espresso.flutter.model.WidgetInfoBuilder; /** A factory that creates {@link WidgetInfo} instances. */ final class WidgetInfoFactory { private static final String TAG = WidgetInfoFactory.class.getSimpleName(); private enum WidgetRuntimeType { TEXT("Text"), RICH_TEXT("RichText"), UNKNOWN("Unknown"); private WidgetRuntimeType(String typeString) { this.type = typeString; } private final String type; @Override public String toString() { return type; } public static WidgetRuntimeType getType(String typeString) { for (WidgetRuntimeType widgetType : WidgetRuntimeType.values()) { if (widgetType.type.equals(typeString)) { return widgetType; } } return UNKNOWN; } } /** * Creates a {@code WidgetInfo} instance based on the given diagnostics info. * *

The current implementation is ugly. As the widget's properties are serialized out as JSON * strings, we have to inspect the content based on the widget type. * * @throws FlutterProtocolException when the given {@code widgetDiagnostics} is invalid. */ public static WidgetInfo createWidgetInfo(GetWidgetDiagnosticsResponse widgetDiagnostics) { checkNotNull(widgetDiagnostics, "The widget diagnostics instance is null."); WidgetInfoBuilder widgetInfo = new WidgetInfoBuilder(); if (widgetDiagnostics.getRuntimeType() == null) { throw new FlutterProtocolException( String.format( "The widget diagnostics info must contain the runtime type of the widget. Illegal" + " widget diagnostics info: %s.", widgetDiagnostics)); } widgetInfo.setRuntimeType(widgetDiagnostics.getRuntimeType()); // Ugly, but let's figure out a better way as this evolves. switch (WidgetRuntimeType.getType(widgetDiagnostics.getRuntimeType())) { case TEXT: // Flutter Text Widget's "data" field stores the text info. if (widgetDiagnostics.getPropertyByName("data") != null) { String text = widgetDiagnostics.getPropertyByName("data").getValue(); widgetInfo.setText(text); } break; case RICH_TEXT: if (widgetDiagnostics.getPropertyByName("text") != null) { String richText = widgetDiagnostics.getPropertyByName("text").getValue(); widgetInfo.setText(richText); } break; default: // Let's be silent when we know little about the widget's type. // The widget's fields will be mostly empty but it can be used for checking the existence // of the widget. Log.i( TAG, String.format( "Unknown widget type: %s. Widget diagnostics info: %s.", widgetDiagnostics.getRuntimeType(), widgetDiagnostics)); } return widgetInfo.build(); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/matcher/FlutterMatchers.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.matcher; import android.view.View; import androidx.test.espresso.flutter.api.WidgetMatcher; import androidx.test.espresso.flutter.model.WidgetInfo; import io.flutter.embedding.android.FlutterView; import javax.annotation.Nonnull; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; /** A collection of matchers that match a Flutter view or Flutter widgets. */ public final class FlutterMatchers { /** * Returns a matcher that matches a {@link FlutterView} or a legacy {@code * io.flutter.view.FlutterView}. */ public static Matcher isFlutterView() { return new IsFlutterViewMatcher(); } /** * Returns a matcher that matches a Flutter widget's tooltip. * * @param tooltip the tooltip String to match. Cannot be {@code null}. */ public static WidgetMatcher withTooltip(@Nonnull String tooltip) { return new WithTooltipMatcher(tooltip); } /** * Returns a matcher that matches a Flutter widget's value key. * * @param valueKey the value key String to match. Cannot be {@code null}. */ public static WidgetMatcher withValueKey(@Nonnull String valueKey) { return new WithValueKeyMatcher(valueKey); } /** * Returns a matcher that matches a Flutter widget's runtime type. * *

Usage: * *

{@code withType("TextField")} can be used to match a Flutter TextField widget. * * @param type the type String to match. Cannot be {@code null}. */ public static WidgetMatcher withType(@Nonnull String type) { return new WithTypeMatcher(type); } /** * Returns a matcher that matches a Flutter widget's text. * * @param text the text String to match. Cannot be {@code null}. */ public static WidgetMatcher withText(@Nonnull String text) { return new WithTextMatcher(text); } /** * Returns a matcher that matches a Flutter widget based on the given ancestor matcher. * * @param ancestorMatcher the ancestor to match on. Cannot be null. * @param widgetMatcher the widget to match on. Cannot be null. */ public static WidgetMatcher isDescendantOf( @Nonnull WidgetMatcher ancestorMatcher, @Nonnull WidgetMatcher widgetMatcher) { return new IsDescendantOfMatcher(ancestorMatcher, widgetMatcher); } /** * Returns a matcher that checks the existence of a Flutter widget. * *

Note, this matcher only guarantees that the widget exists in Flutter's widget tree, but not * necessarily displayed on screen, e.g. the widget is in the cache extend of a Scrollable, but * not scrolled onto the screen. */ public static Matcher isExisting() { return new IsExistingMatcher(); } static final class IsFlutterViewMatcher extends TypeSafeMatcher { private IsFlutterViewMatcher() {} @Override public void describeTo(Description description) { description.appendText("is a FlutterView"); } @SuppressWarnings("deprecation") @Override public boolean matchesSafely(View flutterView) { return flutterView instanceof FlutterView || (flutterView instanceof io.flutter.view.FlutterView); } } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/matcher/IsDescendantOfMatcher.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.matcher; import static com.google.common.base.Preconditions.checkNotNull; import androidx.test.espresso.flutter.api.WidgetMatcher; import androidx.test.espresso.flutter.model.WidgetInfo; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import javax.annotation.Nonnull; import org.hamcrest.Description; /** A matcher that matches a Flutter widget with a given ancestor. */ public final class IsDescendantOfMatcher extends WidgetMatcher { private static final Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); private final WidgetMatcher ancestorMatcher; private final WidgetMatcher widgetMatcher; // Flutter Driver extension APIs only support JSON strings, not other JSON structures. // Thus, explicitly convert the matchers to JSON strings. @SerializedName("of") @Expose private final String jsonAncestorMatcher; @SerializedName("matching") @Expose private final String jsonWidgetMatcher; IsDescendantOfMatcher( @Nonnull WidgetMatcher ancestorMatcher, @Nonnull WidgetMatcher widgetMatcher) { super("Descendant"); this.ancestorMatcher = checkNotNull(ancestorMatcher); this.widgetMatcher = checkNotNull(widgetMatcher); jsonAncestorMatcher = gson.toJson(ancestorMatcher); jsonWidgetMatcher = gson.toJson(widgetMatcher); } /** Returns the matcher to match the widget's ancestor. */ public WidgetMatcher getAncestorMatcher() { return ancestorMatcher; } /** Returns the matcher to match the widget itself. */ public WidgetMatcher getWidgetMatcher() { return widgetMatcher; } @Override public String toString() { return "matched with " + widgetMatcher + " with ancestor: " + ancestorMatcher; } @Override protected boolean matchesSafely(WidgetInfo widget) { // TODO: Using this matcher in the assertion is not supported yet. throw new UnsupportedOperationException("IsDescendantMatcher is not supported for assertion."); } @Override public void describeTo(Description description) { description .appendText("matched with ") .appendText(widgetMatcher.toString()) .appendText(" with ancestor: ") .appendText(ancestorMatcher.toString()); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/matcher/IsExistingMatcher.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.matcher; import androidx.test.espresso.flutter.model.WidgetInfo; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; /** A matcher that checks the existence of a Flutter widget. */ public final class IsExistingMatcher extends TypeSafeMatcher { /** Constructs the matcher. */ IsExistingMatcher() {} @Override public String toString() { return "is existing"; } @Override protected boolean matchesSafely(WidgetInfo widget) { return widget != null; } @Override public void describeTo(Description description) { description.appendText("should exist."); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/matcher/WithTextMatcher.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.matcher; import static com.google.common.base.Preconditions.checkNotNull; import androidx.test.espresso.flutter.api.WidgetMatcher; import androidx.test.espresso.flutter.model.WidgetInfo; import com.google.gson.annotations.Expose; import javax.annotation.Nonnull; import org.hamcrest.Description; /** A matcher that matches a Flutter widget with a given text. */ public final class WithTextMatcher extends WidgetMatcher { @Expose private final String text; /** * Constructs the matcher with the given text to be matched with. * * @param text the text to be matched with. */ WithTextMatcher(@Nonnull String text) { super("ByText"); this.text = checkNotNull(text); } /** Returns the text string that shall be matched for the widget. */ public String getText() { return text; } @Override public String toString() { return "with text: " + text; } @Override protected boolean matchesSafely(WidgetInfo widget) { return text.equals(widget.getText()); } @Override public void describeTo(Description description) { description.appendText("with text: ").appendText(text); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/matcher/WithTooltipMatcher.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.matcher; import static com.google.common.base.Preconditions.checkNotNull; import androidx.test.espresso.flutter.api.WidgetMatcher; import androidx.test.espresso.flutter.model.WidgetInfo; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import javax.annotation.Nonnull; import org.hamcrest.Description; /** A matcher that matches a Flutter widget with a given tooltip. */ public final class WithTooltipMatcher extends WidgetMatcher { @Expose @SerializedName("text") private final String tooltip; /** * Constructs the matcher with the given {@code tooltip} to be matched with. * * @param tooltip the tooltip to be matched with. */ public WithTooltipMatcher(@Nonnull String tooltip) { super("ByTooltipMessage"); this.tooltip = checkNotNull(tooltip); } /** Returns the tooltip string that shall be matched for the widget. */ public String getTooltip() { return tooltip; } @Override public String toString() { return "with tooltip: " + tooltip; } @Override protected boolean matchesSafely(WidgetInfo widget) { return tooltip.equals(widget.getTooltip()); } @Override public void describeTo(Description description) { description.appendText("with tooltip: ").appendText(tooltip); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/matcher/WithTypeMatcher.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.matcher; import static com.google.common.base.Preconditions.checkNotNull; import androidx.test.espresso.flutter.api.WidgetMatcher; import androidx.test.espresso.flutter.model.WidgetInfo; import com.google.gson.annotations.Expose; import javax.annotation.Nonnull; import org.hamcrest.Description; /** A matcher that matches a Flutter widget with a given runtime type. */ public final class WithTypeMatcher extends WidgetMatcher { @Expose private final String type; /** * Constructs the matcher with the given runtime type to be matched with. * * @param type the runtime type to be matched with. */ public WithTypeMatcher(@Nonnull String type) { super("ByType"); this.type = checkNotNull(type); } /** Returns the type string that shall be matched for the widget. */ public String getType() { return type; } @Override public String toString() { return "with runtime type: " + type; } @Override protected boolean matchesSafely(WidgetInfo widget) { return type.equals(widget.getType()); } @Override public void describeTo(Description description) { description.appendText("with runtime type: ").appendText(type); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/matcher/WithValueKeyMatcher.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.matcher; import static com.google.common.base.Preconditions.checkNotNull; import androidx.test.espresso.flutter.api.WidgetMatcher; import androidx.test.espresso.flutter.model.WidgetInfo; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import javax.annotation.Nonnull; import org.hamcrest.Description; /** A matcher that matches a Flutter widget with a given value key. */ public final class WithValueKeyMatcher extends WidgetMatcher { @Expose @SerializedName("keyValueString") private final String valueKey; @Expose private final String keyValueType = "String"; /** * Constructs the matcher with the given value key String to be matched with. * * @param valueKey the value key String to be matched with. */ public WithValueKeyMatcher(@Nonnull String valueKey) { super("ByValueKey"); this.valueKey = checkNotNull(valueKey); } /** Returns the value key string that shall be matched for the widget. */ public String getValueKey() { return valueKey; } @Override public String toString() { return "with value key: " + valueKey; } @Override protected boolean matchesSafely(WidgetInfo widget) { return valueKey.equals(widget.getValueKey()); } @Override public void describeTo(Description description) { description.appendText("with value key: ").appendText(valueKey); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/model/WidgetInfo.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.model; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.Beta; import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * Represents a Flutter widget, containing all the properties that are accessible in Espresso. * *

Note, this class should typically be decoded from the Flutter testing protocol. Users of * Espresso testing framework should rarely have the needs to build their own {@link WidgetInfo} * instance. * *

Also, the current implementation is hard-coded and potentially only works with a limited set * of {@code WidgetMatchers}. Later, we might consider codegen of representations for Flutter * widgets for extensibility. */ @Beta public class WidgetInfo { /** A String representation of a Flutter widget's ValueKey. */ @Nullable private final String valueKey; /** A String representation of the runtime type of the widget. */ private final String runtimeType; /** The widget's text property. */ @Nullable private final String text; /** The widget's tooltip property. */ @Nullable private final String tooltip; WidgetInfo( @Nullable String valueKey, String runtimeType, @Nullable String text, @Nullable String tooltip) { this.valueKey = valueKey; this.runtimeType = checkNotNull(runtimeType, "RuntimeType cannot be null."); this.text = text; this.tooltip = tooltip; } /** Returns a String representation of the Flutter widget's ValueKey. Could be null. */ @Nullable public String getValueKey() { return valueKey; } /** Returns a String representation of the runtime type of the Flutter widget. */ @Nonnull public String getType() { return runtimeType; } /** Returns the widget's 'text' property. Will be null for widgets without a 'text' property. */ @Nullable public String getText() { return text; } /** * Returns the widget's 'tooltip' property. Will be null for widgets without a 'tooltip' property. */ @Nullable public String getTooltip() { return tooltip; } @Override public boolean equals(Object obj) { if (obj instanceof WidgetInfo) { WidgetInfo widget = (WidgetInfo) obj; return Objects.equals(widget.valueKey, this.valueKey) && Objects.equals(widget.runtimeType, this.runtimeType) && Objects.equals(widget.text, this.text) && Objects.equals(widget.tooltip, this.tooltip); } else { return false; } } @Override public int hashCode() { return Objects.hash(valueKey, runtimeType, text, tooltip); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Widget ["); sb.append("runtimeType=").append(runtimeType).append(","); if (valueKey != null) { sb.append("valueKey=").append(valueKey).append(","); } if (text != null) { sb.append("text=").append(text).append(","); } if (tooltip != null) { sb.append("tooltip=").append(tooltip).append(","); } sb.append("]"); return sb.toString(); } } ================================================ FILE: packages/espresso/android/src/main/java/androidx/test/espresso/flutter/model/WidgetInfoBuilder.java ================================================ // Copyright 2013 The Flutter 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 androidx.test.espresso.flutter.model; import static com.google.common.base.Preconditions.checkNotNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * Builder for {@link WidgetInfo}. * *

Internal only. Users of Espresso framework should rarely have the needs to build their own * {@link WidgetInfo} instance. */ public class WidgetInfoBuilder { @Nullable private String valueKey; private String runtimeType; @Nullable private String text; @Nullable private String tooltip; /** Empty constructor. */ public WidgetInfoBuilder() {} /** * Constructs the builder with the given {@code runtimeType}. * * @param runtimeType the runtime type of the widget. Cannot be null. */ public WidgetInfoBuilder(@Nonnull String runtimeType) { this.runtimeType = checkNotNull(runtimeType, "RuntimeType cannot be null."); } /** * Sets the value key of the widget. * * @param valueKey the value key of the widget that shall be set. Could be null. */ public WidgetInfoBuilder setValueKey(@Nullable String valueKey) { this.valueKey = valueKey; return this; } /** * Sets the runtime type of the widget. * * @param runtimeType the runtime type of the widget that shall be set. Cannot be null. */ public WidgetInfoBuilder setRuntimeType(@Nonnull String runtimeType) { this.runtimeType = checkNotNull(runtimeType, "RuntimeType cannot be null."); return this; } /** * Sets the text of the widget. * * @param text the text of the widget that shall be set. Can be null. */ public WidgetInfoBuilder setText(@Nullable String text) { this.text = text; return this; } /** * Sets the tooltip of the widget. * * @param tooltip the tooltip of the widget that shall be set. Can be null. */ public WidgetInfoBuilder setTooltip(@Nullable String tooltip) { this.tooltip = tooltip; return this; } /** Builds and returns the {@code WidgetInfo} instance. */ public WidgetInfo build() { return new WidgetInfo(valueKey, runtimeType, text, tooltip); } } ================================================ FILE: packages/espresso/android/src/main/java/com/example/espresso/EspressoPlugin.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package com.example.espresso; import androidx.annotation.NonNull; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; /** EspressoPlugin */ public class EspressoPlugin implements FlutterPlugin, MethodCallHandler { @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { final MethodChannel channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "espresso"); channel.setMethodCallHandler(new EspressoPlugin()); } // This static function is optional and equivalent to onAttachedToEngine. It supports the old // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting // plugin registration via this function while apps migrate to use the new Android APIs // post-flutter-1.12 via https://flutter.dev/go/android-project-migration. // // It is encouraged to share logic between onAttachedToEngine and registerWith to keep // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called // depending on the user's project. onAttachedToEngine or registerWith must both be defined // in the same class. @SuppressWarnings("deprecation") public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), "espresso"); channel.setMethodCallHandler(new EspressoPlugin()); } @Override public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { if (call.method.equals("getPlatformVersion")) { result.success("Android " + android.os.Build.VERSION.RELEASE); } else { result.notImplemented(); } } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {} } ================================================ FILE: packages/espresso/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: packages/espresso/example/.metadata ================================================ # 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: 0190e40457d43e17bdfaf046dfa634cbc5bf28b9 channel: unknown project_type: app ================================================ FILE: packages/espresso/example/README.md ================================================ # espresso_example Demonstrates how to use the espresso package. The espresso package only runs tests on Android. The example runs on iOS, but this is only to keep our continuous integration bots green. ## Getting Started To run the Espresso tests: ```java flutter build apk --debug ./gradlew app:connectedAndroidTest ``` ================================================ FILE: packages/espresso/example/android/.gitignore ================================================ gradle-wrapper.jar /.gradle /captures/ /gradlew /gradlew.bat /local.properties GeneratedPluginRegistrant.java ================================================ FILE: packages/espresso/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 lintOptions { disable 'InvalidPackage' } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.espresso_example" minSdkVersion 16 targetSdkVersion 29 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { testImplementation 'junit:junit:4.13.2' testImplementation "com.google.truth:truth:1.0" androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' // Core library api 'androidx.test:core:1.2.0' // AndroidJUnitRunner and JUnit Rules androidTestImplementation 'androidx.test:runner:1.1.0' androidTestImplementation 'androidx.test:rules:1.1.0' // Assertions androidTestImplementation 'androidx.test.ext:junit:1.0.0' androidTestImplementation 'androidx.test.ext:truth:1.0.0' androidTestImplementation 'com.google.truth:truth:0.42' // Espresso dependencies androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.0' androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0' androidTestImplementation 'androidx.test.espresso:espresso-accessibility:3.1.0' androidTestImplementation 'androidx.test.espresso:espresso-web:3.1.0' androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.1.0' // The following Espresso dependency can be either "implementation" // or "androidTestImplementation", depending on whether you want the // dependency to appear on your APK's compile classpath or the test APK // classpath. androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.1.0' } ================================================ FILE: packages/espresso/example/android/app/src/androidTest/java/com/example/MainActivityTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package com.example.espresso_example; import static androidx.test.espresso.flutter.EspressoFlutter.onFlutterWidget; import static androidx.test.espresso.flutter.action.FlutterActions.click; import static androidx.test.espresso.flutter.action.FlutterActions.syntheticClick; import static androidx.test.espresso.flutter.assertion.FlutterAssertions.matches; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withText; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withTooltip; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withValueKey; import static com.google.common.truth.Truth.assertThat; import androidx.test.core.app.ActivityScenario; import androidx.test.espresso.flutter.EspressoFlutter.WidgetInteraction; import androidx.test.espresso.flutter.assertion.FlutterAssertions; import androidx.test.espresso.flutter.matcher.FlutterMatchers; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; /** Unit tests for {@link EspressoFlutter}. */ @RunWith(AndroidJUnit4.class) public class MainActivityTest { @Before public void setUp() throws Exception { ActivityScenario.launch(MainActivity.class); } @Test public void performTripleClick() { WidgetInteraction interaction = onFlutterWidget(withTooltip("Increment")).perform(click(), click()).perform(click()); assertThat(interaction).isNotNull(); onFlutterWidget(withValueKey("CountText")).check(matches(withText("Button tapped 3 times."))); } @Test public void performClick() { WidgetInteraction interaction = onFlutterWidget(withTooltip("Increment")).perform(click()); assertThat(interaction).isNotNull(); onFlutterWidget(withValueKey("CountText")).check(matches(withText("Button tapped 1 time."))); } @Test public void performSyntheticClick() { WidgetInteraction interaction = onFlutterWidget(withTooltip("Increment")).perform(syntheticClick()); assertThat(interaction).isNotNull(); onFlutterWidget(withValueKey("CountText")).check(matches(withText("Button tapped 1 time."))); } @Test public void performTwiceSyntheticClicks() { WidgetInteraction interaction = onFlutterWidget(withTooltip("Increment")).perform(syntheticClick(), syntheticClick()); assertThat(interaction).isNotNull(); onFlutterWidget(withValueKey("CountText")).check(matches(withText("Button tapped 2 times."))); } @Test public void isIncrementButtonExists() { onFlutterWidget(FlutterMatchers.withTooltip("Increment")) .check(FlutterAssertions.matches(FlutterMatchers.isExisting())); } @Test public void isAppBarExists() { onFlutterWidget(FlutterMatchers.withType("AppBar")) .check(FlutterAssertions.matches(FlutterMatchers.isExisting())); } } ================================================ FILE: packages/espresso/example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: packages/espresso/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/espresso/example/android/app/src/main/java/com/example/espresso_example/MainActivity.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package com.example.espresso_example; import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; public class MainActivity extends FlutterActivity { @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {} } ================================================ FILE: packages/espresso/example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: packages/espresso/example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: packages/espresso/example/android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: packages/espresso/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/espresso/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/espresso/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true ================================================ FILE: packages/espresso/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/espresso/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); /// Example app for Espresso plugin. class MyApp extends StatelessWidget { /// Default Constructor const MyApp({Key? key}) : super(key: key); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( // This is the theme of your application. // // Try running your application with "flutter run". You'll see the // application has a blue toolbar. Then, without quitting the app, try // changing the primarySwatch below to Colors.green and then invoke // "hot reload" (press "r" in the console where you ran "flutter run", // or simply save your changes to "hot reload" in a Flutter IDE). // Notice that the counter didn't reset back to zero; the application // is not restarted. primarySwatch: Colors.blue, ), home: const _MyHomePage(title: 'Flutter Demo Home Page'), ); } } class _MyHomePage extends StatefulWidget { const _MyHomePage({Key? key, required this.title}) : super(key: key); // This widget is the home page of your application. It is stateful, meaning // that it has a State object (defined below) that contains fields that affect // how it looks. // This class is the configuration for the state. It holds the values (in this // case the title) provided by the parent (in this case the App widget) and // used by the build method of the State. Fields in a Widget subclass are // always marked "final". final String title; @override State<_MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<_MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { // This call to setState tells the Flutter framework that something has // changed in this State, which causes it to rerun the build method below // so that the display can reflect the updated values. If we changed // _counter without calling setState(), then the build method would not be // called again, and so nothing would appear to happen. _counter++; }); } @override Widget build(BuildContext context) { // This method is rerun every time setState is called, for instance as done // by the _incrementCounter method above. // // The Flutter framework has been optimized to make rerunning build methods // fast, so that you can just rebuild anything that needs updating rather // than having to individually change instances of widgets. return Scaffold( appBar: AppBar( // Here we take the value from the MyHomePage object that was created by // the App.build method, and use it to set our appbar title. title: Text(widget.title), ), body: Center( // Center is a layout widget. It takes a single child and positions it // in the middle of the parent. child: Column( // Column is also a layout widget. It takes a list of children and // arranges them vertically. By default, it sizes itself to fit its // children horizontally, and tries to be as tall as its parent. // // Invoke "debug painting" (press "p" in the console, choose the // "Toggle Debug Paint" action from the Flutter Inspector in Android // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) // to see the wireframe for each widget. // // Column has various properties to control how it sizes itself and // how it positions its children. Here we use mainAxisAlignment to // center the children vertically; the main axis here is the vertical // axis because Columns are vertical (the cross axis would be // horizontal). mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'Button tapped $_counter time${_counter == 1 ? '' : 's'}.', key: const ValueKey('CountText'), ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } } ================================================ FILE: packages/espresso/example/pubspec.yaml ================================================ name: espresso_example description: Demonstrates how to use the espresso plugin. publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter dev_dependencies: espresso: # When depending on this package from a real application you should use: # espresso: ^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: ../ flutter_driver: sdk: flutter flutter_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/espresso/example/test_driver/example.dart ================================================ // Copyright 2013 The Flutter 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:espresso_example/main.dart' as app; import 'package:flutter_driver/driver_extension.dart'; void main() { enableFlutterDriverExtension(); app.main(); } ================================================ FILE: packages/espresso/pubspec.yaml ================================================ name: espresso 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+8 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: platforms: android: package: com.example.espresso pluginClass: EspressoPlugin dependencies: flutter: sdk: flutter dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/file_selector/file_selector/AUTHORS ================================================ # 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. 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> TowaYamashita ================================================ FILE: packages/file_selector/file_selector/CHANGELOG.md ================================================ ## 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. * Changes XTypeGroup initialization from final to const. * Updates minimum Flutter version to 2.10. ## 0.9.2 * Adds an endorsed iOS implementation. ## 0.9.1 * Adds an endorsed Linux implementation. ## 0.9.0 * **BREAKING CHANGE**: The following methods: * `openFile` * `openFiles` * `getSavePath` can throw `ArgumentError`s if called with any `XTypeGroup`s that do not contain appropriate filters for the current platform. For example, an `XTypeGroup` that only specifies `webWildCards` will throw on non-web platforms. To avoid runtime errors, ensure that all `XTypeGroup`s (other than wildcards) set filters that cover every platform your application targets. See the README for details. ## 0.8.4+3 * Improves API docs and examples. * Minor fixes for new analysis options. ## 0.8.4+2 * Removes unnecessary imports. * Adds OS version support information to README. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.8.4+1 * Adds README information about macOS entitlements. * Adds necessary entitlement to macOS example. ## 0.8.4 * Adds an endorsed macOS implementation. ## 0.8.3 * Adds an endorsed Windows implementation. ## 0.8.2+1 * Minor code cleanup for new analysis rules. * Updated package description. ## 0.8.2 * Update `platform_plugin_interface` version requirement. ## 0.8.1 Endorse the web implementation. ## 0.8.0 Migrate to null safety. ## 0.7.0+2 * Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets. ## 0.7.0+1 * Update Flutter SDK constraint. ## 0.7.0 * Initial Open Source release. ================================================ FILE: packages/file_selector/file_selector/LICENSE ================================================ 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. ================================================ FILE: packages/file_selector/file_selector/README.md ================================================ # file_selector [![pub package](https://img.shields.io/pub/v/file_selector.svg)](https://pub.dartlang.org/packages/file_selector) A Flutter plugin that manages files and interactions with file dialogs. | | iOS | Linux | macOS | Web | Windows | |-------------|--------|-------|--------|-----|-------------| | **Support** | iOS 9+ | Any | 10.11+ | Any | Windows 10+ | ## Usage To use this plugin, add `file_selector` as a [dependency in your pubspec.yaml file](https://flutter.dev/platform-plugins/). ### macOS You will need to [add an entitlement][entitlement] for either read-only access: ```xml com.apple.security.files.user-selected.read-only ``` or read/write access: ```xml com.apple.security.files.user-selected.read-write ``` depending on your use case. ### Examples Here are small examples that show you how to use the API. Please also take a look at our [example][example] app. #### Open a single file ``` dart const XTypeGroup typeGroup = XTypeGroup( label: 'images', extensions: ['jpg', 'png'], ); final XFile? file = await openFile(acceptedTypeGroups: [typeGroup]); ``` #### Open multiple files at once ``` dart const XTypeGroup jpgsTypeGroup = XTypeGroup( label: 'JPEGs', extensions: ['jpg', 'jpeg'], ); const XTypeGroup pngTypeGroup = XTypeGroup( label: 'PNGs', extensions: ['png'], ); final List files = await openFiles(acceptedTypeGroups: [ jpgsTypeGroup, pngTypeGroup, ]); ``` #### Save a file ```dart const String fileName = 'suggested_name.txt'; final String? path = await getSavePath(suggestedName: fileName); if (path == null) { // Operation was canceled by the user. return; } final Uint8List fileData = Uint8List.fromList('Hello World!'.codeUnits); const String mimeType = 'text/plain'; final XFile textFile = XFile.fromData(fileData, mimeType: mimeType, name: fileName); await textFile.saveTo(path); ``` #### Get a directory path ```dart final String? directoryPath = await getDirectoryPath(); if (directoryPath == null) { // Operation was canceled by the user. return; } ``` ### Filtering by file types Different platforms support different type group filter options. To avoid `ArgumentError`s on some platforms, ensure that any `XTypeGroup`s you pass set filters that cover all platforms you are targeting, or that you conditionally pass different `XTypeGroup`s based on `Platform`. | | Linux | macOS | Web | Windows | |----------------|-------|--------|-----|-------------| | `extensions` | ✔️ | ✔️ | ✔️ | ✔️ | | `mimeTypes` | ✔️ | ✔️† | ✔️ | | | `macUTIs` | | ✔️ | | | | `webWildCards` | | | ✔️ | | † `mimeTypes` are not supported on version of macOS earlier than 11 (Big Sur). ### Features supported by platform | Feature | Description | iOS | Linux | macOS | Windows | Web | | ---------------------- |----------------------------------- |--------- | ---------- | -------- | ------------ | ----------- | | Choose a single file | Pick a file/image | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | | Choose multiple files | Pick multiple files/images | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | | Choose a save location | Pick a directory to save a file in | ❌ | ✔️ | ✔️ | ✔️ | ❌ | | Choose a directory | Pick a folder and get its path | ❌ | ✔️ | ✔️ | ✔️ | ❌ | [example]:./example [entitlement]: https://docs.flutter.dev/desktop#entitlements-and-the-app-sandbox ================================================ FILE: packages/file_selector/file_selector/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: packages/file_selector/file_selector/example/.metadata ================================================ # 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: 7736f3bc90270dcb0480db2ccffbf1d13c28db85 channel: dev project_type: app ================================================ FILE: packages/file_selector/file_selector/example/README.md ================================================ # file_selector_example Demonstrates how to use the file_selector plugin. ================================================ FILE: packages/file_selector/file_selector/example/build.excerpt.yaml ================================================ targets: $default: sources: include: - lib/** # Some default includes that aren't really used here but will prevent # false-negative warnings: - $package$ - lib/$lib$ exclude: - '**/.*/**' - '**/build/**' builders: code_excerpter|code_excerpter: enabled: true ================================================ FILE: packages/file_selector/file_selector/example/ios/.gitignore ================================================ **/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 ================================================ FILE: packages/file_selector/file_selector/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 11.0 ================================================ FILE: packages/file_selector/file_selector/example/ios/Flutter/Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/file_selector/file_selector/example/ios/Flutter/Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/file_selector/file_selector/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/file_selector/file_selector/example/ios/Runner/AppDelegate.swift ================================================ // Copyright 2013 The Flutter 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) } } ================================================ FILE: packages/file_selector/file_selector/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/file_selector/file_selector/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "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" } } ================================================ FILE: packages/file_selector/file_selector/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: packages/file_selector/file_selector/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/file_selector/file_selector/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/file_selector/file_selector/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName example CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents ================================================ FILE: packages/file_selector/file_selector/example/ios/Runner/Runner-Bridging-Header.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "GeneratedPluginRegistrant.h" ================================================ FILE: packages/file_selector/file_selector/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 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.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 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; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/file_selector/file_selector/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/file_selector/file_selector/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/file_selector/file_selector/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: packages/file_selector/file_selector/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/file_selector/file_selector/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/file_selector/file_selector/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/file_selector/file_selector/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: packages/file_selector/file_selector/example/lib/get_directory_page.dart ================================================ // Copyright 2013 The Flutter 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/file_selector.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// Screen that shows an example of getDirectoryPath class GetDirectoryPage extends StatelessWidget { /// Default Constructor GetDirectoryPage({Key? key}) : super(key: key); final bool _isIOS = !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS; Future _getDirectoryPath(BuildContext context) async { const String confirmButtonText = 'Choose'; final String? directoryPath = await getDirectoryPath( confirmButtonText: confirmButtonText, ); if (directoryPath == null) { // Operation was canceled by the user. return; } if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => TextDisplay(directoryPath), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open a text file'), ), 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, ), onPressed: _isIOS ? null : () => _getDirectoryPath(context), child: const Text( 'Press to ask user to choose a directory (not supported on iOS).', ), ), ], ), ), ); } } /// Widget that displays a text file in a dialog class TextDisplay extends StatelessWidget { /// Default Constructor const TextDisplay(this.directoryPath, {Key? key}) : super(key: key); /// Directory path final String directoryPath; @override Widget build(BuildContext context) { return AlertDialog( title: const Text('Selected Directory'), content: Scrollbar( child: SingleChildScrollView( child: Text(directoryPath), ), ), actions: [ TextButton( child: const Text('Close'), onPressed: () => Navigator.pop(context), ), ], ); } } ================================================ FILE: packages/file_selector/file_selector/example/lib/home_page.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; /// Home Page of the application class HomePage extends StatelessWidget { /// Default Constructor const HomePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final ButtonStyle 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, ); return Scaffold( appBar: AppBar( title: const Text('File Selector Demo Home Page'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( style: style, child: const Text('Open a text file'), onPressed: () => Navigator.pushNamed(context, '/open/text'), ), const SizedBox(height: 10), ElevatedButton( style: style, child: const Text('Open an image'), onPressed: () => Navigator.pushNamed(context, '/open/image'), ), const SizedBox(height: 10), ElevatedButton( style: style, child: const Text('Open multiple images'), onPressed: () => Navigator.pushNamed(context, '/open/images'), ), const SizedBox(height: 10), ElevatedButton( style: style, child: const Text('Save a file'), onPressed: () => Navigator.pushNamed(context, '/save/text'), ), const SizedBox(height: 10), ElevatedButton( style: style, child: const Text('Open a get directory dialog'), onPressed: () => Navigator.pushNamed(context, '/directory'), ), ], ), ), ); } } ================================================ FILE: packages/file_selector/file_selector/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'get_directory_page.dart'; import 'home_page.dart'; import 'open_image_page.dart'; import 'open_multiple_images_page.dart'; import 'open_text_page.dart'; import 'save_text_page.dart'; void main() { runApp(const MyApp()); } /// MyApp is the Main Application class MyApp extends StatelessWidget { /// Default Constructor const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'File Selector Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: const HomePage(), routes: { '/open/image': (BuildContext context) => const OpenImagePage(), '/open/images': (BuildContext context) => const OpenMultipleImagesPage(), '/open/text': (BuildContext context) => const OpenTextPage(), '/save/text': (BuildContext context) => SaveTextPage(), '/directory': (BuildContext context) => GetDirectoryPage(), }, ); } } ================================================ FILE: packages/file_selector/file_selector/example/lib/open_image_page.dart ================================================ // Copyright 2013 The Flutter 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:file_selector/file_selector.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// Screen that shows an example of openFiles class OpenImagePage extends StatelessWidget { /// Default Constructor const OpenImagePage({Key? key}) : super(key: key); Future _openImageFile(BuildContext context) async { // #docregion SingleOpen const XTypeGroup typeGroup = XTypeGroup( label: 'images', extensions: ['jpg', 'png'], ); final XFile? file = await openFile(acceptedTypeGroups: [typeGroup]); // #enddocregion SingleOpen if (file == null) { // Operation was canceled by the user. return; } final String fileName = file.name; final String filePath = file.path; if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => ImageDisplay(fileName, filePath), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open an image'), ), 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 open an image file(png, jpg)'), onPressed: () => _openImageFile(context), ), ], ), ), ); } } /// Widget that displays a text file in a dialog class ImageDisplay extends StatelessWidget { /// Default Constructor const ImageDisplay(this.fileName, this.filePath, {Key? key}) : super(key: key); /// Image's name final String fileName; /// Image's path final String filePath; @override Widget build(BuildContext context) { return AlertDialog( title: Text(fileName), // On web the filePath is a blob url // while on other platforms it is a system path. content: kIsWeb ? Image.network(filePath) : Image.file(File(filePath)), actions: [ TextButton( child: const Text('Close'), onPressed: () { Navigator.pop(context); }, ), ], ); } } ================================================ FILE: packages/file_selector/file_selector/example/lib/open_multiple_images_page.dart ================================================ // Copyright 2013 The Flutter 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:file_selector/file_selector.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// Screen that shows an example of openFiles class OpenMultipleImagesPage extends StatelessWidget { /// Default Constructor const OpenMultipleImagesPage({Key? key}) : super(key: key); Future _openImageFile(BuildContext context) async { // #docregion MultiOpen const XTypeGroup jpgsTypeGroup = XTypeGroup( label: 'JPEGs', extensions: ['jpg', 'jpeg'], ); const XTypeGroup pngTypeGroup = XTypeGroup( label: 'PNGs', extensions: ['png'], ); final List files = await openFiles(acceptedTypeGroups: [ jpgsTypeGroup, pngTypeGroup, ]); // #enddocregion MultiOpen if (files.isEmpty) { // Operation was canceled by the user. return; } if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => MultipleImagesDisplay(files), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open multiple images'), ), 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 open multiple images (png, jpg)'), onPressed: () => _openImageFile(context), ), ], ), ), ); } } /// Widget that displays a text file in a dialog class MultipleImagesDisplay extends StatelessWidget { /// Default Constructor const MultipleImagesDisplay(this.files, {Key? key}) : super(key: key); /// The files containing the images final List files; @override Widget build(BuildContext context) { return AlertDialog( title: const Text('Gallery'), // On web the filePath is a blob url // while on other platforms it is a system path. content: Center( child: Row( children: [ ...files.map( (XFile file) => Flexible( child: kIsWeb ? Image.network(file.path) : Image.file(File(file.path))), ) ], ), ), actions: [ TextButton( child: const Text('Close'), onPressed: () { Navigator.pop(context); }, ), ], ); } } ================================================ FILE: packages/file_selector/file_selector/example/lib/open_text_page.dart ================================================ // Copyright 2013 The Flutter 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/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; /// Screen that shows an example of openFile class OpenTextPage extends StatelessWidget { /// Default Constructor const OpenTextPage({Key? key}) : super(key: key); Future _openTextFile(BuildContext context) async { const XTypeGroup typeGroup = XTypeGroup( label: 'text', extensions: ['txt', 'json'], ); // This demonstrates using an initial directory for the prompt, which should // only be done in cases where the application can likely predict where the // file would be. In most cases, this parameter should not be provided. final String initialDirectory = (await getApplicationDocumentsDirectory()).path; final XFile? file = await openFile( acceptedTypeGroups: [typeGroup], initialDirectory: initialDirectory, ); if (file == null) { // Operation was canceled by the user. return; } final String fileName = file.name; final String fileContent = await file.readAsString(); if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => TextDisplay(fileName, fileContent), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open a text file'), ), 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 open a text file (json, txt)'), onPressed: () => _openTextFile(context), ), ], ), ), ); } } /// Widget that displays a text file in a dialog class TextDisplay extends StatelessWidget { /// Default Constructor const TextDisplay(this.fileName, this.fileContent, {Key? key}) : super(key: key); /// File's name final String fileName; /// File to display final String fileContent; @override Widget build(BuildContext context) { return AlertDialog( title: Text(fileName), content: Scrollbar( child: SingleChildScrollView( child: Text(fileContent), ), ), actions: [ TextButton( child: const Text('Close'), onPressed: () => Navigator.pop(context), ), ], ); } } ================================================ FILE: packages/file_selector/file_selector/example/lib/readme_standalone_excerpts.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This file exists solely to host compiled excerpts for README.md, and is not // intended for use as an actual example application. // ignore_for_file: public_member_api_docs // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('README snippet app'), ), body: const Text('See example in main.dart'), ), ); } Future saveFile() async { // #docregion Save const String fileName = 'suggested_name.txt'; final String? path = await getSavePath(suggestedName: fileName); if (path == null) { // Operation was canceled by the user. return; } final Uint8List fileData = Uint8List.fromList('Hello World!'.codeUnits); const String mimeType = 'text/plain'; final XFile textFile = XFile.fromData(fileData, mimeType: mimeType, name: fileName); await textFile.saveTo(path); // #enddocregion Save } Future directoryPath() async { // #docregion GetDirectory final String? directoryPath = await getDirectoryPath(); if (directoryPath == null) { // Operation was canceled by the user. return; } // #enddocregion GetDirectory } } ================================================ FILE: packages/file_selector/file_selector/example/lib/save_text_page.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(tarrinneal): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; /// Page for showing an example of saving with file_selector class SaveTextPage extends StatelessWidget { /// Default Constructor SaveTextPage({Key? key}) : super(key: key); final bool _isIOS = !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS; final TextEditingController _nameController = TextEditingController(); final TextEditingController _contentController = TextEditingController(); Future _saveFile() async { final String fileName = _nameController.text; // This demonstrates using an initial directory for the prompt, which should // only be done in cases where the application can likely predict where the // file will be saved. In most cases, this parameter should not be provided. final String initialDirectory = (await getApplicationDocumentsDirectory()).path; final String? path = await getSavePath( initialDirectory: initialDirectory, suggestedName: fileName, ); if (path == null) { // Operation was canceled by the user. return; } final String text = _contentController.text; final Uint8List fileData = Uint8List.fromList(text.codeUnits); const String fileMimeType = 'text/plain'; final XFile textFile = XFile.fromData(fileData, mimeType: fileMimeType, name: fileName); await textFile.saveTo(path); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Save text into a file'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 300, child: TextField( minLines: 1, maxLines: 12, controller: _nameController, decoration: const InputDecoration( hintText: '(Optional) Suggest File Name', ), ), ), SizedBox( width: 300, child: TextField( minLines: 1, maxLines: 12, controller: _contentController, decoration: const InputDecoration( hintText: 'Enter File Contents', ), ), ), const SizedBox(height: 10), 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, ), onPressed: _isIOS ? null : () => _saveFile(), child: const Text( 'Press to save a text file (not supported on iOS).', ), ), ], ), ), ); } } ================================================ FILE: packages/file_selector/file_selector/example/linux/.gitignore ================================================ flutter/ephemeral ================================================ FILE: packages/file_selector/file_selector/example/linux/CMakeLists.txt ================================================ # Project-level configuration. cmake_minimum_required(VERSION 3.10) project(runner LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. set(BINARY_NAME "example") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID set(APPLICATION_ID "dev.flutter.plugins.file_selector_linux_example") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. cmake_policy(SET CMP0063 NEW) # Load bundled libraries from the lib/ directory relative to the binary. set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Root filesystem for cross-building. if(FLUTTER_TARGET_PLATFORM_SYSROOT) set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) endif() # Define build configuration options. if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() # Compilation settings that should be applied to most targets. # # Be cautious about adding new options here, as plugins use this function by # default. In most cases, you should add new options to specific targets instead # of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_14) target_compile_options(${TARGET} PRIVATE -Wall -Werror) target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") endfunction() # Flutter library and tool build rules. set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") # Define the application target. To change its name, change BINARY_NAME above, # not the value here, or `flutter run` will no longer work. # # Any new source files that you add to the application should be added here. add_executable(${BINARY_NAME} "main.cc" "my_application.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" ) # Apply the standard set of build settings. This can be removed for applications # that need different build settings. apply_standard_settings(${BINARY_NAME}) # Add dependency libraries. Add any application-specific dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) # Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) # Only the install-generated bundle's copy of the executable will launch # correctly, since the resources must in the right relative locations. To avoid # people trying to run the unbundled copy, put it in a subdirectory instead of # the default top-level location. set_target_properties(${BINARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" ) # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # By default, "installing" just makes a relocatable bundle in the build # directory. set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() # Start with a clean build bundle directory every time. install(CODE " file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") " COMPONENT Runtime) set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) install(FILES "${bundled_library}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endforeach(bundled_library) # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() ================================================ FILE: packages/file_selector/file_selector/example/linux/flutter/CMakeLists.txt ================================================ # This file controls Flutter-level build steps. It should not be edited. cmake_minimum_required(VERSION 3.10) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. # Serves the same purpose as list(TRANSFORM ... PREPEND ...), # which isn't available in 3.10. function(list_prepend LIST_NAME PREFIX) set(NEW_LIST "") foreach(element ${${LIST_NAME}}) list(APPEND NEW_LIST "${PREFIX}${element}") endforeach(element) set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) endfunction() # === Flutter Library === # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "fl_basic_message_channel.h" "fl_binary_codec.h" "fl_binary_messenger.h" "fl_dart_project.h" "fl_engine.h" "fl_json_message_codec.h" "fl_json_method_codec.h" "fl_message_codec.h" "fl_method_call.h" "fl_method_channel.h" "fl_method_codec.h" "fl_method_response.h" "fl_plugin_registrar.h" "fl_plugin_registry.h" "fl_standard_message_codec.h" "fl_standard_method_codec.h" "fl_string_codec.h" "fl_value.h" "fl_view.h" "flutter_linux.h" ) list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") target_link_libraries(flutter INTERFACE PkgConfig::GTK PkgConfig::GLIB PkgConfig::GIO ) add_dependencies(flutter flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/_phony_ COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ) ================================================ FILE: packages/file_selector/file_selector/example/linux/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST file_selector_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/file_selector/file_selector/example/linux/main.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "my_application.h" int main(int argc, char** argv) { g_autoptr(MyApplication) app = my_application_new(); return g_application_run(G_APPLICATION(app), argc, argv); } ================================================ FILE: packages/file_selector/file_selector/example/linux/my_application.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "my_application.h" #include #ifdef GDK_WINDOWING_X11 #include #endif #include "flutter/generated_plugin_registrant.h" struct _MyApplication { GtkApplication parent_instance; char** dart_entrypoint_arguments; }; G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // Implements GApplication::activate. static void my_application_activate(GApplication* application) { MyApplication* self = MY_APPLICATION(application); GtkWindow* window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); // Use a header bar when running in GNOME as this is the common style used // by applications and is the setup most users will be using (e.g. Ubuntu // desktop). // If running on X and not using GNOME then just use a traditional title bar // in case the window manager does more exotic layout, e.g. tiling. // If running on Wayland assume the header bar will work (may need changing // if future cases occur). gboolean use_header_bar = TRUE; #ifdef GDK_WINDOWING_X11 GdkScreen* screen = gtk_window_get_screen(window); if (GDK_IS_X11_SCREEN(screen)) { const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); if (g_strcmp0(wm_name, "GNOME Shell") != 0) { use_header_bar = FALSE; } } #endif if (use_header_bar) { GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); gtk_header_bar_set_title(header_bar, "example"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); } else { gtk_window_set_title(window, "example"); } gtk_window_set_default_size(window, 1280, 720); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); fl_dart_project_set_dart_entrypoint_arguments( project, self->dart_entrypoint_arguments); FlView* view = fl_view_new(project); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view)); gtk_widget_grab_focus(GTK_WIDGET(view)); } // Implements GApplication::local_command_line. static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { MyApplication* self = MY_APPLICATION(application); // Strip out the first argument as it is the binary name. self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); g_autoptr(GError) error = nullptr; if (!g_application_register(application, nullptr, &error)) { g_warning("Failed to register: %s", error->message); *exit_status = 1; return TRUE; } g_application_activate(application); *exit_status = 0; return TRUE; } // Implements GObject::dispose. static void my_application_dispose(GObject* object) { MyApplication* self = MY_APPLICATION(object); g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); G_OBJECT_CLASS(my_application_parent_class)->dispose(object); } static void my_application_class_init(MyApplicationClass* klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; G_OBJECT_CLASS(klass)->dispose = my_application_dispose; } static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { return MY_APPLICATION(g_object_new(my_application_get_type(), "application-id", APPLICATION_ID, "flags", G_APPLICATION_NON_UNIQUE, nullptr)); } ================================================ FILE: packages/file_selector/file_selector/example/linux/my_application.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef FLUTTER_MY_APPLICATION_H_ #define FLUTTER_MY_APPLICATION_H_ #include G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, GtkApplication) /** * my_application_new: * * Creates a new Flutter-based application. * * Returns: a new #MyApplication. */ MyApplication* my_application_new(); #endif // FLUTTER_MY_APPLICATION_H_ ================================================ FILE: packages/file_selector/file_selector/example/macos/.gitignore ================================================ # Flutter-related **/Flutter/ephemeral/ **/Pods/ # Xcode-related **/dgph **/xcuserdata/ ================================================ FILE: packages/file_selector/file_selector/example/macos/Flutter/Flutter-Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/file_selector/file_selector/example/macos/Flutter/Flutter-Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/file_selector/file_selector/example/macos/Podfile ================================================ platform :osx, '10.11' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_macos_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) end end ================================================ FILE: packages/file_selector/file_selector/example/macos/Runner/AppDelegate.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } } ================================================ FILE: packages/file_selector/file_selector/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_64.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_1024.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/file_selector/file_selector/example/macos/Runner/Base.lproj/MainMenu.xib ================================================

================================================ FILE: packages/file_selector/file_selector/example/macos/Runner/Configs/AppInfo.xcconfig ================================================ // Application-level settings for the Runner target. // // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the // future. If not, the values below would default to using the project name when this becomes a // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. PRODUCT_NAME = example // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = com.example.example // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2022 com.example. All rights reserved. ================================================ FILE: packages/file_selector/file_selector/example/macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: packages/file_selector/file_selector/example/macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: packages/file_selector/file_selector/example/macos/Runner/Configs/Warnings.xcconfig ================================================ WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings GCC_WARN_UNDECLARED_SELECTOR = YES CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE CLANG_WARN__DUPLICATE_METHOD_MATCH = YES CLANG_WARN_PRAGMA_PACK = YES CLANG_WARN_STRICT_PROTOTYPES = YES CLANG_WARN_COMMA = YES GCC_WARN_STRICT_SELECTOR_MATCH = YES CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES GCC_WARN_SHADOW = YES CLANG_WARN_UNREACHABLE_CODE = YES ================================================ FILE: packages/file_selector/file_selector/example/macos/Runner/DebugProfile.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server com.apple.security.files.user-selected.read-write ================================================ FILE: packages/file_selector/file_selector/example/macos/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: packages/file_selector/file_selector/example/macos/Runner/MainFlutterWindow.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { let flutterViewController = FlutterViewController.init() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } } ================================================ FILE: packages/file_selector/file_selector/example/macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.files.user-selected.read-write ================================================ FILE: packages/file_selector/file_selector/example/macos/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 51; objects = { /* Begin PBXAggregateTarget section */ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { isa = PBXAggregateTarget; buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; buildPhases = ( 33CC111E2044C6BF0003C045 /* ShellScript */, ); dependencies = ( ); name = "Flutter Assemble"; productName = FLX; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 6BA632E5BE2B856B0D473EBF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C6D20B684858422917AB21A6 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC111A2044C6BA0003C045; remoteInfo = FLX; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 33CC110E2044A8840003C045 /* Bundle Framework */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Bundle Framework"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 17DF935FF296A265D8BE378B /* 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 = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 453A41FF685B9AACDF48F0C6 /* 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 = ""; }; 6FA96861AA2D76C12832F6C9 /* 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 = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; C6D20B684858422917AB21A6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 6BA632E5BE2B856B0D473EBF /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( 33E5194F232828860026EE4D /* AppInfo.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, ); path = Configs; sourceTree = ""; }; 33CC10E42044A3C60003C045 = { isa = PBXGroup; children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, 58708F6C9D1522F09C51DA54 /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* example.app */, ); name = Products; sourceTree = ""; }; 33CC11242044D66E0003C045 /* Resources */ = { isa = PBXGroup; children = ( 33CC10F22044A3C60003C045 /* Assets.xcassets */, 33CC10F42044A3C60003C045 /* MainMenu.xib */, 33CC10F72044A3C60003C045 /* Info.plist */, ); name = Resources; path = ..; sourceTree = ""; }; 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, ); path = Flutter; sourceTree = ""; }; 33FAB671232836740065AC1E /* Runner */ = { isa = PBXGroup; children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, 33CC11242044D66E0003C045 /* Resources */, 33BA886A226E78AF003329D5 /* Configs */, ); path = Runner; sourceTree = ""; }; 58708F6C9D1522F09C51DA54 /* Pods */ = { isa = PBXGroup; children = ( 453A41FF685B9AACDF48F0C6 /* Pods-Runner.debug.xcconfig */, 17DF935FF296A265D8BE378B /* Pods-Runner.release.xcconfig */, 6FA96861AA2D76C12832F6C9 /* Pods-Runner.profile.xcconfig */, ); name = Pods; path = Pods; sourceTree = ""; }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( C6D20B684858422917AB21A6 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( A778864BDDD7B12C41D66FBB /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, 028A8DA36859BD4F05694F96 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; }; }; }; 33CC111A2044C6BA0003C045 = { CreatedOnToolsVersion = 9.2; ProvisioningStyle = Manual; }; }; }; buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 33CC10E42044A3C60003C045; productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 028A8DA36859BD4F05694F96 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); 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 = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( Flutter/ephemeral/FlutterInputs.xcfilelist, ); inputPaths = ( Flutter/ephemeral/tripwire, ); outputFileListPaths = ( Flutter/ephemeral/FlutterOutputs.xcfilelist, ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; A778864BDDD7B12C41D66FBB /* [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", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 33CC10F52044A3C60003C045 /* Base */, ); name = MainMenu.xib; path = Runner; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Profile; }; 338D0CEA231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Profile; }; 338D0CEB231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Profile; }; 33CC10F92044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 33CC10FA2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; 33CC10FC2044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; 33CC10FD2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Release; }; 33CC111C2044C6BA0003C045 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 33CC111D2044C6BA0003C045 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10F92044A3C60003C045 /* Debug */, 33CC10FA2044A3C60003C045 /* Release */, 338D0CE9231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10FC2044A3C60003C045 /* Debug */, 33CC10FD2044A3C60003C045 /* Release */, 338D0CEA231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC111C2044C6BA0003C045 /* Debug */, 33CC111D2044C6BA0003C045 /* Release */, 338D0CEB231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } ================================================ FILE: packages/file_selector/file_selector/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/file_selector/file_selector/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/file_selector/file_selector/example/macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/file_selector/file_selector/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/file_selector/file_selector/example/pubspec.yaml ================================================ name: file_selector_example description: A new Flutter project. publish_to: none version: 1.0.0+1 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: file_selector: # When depending on this package from a real application you should use: # file_selector: ^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: ../ flutter: sdk: flutter path_provider: ^2.0.9 dev_dependencies: build_runner: ^2.1.10 flutter_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/file_selector/file_selector/example/web/index.html ================================================ example ================================================ FILE: packages/file_selector/file_selector/example/web/manifest.json ================================================ { "name": "example", "short_name": "example", "start_url": ".", "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", "description": "A new Flutter project.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [ { "src": "icons/Icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" } ] } ================================================ FILE: packages/file_selector/file_selector/example/windows/.gitignore ================================================ flutter/ephemeral/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: packages/file_selector/file_selector/example/windows/CMakeLists.txt ================================================ # Project-level configuration. cmake_minimum_required(VERSION 3.14) project(example LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. set(BINARY_NAME "example") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. cmake_policy(SET CMP0063 NEW) # Define build configuration option. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" CACHE STRING "" FORCE) else() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() endif() # Define settings for the Profile build mode. set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") # Use Unicode for all projects. add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. # # Be cautious about adding new options here, as plugins use this function by # default. In most cases, you should add new options to specific targets instead # of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") target_compile_options(${TARGET} PRIVATE /EHsc) target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() # Flutter library and tool build rules. set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build; see runner/CMakeLists.txt. add_subdirectory("runner") # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # Support files are copied into place next to the executable, so that it can # run in place. This is done instead of making a separate bundle (as on Linux) # so that building and running from within Visual Studio will work. set(BUILD_BUNDLE_DIR "$") # Make the "install" step default, as it's required to run. set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" CONFIGURATIONS Profile;Release COMPONENT Runtime) ================================================ FILE: packages/file_selector/file_selector/example/windows/flutter/CMakeLists.txt ================================================ # This file controls Flutter-level build steps. It should not be edited. cmake_minimum_required(VERSION 3.14) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "flutter_export.h" "flutter_windows.h" "flutter_messenger.h" "flutter_plugin_registrar.h" "flutter_texture_registrar.h" ) list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") add_dependencies(flutter flutter_assemble) # === Wrapper === list(APPEND CPP_WRAPPER_SOURCES_CORE "core_implementations.cc" "standard_codec.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_PLUGIN "plugin_registrar.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_APP "flutter_engine.cc" "flutter_view_controller.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") # Wrapper sources needed for a plugin. add_library(flutter_wrapper_plugin STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ) apply_standard_settings(flutter_wrapper_plugin) set_target_properties(flutter_wrapper_plugin PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(flutter_wrapper_plugin PROPERTIES CXX_VISIBILITY_PRESET hidden) target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) target_include_directories(flutter_wrapper_plugin PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_plugin flutter_assemble) # Wrapper sources needed for the runner. add_library(flutter_wrapper_app STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_APP} ) apply_standard_settings(flutter_wrapper_app) target_link_libraries(flutter_wrapper_app PUBLIC flutter) target_include_directories(flutter_wrapper_app PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_app flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" windows-x64 $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ) ================================================ FILE: packages/file_selector/file_selector/example/windows/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/file_selector/file_selector/example/windows/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) project(runner LANGUAGES CXX) # Define the application target. To change its name, change BINARY_NAME in the # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer # work. # # Any new source files that you add to the application should be added here. add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) # Apply the standard set of build settings. This can be removed for applications # that need different build settings. apply_standard_settings(${BINARY_NAME}) # Disable Windows macros that collide with C++ standard library functions. target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") # Add dependency libraries and include directories. Add any application-specific # dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") # Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) ================================================ FILE: packages/file_selector/file_selector/example/windows/runner/Runner.rc ================================================ // Microsoft Visual C++ generated resource script. // #pragma code_page(65001) #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APP_ICON ICON "resources\\app_icon.ico" ///////////////////////////////////////////////////////////////////////////// // // Version // #ifdef FLUTTER_BUILD_NUMBER #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER #else #define VERSION_AS_NUMBER 1,0,0 #endif #ifdef FLUTTER_BUILD_NAME #define VERSION_AS_STRING #FLUTTER_BUILD_NAME #else #define VERSION_AS_STRING "1.0.0" #endif VS_VERSION_INFO VERSIONINFO FILEVERSION VERSION_AS_NUMBER PRODUCTVERSION VERSION_AS_NUMBER FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "com.example" "\0" VALUE "FileDescription", "example" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "example" "\0" VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0" VALUE "OriginalFilename", "example.exe" "\0" VALUE "ProductName", "example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: packages/file_selector/file_selector/example/windows/runner/flutter_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "flutter_window.h" #include #include "flutter/generated_plugin_registrant.h" FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {} FlutterWindow::~FlutterWindow() {} bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opportunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) { return *result; } } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); } ================================================ FILE: packages/file_selector/file_selector/example/windows/runner/flutter_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_ #include #include #include #include "win32_window.h" // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { public: // Creates a new FlutterWindow hosting a Flutter view running |project|. explicit FlutterWindow(const flutter::DartProject& project); virtual ~FlutterWindow(); protected: // Win32Window: bool OnCreate() override; void OnDestroy() override; LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; private: // The project to run. flutter::DartProject project_; // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; }; #endif // RUNNER_FLUTTER_WINDOW_H_ ================================================ FILE: packages/file_selector/file_selector/example/windows/runner/main.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include "flutter_window.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t* command_line, _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { CreateAndAttachConsole(); } // Initialize COM, so that it is available for use in the library and/or // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); flutter::DartProject project(L"data"); std::vector command_line_arguments = GetCommandLineArguments(); project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.CreateAndShow(L"example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); ::MSG msg; while (::GetMessage(&msg, nullptr, 0, 0)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } ::CoUninitialize(); return EXIT_SUCCESS; } ================================================ FILE: packages/file_selector/file_selector/example/windows/runner/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Runner.rc // #define IDI_APP_ICON 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: packages/file_selector/file_selector/example/windows/runner/runner.exe.manifest ================================================ PerMonitorV2 ================================================ FILE: packages/file_selector/file_selector/example/windows/runner/utils.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "utils.h" #include #include #include #include #include void CreateAndAttachConsole() { if (::AllocConsole()) { FILE* unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } if (freopen_s(&unused, "CONOUT$", "w", stderr)) { _dup2(_fileno(stdout), 2); } std::ios::sync_with_stdio(); FlutterDesktopResyncOutputStreams(); } } std::vector GetCommandLineArguments() { // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. int argc; wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); if (argv == nullptr) { return std::vector(); } std::vector command_line_arguments; // Skip the first argument as it's the binary name. for (int i = 1; i < argc; i++) { command_line_arguments.push_back(Utf8FromUtf16(argv[i])); } ::LocalFree(argv); return command_line_arguments; } std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); } int target_length = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, nullptr, 0, nullptr, nullptr); if (target_length == 0) { return std::string(); } std::string utf8_string; utf8_string.resize(target_length); int converted_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); } return utf8_string; } ================================================ FILE: packages/file_selector/file_selector/example/windows/runner/utils.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_UTILS_H_ #define RUNNER_UTILS_H_ #include #include // Creates a console for the process, and redirects stdout and stderr to // it for both the runner and the Flutter library. void CreateAndAttachConsole(); // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string // encoded in UTF-8. Returns an empty std::string on failure. std::string Utf8FromUtf16(const wchar_t* utf16_string); // Gets the command line arguments passed in as a std::vector, // encoded in UTF-8. Returns an empty std::vector on failure. std::vector GetCommandLineArguments(); #endif // RUNNER_UTILS_H_ ================================================ FILE: packages/file_selector/file_selector/example/windows/runner/win32_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "win32_window.h" #include #include "resource.h" namespace { constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); // Scale helper to convert logical scaler values to physical using passed in // scale factor int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); if (!user32_module) { return; } auto enable_non_client_dpi_scaling = reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); FreeLibrary(user32_module); } } } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; // Returns the singleton registar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; } // Returns the name of the window class, registering the class if it hasn't // previously been registered. const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; bool class_registered_ = false; }; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; RegisterClass(&window_class); class_registered_ = true; } return kWindowClassName; } void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, const Size& size) { Destroy(); const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), static_cast(origin.y)}; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } return OnCreate(); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { case WM_DESTROY: window_handle_ = nullptr; Destroy(); if (quit_on_close_) { PostQuitMessage(0); } return 0; case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left; LONG newHeight = newRectSize->bottom - newRectSize->top; SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_SIZE: { RECT rect = GetClientArea(); if (child_content_ != nullptr) { // Size and position the child window. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); } return 0; } case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_); } return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } void Win32Window::Destroy() { OnDestroy(); if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, true); SetFocus(child_content_); } RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } void Win32Window::OnDestroy() { // No-op; provided for subclasses. } ================================================ FILE: packages/file_selector/file_selector/example/windows/runner/win32_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_ #include #include #include #include // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { public: struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) : width(width), height(height) {} }; Win32Window(); virtual ~Win32Window(); // Creates and shows a win32 window with |title| and position and size using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a // consistent size to will treat the width height passed in to this function // as logical pixels and scale to appropriate for the default monitor. Returns // true if the window was created successfully. bool CreateAndShow(const std::wstring& title, const Point& origin, const Size& size); // Release OS resources associated with window. void Destroy(); // Inserts |content| into the window tree. void SetChildContent(HWND content); // Returns the backing Window handle to enable clients to set icon and other // window properties. Returns nullptr if the window has been destroyed. HWND GetHandle(); // If true, closing this window will quit the application. void SetQuitOnClose(bool quit_on_close); // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Called when CreateAndShow is called, allowing subclass window-related // setup. Subclasses should return false if setup fails. virtual bool OnCreate(); // Called when Destroy is called. virtual void OnDestroy(); private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically // responsponds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; bool quit_on_close_ = false; // window handle for top level window. HWND window_handle_ = nullptr; // window handle for hosted content. HWND child_content_ = nullptr; }; #endif // RUNNER_WIN32_WINDOW_H_ ================================================ FILE: packages/file_selector/file_selector/lib/file_selector.dart ================================================ // Copyright 2013 The Flutter 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:file_selector_platform_interface/file_selector_platform_interface.dart'; export 'package:file_selector_platform_interface/file_selector_platform_interface.dart' show XFile, XTypeGroup; /// Opens a file selection dialog and returns the path chosen by the user. /// /// [acceptedTypeGroups] is a list of file type groups that can be selected in /// the dialog. How this is displayed depends on the pltaform, for example: /// - On Windows and Linux, each group will be an entry in a list of filter /// options. /// - On macOS, the union of all types allowed by all of the groups will be /// allowed. /// Throws an [ArgumentError] if any type groups do not include filters /// supported by the current platform. /// /// [initialDirectory] is the full path to the directory that will be displayed /// when the dialog is opened. When not provided, the platform will pick an /// initial location. This is ignored on the Web platform. /// /// [confirmButtonText] is the text in the confirmation button of the dialog. /// When not provided, the default OS label is used (for example, "Open"). /// This is ignored on the Web platform. /// /// Returns `null` if the user cancels the operation. Future openFile({ List acceptedTypeGroups = const [], String? initialDirectory, String? confirmButtonText, }) { return FileSelectorPlatform.instance.openFile( acceptedTypeGroups: acceptedTypeGroups, initialDirectory: initialDirectory, confirmButtonText: confirmButtonText); } /// Opens a file selection dialog and returns the list of paths chosen by the /// user. /// /// [acceptedTypeGroups] is a list of file type groups that can be selected in /// the dialog. How this is displayed depends on the pltaform, for example: /// - On Windows and Linux, each group will be an entry in a list of filter /// options. /// - On macOS, the union of all types allowed by all of the groups will be /// allowed. /// Throws an [ArgumentError] if any type groups do not include filters /// supported by the current platform. /// /// [initialDirectory] is the full path to the directory that will be displayed /// when the dialog is opened. When not provided, the platform will pick an /// initial location. /// /// [confirmButtonText] is the text in the confirmation button of the dialog. /// When not provided, the default OS label is used (for example, "Open"). /// /// Returns an empty list if the user cancels the operation. Future> openFiles({ List acceptedTypeGroups = const [], String? initialDirectory, String? confirmButtonText, }) { return FileSelectorPlatform.instance.openFiles( acceptedTypeGroups: acceptedTypeGroups, initialDirectory: initialDirectory, confirmButtonText: confirmButtonText); } /// Opens a save dialog and returns the target path chosen by the user. /// /// [acceptedTypeGroups] is a list of file type groups that can be selected in /// the dialog. How this is displayed depends on the pltaform, for example: /// - On Windows and Linux, each group will be an entry in a list of filter /// options. /// - On macOS, the union of all types allowed by all of the groups will be /// allowed. /// Throws an [ArgumentError] if any type groups do not include filters /// supported by the current platform. /// /// [initialDirectory] is the full path to the directory that will be displayed /// when the dialog is opened. When not provided, the platform will pick an /// initial location. /// /// [suggestedName] is initial value of file name. /// /// [confirmButtonText] is the text in the confirmation button of the dialog. /// When not provided, the default OS label is used (for example, "Save"). /// /// Returns `null` if the user cancels the operation. Future getSavePath({ List acceptedTypeGroups = const [], String? initialDirectory, String? suggestedName, String? confirmButtonText, }) async { return FileSelectorPlatform.instance.getSavePath( acceptedTypeGroups: acceptedTypeGroups, initialDirectory: initialDirectory, suggestedName: suggestedName, confirmButtonText: confirmButtonText); } /// Opens a directory selection dialog and returns the path chosen by the user. /// This always returns `null` on the web. /// /// [initialDirectory] is the full path to the directory that will be displayed /// when the dialog is opened. When not provided, the platform will pick an /// initial location. /// /// [confirmButtonText] is the text in the confirmation button of the dialog. /// When not provided, the default OS label is used (for example, "Open"). /// /// Returns `null` if the user cancels the operation. Future getDirectoryPath({ String? initialDirectory, String? confirmButtonText, }) async { return FileSelectorPlatform.instance.getDirectoryPath( initialDirectory: initialDirectory, confirmButtonText: confirmButtonText); } ================================================ FILE: packages/file_selector/file_selector/pubspec.yaml ================================================ name: file_selector description: Flutter plugin for opening and saving files, or selecting directories, using native file selection UI. repository: https://github.com/flutter/plugins/tree/main/packages/file_selector/file_selector issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 version: 0.9.2+2 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: platforms: ios: default_package: file_selector_ios linux: default_package: file_selector_linux macos: default_package: file_selector_macos web: default_package: file_selector_web windows: default_package: file_selector_windows dependencies: file_selector_ios: ^0.5.0 file_selector_linux: ^0.9.0 file_selector_macos: ^0.9.0 file_selector_platform_interface: ^2.2.0 file_selector_web: ^0.9.0 file_selector_windows: ^0.9.0 flutter: sdk: flutter dev_dependencies: flutter_test: sdk: flutter plugin_platform_interface: ^2.0.0 test: ^1.16.3 ================================================ FILE: packages/file_selector/file_selector/test/file_selector_test.dart ================================================ // Copyright 2013 The Flutter 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/file_selector.dart'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; void main() { late FakeFileSelector fakePlatformImplementation; const String initialDirectory = '/home/flutteruser'; const String confirmButtonText = 'Use this profile picture'; const String suggestedName = 'suggested_name'; const List acceptedTypeGroups = [ XTypeGroup(label: 'documents', mimeTypes: [ 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessing', ]), XTypeGroup(label: 'images', extensions: [ 'jpg', 'png', ]), ]; setUp(() { fakePlatformImplementation = FakeFileSelector(); FileSelectorPlatform.instance = fakePlatformImplementation; }); group('openFile', () { final XFile expectedFile = XFile('path'); test('works', () async { fakePlatformImplementation ..setExpectations( initialDirectory: initialDirectory, confirmButtonText: confirmButtonText, acceptedTypeGroups: acceptedTypeGroups) ..setFileResponse([expectedFile]); final XFile? file = await openFile( initialDirectory: initialDirectory, confirmButtonText: confirmButtonText, acceptedTypeGroups: acceptedTypeGroups, ); expect(file, expectedFile); }); test('works with no arguments', () async { fakePlatformImplementation.setFileResponse([expectedFile]); final XFile? file = await openFile(); expect(file, expectedFile); }); test('sets the initial directory', () async { fakePlatformImplementation ..setExpectations(initialDirectory: initialDirectory) ..setFileResponse([expectedFile]); final XFile? file = await openFile(initialDirectory: initialDirectory); expect(file, expectedFile); }); test('sets the button confirmation label', () async { fakePlatformImplementation ..setExpectations(confirmButtonText: confirmButtonText) ..setFileResponse([expectedFile]); final XFile? file = await openFile(confirmButtonText: confirmButtonText); expect(file, expectedFile); }); test('sets the accepted type groups', () async { fakePlatformImplementation ..setExpectations(acceptedTypeGroups: acceptedTypeGroups) ..setFileResponse([expectedFile]); final XFile? file = await openFile(acceptedTypeGroups: acceptedTypeGroups); expect(file, expectedFile); }); }); group('openFiles', () { final List expectedFiles = [XFile('path')]; test('works', () async { fakePlatformImplementation ..setExpectations( initialDirectory: initialDirectory, confirmButtonText: confirmButtonText, acceptedTypeGroups: acceptedTypeGroups) ..setFileResponse(expectedFiles); final List files = await openFiles( initialDirectory: initialDirectory, confirmButtonText: confirmButtonText, acceptedTypeGroups: acceptedTypeGroups, ); expect(files, expectedFiles); }); test('works with no arguments', () async { fakePlatformImplementation.setFileResponse(expectedFiles); final List files = await openFiles(); expect(files, expectedFiles); }); test('sets the initial directory', () async { fakePlatformImplementation ..setExpectations(initialDirectory: initialDirectory) ..setFileResponse(expectedFiles); final List files = await openFiles(initialDirectory: initialDirectory); expect(files, expectedFiles); }); test('sets the button confirmation label', () async { fakePlatformImplementation ..setExpectations(confirmButtonText: confirmButtonText) ..setFileResponse(expectedFiles); final List files = await openFiles(confirmButtonText: confirmButtonText); expect(files, expectedFiles); }); test('sets the accepted type groups', () async { fakePlatformImplementation ..setExpectations(acceptedTypeGroups: acceptedTypeGroups) ..setFileResponse(expectedFiles); final List files = await openFiles(acceptedTypeGroups: acceptedTypeGroups); expect(files, expectedFiles); }); }); group('getSavePath', () { const String expectedSavePath = '/example/path'; test('works', () async { fakePlatformImplementation ..setExpectations( initialDirectory: initialDirectory, confirmButtonText: confirmButtonText, acceptedTypeGroups: acceptedTypeGroups, suggestedName: suggestedName) ..setPathResponse(expectedSavePath); final String? savePath = await getSavePath( initialDirectory: initialDirectory, confirmButtonText: confirmButtonText, acceptedTypeGroups: acceptedTypeGroups, suggestedName: suggestedName, ); expect(savePath, expectedSavePath); }); test('works with no arguments', () async { fakePlatformImplementation.setPathResponse(expectedSavePath); final String? savePath = await getSavePath(); expect(savePath, expectedSavePath); }); test('sets the initial directory', () async { fakePlatformImplementation ..setExpectations(initialDirectory: initialDirectory) ..setPathResponse(expectedSavePath); final String? savePath = await getSavePath(initialDirectory: initialDirectory); expect(savePath, expectedSavePath); }); test('sets the button confirmation label', () async { fakePlatformImplementation ..setExpectations(confirmButtonText: confirmButtonText) ..setPathResponse(expectedSavePath); final String? savePath = await getSavePath(confirmButtonText: confirmButtonText); expect(savePath, expectedSavePath); }); test('sets the accepted type groups', () async { fakePlatformImplementation ..setExpectations(acceptedTypeGroups: acceptedTypeGroups) ..setPathResponse(expectedSavePath); final String? savePath = await getSavePath(acceptedTypeGroups: acceptedTypeGroups); expect(savePath, expectedSavePath); }); test('sets the suggested name', () async { fakePlatformImplementation ..setExpectations(suggestedName: suggestedName) ..setPathResponse(expectedSavePath); final String? savePath = await getSavePath(suggestedName: suggestedName); expect(savePath, expectedSavePath); }); }); group('getDirectoryPath', () { const String expectedDirectoryPath = '/example/path'; test('works', () async { fakePlatformImplementation ..setExpectations( initialDirectory: initialDirectory, confirmButtonText: confirmButtonText) ..setPathResponse(expectedDirectoryPath); final String? directoryPath = await getDirectoryPath( initialDirectory: initialDirectory, confirmButtonText: confirmButtonText, ); expect(directoryPath, expectedDirectoryPath); }); test('works with no arguments', () async { fakePlatformImplementation.setPathResponse(expectedDirectoryPath); final String? directoryPath = await getDirectoryPath(); expect(directoryPath, expectedDirectoryPath); }); test('sets the initial directory', () async { fakePlatformImplementation ..setExpectations(initialDirectory: initialDirectory) ..setPathResponse(expectedDirectoryPath); final String? directoryPath = await getDirectoryPath(initialDirectory: initialDirectory); expect(directoryPath, expectedDirectoryPath); }); test('sets the button confirmation label', () async { fakePlatformImplementation ..setExpectations(confirmButtonText: confirmButtonText) ..setPathResponse(expectedDirectoryPath); final String? directoryPath = await getDirectoryPath(confirmButtonText: confirmButtonText); expect(directoryPath, expectedDirectoryPath); }); }); } class FakeFileSelector extends Fake with MockPlatformInterfaceMixin implements FileSelectorPlatform { // Expectations. List? acceptedTypeGroups = const []; String? initialDirectory; String? confirmButtonText; String? suggestedName; // Return values. List? files; String? path; void setExpectations({ List acceptedTypeGroups = const [], String? initialDirectory, String? suggestedName, String? confirmButtonText, }) { this.acceptedTypeGroups = acceptedTypeGroups; this.initialDirectory = initialDirectory; this.suggestedName = suggestedName; this.confirmButtonText = confirmButtonText; } // ignore: use_setters_to_change_properties void setFileResponse(List files) { this.files = files; } // ignore: use_setters_to_change_properties void setPathResponse(String path) { this.path = path; } @override Future openFile({ List? acceptedTypeGroups, String? initialDirectory, String? confirmButtonText, }) async { expect(acceptedTypeGroups, this.acceptedTypeGroups); expect(initialDirectory, this.initialDirectory); expect(suggestedName, suggestedName); return files?[0]; } @override Future> openFiles({ List? acceptedTypeGroups, String? initialDirectory, String? confirmButtonText, }) async { expect(acceptedTypeGroups, this.acceptedTypeGroups); expect(initialDirectory, this.initialDirectory); expect(suggestedName, suggestedName); return files!; } @override Future getSavePath({ List? acceptedTypeGroups, String? initialDirectory, String? suggestedName, String? confirmButtonText, }) async { expect(acceptedTypeGroups, this.acceptedTypeGroups); expect(initialDirectory, this.initialDirectory); expect(suggestedName, this.suggestedName); expect(confirmButtonText, this.confirmButtonText); return path; } @override Future getDirectoryPath({ String? initialDirectory, String? confirmButtonText, }) async { expect(initialDirectory, this.initialDirectory); expect(confirmButtonText, this.confirmButtonText); return path; } } ================================================ FILE: packages/file_selector/file_selector_ios/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. /pubspec.lock **/doc/api/ .dart_tool/ .packages build/ ================================================ FILE: packages/file_selector/file_selector_ios/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled. version: revision: ac1aa511ca94f46c7e80b94dafd521de35e808e5 channel: master project_type: plugin # Tracks metadata for the flutter migrate command migration: platforms: - platform: root create_revision: ac1aa511ca94f46c7e80b94dafd521de35e808e5 base_revision: ac1aa511ca94f46c7e80b94dafd521de35e808e5 - platform: ios create_revision: ac1aa511ca94f46c7e80b94dafd521de35e808e5 base_revision: ac1aa511ca94f46c7e80b94dafd521de35e808e5 # User provided section # List of Local paths (relative to this file) that should be # ignored by the migrate tool. # # Files that are not part of the templates will be ignored by default. unmanaged_files: - 'lib/main.dart' - 'ios/Runner.xcodeproj/project.pbxproj' ================================================ FILE: packages/file_selector/file_selector_ios/AUTHORS ================================================ # 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. ================================================ FILE: packages/file_selector/file_selector_ios/CHANGELOG.md ================================================ ## 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. * Updates minimum Flutter version to 2.10. ## 0.5.0+1 * Updates README for endorsement. ## 0.5.0 * Initial iOS implementation of `file_selector`. ================================================ FILE: packages/file_selector/file_selector_ios/LICENSE ================================================ 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. ================================================ FILE: packages/file_selector/file_selector_ios/README.md ================================================ # file\_selector\_ios The iOS implementation of [`file_selector`][1]. ## Usage This package is [endorsed][2], which means you can simply use `file_selector` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/file_selector [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/file_selector/file_selector_ios/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Android Studio will place build artifacts here /android/app/debug /android/app/profile /android/app/release ================================================ FILE: packages/file_selector/file_selector_ios/example/.metadata ================================================ # 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: 5464c5bac742001448fe4fc0597be939379f88ea channel: stable project_type: app ================================================ FILE: packages/file_selector/file_selector_ios/example/README.md ================================================ # 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. ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/.gitignore ================================================ **/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 ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 11.0 ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Flutter/Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Flutter/Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths # Pods for testing pod 'OCMock', '~> 3.8.1' end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Runner/AppDelegate.swift ================================================ // Copyright 2013 The Flutter 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) } } ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "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" } } ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName File Selector Ios CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName file_selector_ios_example CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Runner/Runner-Bridging-Header.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "GeneratedPluginRegistrant.h" ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 21160A929DC757957DE39F1E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 000792269CB6B9FE88AC567C /* Pods_Runner.framework */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 6165A2F80DFA224EAF50A1D5 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC3841659BF3693FAC5A2F8F /* Pods_RunnerTests.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; C71AE4C8281C6B6B0086307A /* FileSelectorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C71AE4C5281C6B530086307A /* FileSelectorTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ C71AE4BA281C6A090086307A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 000792269CB6B9FE88AC567C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 4A27CC0DB4EF6669B637A1E8 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 5667547C6832727A744371E2 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 5C0E87EDCB9350EC4916E293 /* 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 = ""; }; 786CCB880423FD6D1019F59B /* 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 = ""; }; 79C120FEED85F112A72B5D35 /* 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; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; AC3841659BF3693FAC5A2F8F /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C71AE4B6281C6A090086307A /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C71AE4C5281C6B530086307A /* FileSelectorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FileSelectorTests.m; sourceTree = ""; }; F818CE2D7CDF8AFF94707327 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 21160A929DC757957DE39F1E /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; C71AE4B3281C6A090086307A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 6165A2F80DFA224EAF50A1D5 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2E44EE3EE3BCCAB6933171F8 /* Pods */ = { isa = PBXGroup; children = ( 79C120FEED85F112A72B5D35 /* Pods-Runner.debug.xcconfig */, F818CE2D7CDF8AFF94707327 /* Pods-Runner.release.xcconfig */, 5C0E87EDCB9350EC4916E293 /* Pods-Runner.profile.xcconfig */, 4A27CC0DB4EF6669B637A1E8 /* Pods-RunnerTests.debug.xcconfig */, 5667547C6832727A744371E2 /* Pods-RunnerTests.release.xcconfig */, 786CCB880423FD6D1019F59B /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( C71AE4C4281C6B370086307A /* RunnerTests */, 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 2E44EE3EE3BCCAB6933171F8 /* Pods */, C832A34FD3BC866442874ED0 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, C71AE4B6281C6A090086307A /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; C71AE4C4281C6B370086307A /* RunnerTests */ = { isa = PBXGroup; children = ( C71AE4C5281C6B530086307A /* FileSelectorTests.m */, ); path = RunnerTests; sourceTree = ""; }; C832A34FD3BC866442874ED0 /* Frameworks */ = { isa = PBXGroup; children = ( 000792269CB6B9FE88AC567C /* Pods_Runner.framework */, AC3841659BF3693FAC5A2F8F /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( AC24910767ED5F17F5245292 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, BE6D85B8F242B768015B938B /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; C71AE4B5281C6A090086307A /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = C71AE4BF281C6A090086307A /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( A68B14611B411E4F96A5C80D /* [CP] Check Pods Manifest.lock */, C71AE4B2281C6A090086307A /* Sources */, C71AE4B3281C6A090086307A /* Frameworks */, C71AE4B4281C6A090086307A /* Resources */, 5BE5886DAAA885227DE0796D /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( C71AE4BB281C6A090086307A /* PBXTargetDependency */, ); name = RunnerTests; productName = FileSelectorTests; productReference = C71AE4B6281C6A090086307A /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; C71AE4B5281C6A090086307A = { CreatedOnToolsVersion = 13.1; TestTargetID = 97C146ED1CF9000F007C117D; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, C71AE4B5281C6A090086307A /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; C71AE4B4281C6A090086307A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 5BE5886DAAA885227DE0796D /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-RunnerTests/Pods-RunnerTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-RunnerTests/Pods-RunnerTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RunnerTests/Pods-RunnerTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 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"; }; A68B14611B411E4F96A5C80D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; AC24910767ED5F17F5245292 /* [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", ); 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; }; BE6D85B8F242B768015B938B /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); 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 = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; C71AE4B2281C6A090086307A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( C71AE4C8281C6B6B0086307A /* FileSelectorTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ C71AE4BB281C6A090086307A /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = C71AE4BA281C6A090086307A /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; 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.fileSelectorIosExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 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; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.fileSelectorIosExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.fileSelectorIosExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; C71AE4BC281C6A090086307A /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 4A27CC0DB4EF6669B637A1E8 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.FileSelectorTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Debug; }; C71AE4BD281C6A090086307A /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5667547C6832727A744371E2 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.FileSelectorTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; }; C71AE4BE281C6A090086307A /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 786CCB880423FD6D1019F59B /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.FileSelectorTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Profile; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; C71AE4BF281C6A090086307A /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( C71AE4BC281C6A090086307A /* Debug */, C71AE4BD281C6A090086307A /* Release */, C71AE4BE281C6A090086307A /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: packages/file_selector/file_selector_ios/example/ios/RunnerTests/FileSelectorTests.m ================================================ // Copyright 2013 The Flutter 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 file_selector_ios; @import file_selector_ios.Test; @import XCTest; #import @interface FileSelectorTests : XCTestCase @end @implementation FileSelectorTests - (void)testPickerPresents { FFSFileSelectorPlugin *plugin = [[FFSFileSelectorPlugin alloc] init]; UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[] inMode:UIDocumentPickerModeImport]; id mockPresentingVC = OCMClassMock([UIViewController class]); plugin.documentPickerViewControllerOverride = picker; plugin.presentingViewControllerOverride = mockPresentingVC; [plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:@[] allowMultiSelection:@NO] completion:^(NSArray *paths, FlutterError *error){ }]; XCTAssertEqualObjects(picker.delegate, plugin); OCMVerify(times(1), [mockPresentingVC presentViewController:picker animated:[OCMArg any] completion:[OCMArg any]]); } - (void)testReturnsPickedFiles { FFSFileSelectorPlugin *plugin = [[FFSFileSelectorPlugin alloc] init]; XCTestExpectation *completionWasCalled = [self expectationWithDescription:@"completion"]; UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[] inMode:UIDocumentPickerModeImport]; plugin.documentPickerViewControllerOverride = picker; [plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:@[] allowMultiSelection:@YES] completion:^(NSArray *paths, FlutterError *error) { NSArray *expectedPaths = @[ @"/file1.txt", @"/file2.txt" ]; XCTAssertEqualObjects(paths, expectedPaths); [completionWasCalled fulfill]; }]; [plugin documentPicker:picker didPickDocumentsAtURLs:@[ [NSURL URLWithString:@"file:///file1.txt"], [NSURL URLWithString:@"file:///file2.txt"] ]]; [self waitForExpectationsWithTimeout:1.0 handler:nil]; } - (void)testReturnsPickedFileLegacy { // Tests that it handles the pre iOS 11 UIDocumentPickerDelegate method. FFSFileSelectorPlugin *plugin = [[FFSFileSelectorPlugin alloc] init]; XCTestExpectation *completionWasCalled = [self expectationWithDescription:@"completion"]; UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[] inMode:UIDocumentPickerModeImport]; plugin.documentPickerViewControllerOverride = picker; [plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:@[] allowMultiSelection:@NO] completion:^(NSArray *paths, FlutterError *error) { NSArray *expectedPaths = @[ @"/file1.txt" ]; XCTAssertEqualObjects(paths, expectedPaths); [completionWasCalled fulfill]; }]; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" [plugin documentPicker:picker didPickDocumentAtURL:[NSURL URLWithString:@"file:///file1.txt"]]; #pragma GCC diagnostic pop [self waitForExpectationsWithTimeout:1.0 handler:nil]; } - (void)testCancellingPickerReturnsNil { FFSFileSelectorPlugin *plugin = [[FFSFileSelectorPlugin alloc] init]; UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[] inMode:UIDocumentPickerModeImport]; plugin.documentPickerViewControllerOverride = picker; XCTestExpectation *completionWasCalled = [self expectationWithDescription:@"completion"]; [plugin openFileSelectorWithConfig:[FFSFileSelectorConfig makeWithUtis:@[] allowMultiSelection:@NO] completion:^(NSArray *paths, FlutterError *error) { XCTAssertEqual(paths.count, 0); [completionWasCalled fulfill]; }]; [plugin documentPickerWasCancelled:picker]; [self waitForExpectationsWithTimeout:1.0 handler:nil]; } @end ================================================ FILE: packages/file_selector/file_selector_ios/example/lib/home_page.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; /// Home Page of the application. class HomePage extends StatelessWidget { /// Default Constructor const HomePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final ButtonStyle 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, ); return Scaffold( appBar: AppBar( title: const Text('File Selector Demo Home Page'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( style: style, child: const Text('Open a text file'), onPressed: () => Navigator.pushNamed(context, '/open/text'), ), const SizedBox(height: 10), ElevatedButton( style: style, child: const Text('Open an image'), onPressed: () => Navigator.pushNamed(context, '/open/image'), ), const SizedBox(height: 10), ElevatedButton( style: style, child: const Text('Open multiple images'), onPressed: () => Navigator.pushNamed(context, '/open/images'), ), const SizedBox(height: 10), ], ), ), ); } } ================================================ FILE: packages/file_selector/file_selector_ios/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'home_page.dart'; import 'open_image_page.dart'; import 'open_multiple_images_page.dart'; import 'open_text_page.dart'; void main() { runApp(const MyApp()); } /// MyApp is the Main Application. class MyApp extends StatelessWidget { /// Default Constructor const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'File Selector Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: const HomePage(), routes: { '/open/image': (BuildContext context) => const OpenImagePage(), '/open/images': (BuildContext context) => const OpenMultipleImagesPage(), '/open/text': (BuildContext context) => const OpenTextPage(), }, ); } } ================================================ FILE: packages/file_selector/file_selector_ios/example/lib/open_image_page.dart ================================================ // Copyright 2013 The Flutter 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:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// Screen that allows the user to select an image file using /// `openFiles`, then displays the selected images in a gallery dialog. class OpenImagePage extends StatelessWidget { /// Default Constructor const OpenImagePage({Key? key}) : super(key: key); Future _openImageFile(BuildContext context) async { const XTypeGroup typeGroup = XTypeGroup( label: 'images', extensions: ['jpg', 'png'], macUTIs: ['public.image'], ); final XFile? file = await FileSelectorPlatform.instance .openFile(acceptedTypeGroups: [typeGroup]); if (file == null) { // Operation was canceled by the user. return; } final String fileName = file.name; final String filePath = file.path; if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => ImageDisplay(fileName, filePath), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open an image'), ), 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 open an image file(png, jpg)'), onPressed: () => _openImageFile(context), ), ], ), ), ); } } /// Widget that displays an image in a dialog. class ImageDisplay extends StatelessWidget { /// Default Constructor. const ImageDisplay(this.fileName, this.filePath, {Key? key}) : super(key: key); /// The name of the selected file. final String fileName; /// The path to the selected file. final String filePath; @override Widget build(BuildContext context) { return AlertDialog( title: Text(fileName), // On web the filePath is a blob url // while on other platforms it is a system path. content: kIsWeb ? Image.network(filePath) : Image.file(File(filePath)), actions: [ TextButton( child: const Text('Close'), onPressed: () { Navigator.pop(context); }, ), ], ); } } ================================================ FILE: packages/file_selector/file_selector_ios/example/lib/open_multiple_images_page.dart ================================================ // Copyright 2013 The Flutter 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:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// Screen that allows the user to select multiple image files using /// `openFiles`, then displays the selected images in a gallery dialog. class OpenMultipleImagesPage extends StatelessWidget { /// Default Constructor const OpenMultipleImagesPage({Key? key}) : super(key: key); Future _openImageFile(BuildContext context) async { const XTypeGroup jpgsTypeGroup = XTypeGroup( label: 'JPEGs', extensions: ['jpg', 'jpeg'], macUTIs: ['public.jpeg'], ); const XTypeGroup pngTypeGroup = XTypeGroup( label: 'PNGs', extensions: ['png'], macUTIs: ['public.png'], ); final List files = await FileSelectorPlatform.instance .openFiles(acceptedTypeGroups: [ jpgsTypeGroup, pngTypeGroup, ]); if (files.isEmpty) { // Operation was canceled by the user. return; } if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => MultipleImagesDisplay(files), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open multiple images'), ), 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 open multiple images (png, jpg)'), onPressed: () => _openImageFile(context), ), ], ), ), ); } } /// Widget that displays a text file in a dialog. class MultipleImagesDisplay extends StatelessWidget { /// Default Constructor. const MultipleImagesDisplay(this.files, {Key? key}) : super(key: key); /// The files containing the images. final List files; @override Widget build(BuildContext context) { return AlertDialog( title: const Text('Gallery'), // On web the filePath is a blob url // while on other platforms it is a system path. content: Center( child: Row( children: [ ...files.map( (XFile file) => Flexible( child: kIsWeb ? Image.network(file.path) : Image.file(File(file.path))), ) ], ), ), actions: [ TextButton( child: const Text('Close'), onPressed: () { Navigator.pop(context); }, ), ], ); } } ================================================ FILE: packages/file_selector/file_selector_ios/example/lib/open_text_page.dart ================================================ // Copyright 2013 The Flutter 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 a text file using `openFile`, then /// displays its contents in a dialog. class OpenTextPage extends StatelessWidget { /// Default Constructor const OpenTextPage({Key? key}) : super(key: key); Future _openTextFile(BuildContext context) async { const XTypeGroup typeGroup = XTypeGroup( label: 'text', extensions: ['txt', 'json'], macUTIs: ['public.text'], ); final XFile? file = await FileSelectorPlatform.instance .openFile(acceptedTypeGroups: [typeGroup]); if (file == null) { // Operation was canceled by the user. return; } final String fileName = file.name; final String fileContent = await file.readAsString(); if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => TextDisplay(fileName, fileContent), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open a text file'), ), 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 open a text file (json, txt)'), onPressed: () => _openTextFile(context), ), ], ), ), ); } } /// Widget that displays a text file in a dialog. class TextDisplay extends StatelessWidget { /// Default Constructor. const TextDisplay(this.fileName, this.fileContent, {Key? key}) : super(key: key); /// The name of the selected file. final String fileName; /// The contents of the text file. final String fileContent; @override Widget build(BuildContext context) { return AlertDialog( title: Text(fileName), content: Scrollbar( child: SingleChildScrollView( child: Text(fileContent), ), ), actions: [ TextButton( child: const Text('Close'), onPressed: () => Navigator.pop(context), ), ], ); } } ================================================ FILE: packages/file_selector/file_selector_ios/example/pubspec.yaml ================================================ name: example description: Example for file_selector_ios implementation. publish_to: 'none' 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. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 file_selector_ios: # When depending on this package from a real application you should use: # file_selector_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: .. file_selector_platform_interface: ^2.2.0 flutter: sdk: flutter dev_dependencies: flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/file_selector/file_selector_ios/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/file_selector/file_selector_ios/ios/.gitignore ================================================ .idea/ .vagrant/ .sconsign.dblite .svn/ .DS_Store *.swp profile DerivedData/ build/ GeneratedPluginRegistrant.h GeneratedPluginRegistrant.m .generated/ *.pbxuser *.mode1v3 *.mode2v3 *.perspectivev3 !default.pbxuser !default.mode1v3 !default.mode2v3 !default.perspectivev3 xcuserdata *.moved-aside *.pyc *sync/ Icon? .tags* /Flutter/Generated.xcconfig /Flutter/ephemeral/ /Flutter/flutter_export_environment.sh ================================================ FILE: packages/file_selector/file_selector_ios/ios/Assets/.gitkeep ================================================ ================================================ FILE: packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin.h ================================================ // Copyright 2013 The Flutter 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 FFSFileSelectorPlugin : NSObject @end ================================================ FILE: packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "FFSFileSelectorPlugin.h" #import "FFSFileSelectorPlugin_Test.h" #import "messages.g.h" #import @implementation FFSFileSelectorPlugin #pragma mark - FFSFileSelectorApi - (void)openFileSelectorWithConfig:(FFSFileSelectorConfig *)config completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { UIDocumentPickerViewController *documentPicker = self.documentPickerViewControllerOverride ?: [[UIDocumentPickerViewController alloc] initWithDocumentTypes:config.utis inMode:UIDocumentPickerModeImport]; documentPicker.delegate = self; if (@available(iOS 11.0, *)) { documentPicker.allowsMultipleSelection = config.allowMultiSelection.boolValue; } UIViewController *presentingVC = self.presentingViewControllerOverride ?: UIApplication.sharedApplication.delegate.window.rootViewController; if (presentingVC) { objc_setAssociatedObject(documentPicker, @selector(openFileSelectorWithConfig:completion:), completion, OBJC_ASSOCIATION_COPY_NONATOMIC); [presentingVC presentViewController:documentPicker animated:YES completion:nil]; } else { completion(nil, [FlutterError errorWithCode:@"error" message:@"Missing root view controller." details:nil]); } } #pragma mark - FlutterPlugin + (void)registerWithRegistrar:(NSObject *)registrar { FFSFileSelectorPlugin *plugin = [[FFSFileSelectorPlugin alloc] init]; FFSFileSelectorApiSetup(registrar.messenger, plugin); } #pragma mark - UIDocumentPickerDelegate // This method is only called in iOS < 11.0. The new codepath is // documentPicker:didPickDocumentsAtURLs:, implemented below. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url { [self sendBackResults:@[ url.path ] error:nil forPicker:controller]; } #pragma clang diagnostic pop - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls { NSMutableArray *paths = [NSMutableArray arrayWithCapacity:urls.count]; for (NSURL *url in urls) { [paths addObject:url.path]; }; [self sendBackResults:paths error:nil forPicker:controller]; } - (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { [self sendBackResults:@[] error:nil forPicker:controller]; } #pragma mark - Helper Methods - (void)sendBackResults:(NSArray *)results error:(FlutterError *)error forPicker:(UIDocumentPickerViewController *)picker { void (^completionBlock)(NSArray *, FlutterError *) = objc_getAssociatedObject(picker, @selector(openFileSelectorWithConfig:completion:)); if (completionBlock) { completionBlock(results, error); objc_setAssociatedObject(picker, @selector(openFileSelectorWithConfig:completion:), nil, OBJC_ASSOCIATION_ASSIGN); } } @end ================================================ FILE: packages/file_selector/file_selector_ios/ios/Classes/FFSFileSelectorPlugin_Test.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "FFSFileSelectorPlugin.h" #import "messages.g.h" // This header is available in the Test module. Import via "@import file_selector_ios.Test;". @interface FFSFileSelectorPlugin () /** * Overrides the view controller used for presenting the document picker. */ @property(nonatomic) UIViewController *_Nullable presentingViewControllerOverride; /** * Overrides the UIDocumentPickerViewController used for file picking. */ @property(nonatomic) UIDocumentPickerViewController *_Nullable documentPickerViewControllerOverride; @end ================================================ FILE: packages/file_selector/file_selector_ios/ios/Classes/FileSelectorPlugin.modulemap ================================================ framework module file_selector_ios { umbrella header "file_selector_ios-umbrella.h" export * module * { export * } explicit module Test { header "FFSFileSelectorPlugin_Test.h" } } ================================================ FILE: packages/file_selector/file_selector_ios/ios/Classes/file_selector_ios-umbrella.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/file_selector/file_selector_ios/ios/Classes/messages.g.h ================================================ // Copyright 2013 The Flutter 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.2.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 @class FFSFileSelectorConfig; @interface FFSFileSelectorConfig : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithUtis:(NSArray *)utis allowMultiSelection:(NSNumber *)allowMultiSelection; @property(nonatomic, strong) NSArray *utis; @property(nonatomic, strong) NSNumber *allowMultiSelection; @end /// The codec used by FFSFileSelectorApi. NSObject *FFSFileSelectorApiGetCodec(void); @protocol FFSFileSelectorApi - (void)openFileSelectorWithConfig:(FFSFileSelectorConfig *)config completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion; @end extern void FFSFileSelectorApiSetup(id binaryMessenger, NSObject *_Nullable api); NS_ASSUME_NONNULL_END ================================================ FILE: packages/file_selector/file_selector_ios/ios/Classes/messages.g.m ================================================ // Copyright 2013 The Flutter 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.2.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, }; } static id GetNullableObject(NSDictionary *dict, id key) { id result = dict[key]; return (result == [NSNull null]) ? nil : result; } static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { id result = array[key]; return (result == [NSNull null]) ? nil : result; } @interface FFSFileSelectorConfig () + (FFSFileSelectorConfig *)fromMap:(NSDictionary *)dict; + (nullable FFSFileSelectorConfig *)nullableFromMap:(NSDictionary *)dict; - (NSDictionary *)toMap; @end @implementation FFSFileSelectorConfig + (instancetype)makeWithUtis:(NSArray *)utis allowMultiSelection:(NSNumber *)allowMultiSelection { FFSFileSelectorConfig *pigeonResult = [[FFSFileSelectorConfig alloc] init]; pigeonResult.utis = utis; pigeonResult.allowMultiSelection = allowMultiSelection; return pigeonResult; } + (FFSFileSelectorConfig *)fromMap:(NSDictionary *)dict { FFSFileSelectorConfig *pigeonResult = [[FFSFileSelectorConfig alloc] init]; pigeonResult.utis = GetNullableObject(dict, @"utis"); NSAssert(pigeonResult.utis != nil, @""); pigeonResult.allowMultiSelection = GetNullableObject(dict, @"allowMultiSelection"); NSAssert(pigeonResult.allowMultiSelection != nil, @""); return pigeonResult; } + (nullable FFSFileSelectorConfig *)nullableFromMap:(NSDictionary *)dict { return (dict) ? [FFSFileSelectorConfig fromMap:dict] : nil; } - (NSDictionary *)toMap { return @{ @"utis" : (self.utis ?: [NSNull null]), @"allowMultiSelection" : (self.allowMultiSelection ?: [NSNull null]), }; } @end @interface FFSFileSelectorApiCodecReader : FlutterStandardReader @end @implementation FFSFileSelectorApiCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { case 128: return [FFSFileSelectorConfig fromMap:[self readValue]]; default: return [super readValueOfType:type]; } } @end @interface FFSFileSelectorApiCodecWriter : FlutterStandardWriter @end @implementation FFSFileSelectorApiCodecWriter - (void)writeValue:(id)value { if ([value isKindOfClass:[FFSFileSelectorConfig class]]) { [self writeByte:128]; [self writeValue:[value toMap]]; } else { [super writeValue:value]; } } @end @interface FFSFileSelectorApiCodecReaderWriter : FlutterStandardReaderWriter @end @implementation FFSFileSelectorApiCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { return [[FFSFileSelectorApiCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { return [[FFSFileSelectorApiCodecReader alloc] initWithData:data]; } @end NSObject *FFSFileSelectorApiGetCodec() { static dispatch_once_t sPred = 0; static FlutterStandardMessageCodec *sSharedObject = nil; dispatch_once(&sPred, ^{ FFSFileSelectorApiCodecReaderWriter *readerWriter = [[FFSFileSelectorApiCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } void FFSFileSelectorApiSetup(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.FileSelectorApi.openFile" binaryMessenger:binaryMessenger codec:FFSFileSelectorApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(openFileSelectorWithConfig:completion:)], @"FFSFileSelectorApi api (%@) doesn't respond to " @"@selector(openFileSelectorWithConfig:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FFSFileSelectorConfig *arg_config = GetNullableObjectAtIndex(args, 0); [api openFileSelectorWithConfig:arg_config completion:^(NSArray *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; }]; } else { [channel setMessageHandler:nil]; } } } ================================================ FILE: packages/file_selector/file_selector_ios/ios/file_selector_ios.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. # Run `pod lib lint file_selector_ios.podspec` to validate before publishing. # Pod::Spec.new do |s| s.name = 'file_selector_ios' s.version = '0.0.1' s.summary = 'iOS implementation of file_selector.' s.description = <<-DESC Displays the native iOS document picker. DESC s.homepage = 'https://github.com/flutter/plugins/tree/main/packages/file_selector' 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/file_selector/file_selector_ios' } s.source_files = 'Classes/**/*.{h,m}' s.module_map = 'Classes/FileSelectorPlugin.modulemap' s.dependency 'Flutter' s.platform = :ios, '9.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } s.swift_version = '5.0' end ================================================ FILE: packages/file_selector/file_selector_ios/lib/file_selector_ios.dart ================================================ // Copyright 2013 The Flutter 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 'src/messages.g.dart'; /// An implementation of [FileSelectorPlatform] for iOS. class FileSelectorIOS extends FileSelectorPlatform { final FileSelectorApi _hostApi = FileSelectorApi(); /// Registers the iOS implementation. static void registerWith() { FileSelectorPlatform.instance = FileSelectorIOS(); } @override Future openFile({ List? acceptedTypeGroups, String? initialDirectory, String? confirmButtonText, }) async { final List path = (await _hostApi.openFile(FileSelectorConfig( utis: _allowedUtiListFromTypeGroups(acceptedTypeGroups), allowMultiSelection: false))) .cast(); return path.isEmpty ? null : XFile(path.first); } @override Future> openFiles({ List? acceptedTypeGroups, String? initialDirectory, String? confirmButtonText, }) async { final List pathList = (await _hostApi.openFile(FileSelectorConfig( utis: _allowedUtiListFromTypeGroups(acceptedTypeGroups), allowMultiSelection: true))) .cast(); return pathList.map((String path) => XFile(path)).toList(); } // Converts the type group list into a list of all allowed UTIs, since // iOS doesn't support filter groups. List _allowedUtiListFromTypeGroups(List? typeGroups) { if (typeGroups == null || typeGroups.isEmpty) { return []; } final List allowedUTIs = []; for (final XTypeGroup typeGroup in typeGroups) { // If any group allows everything, no filtering should be done. if (typeGroup.allowsAny) { return []; } if (typeGroup.macUTIs?.isEmpty ?? true) { throw ArgumentError('The provided type group $typeGroup should either ' 'allow all files, or have a non-empty "macUTIs"'); } allowedUTIs.addAll(typeGroup.macUTIs!); } return allowedUTIs; } } ================================================ FILE: packages/file_selector/file_selector_ios/lib/src/messages.g.dart ================================================ // Copyright 2013 The Flutter 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.2.5), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import 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 FileSelectorConfig { FileSelectorConfig({ required this.utis, required this.allowMultiSelection, }); List utis; bool allowMultiSelection; Object encode() { final Map pigeonMap = {}; pigeonMap['utis'] = utis; pigeonMap['allowMultiSelection'] = allowMultiSelection; return pigeonMap; } static FileSelectorConfig decode(Object message) { final Map pigeonMap = message as Map; return FileSelectorConfig( utis: (pigeonMap['utis'] as List?)!.cast(), allowMultiSelection: pigeonMap['allowMultiSelection']! as bool, ); } } class _FileSelectorApiCodec extends StandardMessageCodec { const _FileSelectorApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is FileSelectorConfig) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return FileSelectorConfig.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(); Future> openFile(FileSelectorConfig arg_config) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.FileSelectorApi.openFile', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_config]) 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 List?)!.cast(); } } } ================================================ FILE: packages/file_selector/file_selector_ios/pigeons/copyright.txt ================================================ Copyright 2013 The Flutter Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. ================================================ FILE: packages/file_selector/file_selector_ios/pigeons/messages.dart ================================================ // Copyright 2013 The Flutter 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', dartTestOut: 'test/test_api.g.dart', objcHeaderOut: 'ios/Classes/messages.g.h', objcSourceOut: 'ios/Classes/messages.g.m', objcOptions: ObjcOptions( prefix: 'FFS', ), copyrightHeader: 'pigeons/copyright.txt', )) class FileSelectorConfig { FileSelectorConfig( {this.utis = const [], this.allowMultiSelection = false}); List utis; bool allowMultiSelection; } @HostApi(dartHostTestHandler: 'TestFileSelectorApi') abstract class FileSelectorApi { @async @ObjCSelector('openFileSelectorWithConfig:') List openFile(FileSelectorConfig config); } ================================================ FILE: packages/file_selector/file_selector_ios/pubspec.yaml ================================================ name: file_selector_ios description: iOS implementation of the file_selector plugin. repository: https://github.com/flutter/plugins/tree/main/packages/file_selector/file_selector_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 version: 0.5.0+2 environment: sdk: ">=2.14.4 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: file_selector platforms: ios: dartPluginClass: FileSelectorIOS pluginClass: FFSFileSelectorPlugin dependencies: file_selector_platform_interface: ^2.2.0 flutter: sdk: flutter dev_dependencies: build_runner: 2.1.11 flutter_test: sdk: flutter mockito: ^5.1.0 pigeon: ^3.2.5 ================================================ FILE: packages/file_selector/file_selector_ios/test/file_selector_ios_test.dart ================================================ // Copyright 2013 The Flutter 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_ios/file_selector_ios.dart'; import 'package:file_selector_ios/src/messages.g.dart'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'file_selector_ios_test.mocks.dart'; import 'test_api.g.dart'; @GenerateMocks([TestFileSelectorApi]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); final FileSelectorIOS plugin = FileSelectorIOS(); late MockTestFileSelectorApi mockApi; setUp(() { mockApi = MockTestFileSelectorApi(); TestFileSelectorApi.setup(mockApi); }); test('registered instance', () { FileSelectorIOS.registerWith(); expect(FileSelectorPlatform.instance, isA()); }); group('openFile', () { setUp(() { when(mockApi.openFile(any)).thenAnswer((_) async => ['foo']); }); test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', extensions: ['txt'], mimeTypes: ['text/plain'], macUTIs: ['public.text'], ); const XTypeGroup groupTwo = XTypeGroup( label: 'image', extensions: ['jpg'], mimeTypes: ['image/jpg'], macUTIs: ['public.image'], webWildCards: ['image/*']); await plugin.openFile(acceptedTypeGroups: [group, groupTwo]); final VerificationResult result = verify(mockApi.openFile(captureAny)); final FileSelectorConfig config = result.captured[0] as FileSelectorConfig; // iOS only accepts macUTIs. expect(listEquals(config.utis, ['public.text', 'public.image']), isTrue); expect(config.allowMultiSelection, isFalse); }); test('throws for a type group that does not support iOS', () async { const XTypeGroup group = XTypeGroup( label: 'images', webWildCards: ['images/*'], ); await expectLater( plugin.openFile(acceptedTypeGroups: [group]), throwsArgumentError); }); test('allows a wildcard group', () async { const XTypeGroup group = XTypeGroup( label: 'text', ); await expectLater( plugin.openFile(acceptedTypeGroups: [group]), completes); }); }); group('openFiles', () { setUp(() { when(mockApi.openFile(any)).thenAnswer((_) async => ['foo']); }); test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', extensions: ['txt'], mimeTypes: ['text/plain'], macUTIs: ['public.text'], ); const XTypeGroup groupTwo = XTypeGroup( label: 'image', extensions: ['jpg'], mimeTypes: ['image/jpg'], macUTIs: ['public.image'], webWildCards: ['image/*']); await plugin.openFiles(acceptedTypeGroups: [group, groupTwo]); final VerificationResult result = verify(mockApi.openFile(captureAny)); final FileSelectorConfig config = result.captured[0] as FileSelectorConfig; // iOS only accepts macUTIs. expect(listEquals(config.utis, ['public.text', 'public.image']), isTrue); expect(config.allowMultiSelection, isTrue); }); test('throws for a type group that does not support iOS', () async { const XTypeGroup group = XTypeGroup( label: 'images', webWildCards: ['images/*'], ); await expectLater( plugin.openFiles(acceptedTypeGroups: [group]), throwsArgumentError); }); test('allows a wildcard group', () async { const XTypeGroup group = XTypeGroup( label: 'text', ); await expectLater( plugin.openFiles(acceptedTypeGroups: [group]), completes); }); }); } ================================================ FILE: packages/file_selector/file_selector_ios/test/file_selector_ios_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.0 from annotations // in file_selector_ios/example/ios/.symlinks/plugins/file_selector_ios/test/file_selector_ios_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i3; import 'package:file_selector_ios/src/messages.g.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'test_api.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> openFile(_i4.FileSelectorConfig? config) => (super.noSuchMethod(Invocation.method(#openFile, [config]), returnValue: _i3.Future>.value([])) as _i3.Future>); } ================================================ FILE: packages/file_selector/file_selector_ios/test/test_api.g.dart ================================================ // Copyright 2013 The Flutter 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.2.5), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import // 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 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; // This line has been hand-edited due to // https://github.com/flutter/flutter/issues/97744 // ignore: directives_ordering import 'package:file_selector_ios/src/messages.g.dart'; class _TestFileSelectorApiCodec extends StandardMessageCodec { const _TestFileSelectorApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is FileSelectorConfig) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return FileSelectorConfig.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } abstract class TestFileSelectorApi { static const MessageCodec codec = _TestFileSelectorApiCodec(); Future> openFile(FileSelectorConfig config); static void setup(TestFileSelectorApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.FileSelectorApi.openFile', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.FileSelectorApi.openFile was null.'); final List args = (message as List?)!; final FileSelectorConfig? arg_config = (args[0] as FileSelectorConfig?); assert(arg_config != null, 'Argument for dev.flutter.pigeon.FileSelectorApi.openFile was null, expected non-null FileSelectorConfig.'); final List output = await api.openFile(arg_config!); return {'result': output}; }); } } } } ================================================ FILE: packages/file_selector/file_selector_linux/.gitignore ================================================ .dart_tool .packages .flutter-plugins .flutter-plugins-dependencies pubspec.lock ================================================ FILE: packages/file_selector/file_selector_linux/AUTHORS ================================================ # 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. ================================================ FILE: packages/file_selector/file_selector_linux/CHANGELOG.md ================================================ ## 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. * Updates minimum Flutter version to 2.10. ## 0.9.0 * Moves source to flutter/plugins. ## 0.0.3 * Adds Dart implementation for in-package method channel. ## 0.0.2+1 * Updates README ## 0.0.2 * Updates SDK constraint to signal compatibility with null safety. ## 0.0.1 * Initial Linux implementation of `file_selector`. ================================================ FILE: packages/file_selector/file_selector_linux/LICENSE ================================================ 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. ================================================ FILE: packages/file_selector/file_selector_linux/README.md ================================================ # file\_selector\_linux The Linux implementation of [`file_selector`][1]. ## Usage This package is [endorsed][2], which means you can simply use `file_selector` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/file_selector [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/file_selector/file_selector_linux/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Currently only web supported android/ ios/ # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: packages/file_selector/file_selector_linux/example/.metadata ================================================ # 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: 7736f3bc90270dcb0480db2ccffbf1d13c28db85 channel: dev project_type: app ================================================ FILE: packages/file_selector/file_selector_linux/example/README.md ================================================ # 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. ================================================ FILE: packages/file_selector/file_selector_linux/example/lib/get_directory_page.dart ================================================ // Copyright 2013 The Flutter 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 a directory using `getDirectoryPath`, /// then displays the selected directory in a dialog. class GetDirectoryPage extends StatelessWidget { /// Default Constructor const GetDirectoryPage({Key? key}) : super(key: key); Future _getDirectoryPath(BuildContext context) async { const String confirmButtonText = 'Choose'; final String? directoryPath = await FileSelectorPlatform.instance.getDirectoryPath( confirmButtonText: confirmButtonText, ); if (directoryPath == null) { // Operation was canceled by the user. return; } if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => TextDisplay(directoryPath), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open a text file'), ), 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 a directory'), onPressed: () => _getDirectoryPath(context), ), ], ), ), ); } } /// Widget that displays a text file in a dialog. class TextDisplay extends StatelessWidget { /// Creates a `TextDisplay`. const TextDisplay(this.directoryPath, {Key? key}) : super(key: key); /// The path selected in the dialog. final String directoryPath; @override Widget build(BuildContext context) { return AlertDialog( title: const Text('Selected Directory'), content: Scrollbar( child: SingleChildScrollView( child: Text(directoryPath), ), ), actions: [ TextButton( child: const Text('Close'), onPressed: () => Navigator.pop(context), ), ], ); } } ================================================ FILE: packages/file_selector/file_selector_linux/example/lib/get_multiple_directories_page.dart ================================================ // Copyright 2013 The Flutter 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), ), ], ); } } ================================================ FILE: packages/file_selector/file_selector_linux/example/lib/home_page.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; /// Home Page of the application. class HomePage extends StatelessWidget { /// Default Constructor const HomePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final ButtonStyle 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, ); return Scaffold( appBar: AppBar( title: const Text('File Selector Demo Home Page'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( style: style, child: const Text('Open a text file'), onPressed: () => Navigator.pushNamed(context, '/open/text'), ), const SizedBox(height: 10), ElevatedButton( style: style, child: const Text('Open an image'), onPressed: () => Navigator.pushNamed(context, '/open/image'), ), const SizedBox(height: 10), ElevatedButton( style: style, child: const Text('Open multiple images'), onPressed: () => Navigator.pushNamed(context, '/open/images'), ), const SizedBox(height: 10), ElevatedButton( style: style, child: const Text('Save a file'), onPressed: () => Navigator.pushNamed(context, '/save/text'), ), const SizedBox(height: 10), ElevatedButton( style: style, 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'), ), ], ), ), ); } } ================================================ FILE: packages/file_selector/file_selector_linux/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import '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'; import 'open_text_page.dart'; import 'save_text_page.dart'; void main() { runApp(const MyApp()); } /// MyApp is the Main Application. class MyApp extends StatelessWidget { /// Default Constructor const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'File Selector Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: const HomePage(), routes: { '/open/image': (BuildContext context) => const OpenImagePage(), '/open/images': (BuildContext context) => const OpenMultipleImagesPage(), '/open/text': (BuildContext context) => const OpenTextPage(), '/save/text': (BuildContext context) => SaveTextPage(), '/directory': (BuildContext context) => const GetDirectoryPage(), '/multi-directories': (BuildContext context) => const GetMultipleDirectoriesPage() }, ); } } ================================================ FILE: packages/file_selector/file_selector_linux/example/lib/open_image_page.dart ================================================ // Copyright 2013 The Flutter 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:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// Screen that allows the user to select an image file using /// `openFiles`, then displays the selected images in a gallery dialog. class OpenImagePage extends StatelessWidget { /// Default Constructor const OpenImagePage({Key? key}) : super(key: key); Future _openImageFile(BuildContext context) async { const XTypeGroup typeGroup = XTypeGroup( label: 'images', extensions: ['jpg', 'png'], ); final XFile? file = await FileSelectorPlatform.instance .openFile(acceptedTypeGroups: [typeGroup]); if (file == null) { // Operation was canceled by the user. return; } final String fileName = file.name; final String filePath = file.path; if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => ImageDisplay(fileName, filePath), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open an image'), ), 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 open an image file(png, jpg)'), onPressed: () => _openImageFile(context), ), ], ), ), ); } } /// Widget that displays an image in a dialog. class ImageDisplay extends StatelessWidget { /// Default Constructor. const ImageDisplay(this.fileName, this.filePath, {Key? key}) : super(key: key); /// The name of the selected file. final String fileName; /// The path to the selected file. final String filePath; @override Widget build(BuildContext context) { return AlertDialog( title: Text(fileName), // On web the filePath is a blob url // while on other platforms it is a system path. content: kIsWeb ? Image.network(filePath) : Image.file(File(filePath)), actions: [ TextButton( child: const Text('Close'), onPressed: () { Navigator.pop(context); }, ), ], ); } } ================================================ FILE: packages/file_selector/file_selector_linux/example/lib/open_multiple_images_page.dart ================================================ // Copyright 2013 The Flutter 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:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// Screen that allows the user to select multiple image files using /// `openFiles`, then displays the selected images in a gallery dialog. class OpenMultipleImagesPage extends StatelessWidget { /// Default Constructor const OpenMultipleImagesPage({Key? key}) : super(key: key); Future _openImageFile(BuildContext context) async { const XTypeGroup jpgsTypeGroup = XTypeGroup( label: 'JPEGs', extensions: ['jpg', 'jpeg'], ); const XTypeGroup pngTypeGroup = XTypeGroup( label: 'PNGs', extensions: ['png'], ); final List files = await FileSelectorPlatform.instance .openFiles(acceptedTypeGroups: [ jpgsTypeGroup, pngTypeGroup, ]); if (files.isEmpty) { // Operation was canceled by the user. return; } if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => MultipleImagesDisplay(files), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open multiple images'), ), 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 open multiple images (png, jpg)'), onPressed: () => _openImageFile(context), ), ], ), ), ); } } /// Widget that displays a text file in a dialog. class MultipleImagesDisplay extends StatelessWidget { /// Default Constructor. const MultipleImagesDisplay(this.files, {Key? key}) : super(key: key); /// The files containing the images. final List files; @override Widget build(BuildContext context) { return AlertDialog( title: const Text('Gallery'), // On web the filePath is a blob url // while on other platforms it is a system path. content: Center( child: Row( children: [ ...files.map( (XFile file) => Flexible( child: kIsWeb ? Image.network(file.path) : Image.file(File(file.path))), ) ], ), ), actions: [ TextButton( child: const Text('Close'), onPressed: () { Navigator.pop(context); }, ), ], ); } } ================================================ FILE: packages/file_selector/file_selector_linux/example/lib/open_text_page.dart ================================================ // Copyright 2013 The Flutter 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 a text file using `openFile`, then /// displays its contents in a dialog. class OpenTextPage extends StatelessWidget { /// Default Constructor const OpenTextPage({Key? key}) : super(key: key); Future _openTextFile(BuildContext context) async { const XTypeGroup typeGroup = XTypeGroup( label: 'text', extensions: ['txt', 'json'], ); final XFile? file = await FileSelectorPlatform.instance .openFile(acceptedTypeGroups: [typeGroup]); if (file == null) { // Operation was canceled by the user. return; } final String fileName = file.name; final String fileContent = await file.readAsString(); if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => TextDisplay(fileName, fileContent), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open a text file'), ), 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 open a text file (json, txt)'), onPressed: () => _openTextFile(context), ), ], ), ), ); } } /// Widget that displays a text file in a dialog. class TextDisplay extends StatelessWidget { /// Default Constructor. const TextDisplay(this.fileName, this.fileContent, {Key? key}) : super(key: key); /// The name of the selected file. final String fileName; /// The contents of the text file. final String fileContent; @override Widget build(BuildContext context) { return AlertDialog( title: Text(fileName), content: Scrollbar( child: SingleChildScrollView( child: Text(fileContent), ), ), actions: [ TextButton( child: const Text('Close'), onPressed: () => Navigator.pop(context), ), ], ); } } ================================================ FILE: packages/file_selector/file_selector_linux/example/lib/save_text_page.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:typed_data'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/material.dart'; /// Screen that allows the user to select a save location using `getSavePath`, /// then writes text to a file at that location. class SaveTextPage extends StatelessWidget { /// Default Constructor SaveTextPage({Key? key}) : super(key: key); final TextEditingController _nameController = TextEditingController(); final TextEditingController _contentController = TextEditingController(); Future _saveFile() async { final String fileName = _nameController.text; final String? path = await FileSelectorPlatform.instance.getSavePath( // Operation was canceled by the user. suggestedName: fileName, ); if (path == null) { return; } final String text = _contentController.text; final Uint8List fileData = Uint8List.fromList(text.codeUnits); const String fileMimeType = 'text/plain'; final XFile textFile = XFile.fromData(fileData, mimeType: fileMimeType, name: fileName); await textFile.saveTo(path); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Save text into a file'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 300, child: TextField( minLines: 1, maxLines: 12, controller: _nameController, decoration: const InputDecoration( hintText: '(Optional) Suggest File Name', ), ), ), SizedBox( width: 300, child: TextField( minLines: 1, maxLines: 12, controller: _contentController, decoration: const InputDecoration( hintText: 'Enter File Contents', ), ), ), const SizedBox(height: 10), 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, ), onPressed: _saveFile, child: const Text('Press to save a text file'), ), ], ), ), ); } } ================================================ FILE: packages/file_selector/file_selector_linux/example/linux/.gitignore ================================================ flutter/ephemeral ================================================ FILE: packages/file_selector/file_selector_linux/example/linux/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(runner LANGUAGES CXX) set(BINARY_NAME "example") set(APPLICATION_ID "com.example.example") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_14) target_compile_options(${TARGET} PRIVATE -Wall -Werror) target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") # Application build add_executable(${BINARY_NAME} "main.cc" "my_application.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" ) apply_standard_settings(${BINARY_NAME}) target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) add_dependencies(${BINARY_NAME} flutter_assemble) # Only the install-generated bundle's copy of the executable will launch # correctly, since the resources must in the right relative locations. To avoid # people trying to run the unbundled copy, put it in a subdirectory instead of # the default top-level location. set_target_properties(${BINARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" ) # Enable the test target. set(include_file_selector_linux_tests TRUE) # Provide an alias for the test target using the name expected by repo tooling. add_custom_target(unit_tests DEPENDS file_selector_linux_test) # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # By default, "installing" just makes a relocatable bundle in the build # directory. set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() # Start with a clean build bundle directory every time. install(CODE " file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") " COMPONENT Runtime) set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() ================================================ FILE: packages/file_selector/file_selector_linux/example/linux/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. # Serves the same purpose as list(TRANSFORM ... PREPEND ...), # which isn't available in 3.10. function(list_prepend LIST_NAME PREFIX) set(NEW_LIST "") foreach(element ${${LIST_NAME}}) list(APPEND NEW_LIST "${PREFIX}${element}") endforeach(element) set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) endfunction() # === Flutter Library === # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "fl_basic_message_channel.h" "fl_binary_codec.h" "fl_binary_messenger.h" "fl_dart_project.h" "fl_engine.h" "fl_json_message_codec.h" "fl_json_method_codec.h" "fl_message_codec.h" "fl_method_call.h" "fl_method_channel.h" "fl_method_codec.h" "fl_method_response.h" "fl_plugin_registrar.h" "fl_plugin_registry.h" "fl_standard_message_codec.h" "fl_standard_method_codec.h" "fl_string_codec.h" "fl_value.h" "fl_view.h" "flutter_linux.h" ) list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") target_link_libraries(flutter INTERFACE PkgConfig::GTK PkgConfig::GLIB PkgConfig::GIO ) add_dependencies(flutter flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/_phony_ COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ) ================================================ FILE: packages/file_selector/file_selector_linux/example/linux/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST file_selector_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/file_selector/file_selector_linux/example/linux/main.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "my_application.h" int main(int argc, char** argv) { g_autoptr(MyApplication) app = my_application_new(); return g_application_run(G_APPLICATION(app), argc, argv); } ================================================ FILE: packages/file_selector/file_selector_linux/example/linux/my_application.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "my_application.h" #include #ifdef GDK_WINDOWING_X11 #include #endif #include "flutter/generated_plugin_registrant.h" struct _MyApplication { GtkApplication parent_instance; char** dart_entrypoint_arguments; }; G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // Implements GApplication::activate. static void my_application_activate(GApplication* application) { MyApplication* self = MY_APPLICATION(application); GtkWindow* window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); // Use a header bar when running in GNOME as this is the common style used // by applications and is the setup most users will be using (e.g. Ubuntu // desktop). // If running on X and not using GNOME then just use a traditional title bar // in case the window manager does more exotic layout, e.g. tiling. // If running on Wayland assume the header bar will work (may need changing // if future cases occur). gboolean use_header_bar = TRUE; #ifdef GDK_WINDOWING_X11 GdkScreen* screen = gtk_window_get_screen(window); if (GDK_IS_X11_SCREEN(screen)) { const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); if (g_strcmp0(wm_name, "GNOME Shell") != 0) { use_header_bar = FALSE; } } #endif if (use_header_bar) { GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); gtk_header_bar_set_title(header_bar, "example"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); } else { gtk_window_set_title(window, "example"); } gtk_window_set_default_size(window, 1280, 720); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); fl_dart_project_set_dart_entrypoint_arguments( project, self->dart_entrypoint_arguments); FlView* view = fl_view_new(project); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view)); gtk_widget_grab_focus(GTK_WIDGET(view)); } // Implements GApplication::local_command_line. static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { MyApplication* self = MY_APPLICATION(application); // Strip out the first argument as it is the binary name. self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); g_autoptr(GError) error = nullptr; if (!g_application_register(application, nullptr, &error)) { g_warning("Failed to register: %s", error->message); *exit_status = 1; return TRUE; } g_application_activate(application); *exit_status = 0; return TRUE; } // Implements GObject::dispose. static void my_application_dispose(GObject* object) { MyApplication* self = MY_APPLICATION(object); g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); G_OBJECT_CLASS(my_application_parent_class)->dispose(object); } static void my_application_class_init(MyApplicationClass* klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; G_OBJECT_CLASS(klass)->dispose = my_application_dispose; } static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { return MY_APPLICATION(g_object_new( my_application_get_type(), "application-id", APPLICATION_ID, nullptr)); } ================================================ FILE: packages/file_selector/file_selector_linux/example/linux/my_application.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef FLUTTER_MY_APPLICATION_H_ #define FLUTTER_MY_APPLICATION_H_ #include G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, GtkApplication) /** * my_application_new: * * Creates a new Flutter-based application. * * Returns: a new #MyApplication. */ MyApplication* my_application_new(); #endif // FLUTTER_MY_APPLICATION_H_ ================================================ FILE: packages/file_selector/file_selector_linux/example/pubspec.yaml ================================================ name: file_selector_linux_example description: Local testbed for Linux file_selector implementation. 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.4.0 flutter: sdk: flutter dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/file_selector/file_selector_linux/lib/file_selector_linux.dart ================================================ // Copyright 2013 The Flutter 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/foundation.dart' show visibleForTesting; import 'package:flutter/services.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.dev/file_selector_linux'); const String _typeGroupLabelKey = 'label'; const String _typeGroupExtensionsKey = 'extensions'; const String _typeGroupMimeTypesKey = 'mimeTypes'; const String _openFileMethod = 'openFile'; const String _getSavePathMethod = 'getSavePath'; const String _getDirectoryPathMethod = 'getDirectoryPath'; const String _acceptedTypeGroupsKey = 'acceptedTypeGroups'; const String _confirmButtonTextKey = 'confirmButtonText'; const String _initialDirectoryKey = 'initialDirectory'; const String _multipleKey = 'multiple'; const String _suggestedNameKey = 'suggestedName'; /// An implementation of [FileSelectorPlatform] for Linux. class FileSelectorLinux extends FileSelectorPlatform { /// The MethodChannel that is being used by this implementation of the plugin. @visibleForTesting MethodChannel get channel => _channel; /// Registers the Linux implementation. static void registerWith() { FileSelectorPlatform.instance = FileSelectorLinux(); } @override Future openFile({ List? acceptedTypeGroups, String? initialDirectory, String? confirmButtonText, }) async { final List> serializedTypeGroups = _serializeTypeGroups(acceptedTypeGroups); final List? path = await _channel.invokeListMethod( _openFileMethod, { if (serializedTypeGroups.isNotEmpty) _acceptedTypeGroupsKey: serializedTypeGroups, 'initialDirectory': initialDirectory, _confirmButtonTextKey: confirmButtonText, _multipleKey: false, }, ); return path == null ? null : XFile(path.first); } @override Future> openFiles({ List? acceptedTypeGroups, String? initialDirectory, String? confirmButtonText, }) async { final List> serializedTypeGroups = _serializeTypeGroups(acceptedTypeGroups); final List? pathList = await _channel.invokeListMethod( _openFileMethod, { if (serializedTypeGroups.isNotEmpty) _acceptedTypeGroupsKey: serializedTypeGroups, _initialDirectoryKey: initialDirectory, _confirmButtonTextKey: confirmButtonText, _multipleKey: true, }, ); return pathList?.map((String path) => XFile(path)).toList() ?? []; } @override Future getSavePath({ List? acceptedTypeGroups, String? initialDirectory, String? suggestedName, String? confirmButtonText, }) async { final List> serializedTypeGroups = _serializeTypeGroups(acceptedTypeGroups); return _channel.invokeMethod( _getSavePathMethod, { if (serializedTypeGroups.isNotEmpty) _acceptedTypeGroupsKey: serializedTypeGroups, _initialDirectoryKey: initialDirectory, _suggestedNameKey: suggestedName, _confirmButtonTextKey: confirmButtonText, }, ); } @override Future getDirectoryPath({ String? initialDirectory, String? confirmButtonText, }) async { 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 ?? []; } } List> _serializeTypeGroups(List? groups) { return (groups ?? []).map(_serializeTypeGroup).toList(); } Map _serializeTypeGroup(XTypeGroup group) { final Map serialization = { _typeGroupLabelKey: group.label ?? '', }; if (group.allowsAny) { serialization[_typeGroupExtensionsKey] = ['*']; } else { if ((group.extensions?.isEmpty ?? true) && (group.mimeTypes?.isEmpty ?? true)) { throw ArgumentError('Provided type group $group does not allow ' 'all files, but does not set any of the Linux-supported filter ' 'categories. "extensions" or "mimeTypes" must be non-empty for Linux ' 'if anything is non-empty.'); } if (group.extensions?.isNotEmpty ?? false) { serialization[_typeGroupExtensionsKey] = group.extensions ?.map((String extension) => '*.$extension') .toList() ?? []; } if (group.mimeTypes?.isNotEmpty ?? false) { serialization[_typeGroupMimeTypesKey] = group.mimeTypes ?? []; } } return serialization; } ================================================ FILE: packages/file_selector/file_selector_linux/linux/.gitignore ================================================ CMakeCache.txt CMakeFiles/ ================================================ FILE: packages/file_selector/file_selector_linux/linux/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) set(PROJECT_NAME "file_selector_linux") project(${PROJECT_NAME} LANGUAGES CXX) set(PLUGIN_NAME "${PROJECT_NAME}_plugin") list(APPEND PLUGIN_SOURCES "file_selector_plugin.cc" ) add_library(${PLUGIN_NAME} SHARED "file_selector_plugin.cc" ) apply_standard_settings(${PLUGIN_NAME}) set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden) target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) target_include_directories(${PLUGIN_NAME} INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) # === Tests === if(${include_${PROJECT_NAME}_tests}) if(${CMAKE_VERSION} VERSION_LESS "3.11.0") message("Unit tests require CMake 3.11.0 or later") else() set(TEST_RUNNER "${PROJECT_NAME}_test") enable_testing() # TODO(stuartmorgan): Consider using a single shared, pre-checked-in googletest # instance rather than downloading for each plugin. This approach makes sense # for a template, but not for a monorepo with many plugins. include(FetchContent) FetchContent_Declare( googletest URL https://github.com/google/googletest/archive/release-1.11.0.zip ) # Prevent overriding the parent project's compiler/linker settings set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Disable install commands for gtest so it doesn't end up in the bundle. set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE) FetchContent_MakeAvailable(googletest) # The plugin's exported API is not very useful for unit testing, so build the # sources directly into the test binary rather than using the shared library. add_executable(${TEST_RUNNER} test/file_selector_plugin_test.cc test/test_main.cc ${PLUGIN_SOURCES} ) apply_standard_settings(${TEST_RUNNER}) target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") target_link_libraries(${TEST_RUNNER} PRIVATE flutter) target_link_libraries(${TEST_RUNNER} PRIVATE PkgConfig::GTK) target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock) include(GoogleTest) # TODO(stuartmorgan): Switch back to gtest_discover_tests when moving to # flutter/plugins; it doesn't work in the FDE CI because it requires actually # running a GTK app, which it hasn't been set up for. gtest_add_tests(TARGET ${TEST_RUNNER}) #gtest_discover_tests(${TEST_RUNNER}) endif() # CMake version check endif() # include_${PROJECT_NAME}_tests ================================================ FILE: packages/file_selector/file_selector_linux/linux/file_selector_plugin.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "include/file_selector_linux/file_selector_plugin.h" #include #include #include "file_selector_plugin_private.h" // From file_selector_linux.dart const char kChannelName[] = "plugins.flutter.dev/file_selector_linux"; const char kOpenFileMethod[] = "openFile"; const char kGetSavePathMethod[] = "getSavePath"; const char kGetDirectoryPathMethod[] = "getDirectoryPath"; const char kAcceptedTypeGroupsKey[] = "acceptedTypeGroups"; const char kConfirmButtonTextKey[] = "confirmButtonText"; const char kInitialDirectoryKey[] = "initialDirectory"; const char kMultipleKey[] = "multiple"; const char kSuggestedNameKey[] = "suggestedName"; const char kTypeGroupLabelKey[] = "label"; const char kTypeGroupExtensionsKey[] = "extensions"; const char kTypeGroupMimeTypesKey[] = "mimeTypes"; // Errors const char kBadArgumentsError[] = "Bad Arguments"; const char kNoScreenError[] = "No Screen"; struct _FlFileSelectorPlugin { GObject parent_instance; FlPluginRegistrar* registrar; // Connection to Flutter engine. FlMethodChannel* channel; }; G_DEFINE_TYPE(FlFileSelectorPlugin, fl_file_selector_plugin, G_TYPE_OBJECT) // Converts a type group received from Flutter into a GTK file filter. static GtkFileFilter* type_group_to_filter(FlValue* value) { g_autoptr(GtkFileFilter) filter = gtk_file_filter_new(); FlValue* label = fl_value_lookup_string(value, kTypeGroupLabelKey); if (label != nullptr && fl_value_get_type(label) == FL_VALUE_TYPE_STRING) { gtk_file_filter_set_name(filter, fl_value_get_string(label)); } FlValue* extensions = fl_value_lookup_string(value, kTypeGroupExtensionsKey); if (extensions != nullptr && fl_value_get_type(extensions) == FL_VALUE_TYPE_LIST) { for (size_t i = 0; i < fl_value_get_length(extensions); i++) { FlValue* v = fl_value_get_list_value(extensions, i); const gchar* pattern = fl_value_get_string(v); gtk_file_filter_add_pattern(filter, pattern); } } FlValue* mime_types = fl_value_lookup_string(value, kTypeGroupMimeTypesKey); if (mime_types != nullptr && fl_value_get_type(mime_types) == FL_VALUE_TYPE_LIST) { for (size_t i = 0; i < fl_value_get_length(mime_types); i++) { FlValue* v = fl_value_get_list_value(mime_types, i); const gchar* pattern = fl_value_get_string(v); gtk_file_filter_add_mime_type(filter, pattern); } } return GTK_FILE_FILTER(g_object_ref(filter)); } // Creates a GtkFileChooserNative for the given method call details. static GtkFileChooserNative* create_dialog( GtkWindow* window, GtkFileChooserAction action, const gchar* title, const gchar* default_confirm_button_text, FlValue* properties) { const gchar* confirm_button_text = default_confirm_button_text; FlValue* value = fl_value_lookup_string(properties, kConfirmButtonTextKey); if (value != nullptr && fl_value_get_type(value) == FL_VALUE_TYPE_STRING) confirm_button_text = fl_value_get_string(value); g_autoptr(GtkFileChooserNative) dialog = GTK_FILE_CHOOSER_NATIVE(gtk_file_chooser_native_new( title, window, action, confirm_button_text, "_Cancel")); value = fl_value_lookup_string(properties, kMultipleKey); if (value != nullptr && fl_value_get_type(value) == FL_VALUE_TYPE_BOOL) { gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), fl_value_get_bool(value)); } value = fl_value_lookup_string(properties, kInitialDirectoryKey); if (value != nullptr && fl_value_get_type(value) == FL_VALUE_TYPE_STRING) { gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), fl_value_get_string(value)); } value = fl_value_lookup_string(properties, kSuggestedNameKey); if (value != nullptr && fl_value_get_type(value) == FL_VALUE_TYPE_STRING) { gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), fl_value_get_string(value)); } value = fl_value_lookup_string(properties, kAcceptedTypeGroupsKey); if (value != nullptr && fl_value_get_type(value) == FL_VALUE_TYPE_LIST) { for (size_t i = 0; i < fl_value_get_length(value); i++) { FlValue* type_group = fl_value_get_list_value(value, i); GtkFileFilter* filter = type_group_to_filter(type_group); if (filter == nullptr) { return nullptr; } gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); } } return GTK_FILE_CHOOSER_NATIVE(g_object_ref(dialog)); } // TODO(stuartmorgan): Move this logic back into method_call_cb once // https://github.com/flutter/flutter/issues/88724 is fixed, and test // through the public API instead. This only exists to move as much // logic as possible behind the private entry point used by unit tests. GtkFileChooserNative* create_dialog_for_method(GtkWindow* window, const gchar* method, FlValue* properties) { if (strcmp(method, kOpenFileMethod) == 0) { return create_dialog(window, GTK_FILE_CHOOSER_ACTION_OPEN, "Open File", "_Open", properties); } else if (strcmp(method, kGetDirectoryPathMethod) == 0) { return create_dialog(window, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, "Choose Directory", "_Open", properties); } else if (strcmp(method, kGetSavePathMethod) == 0) { return create_dialog(window, GTK_FILE_CHOOSER_ACTION_SAVE, "Save File", "_Save", properties); } return nullptr; } // Shows the requested dialog type. static FlMethodResponse* show_dialog(FlFileSelectorPlugin* self, const gchar* method, FlValue* properties, bool return_list) { if (fl_value_get_type(properties) != FL_VALUE_TYPE_MAP) { return FL_METHOD_RESPONSE(fl_method_error_response_new( kBadArgumentsError, "Argument map missing or malformed", nullptr)); } FlView* view = fl_plugin_registrar_get_view(self->registrar); if (view == nullptr) { return FL_METHOD_RESPONSE( fl_method_error_response_new(kNoScreenError, nullptr, nullptr)); } GtkWindow* window = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view))); g_autoptr(GtkFileChooserNative) dialog = create_dialog_for_method(window, method, properties); if (dialog == nullptr) { return FL_METHOD_RESPONSE(fl_method_error_response_new( kBadArgumentsError, "Unable to create dialog from arguments", nullptr)); } gint response = gtk_native_dialog_run(GTK_NATIVE_DIALOG(dialog)); g_autoptr(FlValue) result = nullptr; if (response == GTK_RESPONSE_ACCEPT) { if (return_list) { result = fl_value_new_list(); g_autoptr(GSList) filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); for (GSList* link = filenames; link != nullptr; link = link->next) { g_autofree gchar* filename = static_cast(link->data); fl_value_append_take(result, fl_value_new_string(filename)); } } else { g_autofree gchar* filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); result = fl_value_new_string(filename); } } return FL_METHOD_RESPONSE(fl_method_success_response_new(result)); } // Called when a method call is received from Flutter. static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, gpointer user_data) { FlFileSelectorPlugin* self = FL_FILE_SELECTOR_PLUGIN(user_data); const gchar* method = fl_method_call_get_name(method_call); FlValue* args = fl_method_call_get_args(method_call); g_autoptr(FlMethodResponse) response = nullptr; if (strcmp(method, kOpenFileMethod) == 0 || strcmp(method, kGetDirectoryPathMethod) == 0) { response = show_dialog(self, method, args, true); } else if (strcmp(method, kGetSavePathMethod) == 0) { response = show_dialog(self, method, args, false); } else { response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); } g_autoptr(GError) error = nullptr; if (!fl_method_call_respond(method_call, response, &error)) g_warning("Failed to send method call response: %s", error->message); } static void fl_file_selector_plugin_dispose(GObject* object) { FlFileSelectorPlugin* self = FL_FILE_SELECTOR_PLUGIN(object); g_clear_object(&self->registrar); g_clear_object(&self->channel); G_OBJECT_CLASS(fl_file_selector_plugin_parent_class)->dispose(object); } static void fl_file_selector_plugin_class_init( FlFileSelectorPluginClass* klass) { G_OBJECT_CLASS(klass)->dispose = fl_file_selector_plugin_dispose; } static void fl_file_selector_plugin_init(FlFileSelectorPlugin* self) {} FlFileSelectorPlugin* fl_file_selector_plugin_new( FlPluginRegistrar* registrar) { FlFileSelectorPlugin* self = FL_FILE_SELECTOR_PLUGIN( g_object_new(fl_file_selector_plugin_get_type(), nullptr)); self->registrar = FL_PLUGIN_REGISTRAR(g_object_ref(registrar)); g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); self->channel = fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar), kChannelName, FL_METHOD_CODEC(codec)); fl_method_channel_set_method_call_handler(self->channel, method_call_cb, g_object_ref(self), g_object_unref); return self; } void file_selector_plugin_register_with_registrar( FlPluginRegistrar* registrar) { FlFileSelectorPlugin* plugin = fl_file_selector_plugin_new(registrar); g_object_unref(plugin); } ================================================ FILE: packages/file_selector/file_selector_linux/linux/file_selector_plugin_private.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include "include/file_selector_linux/file_selector_plugin.h" // Creates a GtkFileChooserNative for the given method call. GtkFileChooserNative* create_dialog_for_method(GtkWindow* window, const gchar* method, FlValue* properties); ================================================ FILE: packages/file_selector/file_selector_linux/linux/include/file_selector_linux/file_selector_plugin.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PLUGINS_FILE_SELECTOR_LINUX_FILE_SELECTOR_PLUGIN_H_ #define PLUGINS_FILE_SELECTOR_LINUX_FILE_SELECTOR_PLUGIN_H_ // A plugin to show native save/open file choosers. #include G_BEGIN_DECLS #ifdef FLUTTER_PLUGIN_IMPL #define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) #else #define FLUTTER_PLUGIN_EXPORT #endif G_DECLARE_FINAL_TYPE(FlFileSelectorPlugin, fl_file_selector_plugin, FL, FILE_SELECTOR_PLUGIN, GObject) FLUTTER_PLUGIN_EXPORT FlFileSelectorPlugin* fl_file_selector_plugin_new( FlPluginRegistrar* registrar); FLUTTER_PLUGIN_EXPORT void file_selector_plugin_register_with_registrar( FlPluginRegistrar* registrar); G_END_DECLS #endif // PLUGINS_FILE_SELECTOR_LINUX_FILE_SELECTOR_PLUGIN_H_ ================================================ FILE: packages/file_selector/file_selector_linux/linux/test/file_selector_plugin_test.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "include/file_selector_linux/file_selector_plugin.h" #include #include #include #include "file_selector_plugin_private.h" // TODO(stuartmorgan): Restructure the helper to take a callback for showing // the dialog, so that the tests can mock out that callback with something // that changes the selection so that the return value path can be tested // as well. // TODO(stuartmorgan): Add an injectable wrapper around // gtk_file_chooser_native_new to allow for testing values that are given as // construction paramaters and can't be queried later. TEST(FileSelectorPlugin, TestOpenSimple) { g_autoptr(FlValue) args = fl_value_new_map(); g_autoptr(GtkFileChooserNative) dialog = create_dialog_for_method(nullptr, "openFile", args); ASSERT_NE(dialog, nullptr); EXPECT_EQ(gtk_file_chooser_get_action(GTK_FILE_CHOOSER(dialog)), GTK_FILE_CHOOSER_ACTION_OPEN); EXPECT_EQ(gtk_file_chooser_get_select_multiple(GTK_FILE_CHOOSER(dialog)), false); } TEST(FileSelectorPlugin, TestOpenMultiple) { 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, "openFile", args); ASSERT_NE(dialog, nullptr); EXPECT_EQ(gtk_file_chooser_get_action(GTK_FILE_CHOOSER(dialog)), GTK_FILE_CHOOSER_ACTION_OPEN); EXPECT_EQ(gtk_file_chooser_get_select_multiple(GTK_FILE_CHOOSER(dialog)), true); } TEST(FileSelectorPlugin, TestOpenWithFilter) { g_autoptr(FlValue) type_groups = fl_value_new_list(); { g_autoptr(FlValue) text_group_mime_types = fl_value_new_list(); fl_value_append_take(text_group_mime_types, fl_value_new_string("text/plain")); g_autoptr(FlValue) text_group = fl_value_new_map(); fl_value_set_string_take(text_group, "label", fl_value_new_string("Text")); fl_value_set_string(text_group, "mimeTypes", text_group_mime_types); fl_value_append(type_groups, text_group); } { g_autoptr(FlValue) image_group_extensions = fl_value_new_list(); fl_value_append_take(image_group_extensions, fl_value_new_string("*.png")); fl_value_append_take(image_group_extensions, fl_value_new_string("*.gif")); fl_value_append_take(image_group_extensions, fl_value_new_string("*.jgpeg")); g_autoptr(FlValue) image_group = fl_value_new_map(); fl_value_set_string_take(image_group, "label", fl_value_new_string("Images")); fl_value_set_string(image_group, "extensions", image_group_extensions); fl_value_append(type_groups, image_group); } { g_autoptr(FlValue) any_group_extensions = fl_value_new_list(); fl_value_append_take(any_group_extensions, fl_value_new_string("*")); g_autoptr(FlValue) any_group = fl_value_new_map(); fl_value_set_string_take(any_group, "label", fl_value_new_string("Any")); fl_value_set_string(any_group, "extensions", any_group_extensions); fl_value_append(type_groups, any_group); } g_autoptr(FlValue) args = fl_value_new_map(); fl_value_set_string(args, "acceptedTypeGroups", type_groups); g_autoptr(GtkFileChooserNative) dialog = create_dialog_for_method(nullptr, "openFile", args); ASSERT_NE(dialog, nullptr); EXPECT_EQ(gtk_file_chooser_get_action(GTK_FILE_CHOOSER(dialog)), GTK_FILE_CHOOSER_ACTION_OPEN); EXPECT_EQ(gtk_file_chooser_get_select_multiple(GTK_FILE_CHOOSER(dialog)), false); // Validate filters. g_autoptr(GSList) type_group_list = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(dialog)); EXPECT_EQ(g_slist_length(type_group_list), 3); GtkFileFilter* text_filter = GTK_FILE_FILTER(g_slist_nth_data(type_group_list, 0)); GtkFileFilter* image_filter = GTK_FILE_FILTER(g_slist_nth_data(type_group_list, 1)); GtkFileFilter* any_filter = GTK_FILE_FILTER(g_slist_nth_data(type_group_list, 2)); // Filters can't be inspected, so query them to see that they match expected // filter behavior. GtkFileFilterInfo text_file_info = {}; text_file_info.contains = static_cast( GTK_FILE_FILTER_DISPLAY_NAME | GTK_FILE_FILTER_MIME_TYPE); text_file_info.display_name = "foo.txt"; text_file_info.mime_type = "text/plain"; GtkFileFilterInfo image_file_info = {}; image_file_info.contains = static_cast( GTK_FILE_FILTER_DISPLAY_NAME | GTK_FILE_FILTER_MIME_TYPE); image_file_info.display_name = "foo.png"; image_file_info.mime_type = "image/png"; EXPECT_TRUE(gtk_file_filter_filter(text_filter, &text_file_info)); EXPECT_FALSE(gtk_file_filter_filter(text_filter, &image_file_info)); EXPECT_FALSE(gtk_file_filter_filter(image_filter, &text_file_info)); EXPECT_TRUE(gtk_file_filter_filter(image_filter, &image_file_info)); EXPECT_TRUE(gtk_file_filter_filter(any_filter, &image_file_info)); EXPECT_TRUE(gtk_file_filter_filter(any_filter, &text_file_info)); } TEST(FileSelectorPlugin, TestSaveSimple) { g_autoptr(FlValue) args = fl_value_new_map(); g_autoptr(GtkFileChooserNative) dialog = create_dialog_for_method(nullptr, "getSavePath", args); ASSERT_NE(dialog, nullptr); EXPECT_EQ(gtk_file_chooser_get_action(GTK_FILE_CHOOSER(dialog)), GTK_FILE_CHOOSER_ACTION_SAVE); EXPECT_EQ(gtk_file_chooser_get_select_multiple(GTK_FILE_CHOOSER(dialog)), false); } TEST(FileSelectorPlugin, TestSaveWithArguments) { g_autoptr(FlValue) args = fl_value_new_map(); fl_value_set_string_take(args, "initialDirectory", fl_value_new_string("/tmp")); fl_value_set_string_take(args, "suggestedName", fl_value_new_string("foo.txt")); g_autoptr(GtkFileChooserNative) dialog = create_dialog_for_method(nullptr, "getSavePath", args); ASSERT_NE(dialog, nullptr); EXPECT_EQ(gtk_file_chooser_get_action(GTK_FILE_CHOOSER(dialog)), GTK_FILE_CHOOSER_ACTION_SAVE); EXPECT_EQ(gtk_file_chooser_get_select_multiple(GTK_FILE_CHOOSER(dialog)), false); g_autofree gchar* current_name = gtk_file_chooser_get_current_name(GTK_FILE_CHOOSER(dialog)); EXPECT_STREQ(current_name, "foo.txt"); // TODO(stuartmorgan): gtk_file_chooser_get_current_folder doesn't seem to // return a value set by gtk_file_chooser_set_current_folder, or at least // doesn't in a test context, so that's not currently validated. } TEST(FileSelectorPlugin, TestGetDirectory) { g_autoptr(FlValue) args = fl_value_new_map(); 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)), 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); } ================================================ FILE: packages/file_selector/file_selector_linux/linux/test/test_main.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include int main(int argc, char** argv) { gtk_init(0, nullptr); testing::InitGoogleTest(&argc, argv); int exit_code = RUN_ALL_TESTS(); return exit_code; } ================================================ FILE: packages/file_selector/file_selector_linux/pubspec.yaml ================================================ 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.1 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: file_selector platforms: linux: pluginClass: FileSelectorPlugin dartPluginClass: FileSelectorLinux dependencies: cross_file: ^0.3.1 file_selector_platform_interface: ^2.4.0 flutter: sdk: flutter dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart ================================================ // Copyright 2013 The Flutter 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_linux/file_selector_linux.dart'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); late FileSelectorLinux plugin; late List log; setUp(() { plugin = FileSelectorLinux(); log = []; _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( plugin.channel, (MethodCall methodCall) async { log.add(methodCall); return null; }, ); }); test('registers instance', () { FileSelectorLinux.registerWith(); expect(FileSelectorPlatform.instance, isA()); }); group('#openFile', () { test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', extensions: ['txt'], mimeTypes: ['text/plain'], macUTIs: ['public.text'], ); const XTypeGroup groupTwo = XTypeGroup( label: 'image', extensions: ['jpg'], mimeTypes: ['image/jpg'], macUTIs: ['public.image'], webWildCards: ['image/*'], ); await plugin.openFile(acceptedTypeGroups: [group, groupTwo]); expectMethodCall( log, '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'); expectMethodCall( log, 'openFile', arguments: { 'initialDirectory': '/example/directory', 'confirmButtonText': null, 'multiple': false, }, ); }); test('passes confirmButtonText correctly', () async { await plugin.openFile(confirmButtonText: 'Open File'); expectMethodCall( log, 'openFile', arguments: { 'initialDirectory': null, 'confirmButtonText': 'Open File', 'multiple': false, }, ); }); test('throws for a type group that does not support Linux', () async { const XTypeGroup group = XTypeGroup( label: 'images', webWildCards: ['images/*'], ); await expectLater( plugin.openFile(acceptedTypeGroups: [group]), throwsArgumentError); }); test('passes a wildcard group correctly', () async { const XTypeGroup group = XTypeGroup( label: 'any', ); await plugin.openFile(acceptedTypeGroups: [group]); expectMethodCall( log, 'openFile', arguments: { 'acceptedTypeGroups': >[ { 'label': 'any', 'extensions': ['*'], }, ], 'initialDirectory': null, 'confirmButtonText': null, 'multiple': false, }, ); }); }); group('#openFiles', () { test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', extensions: ['txt'], mimeTypes: ['text/plain'], macUTIs: ['public.text'], ); const XTypeGroup groupTwo = XTypeGroup( label: 'image', extensions: ['jpg'], mimeTypes: ['image/jpg'], macUTIs: ['public.image'], webWildCards: ['image/*'], ); await plugin.openFiles(acceptedTypeGroups: [group, groupTwo]); expectMethodCall( log, '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'); expectMethodCall( log, 'openFile', arguments: { 'initialDirectory': '/example/directory', 'confirmButtonText': null, 'multiple': true, }, ); }); test('passes confirmButtonText correctly', () async { await plugin.openFiles(confirmButtonText: 'Open File'); expectMethodCall( log, 'openFile', arguments: { 'initialDirectory': null, 'confirmButtonText': 'Open File', 'multiple': true, }, ); }); test('throws for a type group that does not support Linux', () async { const XTypeGroup group = XTypeGroup( label: 'images', webWildCards: ['images/*'], ); await expectLater( plugin.openFile(acceptedTypeGroups: [group]), throwsArgumentError); }); test('passes a wildcard group correctly', () async { const XTypeGroup group = XTypeGroup( label: 'any', ); await plugin.openFile(acceptedTypeGroups: [group]); expectMethodCall( log, 'openFile', arguments: { 'acceptedTypeGroups': >[ { 'label': 'any', 'extensions': ['*'], }, ], 'initialDirectory': null, 'confirmButtonText': null, 'multiple': false, }, ); }); }); group('#getSavePath', () { test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', extensions: ['txt'], mimeTypes: ['text/plain'], macUTIs: ['public.text'], ); const XTypeGroup groupTwo = XTypeGroup( label: 'image', extensions: ['jpg'], mimeTypes: ['image/jpg'], macUTIs: ['public.image'], webWildCards: ['image/*'], ); await plugin .getSavePath(acceptedTypeGroups: [group, groupTwo]); expectMethodCall( log, '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'); expectMethodCall( log, 'getSavePath', arguments: { 'initialDirectory': '/example/directory', 'suggestedName': null, 'confirmButtonText': null, }, ); }); test('passes confirmButtonText correctly', () async { await plugin.getSavePath(confirmButtonText: 'Open File'); expectMethodCall( log, 'getSavePath', arguments: { 'initialDirectory': null, 'suggestedName': null, 'confirmButtonText': 'Open File', }, ); }); test('throws for a type group that does not support Linux', () async { const XTypeGroup group = XTypeGroup( label: 'images', webWildCards: ['images/*'], ); await expectLater( plugin.openFile(acceptedTypeGroups: [group]), throwsArgumentError); }); test('passes a wildcard group correctly', () async { const XTypeGroup group = XTypeGroup( label: 'any', ); await plugin.openFile(acceptedTypeGroups: [group]); expectMethodCall( log, 'openFile', arguments: { 'acceptedTypeGroups': >[ { 'label': 'any', 'extensions': ['*'], }, ], 'initialDirectory': null, 'confirmButtonText': null, 'multiple': false, }, ); }); }); group('#getDirectoryPath', () { test('passes initialDirectory correctly', () async { await plugin.getDirectoryPath(initialDirectory: '/example/directory'); expectMethodCall( log, 'getDirectoryPath', arguments: { 'initialDirectory': '/example/directory', 'confirmButtonText': null, }, ); }); test('passes confirmButtonText correctly', () async { await plugin.getDirectoryPath(confirmButtonText: 'Select Folder'); expectMethodCall( log, '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)]); } /// 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; ================================================ FILE: packages/file_selector/file_selector_macos/.gitignore ================================================ .dart_tool .packages .flutter-plugins .flutter-plugins-dependencies pubspec.lock ================================================ FILE: packages/file_selector/file_selector_macos/.metadata ================================================ # 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: 6d1c244b79f3a2747281f718297ce248bd5ad099 channel: master project_type: plugin ================================================ FILE: packages/file_selector/file_selector_macos/AUTHORS ================================================ # 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. ================================================ FILE: packages/file_selector/file_selector_macos/CHANGELOG.md ================================================ ## 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. ## 0.9.0+2 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. ## 0.9.0+1 * Updates README for endorsement. * Updates `flutter_test` to be a `dev_dependencies` entry. ## 0.9.0 * **BREAKING CHANGE**: Methods that take `XTypeGroup`s now throw an `ArgumentError` if any group is not a wildcard (all filter types null or empty), but doesn't include any of the filter types supported by macOS. * Ignores deprecation warnings for upcoming styleFrom button API changes. ## 0.8.2+2 * Updates references to the obsolete master branch. ## 0.8.2+1 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.8.2 * Moves source to flutter/plugins. * Adds native unit tests. * Converts native implementation to Swift. * Switches to an internal method channel implementation. ## 0.0.4+1 * Update README ## 0.0.4 * Treat empty filter lists the same as null. ## 0.0.3 * Fix README ## 0.0.2 * Update SDK constraint to signal compatibility with null safety. ## 0.0.1 * Initial macOS implementation of `file_selector`. ================================================ FILE: packages/file_selector/file_selector_macos/LICENSE ================================================ 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. ================================================ FILE: packages/file_selector/file_selector_macos/README.md ================================================ # file\_selector\_macos The macOS implementation of [`file_selector`][1]. ## Usage This package is [endorsed][2], which means you can simply use `file_selector` normally. This package will be automatically included in your app when you do. ### Entitlements You will need to [add an entitlement][3] for either read-only access: ```xml com.apple.security.files.user-selected.read-only ``` or read/write access: ```xml com.apple.security.files.user-selected.read-write ``` depending on your use case. [1]: https://pub.dev/packages/file_selector [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin [3]: https://flutter.dev/desktop#entitlements-and-the-app-sandbox ================================================ FILE: packages/file_selector/file_selector_macos/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Currently only web supported android/ ios/ # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: packages/file_selector/file_selector_macos/example/.metadata ================================================ # 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: 7736f3bc90270dcb0480db2ccffbf1d13c28db85 channel: dev project_type: app ================================================ FILE: packages/file_selector/file_selector_macos/example/README.md ================================================ # 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. ================================================ FILE: packages/file_selector/file_selector_macos/example/lib/get_directory_page.dart ================================================ // Copyright 2013 The Flutter 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 a directory using `getDirectoryPath`, /// then displays the selected directory in a dialog. class GetDirectoryPage extends StatelessWidget { /// Default Constructor const GetDirectoryPage({Key? key}) : super(key: key); Future _getDirectoryPath(BuildContext context) async { const String confirmButtonText = 'Choose'; final String? directoryPath = await FileSelectorPlatform.instance.getDirectoryPath( confirmButtonText: confirmButtonText, ); if (directoryPath == null) { // Operation was canceled by the user. return; } if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => TextDisplay(directoryPath), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open a text file'), ), 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 a directory'), onPressed: () => _getDirectoryPath(context), ), ], ), ), ); } } /// Widget that displays a text file in a dialog. class TextDisplay extends StatelessWidget { /// Creates a `TextDisplay`. const TextDisplay(this.directoryPath, {Key? key}) : super(key: key); /// The path selected in the dialog. final String directoryPath; @override Widget build(BuildContext context) { return AlertDialog( title: const Text('Selected Directory'), content: Scrollbar( child: SingleChildScrollView( child: Text(directoryPath), ), ), actions: [ TextButton( child: const Text('Close'), onPressed: () => Navigator.pop(context), ), ], ); } } ================================================ FILE: packages/file_selector/file_selector_macos/example/lib/home_page.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; /// Home Page of the application. class HomePage extends StatelessWidget { /// Default Constructor const HomePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final ButtonStyle 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, ); return Scaffold( appBar: AppBar( title: const Text('File Selector Demo Home Page'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( style: style, child: const Text('Open a text file'), onPressed: () => Navigator.pushNamed(context, '/open/text'), ), const SizedBox(height: 10), ElevatedButton( style: style, child: const Text('Open an image'), onPressed: () => Navigator.pushNamed(context, '/open/image'), ), const SizedBox(height: 10), ElevatedButton( style: style, child: const Text('Open multiple images'), onPressed: () => Navigator.pushNamed(context, '/open/images'), ), const SizedBox(height: 10), ElevatedButton( style: style, child: const Text('Save a file'), onPressed: () => Navigator.pushNamed(context, '/save/text'), ), const SizedBox(height: 10), ElevatedButton( style: style, child: const Text('Open a get directory dialog'), onPressed: () => Navigator.pushNamed(context, '/directory'), ), ], ), ), ); } } ================================================ FILE: packages/file_selector/file_selector_macos/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'get_directory_page.dart'; import 'home_page.dart'; import 'open_image_page.dart'; import 'open_multiple_images_page.dart'; import 'open_text_page.dart'; import 'save_text_page.dart'; void main() { runApp(const MyApp()); } /// MyApp is the Main Application. class MyApp extends StatelessWidget { /// Default Constructor const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'File Selector Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: const HomePage(), routes: { '/open/image': (BuildContext context) => const OpenImagePage(), '/open/images': (BuildContext context) => const OpenMultipleImagesPage(), '/open/text': (BuildContext context) => const OpenTextPage(), '/save/text': (BuildContext context) => SaveTextPage(), '/directory': (BuildContext context) => const GetDirectoryPage(), }, ); } } ================================================ FILE: packages/file_selector/file_selector_macos/example/lib/open_image_page.dart ================================================ // Copyright 2013 The Flutter 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:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// Screen that allows the user to select an image file using /// `openFiles`, then displays the selected images in a gallery dialog. class OpenImagePage extends StatelessWidget { /// Default Constructor const OpenImagePage({Key? key}) : super(key: key); Future _openImageFile(BuildContext context) async { const XTypeGroup typeGroup = XTypeGroup( label: 'images', extensions: ['jpg', 'png'], ); final XFile? file = await FileSelectorPlatform.instance .openFile(acceptedTypeGroups: [typeGroup]); if (file == null) { // Operation was canceled by the user. return; } final String fileName = file.name; final String filePath = file.path; if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => ImageDisplay(fileName, filePath), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open an image'), ), 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 open an image file(png, jpg)'), onPressed: () => _openImageFile(context), ), ], ), ), ); } } /// Widget that displays an image in a dialog. class ImageDisplay extends StatelessWidget { /// Default Constructor. const ImageDisplay(this.fileName, this.filePath, {Key? key}) : super(key: key); /// The name of the selected file. final String fileName; /// The path to the selected file. final String filePath; @override Widget build(BuildContext context) { return AlertDialog( title: Text(fileName), // On web the filePath is a blob url // while on other platforms it is a system path. content: kIsWeb ? Image.network(filePath) : Image.file(File(filePath)), actions: [ TextButton( child: const Text('Close'), onPressed: () { Navigator.pop(context); }, ), ], ); } } ================================================ FILE: packages/file_selector/file_selector_macos/example/lib/open_multiple_images_page.dart ================================================ // Copyright 2013 The Flutter 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:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// Screen that allows the user to select multiple image files using /// `openFiles`, then displays the selected images in a gallery dialog. class OpenMultipleImagesPage extends StatelessWidget { /// Default Constructor const OpenMultipleImagesPage({Key? key}) : super(key: key); Future _openImageFile(BuildContext context) async { const XTypeGroup jpgsTypeGroup = XTypeGroup( label: 'JPEGs', extensions: ['jpg', 'jpeg'], ); const XTypeGroup pngTypeGroup = XTypeGroup( label: 'PNGs', extensions: ['png'], ); final List files = await FileSelectorPlatform.instance .openFiles(acceptedTypeGroups: [ jpgsTypeGroup, pngTypeGroup, ]); if (files.isEmpty) { // Operation was canceled by the user. return; } if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => MultipleImagesDisplay(files), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open multiple images'), ), 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 open multiple images (png, jpg)'), onPressed: () => _openImageFile(context), ), ], ), ), ); } } /// Widget that displays a text file in a dialog. class MultipleImagesDisplay extends StatelessWidget { /// Default Constructor. const MultipleImagesDisplay(this.files, {Key? key}) : super(key: key); /// The files containing the images. final List files; @override Widget build(BuildContext context) { return AlertDialog( title: const Text('Gallery'), // On web the filePath is a blob url // while on other platforms it is a system path. content: Center( child: Row( children: [ ...files.map( (XFile file) => Flexible( child: kIsWeb ? Image.network(file.path) : Image.file(File(file.path))), ) ], ), ), actions: [ TextButton( child: const Text('Close'), onPressed: () { Navigator.pop(context); }, ), ], ); } } ================================================ FILE: packages/file_selector/file_selector_macos/example/lib/open_text_page.dart ================================================ // Copyright 2013 The Flutter 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 a text file using `openFile`, then /// displays its contents in a dialog. class OpenTextPage extends StatelessWidget { /// Default Constructor const OpenTextPage({Key? key}) : super(key: key); Future _openTextFile(BuildContext context) async { const XTypeGroup typeGroup = XTypeGroup( label: 'text', extensions: ['txt', 'json'], ); final XFile? file = await FileSelectorPlatform.instance .openFile(acceptedTypeGroups: [typeGroup]); if (file == null) { // Operation was canceled by the user. return; } final String fileName = file.name; final String fileContent = await file.readAsString(); if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => TextDisplay(fileName, fileContent), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open a text file'), ), 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 open a text file (json, txt)'), onPressed: () => _openTextFile(context), ), ], ), ), ); } } /// Widget that displays a text file in a dialog. class TextDisplay extends StatelessWidget { /// Default Constructor. const TextDisplay(this.fileName, this.fileContent, {Key? key}) : super(key: key); /// The name of the selected file. final String fileName; /// The contents of the text file. final String fileContent; @override Widget build(BuildContext context) { return AlertDialog( title: Text(fileName), content: Scrollbar( child: SingleChildScrollView( child: Text(fileContent), ), ), actions: [ TextButton( child: const Text('Close'), onPressed: () => Navigator.pop(context), ), ], ); } } ================================================ FILE: packages/file_selector/file_selector_macos/example/lib/save_text_page.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:typed_data'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/material.dart'; /// Screen that allows the user to select a save location using `getSavePath`, /// then writes text to a file at that location. class SaveTextPage extends StatelessWidget { /// Default Constructor SaveTextPage({Key? key}) : super(key: key); final TextEditingController _nameController = TextEditingController(); final TextEditingController _contentController = TextEditingController(); Future _saveFile() async { final String fileName = _nameController.text; final String? path = await FileSelectorPlatform.instance.getSavePath( suggestedName: fileName, ); if (path == null) { // Operation was canceled by the user. return; } final String text = _contentController.text; final Uint8List fileData = Uint8List.fromList(text.codeUnits); const String fileMimeType = 'text/plain'; final XFile textFile = XFile.fromData(fileData, mimeType: fileMimeType, name: fileName); await textFile.saveTo(path); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Save text into a file'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 300, child: TextField( minLines: 1, maxLines: 12, controller: _nameController, decoration: const InputDecoration( hintText: '(Optional) Suggest File Name', ), ), ), SizedBox( width: 300, child: TextField( minLines: 1, maxLines: 12, controller: _contentController, decoration: const InputDecoration( hintText: 'Enter File Contents', ), ), ), const SizedBox(height: 10), 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, ), onPressed: _saveFile, child: const Text('Press to save a text file'), ), ], ), ), ); } } ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/.gitignore ================================================ # Flutter-related **/Flutter/ephemeral/ **/Pods/ # Xcode-related **/xcuserdata/ ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Flutter/Flutter-Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Flutter/Flutter-Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Podfile ================================================ platform :osx, '10.11' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_macos_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) end end ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Runner/AppDelegate.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } } ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_64.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_1024.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Runner/Base.lproj/MainMenu.xib ================================================ ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Runner/Configs/AppInfo.xcconfig ================================================ // Application-level settings for the Runner target. // // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the // future. If not, the values below would default to using the project name when this becomes a // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. PRODUCT_NAME = example // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.fileSelectorExample // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2020 The Flutter Authors. All rights reserved. ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Runner/Configs/Warnings.xcconfig ================================================ WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings GCC_WARN_UNDECLARED_SELECTOR = YES CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE CLANG_WARN__DUPLICATE_METHOD_MATCH = YES CLANG_WARN_PRAGMA_PACK = YES CLANG_WARN_STRICT_PROTOTYPES = YES CLANG_WARN_COMMA = YES GCC_WARN_STRICT_SELECTOR_MATCH = YES CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES GCC_WARN_SHADOW = YES CLANG_WARN_UNREACHABLE_CODE = YES ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Runner/DebugProfile.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server com.apple.security.files.user-selected.read-write ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Runner/MainFlutterWindow.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { let flutterViewController = FlutterViewController.init() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } } ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.files.user-selected.read-write ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 51; objects = { /* Begin PBXAggregateTarget section */ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { isa = PBXAggregateTarget; buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; buildPhases = ( 33CC111E2044C6BF0003C045 /* ShellScript */, ); dependencies = ( ); name = "Flutter Assemble"; productName = FLX; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 338EA5D426EFE72B0071837A /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 338EA5D326EFE72B0071837A /* RunnerTests.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 4CE8B69FE511476B98B4816C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B61FB9CDECD72211FAB708CA /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 338EA5D626EFE72B0071837A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC10EC2044A3C60003C045; remoteInfo = Runner; }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC111A2044C6BA0003C045; remoteInfo = FLX; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 33CC110E2044A8840003C045 /* Bundle Framework */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Bundle Framework"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 338EA5D126EFE72B0071837A /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 338EA5D326EFE72B0071837A /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 338EA5D526EFE72B0071837A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; B61FB9CDECD72211FAB708CA /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BA03A11192D3E8EEA888D495 /* 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 = ""; }; C03B6D624A05212E07A5D41E /* 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 = ""; }; D921BBD60B6562B7A5F559AC /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 338EA5CE26EFE72B0071837A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 4CE8B69FE511476B98B4816C /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 338EA5D226EFE72B0071837A /* RunnerTests */ = { isa = PBXGroup; children = ( 338EA5D326EFE72B0071837A /* RunnerTests.swift */, 338EA5D526EFE72B0071837A /* Info.plist */, ); path = RunnerTests; sourceTree = ""; }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( 33E5194F232828860026EE4D /* AppInfo.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, ); path = Configs; sourceTree = ""; }; 33CC10E42044A3C60003C045 = { isa = PBXGroup; children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, 338EA5D226EFE72B0071837A /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, CAED34175B65FC224CC4F18C /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* example.app */, 338EA5D126EFE72B0071837A /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 33CC11242044D66E0003C045 /* Resources */ = { isa = PBXGroup; children = ( 33CC10F22044A3C60003C045 /* Assets.xcassets */, 33CC10F42044A3C60003C045 /* MainMenu.xib */, 33CC10F72044A3C60003C045 /* Info.plist */, ); name = Resources; path = ..; sourceTree = ""; }; 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, ); path = Flutter; sourceTree = ""; }; 33FAB671232836740065AC1E /* Runner */ = { isa = PBXGroup; children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, 33CC11242044D66E0003C045 /* Resources */, 33BA886A226E78AF003329D5 /* Configs */, ); path = Runner; sourceTree = ""; }; CAED34175B65FC224CC4F18C /* Pods */ = { isa = PBXGroup; children = ( D921BBD60B6562B7A5F559AC /* Pods-Runner.debug.xcconfig */, C03B6D624A05212E07A5D41E /* Pods-Runner.release.xcconfig */, BA03A11192D3E8EEA888D495 /* Pods-Runner.profile.xcconfig */, ); path = Pods; sourceTree = ""; }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( B61FB9CDECD72211FAB708CA /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 338EA5D026EFE72B0071837A /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = 338EA5DB26EFE72B0071837A /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( 338EA5CD26EFE72B0071837A /* Sources */, 338EA5CE26EFE72B0071837A /* Frameworks */, 338EA5CF26EFE72B0071837A /* Resources */, ); buildRules = ( ); dependencies = ( 338EA5D726EFE72B0071837A /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = 338EA5D126EFE72B0071837A /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 8F1744F37738365955F17998 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, A8D2084B0509A3B3053F3AF7 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1250; LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 338EA5D026EFE72B0071837A = { CreatedOnToolsVersion = 12.5; TestTargetID = 33CC10EC2044A3C60003C045; }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; }; }; }; 33CC111A2044C6BA0003C045 = { CreatedOnToolsVersion = 9.2; ProvisioningStyle = Manual; }; }; }; buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 33CC10E42044A3C60003C045; productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, 338EA5D026EFE72B0071837A /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 338EA5CF26EFE72B0071837A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( Flutter/ephemeral/FlutterInputs.xcfilelist, ); inputPaths = ( Flutter/ephemeral/tripwire, ); outputFileListPaths = ( Flutter/ephemeral/FlutterOutputs.xcfilelist, ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; 8F1744F37738365955F17998 /* [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", ); 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; }; A8D2084B0509A3B3053F3AF7 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); 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 = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 338EA5CD26EFE72B0071837A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 338EA5D426EFE72B0071837A /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 338EA5D726EFE72B0071837A /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC10EC2044A3C60003C045 /* Runner */; targetProxy = 338EA5D626EFE72B0071837A /* PBXContainerItemProxy */; }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 33CC10F52044A3C60003C045 /* Base */, ); name = MainMenu.xib; path = Runner; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Profile; }; 338D0CEA231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Profile; }; 338D0CEB231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Profile; }; 338EA5D826EFE72B0071837A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COMBINE_HIDPI_IMAGES = YES; 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)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/Contents/MacOS/example"; }; name = Debug; }; 338EA5D926EFE72B0071837A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COMBINE_HIDPI_IMAGES = YES; 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)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/Contents/MacOS/example"; }; name = Release; }; 338EA5DA26EFE72B0071837A /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COMBINE_HIDPI_IMAGES = YES; 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)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/Contents/MacOS/example"; }; name = Profile; }; 33CC10F92044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 33CC10FA2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; 33CC10FC2044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; 33CC10FD2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Release; }; 33CC111C2044C6BA0003C045 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 33CC111D2044C6BA0003C045 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 338EA5DB26EFE72B0071837A /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 338EA5D826EFE72B0071837A /* Debug */, 338EA5D926EFE72B0071837A /* Release */, 338EA5DA26EFE72B0071837A /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10F92044A3C60003C045 /* Debug */, 33CC10FA2044A3C60003C045 /* Release */, 338D0CE9231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10FC2044A3C60003C045 /* Debug */, 33CC10FD2044A3C60003C045 /* Release */, 338D0CEA231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC111C2044C6BA0003C045 /* Debug */, 33CC111D2044C6BA0003C045 /* Release */, 338D0CEB231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/RunnerTests/Info.plist ================================================ 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 ================================================ FILE: packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @testable import file_selector_macos import FlutterMacOS import XCTest class TestPanelController: NSObject, PanelController { // The last panels that the relevant display methods were called on. public var savePanel: NSSavePanel? public var openPanel: NSOpenPanel? // Mock return values for the display methods. public var saveURL: URL? public var openURLs: [URL]? func display(_ panel: NSSavePanel, for window: NSWindow?, completionHandler handler: @escaping (URL?) -> Void) { savePanel = panel handler(saveURL) } func display(_ panel: NSOpenPanel, for window: NSWindow?, completionHandler handler: @escaping ([URL]?) -> Void) { openPanel = panel handler(openURLs) } } class TestViewProvider: NSObject, ViewProvider { var view: NSView? { get { window?.contentView } } var window: NSWindow? = NSWindow() } class exampleTests: XCTestCase { func testOpenSimple() throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), panelController: panelController) let returnPath = "/foo/bar" panelController.openURLs = [URL(fileURLWithPath: returnPath)] let called = XCTestExpectation() let options = OpenPanelOptions( allowsMultipleSelection: false, canChooseDirectories: false, canChooseFiles: true, baseOptions: SavePanelOptions()) plugin.displayOpenPanel(options: options) { paths in XCTAssertEqual(paths[0], returnPath) called.fulfill() } wait(for: [called], timeout: 0.5) XCTAssertNotNil(panelController.openPanel) if let panel = panelController.openPanel { XCTAssertTrue(panel.canChooseFiles) // For consistency across platforms, directory selection is disabled. XCTAssertFalse(panel.canChooseDirectories) } } func testOpenWithArguments() throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), panelController: panelController) let returnPath = "/foo/bar" panelController.openURLs = [URL(fileURLWithPath: returnPath)] let called = XCTestExpectation() 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() } wait(for: [called], timeout: 0.5) XCTAssertNotNil(panelController.openPanel) if let panel = panelController.openPanel { XCTAssertEqual(panel.directoryURL?.path, "/some/dir") XCTAssertEqual(panel.nameFieldStringValue, "a name") XCTAssertEqual(panel.prompt, "Open it!") } } func testOpenMultiple() throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), panelController: panelController) let returnPaths = ["/foo/bar", "/foo/baz"] panelController.openURLs = returnPaths.map({ path in URL(fileURLWithPath: path) }) let called = XCTestExpectation() 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]) called.fulfill() } wait(for: [called], timeout: 0.5) XCTAssertNotNil(panelController.openPanel) } func testOpenWithFilter() throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), panelController: panelController) let returnPath = "/foo/bar" panelController.openURLs = [URL(fileURLWithPath: returnPath)] let called = XCTestExpectation() 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() } wait(for: [called], timeout: 0.5) XCTAssertNotNil(panelController.openPanel) if let panel = panelController.openPanel { XCTAssertEqual(panel.allowedFileTypes, ["txt", "json", "public.text", "public.image"]) } } func testOpenCancel() throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), panelController: panelController) let called = XCTestExpectation() let options = OpenPanelOptions( allowsMultipleSelection: false, canChooseDirectories: false, canChooseFiles: true, baseOptions: SavePanelOptions()) plugin.displayOpenPanel(options: options) { paths in XCTAssertEqual(paths.count, 0) called.fulfill() } wait(for: [called], timeout: 0.5) XCTAssertNotNil(panelController.openPanel) } func testSaveSimple() throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), panelController: panelController) let returnPath = "/foo/bar" panelController.saveURL = URL(fileURLWithPath: returnPath) let called = XCTestExpectation() let options = SavePanelOptions() plugin.displaySavePanel(options: options) { path in XCTAssertEqual(path, returnPath) called.fulfill() } wait(for: [called], timeout: 0.5) XCTAssertNotNil(panelController.savePanel) } func testSaveWithArguments() throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), panelController: panelController) let returnPath = "/foo/bar" panelController.saveURL = URL(fileURLWithPath: returnPath) let called = XCTestExpectation() let options = SavePanelOptions( directoryPath: "/some/dir", prompt: "Save it!") plugin.displaySavePanel(options: options) { path in XCTAssertEqual(path, returnPath) called.fulfill() } wait(for: [called], timeout: 0.5) XCTAssertNotNil(panelController.savePanel) if let panel = panelController.savePanel { XCTAssertEqual(panel.directoryURL?.path, "/some/dir") XCTAssertEqual(panel.prompt, "Save it!") } } func testSaveCancel() throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), panelController: panelController) let called = XCTestExpectation() let options = SavePanelOptions() plugin.displaySavePanel(options: options) { path in XCTAssertNil(path) called.fulfill() } wait(for: [called], timeout: 0.5) XCTAssertNotNil(panelController.savePanel) } func testGetDirectorySimple() throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), panelController: panelController) let returnPath = "/foo/bar" panelController.openURLs = [URL(fileURLWithPath: returnPath)] let called = XCTestExpectation() let options = OpenPanelOptions( allowsMultipleSelection: false, canChooseDirectories: true, canChooseFiles: false, baseOptions: SavePanelOptions()) plugin.displayOpenPanel(options: options) { paths in XCTAssertEqual(paths[0], returnPath) called.fulfill() } wait(for: [called], timeout: 0.5) XCTAssertNotNil(panelController.openPanel) if let panel = panelController.openPanel { XCTAssertTrue(panel.canChooseDirectories) // For consistency across platforms, file selection is disabled. XCTAssertFalse(panel.canChooseFiles) // The Dart API only allows a single directory to be returned, so users shouldn't be allowed // to select multiple. XCTAssertFalse(panel.allowsMultipleSelection) } } func testGetDirectoryCancel() throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( viewProvider: TestViewProvider(), panelController: panelController) let called = XCTestExpectation() let options = OpenPanelOptions( allowsMultipleSelection: false, canChooseDirectories: true, canChooseFiles: false, baseOptions: SavePanelOptions()) plugin.displayOpenPanel(options: options) { paths in XCTAssertEqual(paths.count, 0) called.fulfill() } wait(for: [called], timeout: 0.5) XCTAssertNotNil(panelController.openPanel) } } ================================================ FILE: packages/file_selector/file_selector_macos/example/pubspec.yaml ================================================ name: example description: Example for file_selector_macos implementation. publish_to: 'none' version: 1.0.0 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: file_selector_macos: # When depending on this package from a real application you should use: # file_selector_macos: ^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: .. file_selector_platform_interface: ^2.2.0 flutter: sdk: flutter dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/file_selector/file_selector_macos/lib/file_selector_macos.dart ================================================ // Copyright 2013 The Flutter 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 'src/messages.g.dart'; /// An implementation of [FileSelectorPlatform] for macOS. class FileSelectorMacOS extends FileSelectorPlatform { final FileSelectorApi _hostApi = FileSelectorApi(); /// Registers the macOS implementation. static void registerWith() { FileSelectorPlatform.instance = FileSelectorMacOS(); } @override Future openFile({ List? acceptedTypeGroups, String? initialDirectory, String? confirmButtonText, }) async { 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 Future> openFiles({ List? acceptedTypeGroups, String? initialDirectory, String? confirmButtonText, }) async { 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 Future getSavePath({ List? acceptedTypeGroups, String? initialDirectory, String? suggestedName, String? confirmButtonText, }) async { return _hostApi.displaySavePanel(SavePanelOptions( allowedFileTypes: _allowedTypesFromTypeGroups(acceptedTypeGroups), directoryPath: initialDirectory, nameFieldStringValue: suggestedName, prompt: confirmButtonText, )); } @override Future getDirectoryPath({ String? initialDirectory, String? confirmButtonText, }) async { 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. AllowedTypes? _allowedTypesFromTypeGroups(List? typeGroups) { if (typeGroups == null || typeGroups.isEmpty) { return null; } 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) { return null; } // Reject a filter that isn't an allow-any, but doesn't set any // macOS-supported filter categories. if ((typeGroup.extensions?.isEmpty ?? true) && (typeGroup.macUTIs?.isEmpty ?? true) && (typeGroup.mimeTypes?.isEmpty ?? true)) { throw ArgumentError('Provided type group $typeGroup does not allow ' 'all files, but does not set any of the macOS-supported filter ' 'categories. At least one of "extensions", "macUTIs", or ' '"mimeTypes" must be non-empty for macOS if anything is ' 'non-empty.'); } allowedTypes.extensions.addAll(typeGroup.extensions ?? []); allowedTypes.mimeTypes.addAll(typeGroup.mimeTypes ?? []); allowedTypes.utis.addAll(typeGroup.macUTIs ?? []); } return allowedTypes; } } ================================================ FILE: packages/file_selector/file_selector_macos/lib/src/messages.g.dart ================================================ // Copyright 2013 The Flutter 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?); } } } ================================================ FILE: packages/file_selector/file_selector_macos/macos/Classes/FileSelectorPlugin.swift ================================================ // Copyright 2013 The Flutter 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 /// Protocol for showing panels, allowing for depenedency injection in tests. protocol PanelController { /// Displays the given save panel, and provides the selected URL, or nil if the panel is /// cancelled, to the handler. /// - Parameters: /// - panel: The panel to show. /// - window: The window to display the panel for. /// - completionHandler: The completion handler to receive the results. func display( _ panel: NSSavePanel, for window: NSWindow?, completionHandler: @escaping (URL?) -> Void); /// Displays the given open panel, and provides the selected URLs, or nil if the panel is /// cancelled, to the handler. /// - Parameters: /// - panel: The panel to show. /// - window: The window to display the panel for. /// - completionHandler: The completion handler to receive the results. func display( _ panel: NSOpenPanel, for window: NSWindow?, completionHandler: @escaping ([URL]?) -> Void); } /// Protocol to provide access to the Flutter view, allowing for dependency injection in tests. /// /// This is necessary because Swift doesn't allow for only partially implementing a protocol, so /// a stub implementation of FlutterPluginRegistrar for tests would break any time something was /// added to that protocol. protocol ViewProvider { /// Returns the view associated with the Flutter content. var view: NSView? { get } } public class FileSelectorPlugin: NSObject, FlutterPlugin, FileSelectorApi { private let viewProvider: ViewProvider private let panelController: PanelController private let openMethod = "openFile" private let openDirectoryMethod = "getDirectoryPath" private let saveMethod = "getSavePath" public static func register(with registrar: FlutterPluginRegistrar) { let instance = FileSelectorPlugin( viewProvider: DefaultViewProvider(registrar: registrar), panelController: DefaultPanelController()) FileSelectorApiSetup.setUp(binaryMessenger: registrar.messenger, api: instance) } init(viewProvider: ViewProvider, panelController: PanelController) { self.viewProvider = viewProvider self.panelController = panelController } 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) } } /// Configures an NSSavePanel based on channel method call arguments. /// - Parameters: /// - panel: The panel to configure. /// - arguments: The arguments dictionary from a FlutterMethodCall to this plugin. private func configure(panel: NSSavePanel, with options: SavePanelOptions) { if let directoryPath = options.directoryPath { panel.directoryURL = URL(fileURLWithPath: directoryPath) } if let suggestedName = options.nameFieldStringValue { panel.nameFieldStringValue = suggestedName } if let prompt = options.prompt { panel.prompt = prompt } if let acceptedTypes = options.allowedFileTypes { var allowedTypes: [String] = [] // 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 } } } /// Configures an NSOpenPanel based on channel method call arguments. /// - Parameters: /// - panel: The panel to configure. /// - arguments: The arguments dictionary from a FlutterMethodCall to this plugin. /// - choosingDirectory: True if the panel should allow choosing directories rather than files. private func configure( openPanel panel: NSOpenPanel, with options: OpenPanelOptions ) { configure(panel: panel, with: options.baseOptions) panel.allowsMultipleSelection = options.allowsMultipleSelection panel.canChooseDirectories = options.canChooseDirectories; panel.canChooseFiles = options.canChooseFiles; } } /// Non-test implementation of PanelController that calls the standard methods to display the panel /// either as a sheet (if a window is provided) or modal (if not). private class DefaultPanelController: PanelController { func display( _ panel: NSSavePanel, for window: NSWindow?, completionHandler: @escaping (URL?) -> Void ) { let completionAdapter = { response in completionHandler((response == NSApplication.ModalResponse.OK) ? panel.url : nil) } if let window = window { panel.beginSheetModal(for: window, completionHandler: completionAdapter) } else { completionAdapter(panel.runModal()) } } func display( _ panel: NSOpenPanel, for window: NSWindow?, completionHandler: @escaping ([URL]?) -> Void ) { let completionAdapter = { response in completionHandler((response == NSApplication.ModalResponse.OK) ? panel.urls : nil) } if let window = window { panel.beginSheetModal(for: window, completionHandler: completionAdapter) } else { completionAdapter(panel.runModal()) } } } /// Non-test implementation of PanelController that forwards to the plugin registrar. private class DefaultViewProvider: ViewProvider { private let registrar: FlutterPluginRegistrar init(registrar: FlutterPluginRegistrar) { self.registrar = registrar } var view: NSView? { get { registrar.view } } } ================================================ FILE: packages/file_selector/file_selector_macos/macos/Classes/messages.g.swift ================================================ // Copyright 2013 The Flutter 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 ] } ================================================ FILE: packages/file_selector/file_selector_macos/macos/file_selector_macos.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| s.name = 'file_selector_macos' s.version = '0.0.1' s.summary = 'macOS implementation of file_selector.' s.description = <<-DESC Displays native macOS open and save panels. DESC s.license = { :type => 'BSD', :file => '../LICENSE' } s.homepage = 'https://github.com/flutter/plugins/tree/main/packages/file_selector' s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } s.source = { :http => 'https://github.com/flutter/plugins/tree/main/packages/file_selector/file_selector_macos' } s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' s.platform = :osx, '10.11' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' end ================================================ FILE: packages/file_selector/file_selector_macos/pigeons/copyright.txt ================================================ Copyright 2013 The Flutter Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. ================================================ FILE: packages/file_selector/file_selector_macos/pigeons/messages.dart ================================================ // Copyright 2013 The Flutter 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); } ================================================ FILE: packages/file_selector/file_selector_macos/pubspec.yaml ================================================ 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+4 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: file_selector platforms: macos: dartPluginClass: FileSelectorMacOS pluginClass: FileSelectorPlugin dependencies: cross_file: ^0.3.1 file_selector_platform_interface: ^2.2.0 flutter: sdk: flutter dev_dependencies: build_runner: ^2.3.2 flutter_test: sdk: flutter mockito: ^5.3.2 pigeon: ^4.2.14 ================================================ FILE: packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart ================================================ // Copyright 2013 The Flutter 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_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_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(); late FileSelectorMacOS plugin; late MockTestFileSelectorApi mockApi; setUp(() { 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', () { FileSelectorMacOS.registerWith(); expect(FileSelectorPlatform.instance, isA()); }); 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', extensions: ['txt'], mimeTypes: ['text/plain'], macUTIs: ['public.text'], ); const XTypeGroup groupTwo = XTypeGroup( label: 'image', extensions: ['jpg'], mimeTypes: ['image/jpg'], macUTIs: ['public.image'], webWildCards: ['image/*']); await plugin.openFile(acceptedTypeGroups: [group, groupTwo]); 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'); 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'); 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 { const XTypeGroup group = XTypeGroup( label: 'images', webWildCards: ['images/*'], ); await expectLater( plugin.openFile(acceptedTypeGroups: [group]), throwsArgumentError); }); test('allows a wildcard group', () async { const XTypeGroup group = XTypeGroup( label: 'text', ); await expectLater( plugin.openFile(acceptedTypeGroups: [group]), completes); }); }); 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', extensions: ['txt'], mimeTypes: ['text/plain'], macUTIs: ['public.text'], ); const XTypeGroup groupTwo = XTypeGroup( label: 'image', extensions: ['jpg'], mimeTypes: ['image/jpg'], macUTIs: ['public.image'], webWildCards: ['image/*']); await plugin.openFiles(acceptedTypeGroups: [group, groupTwo]); 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'); 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'); 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 { const XTypeGroup group = XTypeGroup( label: 'images', webWildCards: ['images/*'], ); await expectLater( plugin.openFiles(acceptedTypeGroups: [group]), throwsArgumentError); }); test('allows a wildcard group', () async { const XTypeGroup group = XTypeGroup( label: 'text', ); await expectLater( plugin.openFiles(acceptedTypeGroups: [group]), completes); }); }); 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', extensions: ['txt'], mimeTypes: ['text/plain'], macUTIs: ['public.text'], ); const XTypeGroup groupTwo = XTypeGroup( label: 'image', extensions: ['jpg'], mimeTypes: ['image/jpg'], macUTIs: ['public.image'], webWildCards: ['image/*']); await plugin .getSavePath(acceptedTypeGroups: [group, groupTwo]); 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'); 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'); 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 { const XTypeGroup group = XTypeGroup( label: 'images', webWildCards: ['images/*'], ); await expectLater( plugin.getSavePath(acceptedTypeGroups: [group]), throwsArgumentError); }); test('allows a wildcard group', () async { const XTypeGroup group = XTypeGroup( label: 'text', ); await expectLater( plugin.getSavePath(acceptedTypeGroups: [group]), completes); }); }); 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'); 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'); final VerificationResult result = verify(mockApi.displayOpenPanel(captureAny)); final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; expect(options.baseOptions.prompt, 'Open File'); }); }); test('ignores all type groups if any of them is a wildcard', () async { await plugin.getSavePath(acceptedTypeGroups: [ const XTypeGroup( label: 'text', extensions: ['txt'], mimeTypes: ['text/plain'], macUTIs: ['public.text'], ), const XTypeGroup( label: 'image', extensions: ['jpg'], mimeTypes: ['image/jpg'], macUTIs: ['public.image'], ), const XTypeGroup( label: 'any', ), ]); final VerificationResult result = verify(mockApi.displaySavePanel(captureAny)); final SavePanelOptions options = result.captured[0] as SavePanelOptions; expect(options.allowedFileTypes, null); }); } ================================================ FILE: packages/file_selector/file_selector_macos/test/file_selector_macos_test.mocks.dart ================================================ // 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); } ================================================ FILE: packages/file_selector/file_selector_macos/test/messages_test.g.dart ================================================ // Copyright 2013 The Flutter 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]; }); } } } } ================================================ FILE: packages/file_selector/file_selector_platform_interface/AUTHORS ================================================ # 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. 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> ================================================ FILE: packages/file_selector/file_selector_platform_interface/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 2.4.0 * Adds `getDirectoryPaths` method to the interface. ## 2.3.0 * Replaces `macUTIs` with `uniformTypeIdentifiers`. `macUTIs` is available as an alias, but will be deprecated in a future release. ## 2.2.0 * Makes `XTypeGroup`'s constructor constant. ## 2.1.1 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. ## 2.1.0 * Adds `allowsAny` to `XTypeGroup` as a simple and future-proof way of identifying wildcard groups. ## 2.0.4 * Removes dependency on `meta`. ## 2.0.3 * Minor code cleanup for new analysis rules. * Update to use the `verify` method introduced in plugin_platform_interface 2.1.0. ## 2.0.2 * Update platform_plugin_interface version requirement. ## 2.0.1 * Replace extensions with leading dots. ## 2.0.0 * Migration to null-safety ## 1.0.3+1 * Bump the [cross_file](https://pub.dev/packages/cross_file) package version. ## 1.0.3 * Update Flutter SDK constraint. ## 1.0.2 * Replace locally defined `XFile` types with the versions from the [cross_file](https://pub.dev/packages/cross_file) package. ## 1.0.1 * Allow type groups that allow any file. ## 1.0.0 * Initial release. ================================================ FILE: packages/file_selector/file_selector_platform_interface/LICENSE ================================================ 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. ================================================ FILE: packages/file_selector/file_selector_platform_interface/README.md ================================================ # file_selector_platform_interface A common platform interface for the `file_selector` plugin. This interface allows platform-specific implementations of the `file_selector` plugin, as well as the plugin itself, to ensure they are supporting the same interface. # Usage To implement a new platform-specific implementation of `file_selector`, extend [`FileSelectorPlatform`][2] with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `FileSelectorPlatform` by calling `FileSelectorPlatform.instance = MyPlatformFileSelector()`. # Note on breaking changes Strongly prefer non-breaking changes (such as adding a method to the interface) over breaking changes for this package. See https://flutter.dev/go/platform-interface-breaking-changes for a discussion on why a less-clean interface is preferable to a breaking change. [1]: ../file_selector [2]: lib/file_selector_platform_interface.dart ================================================ FILE: packages/file_selector/file_selector_platform_interface/lib/file_selector_platform_interface.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'src/platform_interface/file_selector_interface.dart'; export 'src/types/types.dart'; ================================================ FILE: packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart' show visibleForTesting; import 'package:flutter/services.dart'; import '../../file_selector_platform_interface.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/file_selector'); /// An implementation of [FileSelectorPlatform] that uses method channels. class MethodChannelFileSelector extends FileSelectorPlatform { /// The MethodChannel that is being used by this implementation of the plugin. @visibleForTesting MethodChannel get channel => _channel; @override Future openFile({ List? acceptedTypeGroups, String? initialDirectory, String? confirmButtonText, }) async { final List? path = await _channel.invokeListMethod( 'openFile', { 'acceptedTypeGroups': acceptedTypeGroups ?.map((XTypeGroup group) => group.toJSON()) .toList(), 'initialDirectory': initialDirectory, 'confirmButtonText': confirmButtonText, 'multiple': false, }, ); return path == null ? null : XFile(path.first); } @override Future> openFiles({ List? acceptedTypeGroups, String? initialDirectory, String? confirmButtonText, }) async { final List? pathList = await _channel.invokeListMethod( 'openFile', { 'acceptedTypeGroups': acceptedTypeGroups ?.map((XTypeGroup group) => group.toJSON()) .toList(), 'initialDirectory': initialDirectory, 'confirmButtonText': confirmButtonText, 'multiple': true, }, ); return pathList?.map((String path) => XFile(path)).toList() ?? []; } @override Future getSavePath({ List? acceptedTypeGroups, String? initialDirectory, String? suggestedName, String? confirmButtonText, }) async { return _channel.invokeMethod( 'getSavePath', { 'acceptedTypeGroups': acceptedTypeGroups ?.map((XTypeGroup group) => group.toJSON()) .toList(), 'initialDirectory': initialDirectory, 'suggestedName': suggestedName, 'confirmButtonText': confirmButtonText, }, ); } @override Future getDirectoryPath({ String? initialDirectory, String? confirmButtonText, }) async { return _channel.invokeMethod( 'getDirectoryPath', { 'initialDirectory': initialDirectory, 'confirmButtonText': confirmButtonText, }, ); } @override Future> getDirectoryPaths( {String? initialDirectory, String? confirmButtonText}) async { final List? pathList = await _channel.invokeListMethod( 'getDirectoryPaths', { 'initialDirectory': initialDirectory, 'confirmButtonText': confirmButtonText, }, ); return pathList ?? []; } } ================================================ FILE: packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart ================================================ // Copyright 2013 The Flutter 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:plugin_platform_interface/plugin_platform_interface.dart'; import '../../file_selector_platform_interface.dart'; import '../method_channel/method_channel_file_selector.dart'; /// The interface that implementations of file_selector must implement. /// /// Platform implementations should extend this class rather than implement it as `file_selector` /// does not consider newly added methods to be breaking changes. Extending this class /// (using `extends`) ensures that the subclass will get the default implementation, while /// platform implementations that `implements` this interface will be broken by newly added /// [FileSelectorPlatform] methods. abstract class FileSelectorPlatform extends PlatformInterface { /// Constructs a FileSelectorPlatform. FileSelectorPlatform() : super(token: _token); static final Object _token = Object(); static FileSelectorPlatform _instance = MethodChannelFileSelector(); /// The default instance of [FileSelectorPlatform] to use. /// /// Defaults to [MethodChannelFileSelector]. static FileSelectorPlatform get instance => _instance; /// Platform-specific plugins should set this with their own platform-specific /// class that extends [FileSelectorPlatform] when they register themselves. static set instance(FileSelectorPlatform instance) { PlatformInterface.verify(instance, _token); _instance = instance; } /// Opens a file dialog for loading files and returns a file path. /// Returns `null` if user cancels the operation. Future openFile({ List? acceptedTypeGroups, String? initialDirectory, String? confirmButtonText, }) { throw UnimplementedError('openFile() has not been implemented.'); } /// Opens a file dialog for loading files and returns a list of file paths. Future> openFiles({ List? acceptedTypeGroups, String? initialDirectory, String? confirmButtonText, }) { throw UnimplementedError('openFiles() has not been implemented.'); } /// Opens a file dialog for saving files and returns a file path at which to save. /// Returns `null` if user cancels the operation. Future getSavePath({ List? acceptedTypeGroups, String? initialDirectory, String? suggestedName, String? confirmButtonText, }) { throw UnimplementedError('getSavePath() has not been implemented.'); } /// Opens a file dialog for loading directories and returns a directory path. /// Returns `null` if user cancels the operation. Future getDirectoryPath({ String? initialDirectory, String? confirmButtonText, }) { throw UnimplementedError('getDirectoryPath() has not been implemented.'); } /// Opens a file dialog for loading directories and returns multiple directory paths. Future> getDirectoryPaths({ String? initialDirectory, String? confirmButtonText, }) { throw UnimplementedError('getDirectoryPaths() has not been implemented.'); } } ================================================ FILE: packages/file_selector/file_selector_platform_interface/lib/src/types/types.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'package:cross_file/cross_file.dart'; export 'x_type_group/x_type_group.dart'; ================================================ FILE: packages/file_selector/file_selector_platform_interface/lib/src/types/x_type_group/x_type_group.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart' show immutable; /// A set of allowed XTypes. @immutable class XTypeGroup { /// Creates a new group with the given label and file extensions. /// /// A group with none of the type options provided indicates that any type is /// allowed. const XTypeGroup({ this.label, List? extensions, this.mimeTypes, List? macUTIs, List? uniformTypeIdentifiers, this.webWildCards, }) : _extensions = extensions, assert(uniformTypeIdentifiers == null || macUTIs == null, 'Only one of uniformTypeIdentifiers or macUTIs can be non-null'), uniformTypeIdentifiers = uniformTypeIdentifiers ?? macUTIs; /// The 'name' or reference to this group of types. final String? label; /// The MIME types for this group. final List? mimeTypes; /// The uniform type identifiers for this group final List? uniformTypeIdentifiers; /// The web wild cards for this group (ex: image/*, video/*). final List? webWildCards; final List? _extensions; /// The extensions for this group. List? get extensions { return _removeLeadingDots(_extensions); } /// Converts this object into a JSON formatted object. Map toJSON() { return { 'label': label, 'extensions': extensions, 'mimeTypes': mimeTypes, 'macUTIs': macUTIs, 'webWildCards': webWildCards, }; } /// True if this type group should allow any file. bool get allowsAny { return (extensions?.isEmpty ?? true) && (mimeTypes?.isEmpty ?? true) && (macUTIs?.isEmpty ?? true) && (webWildCards?.isEmpty ?? true); } /// Returns the list of uniform type identifiers for this group List? get macUTIs => uniformTypeIdentifiers; static List? _removeLeadingDots(List? exts) => exts ?.map((String ext) => ext.startsWith('.') ? ext.substring(1) : ext) .toList(); } ================================================ FILE: packages/file_selector/file_selector_platform_interface/lib/src/web_helpers/web_helpers.dart ================================================ // Copyright 2013 The Flutter 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:html'; /// Create anchor element with download attribute AnchorElement createAnchorElement(String href, String? suggestedName) { final AnchorElement element = AnchorElement(href: href); if (suggestedName == null) { element.download = 'download'; } else { element.download = suggestedName; } return element; } /// Add an element to a container and click it void addElementToContainerAndClick(Element container, Element element) { // Add the element and click it // All previous elements will be removed before adding the new one container.children.add(element); element.click(); } /// Initializes a DOM container where we can host elements. Element ensureInitialized(String id) { Element? target = querySelector('#$id'); if (target == null) { final Element targetElement = Element.tag('flt-x-file')..id = id; querySelector('body')!.children.add(targetElement); target = targetElement; } return target; } ================================================ FILE: packages/file_selector/file_selector_platform_interface/pubspec.yaml ================================================ name: file_selector_platform_interface description: A common platform interface for the file_selector plugin. repository: https://github.com/flutter/plugins/tree/main/packages/file_selector/file_selector_platform_interface issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes version: 2.4.0 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: cross_file: ^0.3.0 flutter: sdk: flutter http: ^0.13.0 plugin_platform_interface: ^2.1.0 dev_dependencies: flutter_test: sdk: flutter test: ^1.16.3 ================================================ FILE: packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart ================================================ // Copyright 2013 The Flutter 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:file_selector_platform_interface/src/method_channel/method_channel_file_selector.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { // Store the initial instance before any tests change it. final FileSelectorPlatform initialInstance = FileSelectorPlatform.instance; group('$FileSelectorPlatform', () { test('$MethodChannelFileSelector() is the default instance', () { expect(initialInstance, isInstanceOf()); }); test('Can be extended', () { FileSelectorPlatform.instance = ExtendsFileSelectorPlatform(); }); }); group('#GetDirectoryPaths', () { test('Should throw unimplemented exception', () async { final FileSelectorPlatform fileSelector = ExtendsFileSelectorPlatform(); await expectLater(() async { return fileSelector.getDirectoryPaths(); }, throwsA(isA())); }); }); } class ExtendsFileSelectorPlatform extends FileSelectorPlatform {} ================================================ FILE: packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart ================================================ // Copyright 2013 The Flutter 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:file_selector_platform_interface/src/method_channel/method_channel_file_selector.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$MethodChannelFileSelector()', () { final MethodChannelFileSelector plugin = MethodChannelFileSelector(); final List log = []; setUp(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( plugin.channel, (MethodCall methodCall) async { log.add(methodCall); return null; }, ); log.clear(); }); group('#openFile', () { test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', extensions: ['txt'], mimeTypes: ['text/plain'], macUTIs: ['public.text'], ); const XTypeGroup groupTwo = XTypeGroup( label: 'image', extensions: ['jpg'], mimeTypes: ['image/jpg'], macUTIs: ['public.image'], webWildCards: ['image/*']); await plugin .openFile(acceptedTypeGroups: [group, groupTwo]); expectMethodCall( log, 'openFile', arguments: { 'acceptedTypeGroups': >[ group.toJSON(), groupTwo.toJSON() ], 'initialDirectory': null, 'confirmButtonText': null, 'multiple': false, }, ); }); test('passes initialDirectory correctly', () async { await plugin.openFile(initialDirectory: '/example/directory'); expectMethodCall( log, 'openFile', arguments: { 'acceptedTypeGroups': null, 'initialDirectory': '/example/directory', 'confirmButtonText': null, 'multiple': false, }, ); }); test('passes confirmButtonText correctly', () async { await plugin.openFile(confirmButtonText: 'Open File'); expectMethodCall( log, 'openFile', arguments: { 'acceptedTypeGroups': null, 'initialDirectory': null, 'confirmButtonText': 'Open File', 'multiple': false, }, ); }); }); group('#openFiles', () { test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', extensions: ['txt'], mimeTypes: ['text/plain'], macUTIs: ['public.text'], ); const XTypeGroup groupTwo = XTypeGroup( label: 'image', extensions: ['jpg'], mimeTypes: ['image/jpg'], macUTIs: ['public.image'], webWildCards: ['image/*']); await plugin .openFiles(acceptedTypeGroups: [group, groupTwo]); expectMethodCall( log, 'openFile', arguments: { 'acceptedTypeGroups': >[ group.toJSON(), groupTwo.toJSON() ], 'initialDirectory': null, 'confirmButtonText': null, 'multiple': true, }, ); }); test('passes initialDirectory correctly', () async { await plugin.openFiles(initialDirectory: '/example/directory'); expectMethodCall( log, 'openFile', arguments: { 'acceptedTypeGroups': null, 'initialDirectory': '/example/directory', 'confirmButtonText': null, 'multiple': true, }, ); }); test('passes confirmButtonText correctly', () async { await plugin.openFiles(confirmButtonText: 'Open File'); expectMethodCall( log, 'openFile', arguments: { 'acceptedTypeGroups': null, 'initialDirectory': null, 'confirmButtonText': 'Open File', 'multiple': true, }, ); }); }); group('#getSavePath', () { test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', extensions: ['txt'], mimeTypes: ['text/plain'], macUTIs: ['public.text'], ); const XTypeGroup groupTwo = XTypeGroup( label: 'image', extensions: ['jpg'], mimeTypes: ['image/jpg'], macUTIs: ['public.image'], webWildCards: ['image/*']); await plugin .getSavePath(acceptedTypeGroups: [group, groupTwo]); expectMethodCall( log, 'getSavePath', arguments: { 'acceptedTypeGroups': >[ group.toJSON(), groupTwo.toJSON() ], 'initialDirectory': null, 'suggestedName': null, 'confirmButtonText': null, }, ); }); test('passes initialDirectory correctly', () async { await plugin.getSavePath(initialDirectory: '/example/directory'); expectMethodCall( log, 'getSavePath', arguments: { 'acceptedTypeGroups': null, 'initialDirectory': '/example/directory', 'suggestedName': null, 'confirmButtonText': null, }, ); }); test('passes confirmButtonText correctly', () async { await plugin.getSavePath(confirmButtonText: 'Open File'); expectMethodCall( log, 'getSavePath', arguments: { 'acceptedTypeGroups': null, 'initialDirectory': null, 'suggestedName': null, 'confirmButtonText': 'Open File', }, ); }); }); group('#getDirectoryPath', () { test('passes initialDirectory correctly', () async { await plugin.getDirectoryPath(initialDirectory: '/example/directory'); expectMethodCall( log, 'getDirectoryPath', arguments: { 'initialDirectory': '/example/directory', 'confirmButtonText': null, }, ); }); test('passes confirmButtonText correctly', () async { await plugin.getDirectoryPath(confirmButtonText: 'Select Folder'); expectMethodCall( log, 'getDirectoryPath', arguments: { 'initialDirectory': null, 'confirmButtonText': 'Select Folder', }, ); }); }); group('#getDirectoryPaths', () { test('passes initialDirectory correctly', () async { await plugin.getDirectoryPaths(initialDirectory: '/example/directory'); expectMethodCall( log, 'getDirectoryPaths', arguments: { 'initialDirectory': '/example/directory', 'confirmButtonText': null, }, ); }); test('passes confirmButtonText correctly', () async { await plugin.getDirectoryPaths( confirmButtonText: 'Select one or more Folders'); expectMethodCall( log, 'getDirectoryPaths', arguments: { 'initialDirectory': null, 'confirmButtonText': 'Select one or more Folders', }, ); }); }); }); } void expectMethodCall( List log, String methodName, { Map? arguments, }) { expect(log, [isMethodCall(methodName, arguments: arguments)]); } /// 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; ================================================ FILE: packages/file_selector/file_selector_platform_interface/test/x_type_group_test.dart ================================================ // Copyright 2013 The Flutter 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_test/flutter_test.dart'; void main() { group('XTypeGroup', () { test('toJSON() creates correct map', () { const List extensions = ['txt', 'jpg']; const List mimeTypes = ['text/plain']; const List macUTIs = ['public.plain-text']; const List webWildCards = ['image/*']; const String label = 'test group'; const XTypeGroup group = XTypeGroup( label: label, extensions: extensions, mimeTypes: mimeTypes, macUTIs: macUTIs, webWildCards: webWildCards, ); final Map jsonMap = group.toJSON(); expect(jsonMap['label'], label); expect(jsonMap['extensions'], extensions); expect(jsonMap['mimeTypes'], mimeTypes); expect(jsonMap['macUTIs'], macUTIs); expect(jsonMap['webWildCards'], webWildCards); }); test('a wildcard group can be created', () { const XTypeGroup group = XTypeGroup( label: 'Any', ); final Map jsonMap = group.toJSON(); expect(jsonMap['extensions'], null); expect(jsonMap['mimeTypes'], null); expect(jsonMap['macUTIs'], null); expect(jsonMap['webWildCards'], null); expect(group.allowsAny, true); }); test('allowsAny treats empty arrays the same as null', () { const XTypeGroup group = XTypeGroup( label: 'Any', extensions: [], mimeTypes: [], macUTIs: [], webWildCards: [], ); expect(group.allowsAny, true); }); test('allowsAny returns false if anything is set', () { const XTypeGroup extensionOnly = XTypeGroup(label: 'extensions', extensions: ['txt']); const XTypeGroup mimeOnly = XTypeGroup(label: 'mime', mimeTypes: ['text/plain']); const XTypeGroup utiOnly = XTypeGroup(label: 'utis', macUTIs: ['public.text']); const XTypeGroup webOnly = XTypeGroup(label: 'web', webWildCards: ['.txt']); expect(extensionOnly.allowsAny, false); expect(mimeOnly.allowsAny, false); expect(utiOnly.allowsAny, false); expect(webOnly.allowsAny, false); }); test('passing only macUTIs should fill uniformTypeIdentifiers', () { const List macUTIs = ['public.plain-text']; const XTypeGroup group = XTypeGroup( macUTIs: macUTIs, ); expect(group.uniformTypeIdentifiers, macUTIs); }); test( 'passing only uniformTypeIdentifiers should fill uniformTypeIdentifiers', () { const List uniformTypeIdentifiers = ['public.plain-text']; const XTypeGroup group = XTypeGroup( uniformTypeIdentifiers: uniformTypeIdentifiers, ); expect(group.uniformTypeIdentifiers, uniformTypeIdentifiers); }); test('macUTIs getter return macUTIs value passed in constructor', () { const List macUTIs = ['public.plain-text']; const XTypeGroup group = XTypeGroup( macUTIs: macUTIs, ); expect(group.macUTIs, macUTIs); }); test( 'macUTIs getter returns uniformTypeIdentifiers value passed in constructor', () { const List uniformTypeIdentifiers = ['public.plain-text']; const XTypeGroup group = XTypeGroup( uniformTypeIdentifiers: uniformTypeIdentifiers, ); expect(group.macUTIs, uniformTypeIdentifiers); }); test('passing both uniformTypeIdentifiers and macUTIs should throw', () { const List macUTIs = ['public.plain-text']; const List uniformTypeIndentifiers = [ 'public.plain-images' ]; expect( () => XTypeGroup( macUTIs: macUTIs, uniformTypeIdentifiers: uniformTypeIndentifiers), throwsA(predicate((Object? e) => e is AssertionError && e.message == 'Only one of uniformTypeIdentifiers or macUTIs can be non-null'))); }); test( 'having uniformTypeIdentifiers and macUTIs as null should leave uniformTypeIdentifiers as null', () { const XTypeGroup group = XTypeGroup(); expect(group.uniformTypeIdentifiers, null); }); test('leading dots are removed from extensions', () { const List extensions = ['.txt', '.jpg']; const XTypeGroup group = XTypeGroup(extensions: extensions); expect(group.extensions, ['txt', 'jpg']); }); }); } ================================================ FILE: packages/file_selector/file_selector_web/AUTHORS ================================================ # 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. 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> ================================================ FILE: packages/file_selector/file_selector_web/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 0.9.0+2 * Changes XTypeGroup initialization from final to const. ## 0.9.0+1 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 0.9.0 * **BREAKING CHANGE**: Methods that take `XTypeGroup`s now throw an `ArgumentError` if any group is not a wildcard (all filter types null or empty), but doesn't include any of the filter types supported by web. ## 0.8.1+5 * Minor fixes for new analysis options. ## 0.8.1+4 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.8.1+3 * Minor code cleanup for new analysis rules. * Removes dependency on `meta`. ## 0.8.1+2 * Add `implements` to pubspec. # 0.8.1+1 - Updated installation instructions in README. # 0.8.1 - Return a non-null value from `getSavePath` for consistency with API expectations that null indicates canceling. # 0.8.0 - Migrated to null-safety # 0.7.0+1 - Add dummy `ios` dir, so flutter sdk can be lower than 1.20 # 0.7.0 - Initial open-source release. ================================================ FILE: packages/file_selector/file_selector_web/LICENSE ================================================ 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. ================================================ FILE: packages/file_selector/file_selector_web/README.md ================================================ # file\_selector\_web The web implementation of [`file_selector`][1]. ## Usage This package is [endorsed][2], which means you can simply use `file_selector` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/file_selector [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/file_selector/file_selector_web/example/README.md ================================================ # 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. ## Testing This package uses `package:integration_test` to run its tests in a web browser. See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/wiki/Plugin-Tests#web-tests) in the Flutter wiki for instructions to setup and run the tests in this package. Check [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) for more info. ================================================ FILE: packages/file_selector/file_selector_web/example/integration_test/dom_helper_test.dart ================================================ // Copyright 2013 The Flutter 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:html'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:file_selector_web/src/dom_helper.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; void main() { group('dom_helper', () { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); late DomHelper domHelper; late FileUploadInputElement input; FileList? createFileList(List files) { final DataTransfer dataTransfer = DataTransfer(); files.forEach(dataTransfer.items!.add); return dataTransfer.files as FileList?; } void setFilesAndTriggerChange(List files) { input.files = createFileList(files); input.dispatchEvent(Event('change')); } setUp(() { domHelper = DomHelper(); input = FileUploadInputElement(); }); group('getFiles', () { final File mockFile1 = File(['123456'], 'file1.txt'); final File mockFile2 = File([], 'file2.txt'); testWidgets('works', (_) async { final Future> futureFiles = domHelper.getFiles( input: input, ); setFilesAndTriggerChange([mockFile1, mockFile2]); final List files = await futureFiles; expect(files.length, 2); expect(files[0].name, 'file1.txt'); expect(await files[0].length(), 6); expect(await files[0].readAsString(), '123456'); expect(await files[0].lastModified(), isNotNull); expect(files[1].name, 'file2.txt'); expect(await files[1].length(), 0); expect(await files[1].readAsString(), ''); expect(await files[1].lastModified(), isNotNull); }); testWidgets('works multiple times', (_) async { Future> futureFiles; List files; // It should work the first time futureFiles = domHelper.getFiles(input: input); setFilesAndTriggerChange([mockFile1]); files = await futureFiles; expect(files.length, 1); expect(files.first.name, mockFile1.name); // The same input should work more than once futureFiles = domHelper.getFiles(input: input); setFilesAndTriggerChange([mockFile2]); files = await futureFiles; expect(files.length, 1); expect(files.first.name, mockFile2.name); }); testWidgets('sets the attributes and clicks it', (_) async { const String accept = '.jpg,.png'; const bool multiple = true; bool wasClicked = false; //ignore: unawaited_futures input.onClick.first.then((_) => wasClicked = true); final Future> futureFile = domHelper.getFiles( accept: accept, multiple: multiple, input: input, ); expect(input.matchesWithAncestors('body'), true); expect(input.accept, accept); expect(input.multiple, multiple); expect( wasClicked, true, reason: 'The should be clicked otherwise no dialog will be shown', ); setFilesAndTriggerChange([]); await futureFile; // It should be already removed from the DOM after the file is resolved. expect(input.parent, isNull); }); }); }); } ================================================ FILE: packages/file_selector/file_selector_web/example/integration_test/file_selector_web_test.dart ================================================ // Copyright 2013 The Flutter 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:html'; import 'dart:typed_data'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:file_selector_web/file_selector_web.dart'; import 'package:file_selector_web/src/dom_helper.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; void main() { group('FileSelectorWeb', () { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('openFile', () { testWidgets('works', (WidgetTester _) async { final XFile mockFile = createXFile('1001', 'identity.png'); final MockDomHelper mockDomHelper = MockDomHelper( files: [mockFile], expectAccept: '.jpg,.jpeg,image/png,image/*'); final FileSelectorWeb plugin = FileSelectorWeb(domHelper: mockDomHelper); const XTypeGroup typeGroup = XTypeGroup( label: 'images', extensions: ['jpg', 'jpeg'], mimeTypes: ['image/png'], webWildCards: ['image/*'], ); final XFile file = await plugin.openFile(acceptedTypeGroups: [typeGroup]); expect(file.name, mockFile.name); expect(await file.length(), 4); expect(await file.readAsString(), '1001'); expect(await file.lastModified(), isNotNull); }); }); group('openFiles', () { testWidgets('works', (WidgetTester _) async { final XFile mockFile1 = createXFile('123456', 'file1.txt'); final XFile mockFile2 = createXFile('', 'file2.txt'); final MockDomHelper mockDomHelper = MockDomHelper( files: [mockFile1, mockFile2], expectAccept: '.txt', expectMultiple: true); final FileSelectorWeb plugin = FileSelectorWeb(domHelper: mockDomHelper); const XTypeGroup typeGroup = XTypeGroup( label: 'files', extensions: ['.txt'], ); final List files = await plugin.openFiles(acceptedTypeGroups: [typeGroup]); expect(files.length, 2); expect(files[0].name, mockFile1.name); expect(await files[0].length(), 6); expect(await files[0].readAsString(), '123456'); expect(await files[0].lastModified(), isNotNull); expect(files[1].name, mockFile2.name); expect(await files[1].length(), 0); expect(await files[1].readAsString(), ''); expect(await files[1].lastModified(), isNotNull); }); }); group('getSavePath', () { testWidgets('returns non-null', (WidgetTester _) async { final FileSelectorWeb plugin = FileSelectorWeb(); final Future savePath = plugin.getSavePath(); expect(await savePath, isNotNull); }); }); }); } class MockDomHelper implements DomHelper { MockDomHelper({ List files = const [], String expectAccept = '', bool expectMultiple = false, }) : _files = files, _expectedAccept = expectAccept, _expectedMultiple = expectMultiple; final List _files; final String _expectedAccept; final bool _expectedMultiple; @override Future> getFiles({ String accept = '', bool multiple = false, FileUploadInputElement? input, }) { expect(accept, _expectedAccept, reason: 'Expected "accept" value does not match.'); expect(multiple, _expectedMultiple, reason: 'Expected "multiple" value does not match.'); return Future>.value(_files); } } XFile createXFile(String content, String name) { final Uint8List data = Uint8List.fromList(content.codeUnits); return XFile.fromData(data, name: name, lastModified: DateTime.now()); } ================================================ FILE: packages/file_selector/file_selector_web/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } /// App for testing class MyApp extends StatefulWidget { /// Default Constructor const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override Widget build(BuildContext context) { return const Directionality( textDirection: TextDirection.ltr, child: Text('Testing... Look at the console output for results!'), ); } } ================================================ FILE: packages/file_selector/file_selector_web/example/pubspec.yaml ================================================ name: file_selector_web_integration_tests publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: file_selector_platform_interface: ^2.2.0 file_selector_web: path: ../ flutter: sdk: flutter dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter ================================================ FILE: packages/file_selector/file_selector_web/example/run_test.sh ================================================ #!/usr/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. if pgrep -lf chromedriver > /dev/null; then echo "chromedriver is running." if [ $# -eq 0 ]; then echo "No target specified, running all tests..." find integration_test/ -iname *_test.dart | xargs -n1 -i -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target='{}' else echo "Running test target: $1..." set -x flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target=$1 fi else echo "chromedriver is not running." fi ================================================ FILE: packages/file_selector/file_selector_web/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/file_selector/file_selector_web/example/web/index.html ================================================ Browser Tests ================================================ FILE: packages/file_selector/file_selector_web/lib/file_selector_web.dart ================================================ // Copyright 2013 The Flutter 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:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/foundation.dart' show visibleForTesting; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'src/dom_helper.dart'; import 'src/utils.dart'; /// The web implementation of [FileSelectorPlatform]. /// /// This class implements the `package:file_selector` functionality for the web. class FileSelectorWeb extends FileSelectorPlatform { /// Default constructor, initializes _domHelper that we can use /// to interact with the DOM. /// overrides parameter allows for testing to override functions FileSelectorWeb({@visibleForTesting DomHelper? domHelper}) : _domHelper = domHelper ?? DomHelper(); final DomHelper _domHelper; /// Registers this class as the default instance of [FileSelectorPlatform]. static void registerWith(Registrar registrar) { FileSelectorPlatform.instance = FileSelectorWeb(); } @override Future openFile({ List? acceptedTypeGroups, String? initialDirectory, String? confirmButtonText, }) async { final List files = await _openFiles(acceptedTypeGroups: acceptedTypeGroups); return files.first; } @override Future> openFiles({ List? acceptedTypeGroups, String? initialDirectory, String? confirmButtonText, }) async { return _openFiles(acceptedTypeGroups: acceptedTypeGroups, multiple: true); } // This is intended to be passed to XFile, which ignores the path, but 'null' // indicates a canceled save on other platforms, so provide a non-null dummy // value. @override Future getSavePath({ List? acceptedTypeGroups, String? initialDirectory, String? suggestedName, String? confirmButtonText, }) async => ''; @override Future getDirectoryPath({ String? initialDirectory, String? confirmButtonText, }) async => null; Future> _openFiles({ List? acceptedTypeGroups, bool multiple = false, }) async { final String accept = acceptedTypesToString(acceptedTypeGroups); return _domHelper.getFiles( accept: accept, multiple: multiple, ); } } ================================================ FILE: packages/file_selector/file_selector_web/lib/src/dom_helper.dart ================================================ // Copyright 2013 The Flutter 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 'dart:html'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/foundation.dart' show visibleForTesting; import 'package:flutter/services.dart'; /// Class to manipulate the DOM with the intention of reading files from it. class DomHelper { /// Default constructor, initializes the container DOM element. DomHelper() { final Element body = querySelector('body')!; body.children.add(_container); } final Element _container = Element.tag('file-selector'); /// Sets the attributes and waits for a file to be selected. Future> getFiles({ String accept = '', bool multiple = false, @visibleForTesting FileUploadInputElement? input, }) { final Completer> completer = Completer>(); final FileUploadInputElement inputElement = input ?? FileUploadInputElement(); _container.children.add( inputElement ..accept = accept ..multiple = multiple, ); inputElement.onChange.first.then((_) { final List files = inputElement.files!.map(_convertFileToXFile).toList(); inputElement.remove(); completer.complete(files); }); inputElement.onError.first.then((Event event) { final ErrorEvent error = event as ErrorEvent; final PlatformException platformException = PlatformException( code: error.type, message: error.message, ); inputElement.remove(); completer.completeError(platformException); }); inputElement.click(); return completer.future; } XFile _convertFileToXFile(File file) => XFile( Url.createObjectUrl(file), name: file.name, length: file.size, lastModified: DateTime.fromMillisecondsSinceEpoch( file.lastModified ?? DateTime.now().millisecondsSinceEpoch), ); } ================================================ FILE: packages/file_selector/file_selector_web/lib/src/utils.dart ================================================ // Copyright 2013 The Flutter 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'; /// Convert list of XTypeGroups to a comma-separated string String acceptedTypesToString(List? acceptedTypes) { if (acceptedTypes == null) { return ''; } final List allTypes = []; for (final XTypeGroup group in acceptedTypes) { // If any group allows everything, no filtering should be done. if (group.allowsAny) { return ''; } _validateTypeGroup(group); if (group.extensions != null) { allTypes.addAll(group.extensions!.map(_normalizeExtension)); } if (group.mimeTypes != null) { allTypes.addAll(group.mimeTypes!); } if (group.webWildCards != null) { allTypes.addAll(group.webWildCards!); } } return allTypes.join(','); } /// Make sure that at least one of the supported fields is populated. void _validateTypeGroup(XTypeGroup group) { if ((group.extensions?.isEmpty ?? true) && (group.mimeTypes?.isEmpty ?? true) && (group.webWildCards?.isEmpty ?? true)) { throw ArgumentError('Provided type group $group does not allow ' 'all files, but does not set any of the web-supported filter ' 'categories. At least one of "extensions", "mimeTypes", or ' '"webWildCards" must be non-empty for web if anything is ' 'non-empty.'); } } /// Append a dot at the beggining if it is not there png -> .png String _normalizeExtension(String ext) { return ext.isNotEmpty && ext[0] != '.' ? '.$ext' : ext; } ================================================ FILE: packages/file_selector/file_selector_web/pubspec.yaml ================================================ name: file_selector_web description: Web platform implementation of file_selector repository: https://github.com/flutter/plugins/tree/main/packages/file_selector/file_selector_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 version: 0.9.0+2 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: file_selector platforms: web: pluginClass: FileSelectorWeb fileName: file_selector_web.dart dependencies: file_selector_platform_interface: ^2.2.0 flutter: sdk: flutter flutter_web_plugins: sdk: flutter dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/file_selector/file_selector_web/test/more_tests_exist_elsewhere_test.dart ================================================ // Copyright 2013 The Flutter 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: avoid_print import 'package:flutter_test/flutter_test.dart'; void main() { test('Tell the user where to find the real tests', () { print('---'); print('This package also uses integration_test to run additional tests.'); print('See `example/README.md` for more info.'); print('---'); }); } ================================================ FILE: packages/file_selector/file_selector_web/test/utils_test.dart ================================================ // Copyright 2013 The Flutter 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:file_selector_web/src/utils.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('FileSelectorWeb utils', () { group('acceptedTypesToString', () { test('works', () { const List acceptedTypes = [ XTypeGroup(label: 'images', webWildCards: ['images/*']), XTypeGroup(label: 'jpgs', extensions: ['jpg', 'jpeg']), XTypeGroup(label: 'pngs', mimeTypes: ['image/png']), ]; final String accepts = acceptedTypesToString(acceptedTypes); expect(accepts, 'images/*,.jpg,.jpeg,image/png'); }); test('works with an empty list', () { const List acceptedTypes = []; final String accepts = acceptedTypesToString(acceptedTypes); expect(accepts, ''); }); test('works with extensions', () { const List acceptedTypes = [ XTypeGroup(label: 'jpgs', extensions: ['jpeg', 'jpg']), XTypeGroup(label: 'pngs', extensions: ['png']), ]; final String accepts = acceptedTypesToString(acceptedTypes); expect(accepts, '.jpeg,.jpg,.png'); }); test('works with mime types', () { const List acceptedTypes = [ XTypeGroup( label: 'jpgs', mimeTypes: ['image/jpeg', 'image/jpg']), XTypeGroup(label: 'pngs', mimeTypes: ['image/png']), ]; final String accepts = acceptedTypesToString(acceptedTypes); expect(accepts, 'image/jpeg,image/jpg,image/png'); }); test('works with web wild cards', () { const List acceptedTypes = [ XTypeGroup(label: 'images', webWildCards: ['image/*']), XTypeGroup(label: 'audios', webWildCards: ['audio/*']), XTypeGroup(label: 'videos', webWildCards: ['video/*']), ]; final String accepts = acceptedTypesToString(acceptedTypes); expect(accepts, 'image/*,audio/*,video/*'); }); test('throws for a type group that does not support web', () { const List acceptedTypes = [ XTypeGroup(label: 'text', macUTIs: ['public.text']), ]; expect(() => acceptedTypesToString(acceptedTypes), throwsArgumentError); }); }); }); } ================================================ FILE: packages/file_selector/file_selector_windows/.gitignore ================================================ .dart_tool .packages .flutter-plugins .flutter-plugins-dependencies pubspec.lock ================================================ FILE: packages/file_selector/file_selector_windows/.metadata ================================================ # 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: 6d1c244b79f3a2747281f718297ce248bd5ad099 channel: master project_type: plugin ================================================ FILE: packages/file_selector/file_selector_windows/AUTHORS ================================================ # 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. ================================================ FILE: packages/file_selector/file_selector_windows/CHANGELOG.md ================================================ ## 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. ## 0.9.1+3 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. ## 0.9.1+2 * Fixes the problem that the initial directory does not work after completing a file selection. ## 0.9.1+1 * Updates README for endorsement. * Updates `flutter_test` to be a `dev_dependencies` entry. ## 0.9.1 * Converts the method channel to Pigeon. ## 0.9.0 * **BREAKING CHANGE**: Methods that take `XTypeGroup`s now throw an `ArgumentError` if any group is not a wildcard (all filter types null or empty), but doesn't include any of the filter types supported by Windows. * Ignores deprecation warnings for upcoming styleFrom button API changes. ## 0.8.2+2 * Updates references to the obsolete master branch. ## 0.8.2+1 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.8.2 * Moves source to flutter/plugins, and restructures to allow for unit testing. * Switches to an internal method channel implementation. ## 0.0.2+1 * Update README ## 0.0.2 * Update SDK constraint to signal compatibility with null safety. ## 0.0.1 * Initial Windows implementation of `file_selector`. ================================================ FILE: packages/file_selector/file_selector_windows/LICENSE ================================================ 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. ================================================ FILE: packages/file_selector/file_selector_windows/README.md ================================================ # file\_selector\_windows The Windows implementation of [`file_selector`][1]. ## Usage This package is [endorsed][2], which means you can simply use `file_selector` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/file_selector [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/file_selector/file_selector_windows/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Currently only web supported android/ ios/ # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: packages/file_selector/file_selector_windows/example/.metadata ================================================ # 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: 7736f3bc90270dcb0480db2ccffbf1d13c28db85 channel: dev project_type: app ================================================ FILE: packages/file_selector/file_selector_windows/example/README.md ================================================ # 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. ================================================ FILE: packages/file_selector/file_selector_windows/example/lib/get_directory_page.dart ================================================ // Copyright 2013 The Flutter 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 a directory using `getDirectoryPath`, /// then displays the selected directory in a dialog. class GetDirectoryPage extends StatelessWidget { /// Default Constructor const GetDirectoryPage({Key? key}) : super(key: key); Future _getDirectoryPath(BuildContext context) async { const String confirmButtonText = 'Choose'; final String? directoryPath = await FileSelectorPlatform.instance.getDirectoryPath( confirmButtonText: confirmButtonText, ); if (directoryPath == null) { // Operation was canceled by the user. return; } if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => TextDisplay(directoryPath), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open a text file'), ), 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 a directory'), onPressed: () => _getDirectoryPath(context), ), ], ), ), ); } } /// Widget that displays a text file in a dialog. class TextDisplay extends StatelessWidget { /// Creates a `TextDisplay`. const TextDisplay(this.directoryPath, {Key? key}) : super(key: key); /// The path selected in the dialog. final String directoryPath; @override Widget build(BuildContext context) { return AlertDialog( title: const Text('Selected Directory'), content: Scrollbar( child: SingleChildScrollView( child: Text(directoryPath), ), ), actions: [ TextButton( child: const Text('Close'), onPressed: () => Navigator.pop(context), ), ], ); } } ================================================ FILE: packages/file_selector/file_selector_windows/example/lib/home_page.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; /// Home Page of the application. class HomePage extends StatelessWidget { /// Default Constructor const HomePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final ButtonStyle 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, ); return Scaffold( appBar: AppBar( title: const Text('File Selector Demo Home Page'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( style: style, child: const Text('Open a text file'), onPressed: () => Navigator.pushNamed(context, '/open/text'), ), const SizedBox(height: 10), ElevatedButton( style: style, child: const Text('Open an image'), onPressed: () => Navigator.pushNamed(context, '/open/image'), ), const SizedBox(height: 10), ElevatedButton( style: style, child: const Text('Open multiple images'), onPressed: () => Navigator.pushNamed(context, '/open/images'), ), const SizedBox(height: 10), ElevatedButton( style: style, child: const Text('Save a file'), onPressed: () => Navigator.pushNamed(context, '/save/text'), ), const SizedBox(height: 10), ElevatedButton( style: style, child: const Text('Open a get directory dialog'), onPressed: () => Navigator.pushNamed(context, '/directory'), ), ], ), ), ); } } ================================================ FILE: packages/file_selector/file_selector_windows/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'get_directory_page.dart'; import 'home_page.dart'; import 'open_image_page.dart'; import 'open_multiple_images_page.dart'; import 'open_text_page.dart'; import 'save_text_page.dart'; void main() { runApp(const MyApp()); } /// MyApp is the Main Application. class MyApp extends StatelessWidget { /// Default Constructor const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'File Selector Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: const HomePage(), routes: { '/open/image': (BuildContext context) => const OpenImagePage(), '/open/images': (BuildContext context) => const OpenMultipleImagesPage(), '/open/text': (BuildContext context) => const OpenTextPage(), '/save/text': (BuildContext context) => SaveTextPage(), '/directory': (BuildContext context) => const GetDirectoryPage(), }, ); } } ================================================ FILE: packages/file_selector/file_selector_windows/example/lib/open_image_page.dart ================================================ // Copyright 2013 The Flutter 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:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// Screen that allows the user to select an image file using /// `openFiles`, then displays the selected images in a gallery dialog. class OpenImagePage extends StatelessWidget { /// Default Constructor const OpenImagePage({Key? key}) : super(key: key); Future _openImageFile(BuildContext context) async { const XTypeGroup typeGroup = XTypeGroup( label: 'images', extensions: ['jpg', 'png'], ); final XFile? file = await FileSelectorPlatform.instance .openFile(acceptedTypeGroups: [typeGroup]); if (file == null) { // Operation was canceled by the user. return; } final String fileName = file.name; final String filePath = file.path; if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => ImageDisplay(fileName, filePath), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open an image'), ), 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 open an image file(png, jpg)'), onPressed: () => _openImageFile(context), ), ], ), ), ); } } /// Widget that displays an image in a dialog. class ImageDisplay extends StatelessWidget { /// Default Constructor. const ImageDisplay(this.fileName, this.filePath, {Key? key}) : super(key: key); /// The name of the selected file. final String fileName; /// The path to the selected file. final String filePath; @override Widget build(BuildContext context) { return AlertDialog( title: Text(fileName), // On web the filePath is a blob url // while on other platforms it is a system path. content: kIsWeb ? Image.network(filePath) : Image.file(File(filePath)), actions: [ TextButton( child: const Text('Close'), onPressed: () { Navigator.pop(context); }, ), ], ); } } ================================================ FILE: packages/file_selector/file_selector_windows/example/lib/open_multiple_images_page.dart ================================================ // Copyright 2013 The Flutter 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:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// Screen that allows the user to select multiple image files using /// `openFiles`, then displays the selected images in a gallery dialog. class OpenMultipleImagesPage extends StatelessWidget { /// Default Constructor const OpenMultipleImagesPage({Key? key}) : super(key: key); Future _openImageFile(BuildContext context) async { const XTypeGroup jpgsTypeGroup = XTypeGroup( label: 'JPEGs', extensions: ['jpg', 'jpeg'], ); const XTypeGroup pngTypeGroup = XTypeGroup( label: 'PNGs', extensions: ['png'], ); final List files = await FileSelectorPlatform.instance .openFiles(acceptedTypeGroups: [ jpgsTypeGroup, pngTypeGroup, ]); if (files.isEmpty) { // Operation was canceled by the user. return; } if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => MultipleImagesDisplay(files), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open multiple images'), ), 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 open multiple images (png, jpg)'), onPressed: () => _openImageFile(context), ), ], ), ), ); } } /// Widget that displays a text file in a dialog. class MultipleImagesDisplay extends StatelessWidget { /// Default Constructor. const MultipleImagesDisplay(this.files, {Key? key}) : super(key: key); /// The files containing the images. final List files; @override Widget build(BuildContext context) { return AlertDialog( title: const Text('Gallery'), // On web the filePath is a blob url // while on other platforms it is a system path. content: Center( child: Row( children: [ ...files.map( (XFile file) => Flexible( child: kIsWeb ? Image.network(file.path) : Image.file(File(file.path))), ) ], ), ), actions: [ TextButton( child: const Text('Close'), onPressed: () { Navigator.pop(context); }, ), ], ); } } ================================================ FILE: packages/file_selector/file_selector_windows/example/lib/open_text_page.dart ================================================ // Copyright 2013 The Flutter 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 a text file using `openFile`, then /// displays its contents in a dialog. class OpenTextPage extends StatelessWidget { /// Default Constructor const OpenTextPage({Key? key}) : super(key: key); Future _openTextFile(BuildContext context) async { const XTypeGroup typeGroup = XTypeGroup( label: 'text', extensions: ['txt', 'json'], ); final XFile? file = await FileSelectorPlatform.instance .openFile(acceptedTypeGroups: [typeGroup]); if (file == null) { // Operation was canceled by the user. return; } final String fileName = file.name; final String fileContent = await file.readAsString(); if (context.mounted) { await showDialog( context: context, builder: (BuildContext context) => TextDisplay(fileName, fileContent), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Open a text file'), ), 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 open a text file (json, txt)'), onPressed: () => _openTextFile(context), ), ], ), ), ); } } /// Widget that displays a text file in a dialog. class TextDisplay extends StatelessWidget { /// Default Constructor. const TextDisplay(this.fileName, this.fileContent, {Key? key}) : super(key: key); /// The name of the selected file. final String fileName; /// The contents of the text file. final String fileContent; @override Widget build(BuildContext context) { return AlertDialog( title: Text(fileName), content: Scrollbar( child: SingleChildScrollView( child: Text(fileContent), ), ), actions: [ TextButton( child: const Text('Close'), onPressed: () => Navigator.pop(context), ), ], ); } } ================================================ FILE: packages/file_selector/file_selector_windows/example/lib/save_text_page.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:typed_data'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/material.dart'; /// Screen that allows the user to select a save location using `getSavePath`, /// then writes text to a file at that location. class SaveTextPage extends StatelessWidget { /// Default Constructor SaveTextPage({Key? key}) : super(key: key); final TextEditingController _nameController = TextEditingController(); final TextEditingController _contentController = TextEditingController(); Future _saveFile() async { final String fileName = _nameController.text; final String? path = await FileSelectorPlatform.instance.getSavePath( // Operation was canceled by the user. suggestedName: fileName, ); if (path == null) { return; } final String text = _contentController.text; final Uint8List fileData = Uint8List.fromList(text.codeUnits); const String fileMimeType = 'text/plain'; final XFile textFile = XFile.fromData(fileData, mimeType: fileMimeType, name: fileName); await textFile.saveTo(path); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Save text into a file'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 300, child: TextField( minLines: 1, maxLines: 12, controller: _nameController, decoration: const InputDecoration( hintText: '(Optional) Suggest File Name', ), ), ), SizedBox( width: 300, child: TextField( minLines: 1, maxLines: 12, controller: _contentController, decoration: const InputDecoration( hintText: 'Enter File Contents', ), ), ), const SizedBox(height: 10), 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, ), onPressed: _saveFile, child: const Text('Press to save a text file'), ), ], ), ), ); } } ================================================ FILE: packages/file_selector/file_selector_windows/example/pubspec.yaml ================================================ name: example description: Example for file_selector_windows implementation. publish_to: 'none' version: 1.0.0 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: file_selector_platform_interface: ^2.2.0 file_selector_windows: # When depending on this package from a real application you should use: # file_selector_windows: ^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: .. flutter: sdk: flutter dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/file_selector/file_selector_windows/example/windows/.gitignore ================================================ flutter/ephemeral/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: packages/file_selector/file_selector_windows/example/windows/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) project(example LANGUAGES CXX) set(BINARY_NAME "example") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" CACHE STRING "" FORCE) else() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() endif() set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") # Use Unicode for all projects. add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") target_compile_options(${TARGET} PRIVATE /EHsc) target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build add_subdirectory("runner") # Enable the test target set(include_file_selector_windows_tests TRUE) # Provide an alias for the test target using the name expected by repo tooling. add_custom_target(unit_tests DEPENDS file_selector_windows_test) # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # Support files are copied into place next to the executable, so that it can # run in place. This is done instead of making a separate bundle (as on Linux) # so that building and running from within Visual Studio will work. set(BUILD_BUNDLE_DIR "$") # Make the "install" step default, as it's required to run. set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" CONFIGURATIONS Profile;Release COMPONENT Runtime) ================================================ FILE: packages/file_selector/file_selector_windows/example/windows/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "flutter_export.h" "flutter_windows.h" "flutter_messenger.h" "flutter_plugin_registrar.h" "flutter_texture_registrar.h" ) list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") add_dependencies(flutter flutter_assemble) # === Wrapper === list(APPEND CPP_WRAPPER_SOURCES_CORE "core_implementations.cc" "standard_codec.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_PLUGIN "plugin_registrar.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_APP "flutter_engine.cc" "flutter_view_controller.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") # Wrapper sources needed for a plugin. add_library(flutter_wrapper_plugin STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ) apply_standard_settings(flutter_wrapper_plugin) set_target_properties(flutter_wrapper_plugin PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(flutter_wrapper_plugin PROPERTIES CXX_VISIBILITY_PRESET hidden) target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) target_include_directories(flutter_wrapper_plugin PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_plugin flutter_assemble) # Wrapper sources needed for the runner. add_library(flutter_wrapper_app STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_APP} ) apply_standard_settings(flutter_wrapper_app) target_link_libraries(flutter_wrapper_app PUBLIC flutter) target_include_directories(flutter_wrapper_app PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_app flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" windows-x64 $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ) ================================================ FILE: packages/file_selector/file_selector_windows/example/windows/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/file_selector/file_selector_windows/example/windows/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) project(runner LANGUAGES CXX) add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) apply_standard_settings(${BINARY_NAME}) target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") add_dependencies(${BINARY_NAME} flutter_assemble) ================================================ FILE: packages/file_selector/file_selector_windows/example/windows/runner/Runner.rc ================================================ // Microsoft Visual C++ generated resource script. // #pragma code_page(65001) #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APP_ICON ICON "resources\\app_icon.ico" ///////////////////////////////////////////////////////////////////////////// // // Version // #ifdef FLUTTER_BUILD_NUMBER #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER #else #define VERSION_AS_NUMBER 1,0,0 #endif #ifdef FLUTTER_BUILD_NAME #define VERSION_AS_STRING #FLUTTER_BUILD_NAME #else #define VERSION_AS_STRING "1.0.0" #endif VS_VERSION_INFO VERSIONINFO FILEVERSION VERSION_AS_NUMBER PRODUCTVERSION VERSION_AS_NUMBER FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "com.example" "\0" VALUE "FileDescription", "A new Flutter project." "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "example" "\0" VALUE "LegalCopyright", "Copyright (C) 2021 com.example. All rights reserved." "\0" VALUE "OriginalFilename", "example.exe" "\0" VALUE "ProductName", "example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: packages/file_selector/file_selector_windows/example/windows/runner/flutter_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "flutter_window.h" #include #include "flutter/generated_plugin_registrant.h" FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {} FlutterWindow::~FlutterWindow() {} bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opportunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) { return *result; } } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); } ================================================ FILE: packages/file_selector/file_selector_windows/example/windows/runner/flutter_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_ #include #include #include #include "win32_window.h" // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { public: // Creates a new FlutterWindow hosting a Flutter view running |project|. explicit FlutterWindow(const flutter::DartProject& project); virtual ~FlutterWindow(); protected: // Win32Window: bool OnCreate() override; void OnDestroy() override; LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; private: // The project to run. flutter::DartProject project_; // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; }; #endif // RUNNER_FLUTTER_WINDOW_H_ ================================================ FILE: packages/file_selector/file_selector_windows/example/windows/runner/main.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include "flutter_window.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t* command_line, _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { CreateAndAttachConsole(); } // Initialize COM, so that it is available for use in the library and/or // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); flutter::DartProject project(L"data"); std::vector command_line_arguments = GetCommandLineArguments(); project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.CreateAndShow(L"example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); ::MSG msg; while (::GetMessage(&msg, nullptr, 0, 0)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } ::CoUninitialize(); return EXIT_SUCCESS; } ================================================ FILE: packages/file_selector/file_selector_windows/example/windows/runner/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Runner.rc // #define IDI_APP_ICON 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: packages/file_selector/file_selector_windows/example/windows/runner/runner.exe.manifest ================================================ PerMonitorV2 ================================================ FILE: packages/file_selector/file_selector_windows/example/windows/runner/utils.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "utils.h" #include #include #include #include #include void CreateAndAttachConsole() { if (::AllocConsole()) { FILE* unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } if (freopen_s(&unused, "CONOUT$", "w", stderr)) { _dup2(_fileno(stdout), 2); } std::ios::sync_with_stdio(); FlutterDesktopResyncOutputStreams(); } } std::vector GetCommandLineArguments() { // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. int argc; wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); if (argv == nullptr) { return std::vector(); } std::vector command_line_arguments; // Skip the first argument as it's the binary name. for (int i = 1; i < argc; i++) { command_line_arguments.push_back(Utf8FromUtf16(argv[i])); } ::LocalFree(argv); return command_line_arguments; } std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); } int target_length = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, nullptr, 0, nullptr, nullptr); if (target_length == 0) { return std::string(); } std::string utf8_string; utf8_string.resize(target_length); int converted_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); } return utf8_string; } ================================================ FILE: packages/file_selector/file_selector_windows/example/windows/runner/utils.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_UTILS_H_ #define RUNNER_UTILS_H_ #include #include // Creates a console for the process, and redirects stdout and stderr to // it for both the runner and the Flutter library. void CreateAndAttachConsole(); // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string // encoded in UTF-8. Returns an empty std::string on failure. std::string Utf8FromUtf16(const wchar_t* utf16_string); // Gets the command line arguments passed in as a std::vector, // encoded in UTF-8. Returns an empty std::vector on failure. std::vector GetCommandLineArguments(); #endif // RUNNER_UTILS_H_ ================================================ FILE: packages/file_selector/file_selector_windows/example/windows/runner/win32_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "win32_window.h" #include #include "resource.h" namespace { constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); // Scale helper to convert logical scaler values to physical using passed in // scale factor int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); if (!user32_module) { return; } auto enable_non_client_dpi_scaling = reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); FreeLibrary(user32_module); } } } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; // Returns the singleton registar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; } // Returns the name of the window class, registering the class if it hasn't // previously been registered. const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; bool class_registered_ = false; }; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; RegisterClass(&window_class); class_registered_ = true; } return kWindowClassName; } void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, const Size& size) { Destroy(); const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), static_cast(origin.y)}; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } return OnCreate(); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { case WM_DESTROY: window_handle_ = nullptr; Destroy(); if (quit_on_close_) { PostQuitMessage(0); } return 0; case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left; LONG newHeight = newRectSize->bottom - newRectSize->top; SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_SIZE: { RECT rect = GetClientArea(); if (child_content_ != nullptr) { // Size and position the child window. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); } return 0; } case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_); } return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } void Win32Window::Destroy() { OnDestroy(); if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, true); SetFocus(child_content_); } RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } void Win32Window::OnDestroy() { // No-op; provided for subclasses. } ================================================ FILE: packages/file_selector/file_selector_windows/example/windows/runner/win32_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_ #include #include #include #include // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { public: struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) : width(width), height(height) {} }; Win32Window(); virtual ~Win32Window(); // Creates and shows a win32 window with |title| and position and size using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a // consistent size to will treat the width height passed in to this function // as logical pixels and scale to appropriate for the default monitor. Returns // true if the window was created successfully. bool CreateAndShow(const std::wstring& title, const Point& origin, const Size& size); // Release OS resources associated with window. void Destroy(); // Inserts |content| into the window tree. void SetChildContent(HWND content); // Returns the backing Window handle to enable clients to set icon and other // window properties. Returns nullptr if the window has been destroyed. HWND GetHandle(); // If true, closing this window will quit the application. void SetQuitOnClose(bool quit_on_close); // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Called when CreateAndShow is called, allowing subclass window-related // setup. Subclasses should return false if setup fails. virtual bool OnCreate(); // Called when Destroy is called. virtual void OnDestroy(); private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically // responsponds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; bool quit_on_close_ = false; // window handle for top level window. HWND window_handle_ = nullptr; // window handle for hosted content. HWND child_content_ = nullptr; }; #endif // RUNNER_WIN32_WINDOW_H_ ================================================ FILE: packages/file_selector/file_selector_windows/lib/file_selector_windows.dart ================================================ // Copyright 2013 The Flutter 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 'src/messages.g.dart'; /// An implementation of [FileSelectorPlatform] for Windows. class FileSelectorWindows extends FileSelectorPlatform { final FileSelectorApi _hostApi = FileSelectorApi(); /// Registers the Windows implementation. static void registerWith() { FileSelectorPlatform.instance = FileSelectorWindows(); } @override Future openFile({ List? acceptedTypeGroups, String? initialDirectory, String? confirmButtonText, }) async { final List paths = await _hostApi.showOpenDialog( SelectionOptions( allowMultiple: false, selectFolders: false, allowedTypes: _typeGroupsFromXTypeGroups(acceptedTypeGroups), ), initialDirectory, confirmButtonText); return paths.isEmpty ? null : XFile(paths.first!); } @override Future> openFiles({ List? acceptedTypeGroups, String? initialDirectory, String? confirmButtonText, }) async { final List paths = await _hostApi.showOpenDialog( SelectionOptions( allowMultiple: true, selectFolders: false, allowedTypes: _typeGroupsFromXTypeGroups(acceptedTypeGroups), ), initialDirectory, confirmButtonText); return paths.map((String? path) => XFile(path!)).toList(); } @override Future getSavePath({ List? acceptedTypeGroups, String? initialDirectory, String? suggestedName, String? confirmButtonText, }) async { final List paths = await _hostApi.showSaveDialog( SelectionOptions( allowMultiple: false, selectFolders: false, allowedTypes: _typeGroupsFromXTypeGroups(acceptedTypeGroups), ), initialDirectory, suggestedName, confirmButtonText); return paths.isEmpty ? null : paths.first!; } @override Future getDirectoryPath({ String? initialDirectory, String? confirmButtonText, }) async { final List paths = await _hostApi.showOpenDialog( SelectionOptions( allowMultiple: false, selectFolders: true, allowedTypes: [], ), initialDirectory, confirmButtonText); return paths.isEmpty ? null : paths.first!; } } List _typeGroupsFromXTypeGroups(List? xtypes) { return (xtypes ?? []).map((XTypeGroup xtype) { if (!xtype.allowsAny && (xtype.extensions?.isEmpty ?? true)) { throw ArgumentError('Provided type group $xtype does not allow ' 'all files, but does not set any of the Windows-supported filter ' 'categories. "extensions" must be non-empty for Windows if ' 'anything is non-empty.'); } return TypeGroup( label: xtype.label ?? '', extensions: xtype.extensions ?? []); }).toList(); } ================================================ FILE: packages/file_selector/file_selector_windows/lib/src/messages.g.dart ================================================ // Copyright 2013 The Flutter 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.2.5), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import 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 TypeGroup { TypeGroup({ required this.label, required this.extensions, }); String label; List extensions; Object encode() { final Map pigeonMap = {}; pigeonMap['label'] = label; pigeonMap['extensions'] = extensions; return pigeonMap; } static TypeGroup decode(Object message) { final Map pigeonMap = message as Map; return TypeGroup( label: pigeonMap['label']! as String, extensions: (pigeonMap['extensions'] as List?)!.cast(), ); } } class SelectionOptions { SelectionOptions({ required this.allowMultiple, required this.selectFolders, required this.allowedTypes, }); bool allowMultiple; bool selectFolders; List allowedTypes; Object encode() { final Map pigeonMap = {}; pigeonMap['allowMultiple'] = allowMultiple; pigeonMap['selectFolders'] = selectFolders; pigeonMap['allowedTypes'] = allowedTypes; return pigeonMap; } static SelectionOptions decode(Object message) { final Map pigeonMap = message as Map; return SelectionOptions( allowMultiple: pigeonMap['allowMultiple']! as bool, selectFolders: pigeonMap['selectFolders']! as bool, allowedTypes: (pigeonMap['allowedTypes'] as List?)!.cast(), ); } } class _FileSelectorApiCodec extends StandardMessageCodec { const _FileSelectorApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is SelectionOptions) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is TypeGroup) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return SelectionOptions.decode(readValue(buffer)!); case 129: return TypeGroup.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(); Future> showOpenDialog(SelectionOptions arg_options, String? arg_initialDirectory, String? arg_confirmButtonText) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.FileSelectorApi.showOpenDialog', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send( [arg_options, arg_initialDirectory, arg_confirmButtonText]) 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 List?)!.cast(); } } Future> showSaveDialog( SelectionOptions arg_options, String? arg_initialDirectory, String? arg_suggestedName, String? arg_confirmButtonText) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.FileSelectorApi.showSaveDialog', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([ arg_options, arg_initialDirectory, arg_suggestedName, arg_confirmButtonText ]) 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 List?)!.cast(); } } } ================================================ FILE: packages/file_selector/file_selector_windows/pigeons/copyright.txt ================================================ Copyright 2013 The Flutter Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. ================================================ FILE: packages/file_selector/file_selector_windows/pigeons/messages.dart ================================================ // Copyright 2013 The Flutter 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', dartTestOut: 'test/test_api.g.dart', cppOptions: CppOptions(namespace: 'file_selector_windows'), cppHeaderOut: 'windows/messages.g.h', cppSourceOut: 'windows/messages.g.cpp', copyrightHeader: 'pigeons/copyright.txt', )) class TypeGroup { TypeGroup(this.label, {required this.extensions}); String label; // TODO(stuartmorgan): Make the generic type non-nullable once supported. // https://github.com/flutter/flutter/issues/97848 // The C++ code treats all of it as non-nullable. List extensions; } class SelectionOptions { SelectionOptions({ this.allowMultiple = false, this.selectFolders = false, this.allowedTypes = const [], }); bool allowMultiple; bool selectFolders; // TODO(stuartmorgan): Make the generic type non-nullable once supported. // https://github.com/flutter/flutter/issues/97848 // The C++ code treats the values as non-nullable. List allowedTypes; } @HostApi(dartHostTestHandler: 'TestFileSelectorApi') abstract class FileSelectorApi { List showOpenDialog( SelectionOptions options, String? initialDirectory, String? confirmButtonText, ); List showSaveDialog( SelectionOptions options, String? initialDirectory, String? suggestedName, String? confirmButtonText, ); } ================================================ FILE: packages/file_selector/file_selector_windows/pubspec.yaml ================================================ name: file_selector_windows description: Windows implementation of the file_selector plugin. repository: https://github.com/flutter/plugins/tree/main/packages/file_selector/file_selector_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 version: 0.9.1+4 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: file_selector platforms: windows: dartPluginClass: FileSelectorWindows pluginClass: FileSelectorWindows dependencies: cross_file: ^0.3.1 file_selector_platform_interface: ^2.2.0 flutter: sdk: flutter dev_dependencies: build_runner: 2.1.11 flutter_test: sdk: flutter mockito: ^5.1.0 pigeon: ^3.2.5 ================================================ FILE: packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart ================================================ // Copyright 2013 The Flutter 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:file_selector_windows/file_selector_windows.dart'; import 'package:file_selector_windows/src/messages.g.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'file_selector_windows_test.mocks.dart'; import 'test_api.g.dart'; @GenerateMocks([TestFileSelectorApi]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); final FileSelectorWindows plugin = FileSelectorWindows(); late MockTestFileSelectorApi mockApi; setUp(() { mockApi = MockTestFileSelectorApi(); TestFileSelectorApi.setup(mockApi); }); test('registered instance', () { FileSelectorWindows.registerWith(); expect(FileSelectorPlatform.instance, isA()); }); group('#openFile', () { setUp(() { when(mockApi.showOpenDialog(any, any, any)).thenReturn(['foo']); }); test('simple call works', () async { final XFile? file = await plugin.openFile(); expect(file!.path, 'foo'); final VerificationResult result = verify(mockApi.showOpenDialog(captureAny, null, null)); final SelectionOptions options = result.captured[0] as SelectionOptions; expect(options.allowMultiple, false); expect(options.selectFolders, false); }); test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', extensions: ['txt'], mimeTypes: ['text/plain'], macUTIs: ['public.text'], ); const XTypeGroup groupTwo = XTypeGroup( label: 'image', extensions: ['jpg'], mimeTypes: ['image/jpg'], macUTIs: ['public.image']); await plugin.openFile(acceptedTypeGroups: [group, groupTwo]); final VerificationResult result = verify(mockApi.showOpenDialog(captureAny, null, null)); final SelectionOptions options = result.captured[0] as SelectionOptions; expect( _typeGroupListsMatch(options.allowedTypes, [ TypeGroup(label: 'text', extensions: ['txt']), TypeGroup(label: 'image', extensions: ['jpg']), ]), true); }); test('passes initialDirectory correctly', () async { await plugin.openFile(initialDirectory: '/example/directory'); verify(mockApi.showOpenDialog(any, '/example/directory', null)); }); test('passes confirmButtonText correctly', () async { await plugin.openFile(confirmButtonText: 'Open File'); verify(mockApi.showOpenDialog(any, null, 'Open File')); }); test('throws for a type group that does not support Windows', () async { const XTypeGroup group = XTypeGroup( label: 'text', mimeTypes: ['text/plain'], ); await expectLater( plugin.openFile(acceptedTypeGroups: [group]), throwsArgumentError); }); test('allows a wildcard group', () async { const XTypeGroup group = XTypeGroup( label: 'text', ); await expectLater( plugin.openFile(acceptedTypeGroups: [group]), completes); }); }); group('#openFiles', () { setUp(() { when(mockApi.showOpenDialog(any, any, any)) .thenReturn(['foo', 'bar']); }); test('simple call works', () async { final List file = await plugin.openFiles(); expect(file[0].path, 'foo'); expect(file[1].path, 'bar'); final VerificationResult result = verify(mockApi.showOpenDialog(captureAny, null, null)); final SelectionOptions options = result.captured[0] as SelectionOptions; expect(options.allowMultiple, true); expect(options.selectFolders, false); }); test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', extensions: ['txt'], mimeTypes: ['text/plain'], macUTIs: ['public.text'], ); const XTypeGroup groupTwo = XTypeGroup( label: 'image', extensions: ['jpg'], mimeTypes: ['image/jpg'], macUTIs: ['public.image']); await plugin.openFiles(acceptedTypeGroups: [group, groupTwo]); final VerificationResult result = verify(mockApi.showOpenDialog(captureAny, null, null)); final SelectionOptions options = result.captured[0] as SelectionOptions; expect( _typeGroupListsMatch(options.allowedTypes, [ TypeGroup(label: 'text', extensions: ['txt']), TypeGroup(label: 'image', extensions: ['jpg']), ]), true); }); test('passes initialDirectory correctly', () async { await plugin.openFiles(initialDirectory: '/example/directory'); verify(mockApi.showOpenDialog(any, '/example/directory', null)); }); test('passes confirmButtonText correctly', () async { await plugin.openFiles(confirmButtonText: 'Open Files'); verify(mockApi.showOpenDialog(any, null, 'Open Files')); }); test('throws for a type group that does not support Windows', () async { const XTypeGroup group = XTypeGroup( label: 'text', mimeTypes: ['text/plain'], ); await expectLater( plugin.openFiles(acceptedTypeGroups: [group]), throwsArgumentError); }); test('allows a wildcard group', () async { const XTypeGroup group = XTypeGroup( label: 'text', ); await expectLater( plugin.openFiles(acceptedTypeGroups: [group]), completes); }); }); group('#getDirectoryPath', () { setUp(() { when(mockApi.showOpenDialog(any, any, any)).thenReturn(['foo']); }); test('simple call works', () async { final String? path = await plugin.getDirectoryPath(); expect(path, 'foo'); final VerificationResult result = verify(mockApi.showOpenDialog(captureAny, null, null)); final SelectionOptions options = result.captured[0] as SelectionOptions; expect(options.allowMultiple, false); expect(options.selectFolders, true); }); test('passes initialDirectory correctly', () async { await plugin.getDirectoryPath(initialDirectory: '/example/directory'); verify(mockApi.showOpenDialog(any, '/example/directory', null)); }); test('passes confirmButtonText correctly', () async { await plugin.getDirectoryPath(confirmButtonText: 'Open Directory'); verify(mockApi.showOpenDialog(any, null, 'Open Directory')); }); }); group('#getSavePath', () { setUp(() { when(mockApi.showSaveDialog(any, any, any, any)) .thenReturn(['foo']); }); test('simple call works', () async { final String? path = await plugin.getSavePath(); expect(path, 'foo'); final VerificationResult result = verify(mockApi.showSaveDialog(captureAny, null, null, null)); final SelectionOptions options = result.captured[0] as SelectionOptions; expect(options.allowMultiple, false); expect(options.selectFolders, false); }); test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', extensions: ['txt'], mimeTypes: ['text/plain'], macUTIs: ['public.text'], ); const XTypeGroup groupTwo = XTypeGroup( label: 'image', extensions: ['jpg'], mimeTypes: ['image/jpg'], macUTIs: ['public.image']); await plugin .getSavePath(acceptedTypeGroups: [group, groupTwo]); final VerificationResult result = verify(mockApi.showSaveDialog(captureAny, null, null, null)); final SelectionOptions options = result.captured[0] as SelectionOptions; expect( _typeGroupListsMatch(options.allowedTypes, [ TypeGroup(label: 'text', extensions: ['txt']), TypeGroup(label: 'image', extensions: ['jpg']), ]), true); }); test('passes initialDirectory correctly', () async { await plugin.getSavePath(initialDirectory: '/example/directory'); verify(mockApi.showSaveDialog(any, '/example/directory', null, null)); }); test('passes suggestedName correctly', () async { await plugin.getSavePath(suggestedName: 'baz.txt'); verify(mockApi.showSaveDialog(any, null, 'baz.txt', null)); }); test('passes confirmButtonText correctly', () async { await plugin.getSavePath(confirmButtonText: 'Save File'); verify(mockApi.showSaveDialog(any, null, null, 'Save File')); }); test('throws for a type group that does not support Windows', () async { const XTypeGroup group = XTypeGroup( label: 'text', mimeTypes: ['text/plain'], ); await expectLater( plugin.getSavePath(acceptedTypeGroups: [group]), throwsArgumentError); }); test('allows a wildcard group', () async { const XTypeGroup group = XTypeGroup( label: 'text', ); await expectLater( plugin.getSavePath(acceptedTypeGroups: [group]), completes); }); }); } // True if the given options match. // // This is needed because Pigeon data classes don't have custom equality checks, // so only match for identical instances. bool _typeGroupListsMatch(List a, List b) { if (a.length != b.length) { return false; } for (int i = 0; i < a.length; i++) { if (!_typeGroupsMatch(a[i], b[i])) { return false; } } return true; } // True if the given type groups match. // // This is needed because Pigeon data classes don't have custom equality checks, // so only match for identical instances. bool _typeGroupsMatch(TypeGroup? a, TypeGroup? b) { return a!.label == b!.label && listEquals(a.extensions, b.extensions); } ================================================ FILE: packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart ================================================ // Mocks generated by Mockito 5.2.0 from annotations // in file_selector_windows/example/windows/flutter/ephemeral/.plugin_symlinks/file_selector_windows/test/file_selector_windows_test.dart. // Do not manually edit this file. import 'package:file_selector_windows/src/messages.g.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; import 'test_api.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 /// 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 List showOpenDialog(_i3.SelectionOptions? options, String? initialDirectory, String? confirmButtonText) => (super.noSuchMethod( Invocation.method( #showOpenDialog, [options, initialDirectory, confirmButtonText]), returnValue: []) as List); @override List showSaveDialog( _i3.SelectionOptions? options, String? initialDirectory, String? suggestedName, String? confirmButtonText) => (super.noSuchMethod( Invocation.method(#showSaveDialog, [options, initialDirectory, suggestedName, confirmButtonText]), returnValue: []) as List); } ================================================ FILE: packages/file_selector/file_selector_windows/test/test_api.g.dart ================================================ // Copyright 2013 The Flutter 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.2.5), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import // 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 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; // ignore: directives_ordering import 'package:file_selector_windows/src/messages.g.dart'; class _TestFileSelectorApiCodec extends StandardMessageCodec { const _TestFileSelectorApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is SelectionOptions) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is TypeGroup) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return SelectionOptions.decode(readValue(buffer)!); case 129: return TypeGroup.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } abstract class TestFileSelectorApi { static const MessageCodec codec = _TestFileSelectorApiCodec(); List showOpenDialog(SelectionOptions options, String? initialDirectory, String? confirmButtonText); List showSaveDialog( SelectionOptions options, String? initialDirectory, String? suggestedName, String? confirmButtonText); static void setup(TestFileSelectorApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.FileSelectorApi.showOpenDialog', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.FileSelectorApi.showOpenDialog was null.'); final List args = (message as List?)!; final SelectionOptions? arg_options = (args[0] as SelectionOptions?); assert(arg_options != null, 'Argument for dev.flutter.pigeon.FileSelectorApi.showOpenDialog was null, expected non-null SelectionOptions.'); final String? arg_initialDirectory = (args[1] as String?); final String? arg_confirmButtonText = (args[2] as String?); final List output = api.showOpenDialog( arg_options!, arg_initialDirectory, arg_confirmButtonText); return {'result': output}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.FileSelectorApi.showSaveDialog', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.FileSelectorApi.showSaveDialog was null.'); final List args = (message as List?)!; final SelectionOptions? arg_options = (args[0] as SelectionOptions?); assert(arg_options != null, 'Argument for dev.flutter.pigeon.FileSelectorApi.showSaveDialog was null, expected non-null SelectionOptions.'); final String? arg_initialDirectory = (args[1] as String?); final String? arg_suggestedName = (args[2] as String?); final String? arg_confirmButtonText = (args[3] as String?); final List output = api.showSaveDialog(arg_options!, arg_initialDirectory, arg_suggestedName, arg_confirmButtonText); return {'result': output}; }); } } } } ================================================ FILE: packages/file_selector/file_selector_windows/windows/.gitignore ================================================ flutter/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: packages/file_selector/file_selector_windows/windows/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) set(PROJECT_NAME "file_selector_windows") project(${PROJECT_NAME} LANGUAGES CXX) set(PLUGIN_NAME "${PROJECT_NAME}_plugin") list(APPEND PLUGIN_SOURCES "file_dialog_controller.cpp" "file_dialog_controller.h" "file_selector_plugin.cpp" "file_selector_plugin.h" "messages.g.cpp" "messages.g.h" "string_utils.cpp" "string_utils.h" ) add_library(${PLUGIN_NAME} SHARED "file_selector_windows.cpp" "include/file_selector_windows/file_selector_windows.h" ${PLUGIN_SOURCES} ) apply_standard_settings(${PLUGIN_NAME}) set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden) target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) target_include_directories(${PLUGIN_NAME} INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) # Override apply_standard_settings for exceptions due to # https://developercommunity.visualstudio.com/t/stdany-doesnt-link-when-exceptions-are-disabled/376072 target_compile_definitions(${PLUGIN_NAME} PRIVATE "_HAS_EXCEPTIONS=1") # List of absolute paths to libraries that should be bundled with the plugin set(file_selector_bundled_libraries "" PARENT_SCOPE ) # === Tests === if (${include_${PROJECT_NAME}_tests}) set(TEST_RUNNER "${PROJECT_NAME}_test") enable_testing() # TODO(stuartmorgan): Consider using a single shared, pre-checked-in googletest # instance rather than downloading for each plugin. This approach makes sense # for a template, but not for a monorepo with many plugins. include(FetchContent) FetchContent_Declare( googletest URL https://github.com/google/googletest/archive/release-1.11.0.zip ) # Prevent overriding the parent project's compiler/linker settings set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Disable install commands for gtest so it doesn't end up in the bundle. set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE) FetchContent_MakeAvailable(googletest) # The plugin's C API is not very useful for unit testing, so build the sources # directly into the test binary rather than using the DLL. add_executable(${TEST_RUNNER} test/file_selector_plugin_test.cpp test/test_main.cpp test/test_file_dialog_controller.cpp test/test_file_dialog_controller.h test/test_utils.cpp test/test_utils.h ${PLUGIN_SOURCES} ) apply_standard_settings(${TEST_RUNNER}) target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") target_link_libraries(${TEST_RUNNER} PRIVATE flutter_wrapper_plugin) target_link_libraries(${TEST_RUNNER} PRIVATE gtest gmock) # Override apply_standard_settings for exceptions due to # https://developercommunity.visualstudio.com/t/stdany-doesnt-link-when-exceptions-are-disabled/376072 target_compile_definitions(${TEST_RUNNER} PRIVATE "_HAS_EXCEPTIONS=1") # flutter_wrapper_plugin has link dependencies on the Flutter DLL. add_custom_command(TARGET ${TEST_RUNNER} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${FLUTTER_LIBRARY}" $ ) include(GoogleTest) gtest_discover_tests(${TEST_RUNNER}) endif() ================================================ FILE: packages/file_selector/file_selector_windows/windows/file_dialog_controller.cpp ================================================ // Copyright 2013 The Flutter 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 "file_dialog_controller.h" #include #include #include _COM_SMARTPTR_TYPEDEF(IFileOpenDialog, IID_IFileOpenDialog); namespace file_selector_windows { FileDialogController::FileDialogController(IFileDialog* dialog) : dialog_(dialog) {} FileDialogController::~FileDialogController() {} HRESULT FileDialogController::SetFolder(IShellItem* folder) { return dialog_->SetFolder(folder); } HRESULT FileDialogController::SetFileName(const wchar_t* name) { return dialog_->SetFileName(name); } HRESULT FileDialogController::SetFileTypes(UINT count, COMDLG_FILTERSPEC* filters) { return dialog_->SetFileTypes(count, filters); } HRESULT FileDialogController::SetOkButtonLabel(const wchar_t* text) { return dialog_->SetOkButtonLabel(text); } HRESULT FileDialogController::GetOptions( FILEOPENDIALOGOPTIONS* out_options) const { return dialog_->GetOptions(out_options); } HRESULT FileDialogController::SetOptions(FILEOPENDIALOGOPTIONS options) { return dialog_->SetOptions(options); } HRESULT FileDialogController::Show(HWND parent) { return dialog_->Show(parent); } HRESULT FileDialogController::GetResult(IShellItem** out_item) const { return dialog_->GetResult(out_item); } HRESULT FileDialogController::GetResults(IShellItemArray** out_items) const { IFileOpenDialogPtr open_dialog; HRESULT result = dialog_->QueryInterface(IID_PPV_ARGS(&open_dialog)); if (!SUCCEEDED(result)) { return result; } result = open_dialog->GetResults(out_items); return result; } FileDialogControllerFactory::~FileDialogControllerFactory() {} } // namespace file_selector_windows ================================================ FILE: packages/file_selector/file_selector_windows/windows/file_dialog_controller.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_FILE_SELECTOR_FILE_SELECTOR_WINDOWS_WINDOWS_FILE_DIALOG_CONTROLLER_H_ #define PACKAGES_FILE_SELECTOR_FILE_SELECTOR_WINDOWS_WINDOWS_FILE_DIALOG_CONTROLLER_H_ #include #include #include #include #include _COM_SMARTPTR_TYPEDEF(IFileDialog, IID_IFileDialog); namespace file_selector_windows { // A thin wrapper for IFileDialog to allow for faking and inspection in tests. // // Since this class defines the end of what can be unit tested, it should // contain as little logic as possible. class FileDialogController { public: // Creates a controller managing |dialog|. FileDialogController(IFileDialog* dialog); virtual ~FileDialogController(); // Disallow copy and assign. FileDialogController(const FileDialogController&) = delete; FileDialogController& operator=(const FileDialogController&) = delete; // IFileDialog wrappers: virtual HRESULT SetFolder(IShellItem* folder); virtual HRESULT SetFileName(const wchar_t* name); virtual HRESULT SetFileTypes(UINT count, COMDLG_FILTERSPEC* filters); virtual HRESULT SetOkButtonLabel(const wchar_t* text); virtual HRESULT GetOptions(FILEOPENDIALOGOPTIONS* out_options) const; virtual HRESULT SetOptions(FILEOPENDIALOGOPTIONS options); virtual HRESULT Show(HWND parent); virtual HRESULT GetResult(IShellItem** out_item) const; // IFileOpenDialog wrapper. This will fail if the IFileDialog* provided to the // constructor was not an IFileOpenDialog instance. virtual HRESULT GetResults(IShellItemArray** out_items) const; private: IFileDialogPtr dialog_ = nullptr; }; // Interface for creating FileDialogControllers, to allow for dependency // injection. class FileDialogControllerFactory { public: virtual ~FileDialogControllerFactory(); virtual std::unique_ptr CreateController( IFileDialog* dialog) const = 0; }; } // namespace file_selector_windows #endif // PACKAGES_FILE_SELECTOR_FILE_SELECTOR_WINDOWS_WINDOWS_FILE_DIALOG_CONTROLLER_H_ ================================================ FILE: packages/file_selector/file_selector_windows/windows/file_selector_plugin.cpp ================================================ // Copyright 2013 The Flutter 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 "file_selector_plugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "file_dialog_controller.h" #include "string_utils.h" _COM_SMARTPTR_TYPEDEF(IEnumShellItems, IID_IEnumShellItems); _COM_SMARTPTR_TYPEDEF(IFileDialog, IID_IFileDialog); _COM_SMARTPTR_TYPEDEF(IShellItem, IID_IShellItem); _COM_SMARTPTR_TYPEDEF(IShellItemArray, IID_IShellItemArray); namespace file_selector_windows { namespace { using flutter::EncodableList; using flutter::EncodableMap; using flutter::EncodableValue; // The kind of file dialog to show. enum class DialogMode { open, save }; // Returns the path for |shell_item| as a UTF-8 string, or an // empty string on failure. std::string GetPathForShellItem(IShellItem* shell_item) { if (shell_item == nullptr) { return ""; } wchar_t* wide_path = nullptr; if (!SUCCEEDED(shell_item->GetDisplayName(SIGDN_FILESYSPATH, &wide_path))) { return ""; } std::string path = Utf8FromUtf16(wide_path); ::CoTaskMemFree(wide_path); return path; } // Implementation of FileDialogControllerFactory that makes standard // FileDialogController instances. class DefaultFileDialogControllerFactory : public FileDialogControllerFactory { public: DefaultFileDialogControllerFactory() {} virtual ~DefaultFileDialogControllerFactory() {} // Disallow copy and assign. DefaultFileDialogControllerFactory( const DefaultFileDialogControllerFactory&) = delete; DefaultFileDialogControllerFactory& operator=( const DefaultFileDialogControllerFactory&) = delete; std::unique_ptr CreateController( IFileDialog* dialog) const override { assert(dialog != nullptr); return std::make_unique(dialog); } }; // Wraps an IFileDialog, managing object lifetime as a scoped object and // providing a simplified API for interacting with it as needed for the plugin. class DialogWrapper { public: explicit DialogWrapper(const FileDialogControllerFactory& dialog_factory, IID type) { is_open_dialog_ = type == CLSID_FileOpenDialog; IFileDialogPtr dialog = nullptr; last_result_ = CoCreateInstance(type, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&dialog)); dialog_controller_ = dialog_factory.CreateController(dialog); } // Attempts to set the default folder for the dialog to |path|, // if it exists. void SetFolder(std::string_view path) { std::wstring wide_path = Utf16FromUtf8(path); IShellItemPtr item; last_result_ = SHCreateItemFromParsingName(wide_path.c_str(), nullptr, IID_PPV_ARGS(&item)); if (!SUCCEEDED(last_result_)) { return; } dialog_controller_->SetFolder(item); } // Sets the file name that is initially shown in the dialog. void SetFileName(std::string_view name) { std::wstring wide_name = Utf16FromUtf8(name); last_result_ = dialog_controller_->SetFileName(wide_name.c_str()); } // Sets the label of the confirmation button. void SetOkButtonLabel(std::string_view label) { std::wstring wide_label = Utf16FromUtf8(label); last_result_ = dialog_controller_->SetOkButtonLabel(wide_label.c_str()); } // Adds the given options to the dialog's current option set. void AddOptions(FILEOPENDIALOGOPTIONS new_options) { FILEOPENDIALOGOPTIONS options; last_result_ = dialog_controller_->GetOptions(&options); if (!SUCCEEDED(last_result_)) { return; } options |= new_options; if (options & FOS_PICKFOLDERS) { opening_directory_ = true; } last_result_ = dialog_controller_->SetOptions(options); } // Sets the filters for allowed file types to select. void SetFileTypeFilters(const EncodableList& filters) { const std::wstring spec_delimiter = L";"; const std::wstring file_wildcard = L"*."; std::vector filter_specs; // Temporary ownership of the constructed strings whose data is used in // filter_specs, so that they live until the call to SetFileTypes is done. std::vector filter_names; std::vector filter_extensions; filter_extensions.reserve(filters.size()); filter_names.reserve(filters.size()); for (const EncodableValue& filter_info_value : filters) { const auto& type_group = std::any_cast( std::get(filter_info_value)); filter_names.push_back(Utf16FromUtf8(type_group.label())); filter_extensions.push_back(L""); std::wstring& spec = filter_extensions.back(); if (type_group.extensions().empty()) { spec += L"*.*"; } else { for (const EncodableValue& extension : type_group.extensions()) { if (!spec.empty()) { spec += spec_delimiter; } spec += file_wildcard + Utf16FromUtf8(std::get(extension)); } } filter_specs.push_back({filter_names.back().c_str(), spec.c_str()}); } last_result_ = dialog_controller_->SetFileTypes( static_cast(filter_specs.size()), filter_specs.data()); } // Displays the dialog, and returns the selected files, or nullopt on error. std::optional Show(HWND parent_window) { assert(dialog_controller_); last_result_ = dialog_controller_->Show(parent_window); if (!SUCCEEDED(last_result_)) { return std::nullopt; } EncodableList files; if (is_open_dialog_) { IShellItemArrayPtr shell_items; last_result_ = dialog_controller_->GetResults(&shell_items); if (!SUCCEEDED(last_result_)) { return std::nullopt; } IEnumShellItemsPtr item_enumerator; last_result_ = shell_items->EnumItems(&item_enumerator); if (!SUCCEEDED(last_result_)) { return std::nullopt; } IShellItemPtr shell_item; while (item_enumerator->Next(1, &shell_item, nullptr) == S_OK) { files.push_back(EncodableValue(GetPathForShellItem(shell_item))); } } else { IShellItemPtr shell_item; last_result_ = dialog_controller_->GetResult(&shell_item); if (!SUCCEEDED(last_result_)) { return std::nullopt; } files.push_back(EncodableValue(GetPathForShellItem(shell_item))); } return files; } // Returns the result of the last Win32 API call related to this object. HRESULT last_result() { return last_result_; } private: // The dialog controller that all interactions are mediated through, to allow // for unit testing. std::unique_ptr dialog_controller_; bool is_open_dialog_; bool opening_directory_ = false; HRESULT last_result_; }; ErrorOr ShowDialog( const FileDialogControllerFactory& dialog_factory, HWND parent_window, DialogMode mode, const SelectionOptions& options, const std::string* initial_directory, const std::string* suggested_name, const std::string* confirm_label) { IID dialog_type = mode == DialogMode::save ? CLSID_FileSaveDialog : CLSID_FileOpenDialog; DialogWrapper dialog(dialog_factory, dialog_type); if (!SUCCEEDED(dialog.last_result())) { return FlutterError("System error", "Could not create dialog", EncodableValue(dialog.last_result())); } FILEOPENDIALOGOPTIONS dialog_options = 0; if (options.select_folders()) { dialog_options |= FOS_PICKFOLDERS; } if (options.allow_multiple()) { dialog_options |= FOS_ALLOWMULTISELECT; } if (dialog_options != 0) { dialog.AddOptions(dialog_options); } if (initial_directory) { dialog.SetFolder(*initial_directory); } if (suggested_name) { dialog.SetFileName(*suggested_name); } if (confirm_label) { dialog.SetOkButtonLabel(*confirm_label); } if (!options.allowed_types().empty()) { dialog.SetFileTypeFilters(options.allowed_types()); } std::optional files = dialog.Show(parent_window); if (!files) { if (dialog.last_result() != HRESULT_FROM_WIN32(ERROR_CANCELLED)) { return FlutterError("System error", "Could not show dialog", EncodableValue(dialog.last_result())); } else { return EncodableList(); } } return std::move(files.value()); } // Returns the top-level window that owns |view|. HWND GetRootWindow(flutter::FlutterView* view) { return ::GetAncestor(view->GetNativeWindow(), GA_ROOT); } } // namespace // static void FileSelectorPlugin::RegisterWithRegistrar( flutter::PluginRegistrarWindows* registrar) { std::unique_ptr plugin = std::make_unique( [registrar] { return GetRootWindow(registrar->GetView()); }, std::make_unique()); FileSelectorApi::SetUp(registrar->messenger(), plugin.get()); registrar->AddPlugin(std::move(plugin)); } FileSelectorPlugin::FileSelectorPlugin( FlutterRootWindowProvider window_provider, std::unique_ptr dialog_controller_factory) : get_root_window_(std::move(window_provider)), controller_factory_(std::move(dialog_controller_factory)) {} FileSelectorPlugin::~FileSelectorPlugin() = default; ErrorOr FileSelectorPlugin::ShowOpenDialog( const SelectionOptions& options, const std::string* initialDirectory, const std::string* confirmButtonText) { return ShowDialog(*controller_factory_, get_root_window_(), DialogMode::open, options, initialDirectory, nullptr, confirmButtonText); } ErrorOr FileSelectorPlugin::ShowSaveDialog( const SelectionOptions& options, const std::string* initialDirectory, const std::string* suggestedName, const std::string* confirmButtonText) { return ShowDialog(*controller_factory_, get_root_window_(), DialogMode::save, options, initialDirectory, suggestedName, confirmButtonText); } } // namespace file_selector_windows ================================================ FILE: packages/file_selector/file_selector_windows/windows/file_selector_plugin.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_FILE_SELECTOR_FILE_SELECTOR_WINDOWS_WINDOWS_FILE_SELECTOR_PLUGIN_H_ #define PACKAGES_FILE_SELECTOR_FILE_SELECTOR_WINDOWS_WINDOWS_FILE_SELECTOR_PLUGIN_H_ #include #include #include #include "file_dialog_controller.h" #include "messages.g.h" namespace file_selector_windows { // Abstraction for accessing the Flutter view's root window, to allow for faking // in unit tests without creating fake window hierarchies, as well as to work // around https://github.com/flutter/flutter/issues/90694. using FlutterRootWindowProvider = std::function; class FileSelectorPlugin : public flutter::Plugin, public FileSelectorApi { public: static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); // Creates a new plugin instance for the given registar, using the given // factory to create native dialog controllers. FileSelectorPlugin( FlutterRootWindowProvider window_provider, std::unique_ptr dialog_controller_factory); virtual ~FileSelectorPlugin(); // FileSelectorApi ErrorOr ShowOpenDialog( const SelectionOptions& options, const std::string* initial_directory, const std::string* confirm_button_text) override; ErrorOr ShowSaveDialog( const SelectionOptions& options, const std::string* initialDirectory, const std::string* suggestedName, const std::string* confirmButtonText) override; private: // The provider for the root window to attach the dialog to. FlutterRootWindowProvider get_root_window_; // The factory for creating dialog controller instances. std::unique_ptr controller_factory_; }; } // namespace file_selector_windows #endif // PACKAGES_FILE_SELECTOR_FILE_SELECTOR_WINDOWS_WINDOWS_FILE_SELECTOR_PLUGIN_H_ ================================================ FILE: packages/file_selector/file_selector_windows/windows/file_selector_windows.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "include/file_selector_windows/file_selector_windows.h" #include #include "file_selector_plugin.h" void FileSelectorWindowsRegisterWithRegistrar( FlutterDesktopPluginRegistrarRef registrar) { file_selector_windows::FileSelectorPlugin::RegisterWithRegistrar( flutter::PluginRegistrarManager::GetInstance() ->GetRegistrar(registrar)); } ================================================ FILE: packages/file_selector/file_selector_windows/windows/include/file_selector_windows/file_selector_windows.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_FILE_SELECTOR_FILE_SELECTOR_WINDOWS_WINDOWS_INCLUDE_FILE_SELECTOR_WINDOWS_FILE_SELECTOR_WINDOWS_H_ #define PACKAGES_FILE_SELECTOR_FILE_SELECTOR_WINDOWS_WINDOWS_INCLUDE_FILE_SELECTOR_WINDOWS_FILE_SELECTOR_WINDOWS_H_ #include #ifdef FLUTTER_PLUGIN_IMPL #define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) #else #define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) #endif #if defined(__cplusplus) extern "C" { #endif FLUTTER_PLUGIN_EXPORT void FileSelectorWindowsRegisterWithRegistrar( FlutterDesktopPluginRegistrarRef registrar); #if defined(__cplusplus) } // extern "C" #endif #endif // PACKAGES_FILE_SELECTOR_FILE_SELECTOR_WINDOWS_WINDOWS_INCLUDE_FILE_SELECTOR_WINDOWS_FILE_SELECTOR_WINDOWS_H_ ================================================ FILE: packages/file_selector/file_selector_windows/windows/messages.g.cpp ================================================ // Copyright 2013 The Flutter 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.2.5), 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 file_selector_windows { /* TypeGroup */ const std::string& TypeGroup::label() const { return label_; } void TypeGroup::set_label(std::string_view value_arg) { label_ = value_arg; } const flutter::EncodableList& TypeGroup::extensions() const { return extensions_; } void TypeGroup::set_extensions(const flutter::EncodableList& value_arg) { extensions_ = value_arg; } flutter::EncodableMap TypeGroup::ToEncodableMap() const { return flutter::EncodableMap{ {flutter::EncodableValue("label"), flutter::EncodableValue(label_)}, {flutter::EncodableValue("extensions"), flutter::EncodableValue(extensions_)}, }; } TypeGroup::TypeGroup() {} TypeGroup::TypeGroup(flutter::EncodableMap map) { auto& encodable_label = map.at(flutter::EncodableValue("label")); if (const std::string* pointer_label = std::get_if(&encodable_label)) { label_ = *pointer_label; } auto& encodable_extensions = map.at(flutter::EncodableValue("extensions")); if (const flutter::EncodableList* pointer_extensions = std::get_if(&encodable_extensions)) { extensions_ = *pointer_extensions; } } /* SelectionOptions */ bool SelectionOptions::allow_multiple() const { return allow_multiple_; } void SelectionOptions::set_allow_multiple(bool value_arg) { allow_multiple_ = value_arg; } bool SelectionOptions::select_folders() const { return select_folders_; } void SelectionOptions::set_select_folders(bool value_arg) { select_folders_ = value_arg; } const flutter::EncodableList& SelectionOptions::allowed_types() const { return allowed_types_; } void SelectionOptions::set_allowed_types( const flutter::EncodableList& value_arg) { allowed_types_ = value_arg; } flutter::EncodableMap SelectionOptions::ToEncodableMap() const { return flutter::EncodableMap{ {flutter::EncodableValue("allowMultiple"), flutter::EncodableValue(allow_multiple_)}, {flutter::EncodableValue("selectFolders"), flutter::EncodableValue(select_folders_)}, {flutter::EncodableValue("allowedTypes"), flutter::EncodableValue(allowed_types_)}, }; } SelectionOptions::SelectionOptions() {} SelectionOptions::SelectionOptions(flutter::EncodableMap map) { auto& encodable_allow_multiple = map.at(flutter::EncodableValue("allowMultiple")); if (const bool* pointer_allow_multiple = std::get_if(&encodable_allow_multiple)) { allow_multiple_ = *pointer_allow_multiple; } auto& encodable_select_folders = map.at(flutter::EncodableValue("selectFolders")); if (const bool* pointer_select_folders = std::get_if(&encodable_select_folders)) { select_folders_ = *pointer_select_folders; } auto& encodable_allowed_types = map.at(flutter::EncodableValue("allowedTypes")); if (const flutter::EncodableList* pointer_allowed_types = std::get_if(&encodable_allowed_types)) { allowed_types_ = *pointer_allowed_types; } } FileSelectorApiCodecSerializer::FileSelectorApiCodecSerializer() {} flutter::EncodableValue FileSelectorApiCodecSerializer::ReadValueOfType( uint8_t type, flutter::ByteStreamReader* stream) const { switch (type) { case 128: return flutter::CustomEncodableValue( SelectionOptions(std::get(ReadValue(stream)))); case 129: return flutter::CustomEncodableValue( TypeGroup(std::get(ReadValue(stream)))); default: return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); } } void FileSelectorApiCodecSerializer::WriteValue( const flutter::EncodableValue& value, flutter::ByteStreamWriter* stream) const { if (const flutter::CustomEncodableValue* custom_value = std::get_if(&value)) { if (custom_value->type() == typeid(SelectionOptions)) { stream->WriteByte(128); WriteValue( std::any_cast(*custom_value).ToEncodableMap(), stream); return; } if (custom_value->type() == typeid(TypeGroup)) { stream->WriteByte(129); WriteValue(std::any_cast(*custom_value).ToEncodableMap(), stream); return; } } flutter::StandardCodecSerializer::WriteValue(value, stream); } /** The codec used by FileSelectorApi. */ const flutter::StandardMessageCodec& FileSelectorApi::GetCodec() { return flutter::StandardMessageCodec::GetInstance( &FileSelectorApiCodecSerializer::GetInstance()); } /** Sets up an instance of `FileSelectorApi` to handle messages through the * `binary_messenger`. */ void FileSelectorApi::SetUp(flutter::BinaryMessenger* binary_messenger, FileSelectorApi* api) { { auto channel = std::make_unique>( binary_messenger, "dev.flutter.pigeon.FileSelectorApi.showOpenDialog", &GetCodec()); if (api != nullptr) { channel->SetMessageHandler( [api](const flutter::EncodableValue& message, const flutter::MessageReply& reply) { flutter::EncodableMap wrapped; try { const auto& args = std::get(message); const auto& encodable_options_arg = args.at(0); if (encodable_options_arg.IsNull()) { wrapped.emplace(flutter::EncodableValue("error"), WrapError("options_arg unexpectedly null.")); reply(wrapped); return; } const auto& options_arg = std::any_cast( std::get( encodable_options_arg)); const auto& encodable_initial_directory_arg = args.at(1); const auto* initial_directory_arg = std::get_if(&encodable_initial_directory_arg); const auto& encodable_confirm_button_text_arg = args.at(2); const auto* confirm_button_text_arg = std::get_if(&encodable_confirm_button_text_arg); ErrorOr output = api->ShowOpenDialog( options_arg, initial_directory_arg, confirm_button_text_arg); if (output.has_error()) { wrapped.emplace(flutter::EncodableValue("error"), WrapError(output.error())); } else { wrapped.emplace( flutter::EncodableValue("result"), flutter::EncodableValue(std::move(output).TakeValue())); } } catch (const std::exception& exception) { wrapped.emplace(flutter::EncodableValue("error"), WrapError(exception.what())); } reply(wrapped); }); } else { channel->SetMessageHandler(nullptr); } } { auto channel = std::make_unique>( binary_messenger, "dev.flutter.pigeon.FileSelectorApi.showSaveDialog", &GetCodec()); if (api != nullptr) { channel->SetMessageHandler( [api](const flutter::EncodableValue& message, const flutter::MessageReply& reply) { flutter::EncodableMap wrapped; try { const auto& args = std::get(message); const auto& encodable_options_arg = args.at(0); if (encodable_options_arg.IsNull()) { wrapped.emplace(flutter::EncodableValue("error"), WrapError("options_arg unexpectedly null.")); reply(wrapped); return; } const auto& options_arg = std::any_cast( std::get( encodable_options_arg)); const auto& encodable_initial_directory_arg = args.at(1); const auto* initial_directory_arg = std::get_if(&encodable_initial_directory_arg); const auto& encodable_suggested_name_arg = args.at(2); const auto* suggested_name_arg = std::get_if(&encodable_suggested_name_arg); const auto& encodable_confirm_button_text_arg = args.at(3); const auto* confirm_button_text_arg = std::get_if(&encodable_confirm_button_text_arg); ErrorOr output = api->ShowSaveDialog( options_arg, initial_directory_arg, suggested_name_arg, confirm_button_text_arg); if (output.has_error()) { wrapped.emplace(flutter::EncodableValue("error"), WrapError(output.error())); } else { wrapped.emplace( flutter::EncodableValue("result"), flutter::EncodableValue(std::move(output).TakeValue())); } } catch (const std::exception& exception) { wrapped.emplace(flutter::EncodableValue("error"), WrapError(exception.what())); } reply(wrapped); }); } else { channel->SetMessageHandler(nullptr); } } } flutter::EncodableMap FileSelectorApi::WrapError( std::string_view error_message) { return flutter::EncodableMap( {{flutter::EncodableValue("message"), flutter::EncodableValue(std::string(error_message))}, {flutter::EncodableValue("code"), flutter::EncodableValue("Error")}, {flutter::EncodableValue("details"), flutter::EncodableValue()}}); } flutter::EncodableMap FileSelectorApi::WrapError(const FlutterError& error) { return flutter::EncodableMap( {{flutter::EncodableValue("message"), flutter::EncodableValue(error.message())}, {flutter::EncodableValue("code"), flutter::EncodableValue(error.code())}, {flutter::EncodableValue("details"), error.details()}}); } } // namespace file_selector_windows ================================================ FILE: packages/file_selector/file_selector_windows/windows/messages.g.h ================================================ // Copyright 2013 The Flutter 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.2.5), do not edit directly. // See also: https://pub.dev/packages/pigeon #ifndef PIGEON_MESSAGES_G_FILE_SELECTOR_WINDOWS_H_ #define PIGEON_MESSAGES_G_FILE_SELECTOR_WINDOWS_H_ #include #include #include #include #include #include #include namespace file_selector_windows { /* Generated class from Pigeon. */ class FlutterError { public: FlutterError(const std::string& code) : code_(code) {} FlutterError(const std::string& code, const std::string& message) : code_(code), message_(message) {} 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 FileSelectorApi; ErrorOr() = default; T TakeValue() && { return std::get(std::move(v_)); } std::variant v_; }; /* Generated class from Pigeon that represents data sent in messages. */ class TypeGroup { public: TypeGroup(); const std::string& label() const; void set_label(std::string_view value_arg); const flutter::EncodableList& extensions() const; void set_extensions(const flutter::EncodableList& value_arg); private: TypeGroup(flutter::EncodableMap map); flutter::EncodableMap ToEncodableMap() const; friend class FileSelectorApi; friend class FileSelectorApiCodecSerializer; std::string label_; flutter::EncodableList extensions_; }; /* Generated class from Pigeon that represents data sent in messages. */ class SelectionOptions { public: SelectionOptions(); bool allow_multiple() const; void set_allow_multiple(bool value_arg); bool select_folders() const; void set_select_folders(bool value_arg); const flutter::EncodableList& allowed_types() const; void set_allowed_types(const flutter::EncodableList& value_arg); private: SelectionOptions(flutter::EncodableMap map); flutter::EncodableMap ToEncodableMap() const; friend class FileSelectorApi; friend class FileSelectorApiCodecSerializer; bool allow_multiple_; bool select_folders_; flutter::EncodableList allowed_types_; }; class FileSelectorApiCodecSerializer : public flutter::StandardCodecSerializer { public: inline static FileSelectorApiCodecSerializer& GetInstance() { static FileSelectorApiCodecSerializer sInstance; return sInstance; } FileSelectorApiCodecSerializer(); public: void WriteValue(const flutter::EncodableValue& value, flutter::ByteStreamWriter* stream) const override; protected: flutter::EncodableValue ReadValueOfType( uint8_t type, flutter::ByteStreamReader* stream) const override; }; /* Generated class from Pigeon that represents a handler of messages from * Flutter. */ class FileSelectorApi { public: FileSelectorApi(const FileSelectorApi&) = delete; FileSelectorApi& operator=(const FileSelectorApi&) = delete; virtual ~FileSelectorApi(){}; virtual ErrorOr ShowOpenDialog( const SelectionOptions& options, const std::string* initial_directory, const std::string* confirm_button_text) = 0; virtual ErrorOr ShowSaveDialog( const SelectionOptions& options, const std::string* initial_directory, const std::string* suggested_name, const std::string* confirm_button_text) = 0; /** The codec used by FileSelectorApi. */ static const flutter::StandardMessageCodec& GetCodec(); /** Sets up an instance of `FileSelectorApi` to handle messages through the * `binary_messenger`. */ static void SetUp(flutter::BinaryMessenger* binary_messenger, FileSelectorApi* api); static flutter::EncodableMap WrapError(std::string_view error_message); static flutter::EncodableMap WrapError(const FlutterError& error); protected: FileSelectorApi() = default; }; } // namespace file_selector_windows #endif // PIGEON_MESSAGES_G_FILE_SELECTOR_WINDOWS_H_ ================================================ FILE: packages/file_selector/file_selector_windows/windows/string_utils.cpp ================================================ // Copyright 2013 The Flutter 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 "string_utils.h" #include #include #include namespace file_selector_windows { // Converts the given UTF-16 string to UTF-8. std::string Utf8FromUtf16(std::wstring_view utf16_string) { if (utf16_string.empty()) { return std::string(); } int target_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string.data(), static_cast(utf16_string.length()), nullptr, 0, nullptr, nullptr); if (target_length == 0) { return std::string(); } std::string utf8_string; utf8_string.resize(target_length); int converted_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string.data(), static_cast(utf16_string.length()), utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); } return utf8_string; } // Converts the given UTF-8 string to UTF-16. std::wstring Utf16FromUtf8(std::string_view utf8_string) { if (utf8_string.empty()) { return std::wstring(); } int target_length = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), static_cast(utf8_string.length()), nullptr, 0); if (target_length == 0) { return std::wstring(); } std::wstring utf16_string; utf16_string.resize(target_length); int converted_length = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), static_cast(utf8_string.length()), utf16_string.data(), target_length); if (converted_length == 0) { return std::wstring(); } return utf16_string; } } // namespace file_selector_windows ================================================ FILE: packages/file_selector/file_selector_windows/windows/string_utils.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_FILE_SELECTOR_FILE_SELECTOR_WINDOWS_WINDOWS_STRING_UTILS_H_ #define PACKAGES_FILE_SELECTOR_FILE_SELECTOR_WINDOWS_WINDOWS_STRING_UTILS_H_ #include #include namespace file_selector_windows { // Converts the given UTF-16 string to UTF-8. std::string Utf8FromUtf16(std::wstring_view utf16_string); // Converts the given UTF-8 string to UTF-16. std::wstring Utf16FromUtf8(std::string_view utf8_string); } // namespace file_selector_windows #endif // PACKAGES_FILE_SELECTOR_FILE_SELECTOR_WINDOWS_WINDOWS_STRING_UTILS_H_ ================================================ FILE: packages/file_selector/file_selector_windows/windows/test/file_selector_plugin_test.cpp ================================================ // Copyright 2013 The Flutter 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 "file_selector_plugin.h" #include #include #include #include #include #include #include #include #include #include #include #include "file_dialog_controller.h" #include "string_utils.h" #include "test/test_file_dialog_controller.h" #include "test/test_utils.h" namespace file_selector_windows { namespace test { namespace { using flutter::CustomEncodableValue; using flutter::EncodableList; using flutter::EncodableMap; using flutter::EncodableValue; // These structs and classes are a workaround for // https://github.com/flutter/flutter/issues/104286 and // https://github.com/flutter/flutter/issues/104653. struct AllowMultipleArg { bool value = false; AllowMultipleArg(bool val) : value(val) {} }; struct SelectFoldersArg { bool value = false; SelectFoldersArg(bool val) : value(val) {} }; SelectionOptions CreateOptions(AllowMultipleArg allow_multiple, SelectFoldersArg select_folders, const EncodableList& allowed_types) { SelectionOptions options; options.set_allow_multiple(allow_multiple.value); options.set_select_folders(select_folders.value); options.set_allowed_types(allowed_types); return options; } TypeGroup CreateTypeGroup(std::string_view label, const EncodableList& extensions) { TypeGroup group; group.set_label(label); group.set_extensions(extensions); return group; } } // namespace TEST(FileSelectorPlugin, TestOpenSimple) { const HWND fake_window = reinterpret_cast(1337); ScopedTestShellItem fake_selected_file; IShellItemArrayPtr fake_result_array; ::SHCreateShellItemArrayFromShellItem(fake_selected_file.file(), IID_PPV_ARGS(&fake_result_array)); bool shown = false; MockShow show_validator = [&shown, fake_result_array, fake_window]( const TestFileDialogController& dialog, HWND parent) { shown = true; EXPECT_EQ(parent, fake_window); // Validate options. FILEOPENDIALOGOPTIONS options; dialog.GetOptions(&options); EXPECT_EQ(options & FOS_ALLOWMULTISELECT, 0U); EXPECT_EQ(options & FOS_PICKFOLDERS, 0U); return MockShowResult(fake_result_array); }; FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); ErrorOr result = plugin.ShowOpenDialog( CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), EncodableList()), nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value(); EXPECT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); } TEST(FileSelectorPlugin, TestOpenWithArguments) { const HWND fake_window = reinterpret_cast(1337); ScopedTestShellItem fake_selected_file; IShellItemArrayPtr fake_result_array; ::SHCreateShellItemArrayFromShellItem(fake_selected_file.file(), IID_PPV_ARGS(&fake_result_array)); bool shown = false; MockShow show_validator = [&shown, fake_result_array, fake_window]( const TestFileDialogController& dialog, HWND parent) { shown = true; EXPECT_EQ(parent, fake_window); // Validate arguments. EXPECT_EQ(dialog.GetDialogFolderPath(), L"C:\\Program Files"); // Make sure that the folder was called via SetFolder, not SetDefaultFolder. EXPECT_EQ(dialog.GetSetFolderPath(), L"C:\\Program Files"); EXPECT_EQ(dialog.GetOkButtonLabel(), L"Open it!"); return MockShowResult(fake_result_array); }; FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); // This directory must exist. std::string initial_directory("C:\\Program Files"); std::string confirm_button("Open it!"); ErrorOr result = plugin.ShowOpenDialog( CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), EncodableList()), &initial_directory, &confirm_button); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value(); EXPECT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); } TEST(FileSelectorPlugin, TestOpenMultiple) { const HWND fake_window = reinterpret_cast(1337); ScopedTestFileIdList fake_selected_file_1; ScopedTestFileIdList fake_selected_file_2; LPCITEMIDLIST fake_selected_files[] = { fake_selected_file_1.file(), fake_selected_file_2.file(), }; IShellItemArrayPtr fake_result_array; ::SHCreateShellItemArrayFromIDLists(2, fake_selected_files, &fake_result_array); bool shown = false; MockShow show_validator = [&shown, fake_result_array, fake_window]( const TestFileDialogController& dialog, HWND parent) { shown = true; EXPECT_EQ(parent, fake_window); // Validate options. FILEOPENDIALOGOPTIONS options; dialog.GetOptions(&options); EXPECT_NE(options & FOS_ALLOWMULTISELECT, 0U); EXPECT_EQ(options & FOS_PICKFOLDERS, 0U); return MockShowResult(fake_result_array); }; FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); ErrorOr result = plugin.ShowOpenDialog( CreateOptions(AllowMultipleArg(true), SelectFoldersArg(false), EncodableList()), nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value(); EXPECT_EQ(paths.size(), 2); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file_1.path())); EXPECT_EQ(std::get(paths[1]), Utf8FromUtf16(fake_selected_file_2.path())); } TEST(FileSelectorPlugin, TestOpenWithFilter) { const HWND fake_window = reinterpret_cast(1337); ScopedTestShellItem fake_selected_file; IShellItemArrayPtr fake_result_array; ::SHCreateShellItemArrayFromShellItem(fake_selected_file.file(), IID_PPV_ARGS(&fake_result_array)); const EncodableValue text_group = CustomEncodableValue(CreateTypeGroup("Text", EncodableList({ EncodableValue("txt"), EncodableValue("json"), }))); const EncodableValue image_group = CustomEncodableValue(CreateTypeGroup("Images", EncodableList({ EncodableValue("png"), EncodableValue("gif"), EncodableValue("jpeg"), }))); const EncodableValue any_group = CustomEncodableValue(CreateTypeGroup("Any", EncodableList())); bool shown = false; MockShow show_validator = [&shown, fake_result_array, fake_window]( const TestFileDialogController& dialog, HWND parent) { shown = true; EXPECT_EQ(parent, fake_window); // Validate filter. const std::vector& filters = dialog.GetFileTypes(); EXPECT_EQ(filters.size(), 3U); if (filters.size() == 3U) { EXPECT_EQ(filters[0].name, L"Text"); EXPECT_EQ(filters[0].spec, L"*.txt;*.json"); EXPECT_EQ(filters[1].name, L"Images"); EXPECT_EQ(filters[1].spec, L"*.png;*.gif;*.jpeg"); EXPECT_EQ(filters[2].name, L"Any"); EXPECT_EQ(filters[2].spec, L"*.*"); } return MockShowResult(fake_result_array); }; FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); ErrorOr result = plugin.ShowOpenDialog( CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), EncodableList({ text_group, image_group, any_group, })), nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value(); EXPECT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); } TEST(FileSelectorPlugin, TestOpenCancel) { const HWND fake_window = reinterpret_cast(1337); bool shown = false; MockShow show_validator = [&shown, fake_window]( const TestFileDialogController& dialog, HWND parent) { shown = true; return MockShowResult(); }; FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); ErrorOr result = plugin.ShowOpenDialog( CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), EncodableList()), nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value(); EXPECT_EQ(paths.size(), 0); } TEST(FileSelectorPlugin, TestSaveSimple) { const HWND fake_window = reinterpret_cast(1337); ScopedTestShellItem fake_selected_file; bool shown = false; MockShow show_validator = [&shown, fake_result = fake_selected_file.file(), fake_window]( const TestFileDialogController& dialog, HWND parent) { shown = true; EXPECT_EQ(parent, fake_window); // Validate options. FILEOPENDIALOGOPTIONS options; dialog.GetOptions(&options); EXPECT_EQ(options & FOS_ALLOWMULTISELECT, 0U); EXPECT_EQ(options & FOS_PICKFOLDERS, 0U); return MockShowResult(fake_result); }; FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); ErrorOr result = plugin.ShowSaveDialog( CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), EncodableList()), nullptr, nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value(); EXPECT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); } TEST(FileSelectorPlugin, TestSaveWithArguments) { const HWND fake_window = reinterpret_cast(1337); ScopedTestShellItem fake_selected_file; bool shown = false; MockShow show_validator = [&shown, fake_result = fake_selected_file.file(), fake_window]( const TestFileDialogController& dialog, HWND parent) { shown = true; EXPECT_EQ(parent, fake_window); // Validate arguments. EXPECT_EQ(dialog.GetDialogFolderPath(), L"C:\\Program Files"); // Make sure that the folder was called via SetFolder, not // SetDefaultFolder. EXPECT_EQ(dialog.GetSetFolderPath(), L"C:\\Program Files"); EXPECT_EQ(dialog.GetFileName(), L"a name"); EXPECT_EQ(dialog.GetOkButtonLabel(), L"Save it!"); return MockShowResult(fake_result); }; FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); // This directory must exist. std::string initial_directory("C:\\Program Files"); std::string suggested_name("a name"); std::string confirm_button("Save it!"); ErrorOr result = plugin.ShowSaveDialog( CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), EncodableList()), &initial_directory, &suggested_name, &confirm_button); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value(); EXPECT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); } TEST(FileSelectorPlugin, TestSaveCancel) { const HWND fake_window = reinterpret_cast(1337); bool shown = false; MockShow show_validator = [&shown, fake_window]( const TestFileDialogController& dialog, HWND parent) { shown = true; return MockShowResult(); }; FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); ErrorOr result = plugin.ShowSaveDialog( CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), EncodableList()), nullptr, nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value(); EXPECT_EQ(paths.size(), 0); } TEST(FileSelectorPlugin, TestGetDirectorySimple) { const HWND fake_window = reinterpret_cast(1337); IShellItemPtr fake_selected_directory; // This must be a directory that actually exists. ::SHCreateItemFromParsingName(L"C:\\Program Files", nullptr, IID_PPV_ARGS(&fake_selected_directory)); IShellItemArrayPtr fake_result_array; ::SHCreateShellItemArrayFromShellItem(fake_selected_directory, IID_PPV_ARGS(&fake_result_array)); bool shown = false; MockShow show_validator = [&shown, fake_result_array, fake_window]( const TestFileDialogController& dialog, HWND parent) { shown = true; EXPECT_EQ(parent, fake_window); // Validate options. FILEOPENDIALOGOPTIONS options; dialog.GetOptions(&options); EXPECT_EQ(options & FOS_ALLOWMULTISELECT, 0U); EXPECT_NE(options & FOS_PICKFOLDERS, 0U); return MockShowResult(fake_result_array); }; FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); ErrorOr result = plugin.ShowOpenDialog( CreateOptions(AllowMultipleArg(false), SelectFoldersArg(true), EncodableList()), nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value(); EXPECT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), "C:\\Program Files"); } TEST(FileSelectorPlugin, TestGetDirectoryCancel) { const HWND fake_window = reinterpret_cast(1337); bool shown = false; MockShow show_validator = [&shown, fake_window]( const TestFileDialogController& dialog, HWND parent) { shown = true; return MockShowResult(); }; FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); ErrorOr result = plugin.ShowOpenDialog( CreateOptions(AllowMultipleArg(false), SelectFoldersArg(true), EncodableList()), nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value(); EXPECT_EQ(paths.size(), 0); } } // namespace test } // namespace file_selector_windows ================================================ FILE: packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.cpp ================================================ // Copyright 2013 The Flutter 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 "test/test_file_dialog_controller.h" #include #include #include #include namespace file_selector_windows { namespace test { TestFileDialogController::TestFileDialogController(IFileDialog* dialog, MockShow mock_show) : dialog_(dialog), mock_show_(std::move(mock_show)), FileDialogController(dialog) {} TestFileDialogController::~TestFileDialogController() {} HRESULT TestFileDialogController::SetFolder(IShellItem* folder) { wchar_t* path_chars = nullptr; if (SUCCEEDED(folder->GetDisplayName(SIGDN_FILESYSPATH, &path_chars))) { set_folder_path_ = path_chars; } else { set_folder_path_ = L""; } return FileDialogController::SetFolder(folder); } HRESULT TestFileDialogController::SetFileTypes(UINT count, COMDLG_FILTERSPEC* filters) { filter_groups_.clear(); for (unsigned int i = 0; i < count; ++i) { filter_groups_.push_back( DialogFilter(filters[i].pszName, filters[i].pszSpec)); } return FileDialogController::SetFileTypes(count, filters); } HRESULT TestFileDialogController::SetOkButtonLabel(const wchar_t* text) { ok_button_label_ = text; return FileDialogController::SetOkButtonLabel(text); } HRESULT TestFileDialogController::Show(HWND parent) { mock_result_ = mock_show_(*this, parent); if (std::holds_alternative(mock_result_)) { return HRESULT_FROM_WIN32(ERROR_CANCELLED); } return S_OK; } HRESULT TestFileDialogController::GetResult(IShellItem** out_item) const { *out_item = std::get(mock_result_); (*out_item)->AddRef(); return S_OK; } HRESULT TestFileDialogController::GetResults( IShellItemArray** out_items) const { *out_items = std::get(mock_result_); (*out_items)->AddRef(); return S_OK; } std::wstring TestFileDialogController::GetSetFolderPath() const { return set_folder_path_; } std::wstring TestFileDialogController::GetDialogFolderPath() const { IShellItemPtr item; if (!SUCCEEDED(dialog_->GetFolder(&item))) { return L""; } wchar_t* path_chars = nullptr; if (!SUCCEEDED(item->GetDisplayName(SIGDN_FILESYSPATH, &path_chars))) { return L""; } std::wstring path(path_chars); ::CoTaskMemFree(path_chars); return path; } std::wstring TestFileDialogController::GetFileName() const { wchar_t* name_chars = nullptr; if (!SUCCEEDED(dialog_->GetFileName(&name_chars))) { return L""; } std::wstring name(name_chars); ::CoTaskMemFree(name_chars); return name; } const std::vector& TestFileDialogController::GetFileTypes() const { return filter_groups_; } std::wstring TestFileDialogController::GetOkButtonLabel() const { return ok_button_label_; } // ---------------------------------------- TestFileDialogControllerFactory::TestFileDialogControllerFactory( MockShow mock_show) : mock_show_(std::move(mock_show)) {} TestFileDialogControllerFactory::~TestFileDialogControllerFactory() {} std::unique_ptr TestFileDialogControllerFactory::CreateController(IFileDialog* dialog) const { return std::make_unique(dialog, mock_show_); } } // namespace test } // namespace file_selector_windows ================================================ FILE: packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_FILE_SELECTOR_FILE_SELECTOR_WINDOWS_WINDOWS_TEST_TEST_FILE_DIALOG_CONTROLLER_H_ #define PACKAGES_FILE_SELECTOR_FILE_SELECTOR_WINDOWS_WINDOWS_TEST_TEST_FILE_DIALOG_CONTROLLER_H_ #include #include #include #include #include #include #include #include "file_dialog_controller.h" #include "test/test_utils.h" _COM_SMARTPTR_TYPEDEF(IFileDialog, IID_IFileDialog); namespace file_selector_windows { namespace test { class TestFileDialogController; // A value to use for GetResult(s) in TestFileDialogController. The type depends // on whether the dialog is an open or save dialog. using MockShowResult = std::variant; // Called for TestFileDialogController::Show, to do validation and provide a // mock return value for GetResult(s). using MockShow = std::function; // A C++-friendly version of a COMDLG_FILTERSPEC. struct DialogFilter { std::wstring name; std::wstring spec; DialogFilter(const wchar_t* name, const wchar_t* spec) : name(name), spec(spec) {} }; // An extension of the normal file dialog controller that: // - Allows for inspection of set values. // - Allows faking the 'Show' interaction, providing tests an opportunity to // validate the dialog settings and provide a return value, via MockShow. class TestFileDialogController : public FileDialogController { public: TestFileDialogController(IFileDialog* dialog, MockShow mock_show); ~TestFileDialogController(); // FileDialogController: HRESULT SetFolder(IShellItem* folder) override; HRESULT SetFileTypes(UINT count, COMDLG_FILTERSPEC* filters) override; HRESULT SetOkButtonLabel(const wchar_t* text) override; HRESULT Show(HWND parent) override; HRESULT GetResult(IShellItem** out_item) const override; HRESULT GetResults(IShellItemArray** out_items) const override; // Accessors for validating IFileDialogController setter calls. // Gets the folder path set by FileDialogController::SetFolder. // // This exists because there are multiple ways that the value returned by // GetDialogFolderPath can be changed, so this allows specifically validating // calls to SetFolder. std::wstring GetSetFolderPath() const; // Gets dialog folder path by calling IFileDialog::GetFolder. std::wstring GetDialogFolderPath() const; std::wstring GetFileName() const; const std::vector& GetFileTypes() const; std::wstring GetOkButtonLabel() const; private: IFileDialogPtr dialog_; MockShow mock_show_; MockShowResult mock_result_; // The last set values, for IFileDialog properties that have setters but no // corresponding getters. std::wstring set_folder_path_; std::wstring ok_button_label_; std::vector filter_groups_; }; // A controller factory that vends TestFileDialogController instances. class TestFileDialogControllerFactory : public FileDialogControllerFactory { public: // Creates a factory whose instances use mock_show for the Show callback. TestFileDialogControllerFactory(MockShow mock_show); virtual ~TestFileDialogControllerFactory(); // Disallow copy and assign. TestFileDialogControllerFactory(const TestFileDialogControllerFactory&) = delete; TestFileDialogControllerFactory& operator=( const TestFileDialogControllerFactory&) = delete; // FileDialogControllerFactory: std::unique_ptr CreateController( IFileDialog* dialog) const override; private: MockShow mock_show_; }; } // namespace test } // namespace file_selector_windows #endif // PACKAGES_FILE_SELECTOR_FILE_SELECTOR_WINDOWS_WINDOWS_TEST_TEST_FILE_DIALOG_CONTROLLER_H_ ================================================ FILE: packages/file_selector/file_selector_windows/windows/test/test_main.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include int main(int argc, char** argv) { ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); testing::InitGoogleTest(&argc, argv); int exit_code = RUN_ALL_TESTS(); ::CoUninitialize(); return exit_code; } ================================================ FILE: packages/file_selector/file_selector_windows/windows/test/test_utils.cpp ================================================ // Copyright 2013 The Flutter 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 "test/test_utils.h" #include #include #include namespace file_selector_windows { namespace test { namespace { // Creates a temp file and returns its path. std::wstring CreateTempFile() { wchar_t temp_dir[MAX_PATH]; wchar_t temp_file[MAX_PATH]; wchar_t long_path[MAX_PATH]; ::GetTempPath(MAX_PATH, temp_dir); ::GetTempFileName(temp_dir, L"test", 0, temp_file); // Convert to long form to match what IShellItem queries will return. ::GetLongPathName(temp_file, long_path, MAX_PATH); return long_path; } } // namespace ScopedTestShellItem::ScopedTestShellItem() { path_ = CreateTempFile(); ::SHCreateItemFromParsingName(path_.c_str(), nullptr, IID_PPV_ARGS(&item_)); } ScopedTestShellItem::~ScopedTestShellItem() { ::DeleteFile(path_.c_str()); } ScopedTestFileIdList::ScopedTestFileIdList() { path_ = CreateTempFile(); item_ = ItemIdListPtr(::ILCreateFromPath(path_.c_str())); } ScopedTestFileIdList::~ScopedTestFileIdList() { ::DeleteFile(path_.c_str()); } } // namespace test } // namespace file_selector_windows ================================================ FILE: packages/file_selector/file_selector_windows/windows/test/test_utils.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_FILE_SELECTOR_FILE_SELECTOR_WINDOWS_WINDOWS_TEST_TEST_UTILS_H_ #define PACKAGES_FILE_SELECTOR_FILE_SELECTOR_WINDOWS_WINDOWS_TEST_TEST_UTILS_H_ #include #include #include #include #include #include #include #include #include #include "file_dialog_controller.h" _COM_SMARTPTR_TYPEDEF(IShellItem, IID_IShellItem); _COM_SMARTPTR_TYPEDEF(IShellItemArray, IID_IShellItemArray); namespace file_selector_windows { namespace test { // Creates a temp file, managed as an IShellItem, which will be deleted when // the instance goes out of scope. // // This creates a file on the filesystem since creating IShellItem instances for // files that don't exist is non-trivial. class ScopedTestShellItem { public: ScopedTestShellItem(); ~ScopedTestShellItem(); // Disallow copy and assign. ScopedTestShellItem(const ScopedTestShellItem&) = delete; ScopedTestShellItem& operator=(const ScopedTestShellItem&) = delete; // Returns the file's IShellItem reference. IShellItemPtr file() { return item_; } // Returns the file's path. const std::wstring& path() { return path_; } private: IShellItemPtr item_; std::wstring path_; }; // Creates a temp file, managed as an ITEMIDLIST, which will be deleted when // the instance goes out of scope. // // This creates a file on the filesystem since creating IShellItem instances for // files that don't exist is non-trivial, and this is intended for use in // creating IShellItemArray instances. class ScopedTestFileIdList { public: ScopedTestFileIdList(); ~ScopedTestFileIdList(); // Disallow copy and assign. ScopedTestFileIdList(const ScopedTestFileIdList&) = delete; ScopedTestFileIdList& operator=(const ScopedTestFileIdList&) = delete; // Returns the file's ITEMIDLIST reference. PIDLIST_ABSOLUTE file() { return item_.get(); } // Returns the file's path. const std::wstring& path() { return path_; } private: // Smart pointer for managing ITEMIDLIST instances. struct ItemIdListDeleter { void operator()(LPITEMIDLIST item) { if (item) { ::ILFree(item); } } }; using ItemIdListPtr = std::unique_ptr, ItemIdListDeleter>; ItemIdListPtr item_; std::wstring path_; }; } // namespace test } // namespace file_selector_windows #endif // PACKAGES_FILE_SELECTOR_FILE_SELECTOR_WINDOWS_WINDOWS_TEST_TEST_UTILS_H_ ================================================ FILE: packages/flutter_plugin_android_lifecycle/.gitignore ================================================ .DS_Store .dart_tool/ .packages .pub/ build/ ================================================ FILE: packages/flutter_plugin_android_lifecycle/.metadata ================================================ # 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: 0e605cc4dd83137f785769dea5e8ae7da1afb361 channel: master project_type: plugin ================================================ FILE: packages/flutter_plugin_android_lifecycle/AUTHORS ================================================ # 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> ================================================ FILE: packages/flutter_plugin_android_lifecycle/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 2.0.7 * Bumps gradle from 3.5.0 to 7.2.1. ## 2.0.6 * Adds OS version support information to README. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 2.0.5 * Updates compileSdkVersion to 31. ## 2.0.4 * Updated Android lint settings. * Remove placeholder Dart file. ## 2.0.3 * Remove references to the Android V1 embedding. ## 2.0.2 * Migrate maven repo from jcenter to mavenCentral. ## 2.0.1 * Make sure androidx.lifecycle.DefaultLifecycleObservable doesn't get shrunk away. ## 2.0.0 * Bump Dart SDK for null-safety compatibility. * Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) ## 1.0.12 * Update Flutter SDK constraint. ## 1.0.11 * Keep handling deprecated Android v1 classes for backward compatibility. ## 1.0.10 * Update android compileSdkVersion to 29. ## 1.0.9 * Let the no-op plugin implement the `FlutterPlugin` interface. ## 1.0.8 * Post-v2 Android embedding cleanup. ## 1.0.7 * Update Gradle version. Fixes https://github.com/flutter/flutter/issues/48724. * Fix CocoaPods podspec lint warnings. ## 1.0.6 * Make the pedantic dev_dependency explicit. ## 1.0.5 * Add notice in example this plugin only provides Android Lifecycle API. ## 1.0.4 * Require Flutter SDK 1.12.13 or greater. * Change to avoid reflection. ## 1.0.3 * Remove the deprecated `author:` field from pubspec.yaml * Require Flutter SDK 1.10.0 or greater. ## 1.0.2 * Adapt to the embedding API changes in https://github.com/flutter/engine/pull/13280 (only supports Activity Lifecycle). ## 1.0.1 * Register the E2E plugin in the example app. ## 1.0.0 * Introduces a `FlutterLifecycleAdapter`, which can be used by other plugins to obtain a `Lifecycle` reference from a `FlutterPluginBinding`. ================================================ FILE: packages/flutter_plugin_android_lifecycle/LICENSE ================================================ 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. ================================================ FILE: packages/flutter_plugin_android_lifecycle/README.md ================================================ # Flutter Android Lifecycle Plugin [![pub package](https://img.shields.io/pub/v/flutter_plugin_android_lifecycle.svg)](https://pub.dev/packages/flutter_plugin_android_lifecycle) A Flutter plugin for Android to allow other Flutter plugins to access Android `Lifecycle` objects in the plugin's binding. The purpose of having this plugin instead of exposing an Android `Lifecycle` object in the engine's Android embedding plugins API is to force plugins to have a pub constraint that signifies the major version of the Android `Lifecycle` API they expect. | | Android | |-------------|---------| | **Support** | SDK 16+ | ## Installation Add `flutter_plugin_android_lifecycle` as a [dependency in your pubspec.yaml file](https://flutter.dev/using-packages/). ## Example Use a `FlutterLifecycleAdapter` within another Flutter plugin's Android implementation, as shown below: ```java import androidx.lifecycle.Lifecycle; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding; import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter; public class MyPlugin implements FlutterPlugin, ActivityAware { @Override public void onAttachedToActivity(ActivityPluginBinding binding) { Lifecycle lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); // Use lifecycle as desired. } //... } ``` [Feedback welcome](https://github.com/flutter/flutter/issues) and [Pull Requests](https://github.com/flutter/plugins/pulls) are most welcome! ================================================ FILE: packages/flutter_plugin_android_lifecycle/android/.gitignore ================================================ *.iml .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures ================================================ FILE: packages/flutter_plugin_android_lifecycle/android/build.gradle ================================================ group 'io.flutter.plugins.flutter_plugin_android_lifecycle' version '1.0' buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.2.1' } } rootProject.allprojects { repositories { google() mavenCentral() } } apply plugin: 'com.android.library' android { compileSdkVersion 31 defaultConfig { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles 'proguard.txt' } lintOptions { disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } dependencies { implementation "androidx.annotation:annotation:1.1.0" } testOptions { unitTests.includeAndroidResources = true unitTests.returnDefaultValues = true unitTests.all { testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" outputs.upToDateWhen {false} showStandardStreams = true } } } } dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:5.1.1' } ================================================ FILE: packages/flutter_plugin_android_lifecycle/android/proguard.txt ================================================ # The point of this package is to specify that a dependent plugin intends to # use the AndroidX lifecycle classes. Make sure no R8 heuristics shrink classes # brought in by the embedding's pom. # # This isn't strictly needed since by definition, plugins using Android # lifecycles should implement DefaultLifecycleObserver and therefore keep it # from being shrunk. But there seems to be an R8 bug so this needs to stay # https://issuetracker.google.com/issues/142778206. -keep class androidx.lifecycle.DefaultLifecycleObserver ================================================ FILE: packages/flutter_plugin_android_lifecycle/android/settings.gradle ================================================ rootProject.name = 'flutter_plugin_android_lifecycle' ================================================ FILE: packages/flutter_plugin_android_lifecycle/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/flutter_plugin_android_lifecycle/android/src/main/java/io/flutter/embedding/engine/plugins/lifecycle/FlutterLifecycleAdapter.java ================================================ // Copyright 2013 The Flutter 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.embedding.engine.plugins.lifecycle; import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; /** Provides a static method for extracting lifecycle objects from Flutter plugin bindings. */ public class FlutterLifecycleAdapter { private static final String TAG = "FlutterLifecycleAdapter"; /** * Returns the lifecycle object for the activity a plugin is bound to. * *

Returns null if the Flutter engine version does not include the lifecycle extraction code. * (this probably means the Flutter engine version is too old). */ @NonNull public static Lifecycle getActivityLifecycle( @NonNull ActivityPluginBinding activityPluginBinding) { HiddenLifecycleReference reference = (HiddenLifecycleReference) activityPluginBinding.getLifecycle(); return reference.getLifecycle(); } } ================================================ FILE: packages/flutter_plugin_android_lifecycle/android/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle/FlutterAndroidLifecyclePlugin.java ================================================ // Copyright 2013 The Flutter 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.flutter_plugin_android_lifecycle; import androidx.annotation.NonNull; import io.flutter.embedding.engine.plugins.FlutterPlugin; /** * Plugin class that exists because the Flutter tool expects such a class to exist for every Android * plugin. * *

DO NOT USE THIS CLASS. */ public class FlutterAndroidLifecyclePlugin implements FlutterPlugin { @SuppressWarnings("deprecation") public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { // no-op } @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { // no-op } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { // no-op } } ================================================ FILE: packages/flutter_plugin_android_lifecycle/android/src/test/java/io/flutter/embedding/engine/plugins/lifecycle/FlutterLifecycleAdapterTest.java ================================================ // Copyright 2013 The Flutter 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.embedding.engine.plugins.lifecycle; import static org.junit.Assert.assertEquals; import android.app.Activity; import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.PluginRegistry; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; public class FlutterLifecycleAdapterTest { @Mock Lifecycle lifecycle; @Before public void setUp() { MockitoAnnotations.initMocks(this); } @Test public void getActivityLifecycle() { TestActivityPluginBinding binding = new TestActivityPluginBinding(lifecycle); Lifecycle parsedLifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); assertEquals(lifecycle, parsedLifecycle); } private static final class TestActivityPluginBinding implements ActivityPluginBinding { private final Lifecycle lifecycle; TestActivityPluginBinding(Lifecycle lifecycle) { this.lifecycle = lifecycle; } @NonNull public Object getLifecycle() { return new HiddenLifecycleReference(lifecycle); } @Override public Activity getActivity() { return null; } @Override public void addRequestPermissionsResultListener( @NonNull PluginRegistry.RequestPermissionsResultListener listener) {} @Override public void removeRequestPermissionsResultListener( @NonNull PluginRegistry.RequestPermissionsResultListener listener) {} @Override public void addActivityResultListener( @NonNull PluginRegistry.ActivityResultListener listener) {} @Override public void removeActivityResultListener( @NonNull PluginRegistry.ActivityResultListener listener) {} @Override public void addOnNewIntentListener(@NonNull PluginRegistry.NewIntentListener listener) {} @Override public void removeOnNewIntentListener(@NonNull PluginRegistry.NewIntentListener listener) {} @Override public void addOnUserLeaveHintListener( @NonNull PluginRegistry.UserLeaveHintListener listener) {} @Override public void removeOnUserLeaveHintListener( @NonNull PluginRegistry.UserLeaveHintListener listener) {} @Override public void addOnSaveStateListener( @NonNull ActivityPluginBinding.OnSaveInstanceStateListener listener) {} @Override public void removeOnSaveStateListener( @NonNull ActivityPluginBinding.OnSaveInstanceStateListener listener) {} } } ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/.metadata ================================================ # 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: 0e605cc4dd83137f785769dea5e8ae7da1afb361 channel: master project_type: app ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/android/.gitignore ================================================ gradle-wrapper.jar /.gradle /captures/ /gradlew /gradlew.bat /local.properties GeneratedPluginRegistrant.java ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 lintOptions { disable 'InvalidPackage' } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "io.flutter.plugins.flutter_plugin_android_lifecycle_example" minSdkVersion 16 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/android/app/src/androidTest/java/io/flutter/plugins/flutter_plugin_android_lifecycle/MainActivityTest.java ================================================ // Copyright 2013 The Flutter 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.flutter_plugin_android_lifecycle_example; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class MainActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class); } ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/android/app/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle_example/MainActivity.java ================================================ // Copyright 2013 The Flutter 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.flutter_plugin_android_lifecycle_example; import android.util.Log; import androidx.lifecycle.Lifecycle; import dev.flutter.plugins.integration_test.IntegrationTestPlugin; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; 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.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter; public class MainActivity extends FlutterActivity { private static final String TAG = "MainActivity"; @Override public void configureFlutterEngine(FlutterEngine flutterEngine) { flutterEngine.getPlugins().add(new TestPlugin()); flutterEngine.getPlugins().add(new IntegrationTestPlugin()); } private static class TestPlugin implements FlutterPlugin, ActivityAware { @Override public void onAttachedToEngine(FlutterPluginBinding flutterPluginBinding) {} @Override public void onDetachedFromEngine(FlutterPluginBinding binding) {} @Override public void onAttachedToActivity(ActivityPluginBinding binding) { Lifecycle lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); if (lifecycle == null) { Log.d(TAG, "Couldn't obtained Lifecycle!"); return; // TODO(amirh): make this throw once the lifecycle API is available on stable. // https://github.com/flutter/flutter/issues/42875 // throw new RuntimeException( // "The FlutterLifecycleAdapter did not correctly provide a Lifecycle instance. Source reference: " // + flutterPluginBinding.getLifecycle()); } Log.d(TAG, "Successfully obtained Lifecycle: " + lifecycle); } @Override public void onDetachedFromActivity() {} @Override public void onDetachedFromActivityForConfigChanges() {} @Override public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {} } } ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/integration_test/flutter_plugin_android_lifecycle_test.dart ================================================ // Copyright 2013 The Flutter 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_plugin_android_lifecycle_example/main.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('loads', (WidgetTester tester) async { await tester.pumpWidget(const MyApp()); }); } ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); /// MyApp is the Main Application. class MyApp extends StatelessWidget { /// Default Constructor const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Sample flutter_plugin_android_lifecycle usage'), ), body: const Center( child: Text( 'This plugin only provides Android Lifecycle API\n for other Android plugins.')), ), ); } } ================================================ FILE: packages/flutter_plugin_android_lifecycle/example/pubspec.yaml ================================================ name: flutter_plugin_android_lifecycle_example description: Demonstrates how to use the flutter_plugin_android_lifecycle plugin. publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter flutter_plugin_android_lifecycle: # When depending on this package from a real application you should use: # flutter_plugin_android_lifecycle: ^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: ../ dev_dependencies: flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/flutter_plugin_android_lifecycle/pubspec.yaml ================================================ name: flutter_plugin_android_lifecycle description: Flutter plugin for accessing an Android Lifecycle within other plugins. repository: https://github.com/flutter/plugins/tree/main/packages/flutter_plugin_android_lifecycle issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+flutter_plugin_android_lifecycle%22 version: 2.0.7 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: platforms: android: package: io.flutter.plugins.flutter_plugin_android_lifecycle pluginClass: FlutterAndroidLifecyclePlugin dependencies: flutter: sdk: flutter dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/AUTHORS ================================================ # 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> Taha Tesser ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md ================================================ ## 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`. * Updates code for new analysis options. * Updates code for `no_leading_underscores_for_local_identifiers` lint. ## 2.2.1 * Updates imports for `prefer_relative_imports`. ## 2.2.0 * Deprecates `AndroidGoogleMapsFlutter.useAndroidViewSurface` in favor of [setting the flag directly in the Android implementation](https://pub.dev/packages/google_maps_flutter_android#display-mode). * Updates minimum Flutter version to 2.10. ## 2.1.12 * Fixes violations of new analysis option use_named_constants. ## 2.1.11 * Fixes avoid_redundant_argument_values lint warnings and minor typos. * Moves Android and iOS implementations to federated packages. ## 2.1.10 * Avoids map shift when scrolling on iOS. ## 2.1.9 * Updates integration tests to use the new inspector interface. * Removes obsolete test-only method for accessing a map controller's method channel. * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/106316). ## 2.1.8 * Switches to new platform interface versions of `buildView` and `updateOptions`. * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/104231). ## 2.1.7 * Objective-C code cleanup. ## 2.1.6 * Fixes issue in Flutter v3.0.0 where some updates to the map don't take effect on Android. * Fixes iOS native unit tests on M1 devices. * Minor fixes for new analysis options. ## 2.1.5 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 2.1.4 * Updates Android Google maps sdk version to `18.0.2`. * Adds OS version support information to README. ## 2.1.3 * Fixes iOS crash on `EXC_BAD_ACCESS KERN_PROTECTION_FAILURE` if the map frame changes long after creation. ## 2.1.2 * Removes dependencies from `pubspec.yaml` that are only needed in `example/pubspec.yaml` * Updates Android compileSdkVersion to 31. * Internal code cleanup for stricter analysis options. ## 2.1.1 * Suppresses unchecked cast warning. ## 2.1.0 * Add iOS unit and UI integration test targets. * Provide access to Hybrid Composition on Android through the `GoogleMap` widget. ## 2.0.11 * Add additional marker drag events. ## 2.0.10 * Update minimum Flutter SDK to 2.5 and iOS deployment target to 9.0. ## 2.0.9 * Fix Android `NullPointerException` caused by the `GoogleMapController` being disposed before `GoogleMap` was ready. ## 2.0.8 * Mark iOS arm64 simulators as unsupported. ## 2.0.7 * Add iOS unit and UI integration test targets. * Exclude arm64 simulators in example app. * Remove references to the Android V1 embedding. ## 2.0.6 * Migrate maven repo from jcenter to mavenCentral. ## 2.0.5 * Google Maps requires at least Android SDK 20. ## 2.0.4 * Unpin iOS GoogleMaps pod dependency version. ## 2.0.3 * Fix incorrect typecast in TileOverlay example. * Fix english wording in instructions. ## 2.0.2 * Update flutter\_plugin\_android\_lifecycle dependency to 2.0.1 to fix an R8 issue on some versions. ## 2.0.1 * Update platform\_plugin\_interface version requirement. ## 2.0.0 * Migrate to null-safety * BREAKING CHANGE: Passing an unknown map object ID (e.g., MarkerId) to a method, it will throw an `UnknownMapObjectIDError`. Previously it would either silently do nothing, or throw an error trying to call a function on `null`, depneding on the method. ## 1.2.0 * Support custom tiles. ## 1.1.1 * Fix in example app to properly place polyline at initial camera position. ## 1.1.0 * Add support for holes in Polygons. ## 1.0.10 * Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets. ## 1.0.9 * Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) ## 1.0.8 * Update Flutter SDK constraint. ## 1.0.7 * Android: Handle deprecation & unchecked warning as error. ## 1.0.6 * Update Dart SDK constraint in example. * Remove unused `test` dependency in the example app. ## 1.0.5 Overhaul lifecycle management in GoogleMapsPlugin. GoogleMapController is now uniformly driven by implementing `DefaultLifecycleObserver`. That observer is registered to a lifecycle from one of three sources: 1. For v2 plugin registration, `GoogleMapsPlugin` obtains the lifecycle via `ActivityAware` methods. 2. For v1 plugin registration, if the activity implements `LifecycleOwner`, it's lifecycle is used directly. 3. For v1 plugin registration, if the activity does not implement `LifecycleOwner`, a proxy lifecycle is created and driven via `ActivityLifecycleCallbacks`. ## 1.0.4 * Cleanup of Android code: * A few minor formatting changes and additions of `@Nullable` annotations. * Removed pass-through of `activityHashCode` to `GoogleMapController`. * Replaced custom lifecycle state ints with `androidx.lifecycle.Lifecycle.State` enum. * Fixed a bug where the Lifecycle object was being leaked `onDetachFromActivity`, by nulling out the field. * Moved GoogleMapListener to its own file. Declaring multiple top level classes in the same file is discouraged. ## 1.0.3 * Update android compileSdkVersion to 29. ## 1.0.2 * Remove `io.flutter.embedded_views_preview` requirement from readme. ## 1.0.1 * Fix headline in the readme. ## 1.0.0 - Out of developer preview 🎉. * Bump the minimal Flutter SDK to 1.22 where platform views are out of developer preview and performing better on iOS. Flutter 1.22 no longer requires adding the `io.flutter.embedded_views_preview` to `Info.plist` in iOS. ## 0.5.33 * Keep handling deprecated Android v1 classes for backward compatibility. ## 0.5.32 * Fix typo in google_maps_flutter/example/map_ui.dart. ## 0.5.31 * Geodesic Polyline support for iOS ## 0.5.30 * Add a `dispose` method to the controller to let the native side know that we're done with said controller. * Call `controller.dispose()` from the `dispose` method of the `GoogleMap` widget. ## 0.5.29+1 * (ios) Pin dependency on GoogleMaps pod to `< 3.10`, to address https://github.com/flutter/flutter/issues/63447 ## 0.5.29 * Pass a constant `_web_only_mapCreationId` to `platform.buildView`, so web can return a cached widget DOM when flutter attempts to repaint there. * Modify some examples slightly so they're more web-friendly. ## 0.5.28+2 * Move test introduced in #2449 to its right location. ## 0.5.28+1 * Android: Make sure map view only calls onDestroy once. * Android: Fix a memory leak regression caused in `0.5.26+4`. ## 0.5.28 * Android: Add liteModeEnabled option. ## 0.5.27+3 * iOS: Update the gesture recognizer blocking policy to "WaitUntilTouchesEnded", which fixes the camera idle callback not triggered issue. * Update the min flutter version to 1.16.3. * Skip `testTakeSnapshot` test on Android. ## 0.5.27+2 * Update lower bound of dart dependency to 2.1.0. ## 0.5.27+1 * Remove endorsement of `web` platform, it's not ready yet. ## 0.5.27 * Migrate the core plugin to use `google_maps_flutter_platform_interface` APIs. ## 0.5.26+4 * Android: Fix map view crash when "exit app" while using `FragmentActivity`. * Android: Remove listeners from `GoogleMap` when disposing. ## 0.5.26+3 * iOS: observe the bounds update for the `GMSMapView` to reset the camera setting. * Update UI related e2e tests to wait for camera update on the platform thread. ## 0.5.26+2 * Fix UIKit availability warnings and CocoaPods podspec lint warnings. ## 0.5.26+1 * Removes an erroneously added method from the GoogleMapController.h header file. ## 0.5.26 * Adds support for toggling zoom controls (Android only) ## 0.5.25+3 * Rename 'Page' in the example app to avoid type conflict with the Flutter Framework. ## 0.5.25+2 * Avoid unnecessary map elements updates by ignoring not platform related attributes (eg. onTap) ## 0.5.25+1 * Add takeSnapshot that takes a snapshot of the map. ## 0.5.25 * Add an optional param `mipmaps` for `BitmapDescriptor.fromAssetImage`. ## 0.5.24+1 * Make the pedantic dev_dependency explicit. ## 0.5.24 * Exposed `getZoomLevel` in `GoogleMapController`. ## 0.5.23+1 * Move core plugin to its own subdirectory, to prepare for federation. ## 0.5.23 * Add methods to programmatically control markers info windows. ## 0.5.22+3 * Fix polygon and circle stroke width according to device density ## 0.5.22+2 * Update README: Add steps to enable Google Map SDK in the Google Developer Console. ## 0.5.22+1 * Fix for toggling traffic layer on Android not working ## 0.5.22 * Support Android v2 embedding. * Bump the min flutter version to `1.12.13+hotfix.5`. * Fixes some e2e tests on Android. ## 0.5.21+17 * Fix Swift example in README.md. ## 0.5.21+16 * Fixed typo in LatLng's documentation. ## 0.5.21+15 * Remove the deprecated `author:` field from pubspec.yaml * Migrate the plugin to the pubspec platforms manifest. * Require Flutter SDK 1.10.0 or greater. ## 0.5.21+14 * Adds support for toggling 3D buildings. ## 0.5.21+13 * Add documentation. ## 0.5.21+12 * Update driver tests in the example app to e2e tests. ## 0.5.21+11 * Define clang module for iOS, fix analyzer warnings. ## 0.5.21+10 * Cast error.code to unsigned long to avoid using NSInteger as %ld format warnings. ## 0.5.21+9 * Remove AndroidX warnings. ## 0.5.21+8 * Add NS*ASSUME_NONNULL*\* macro to reduce iOS compiler warnings. ## 0.5.21+7 * Create a clone of cached elements in GoogleMap (Polyline, Polygon, etc.) to detect modifications if these objects are mutated instead of modified by copy. ## 0.5.21+6 * Override a default method to work around flutter/flutter#40126. ## 0.5.21+5 * Update and migrate iOS example project. ## 0.5.21+4 * Support projection methods to translate between screen and latlng coordinates. ## 0.5.21+3 * Fix `myLocationButton` bug in `google_maps_flutter` iOS. ## 0.5.21+2 * Fix more `prefer_const_constructors` analyzer warnings in example app. ## 0.5.21+1 * Fix `prefer_const_constructors` analyzer warnings in example app. ## 0.5.21 * Don't recreate map elements if they didn't change since last widget build. ## 0.5.20+6 * Adds support for toggling the traffic layer ## 0.5.20+5 * Allow (de-)serialization of CameraPosition ## 0.5.20+4 * Marker drag event ## 0.5.20+3 * Update Android play-services-maps to 17.0.0 ## 0.5.20+2 * Android: Fix polyline width in building phase. ## 0.5.20+1 * Android: Unregister ActivityLifecycleCallbacks on activity destroy (fixes a memory leak). ## 0.5.20 * Add map toolbar support ## 0.5.19+2 * Fix polygons for iOS ## 0.5.19+1 * Fix polyline width according to device density ## 0.5.19 * Adds support for toggling Indoor View on or off. * Allow BitmapDescriptor scaling override ## 0.5.18 * Fixed build issue on iOS. ## 0.5.17 * Add support for Padding. ## 0.5.16+1 * Update Dart code to conform to current Dart formatter. ## 0.5.16 * Add support for custom map styling. ## 0.5.15+1 * Add missing template type parameter to `invokeMethod` calls. * Bump minimum Flutter version to 1.5.0. * Replace invokeMethod with invokeMapMethod wherever necessary. ## 0.5.15 * Add support for Polygons. ## 0.5.14+1 * Example app update(comment out usage of the ImageStreamListener API which has a breaking change that's not yet on master). See: https://github.com/flutter/flutter/issues/33438 ## 0.5.14 * Adds onLongPress callback for GoogleMap. ## 0.5.13 * Add support for Circle overlays. ## 0.5.12 * Prevent calling null callbacks and callbacks on removed objects. ## 0.5.11+1 * Android: Fix an issue where myLocationButtonEnabled setting was not propagated when set to false onMapLoad. ## 0.5.11 * Add myLocationButtonEnabled option. ## 0.5.10 * Support Color's alpha channel when converting to UIColor on iOS. ## 0.5.9 * BitmapDescriptor#fromBytes accounts for screen scale on ios. ## 0.5.8 * Remove some unused variables and rename method ## 0.5.7 * Add a BitmapDescriptor that is aware of scale. ## 0.5.6 * Add support for Polylines on GoogleMap. ## 0.5.5 * Enable iOS accessibility. ## 0.5.4 * Add method getVisibleRegion for get the latlng bounds of the visible map area. ## 0.5.3 * Added support setting marker icons from bytes. ## 0.5.2 * Added onTap for callback for GoogleMap. ## 0.5.1 * Update Android gradle version. * Added infrastructure to write integration tests. ## 0.5.0 * Add a key parameter to the GoogleMap widget. ## 0.4.0 * Change events are call backs on GoogleMap widget. * GoogleMapController no longer handles change events. * trackCameraPosition is inferred from GoogleMap.onCameraMove being set. ## 0.3.0+3 * Update Android play-services-maps to 16.1.0 ## 0.3.0+2 * Address an issue on iOS where icons were not loading. * Add apache http library required false for Android. ## 0.3.0+1 * Add NSNull Checks for markers controller in iOS. * Also address an issue where initial markers are set before initialization. ## 0.3.0 * **Breaking change**. Changed the Marker API to be widget based, it was controller based. Also changed the example app to account for the same. ## 0.2.0+6 * Updated the sample app in README.md. ## 0.2.0+5 * Skip the Gradle Android permissions lint for MyLocation (https://github.com/flutter/flutter/issues/28339) * Suppress unchecked cast warning for the PlatformViewFactory creation parameters. ## 0.2.0+4 * Fixed a crash when the plugin is registered by a background FlutterView. ## 0.2.0+3 * Fixed a memory leak on Android - the map was not properly disposed. ## 0.2.0+2 * Log a more detailed warning at build time about the previous AndroidX migration. ## 0.2.0+1 * Fixed a bug which the camera is not positioned correctly at map initialization(temporary workaround)(https://github.com/flutter/flutter/issues/27550). ## 0.2.0 * **Breaking change**. Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to [also migrate](https://developer.android.com/jetpack/androidx/migrate) if they're using the original support library. ## 0.1.0 * Move the map options from the GoogleMapOptions class to GoogleMap widget parameters. ## 0.0.3+3 * Relax Flutter version requirement to 0.11.9. ## 0.0.3+2 * Update README to recommend using the package from pub. ## 0.0.3+1 * Bug fix: custom marker images were not working on iOS as we were not keeping a reference to the plugin registrar so couldn't fetch assets. ## 0.0.3 * Don't export `dart:async`. * Update the minimal required Flutter SDK version to one that supports embedding platform views. ## 0.0.2 * Initial developers preview release. ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/LICENSE ================================================ 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. ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/README.md ================================================ # Google Maps for Flutter [![pub package](https://img.shields.io/pub/v/google_maps_flutter.svg)](https://pub.dev/packages/google_maps_flutter) A Flutter plugin that provides a [Google Maps](https://developers.google.com/maps/) widget. | | Android | iOS | |-------------|---------|--------| | **Support** | SDK 20+ | iOS 9+ | ## Usage To use this plugin, add `google_maps_flutter` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels). ## Getting Started * Get an API key at . * Enable Google Map SDK for each platform. * Go to [Google Developers Console](https://console.cloud.google.com/). * Choose the project that you want to enable Google Maps on. * Select the navigation menu and then select "Google Maps". * Select "APIs" under the Google Maps menu. * To enable Google Maps for Android, select "Maps SDK for Android" in the "Additional APIs" section, then select "ENABLE". * To enable Google Maps for iOS, select "Maps SDK for iOS" in the "Additional APIs" section, then select "ENABLE". * Make sure the APIs you enabled are under the "Enabled APIs" section. For more details, see [Getting started with Google Maps Platform](https://developers.google.com/maps/gmp-get-started). ### Android 1. Set the `minSdkVersion` in `android/app/build.gradle`: ```groovy android { defaultConfig { minSdkVersion 20 } } ``` This means that app will only be available for users that run Android SDK 20 or higher. 2. Specify your API key in the application manifest `android/app/src/main/AndroidManifest.xml`: ```xml ``` #### Display Mode The Android implementation supports multiple [platform view display modes](https://flutter.dev/docs/development/platform-integration/platform-views). For details, see [the Android README](https://pub.dev/packages/google_maps_flutter_android#display-mode). ### iOS To set up, specify your API key in the application delegate `ios/Runner/AppDelegate.m`: ```objectivec #include "AppDelegate.h" #include "GeneratedPluginRegistrant.h" #import "GoogleMaps/GoogleMaps.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [GMSServices provideAPIKey:@"YOUR KEY HERE"]; [GeneratedPluginRegistrant registerWithRegistry:self]; return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end ``` Or in your swift code, specify your API key in the application delegate `ios/Runner/AppDelegate.swift`: ```swift import UIKit import Flutter import GoogleMaps @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GMSServices.provideAPIKey("YOUR KEY HERE") GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } ``` ### Both You can now add a `GoogleMap` widget to your widget tree. The map view can be controlled with the `GoogleMapController` that is passed to the `GoogleMap`'s `onMapCreated` callback. ### Sample Usage ```dart class MapSample extends StatefulWidget { const MapSample({Key? key}) : super(key: key); @override State createState() => MapSampleState(); } class MapSampleState extends State { final Completer _controller = Completer(); static const CameraPosition _kGooglePlex = CameraPosition( target: LatLng(37.42796133580664, -122.085749655962), zoom: 14.4746, ); static const CameraPosition _kLake = CameraPosition( bearing: 192.8334901395799, target: LatLng(37.43296265331129, -122.08832357078792), tilt: 59.440717697143555, zoom: 19.151926040649414); @override Widget build(BuildContext context) { return Scaffold( body: GoogleMap( mapType: MapType.hybrid, initialCameraPosition: _kGooglePlex, onMapCreated: (GoogleMapController controller) { _controller.complete(controller); }, ), floatingActionButton: FloatingActionButton.extended( onPressed: _goToTheLake, label: const Text('To the lake!'), icon: const Icon(Icons.directions_boat), ), ); } Future _goToTheLake() async { final GoogleMapController controller = await _controller.future; controller.animateCamera(CameraUpdate.newCameraPosition(_kLake)); } } ``` See the `example` directory for a complete sample app. ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/.metadata ================================================ # 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: 3ea4d06340a97a1e9d7cae97567c64e0569dcaa2 channel: beta ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/README.md ================================================ # google_maps_flutter_example Demonstrates how to use the google_maps_flutter plugin. ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 lintOptions { disable 'InvalidPackage' } defaultConfig { applicationId "io.flutter.plugins.googlemapsexample" minSdkVersion 20 targetSdkVersion 28 multiDexEnabled true versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } defaultConfig { manifestPlaceholders = [mapsApiKey: "$System.env.MAPS_API_KEY"] } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } testOptions { unitTests { includeAndroidResources = true } } dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' api 'androidx.test:core:1.2.0' testImplementation 'com.google.android.gms:play-services-maps:17.0.0' } } flutter { source '../..' } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/android/app/src/androidTest/java/io/flutter/plugins/googlemapsexample/MainActivityTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class MainActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/android/app/src/debug/java/io/flutter/plugins/googlemapsexample/GoogleMapsTestActivity.java ================================================ // Copyright 2013 The Flutter 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.googlemapsexample; import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; // Makes the FlutterEngine accessible for testing. public class GoogleMapsTestActivity extends FlutterActivity { public FlutterEngine engine; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); engine = flutterEngine; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/assets/night_mode.json ================================================ [ { "elementType": "geometry", "stylers": [ { "color": "#242f3e" } ] }, { "elementType": "labels.text.fill", "stylers": [ { "color": "#746855" } ] }, { "elementType": "labels.text.stroke", "stylers": [ { "color": "#242f3e" } ] }, { "featureType": "administrative.locality", "elementType": "labels.text.fill", "stylers": [ { "color": "#d59563" } ] }, { "featureType": "poi", "elementType": "labels.text.fill", "stylers": [ { "color": "#d59563" } ] }, { "featureType": "poi.park", "elementType": "geometry", "stylers": [ { "color": "#263c3f" } ] }, { "featureType": "poi.park", "elementType": "labels.text.fill", "stylers": [ { "color": "#6b9a76" } ] }, { "featureType": "road", "elementType": "geometry", "stylers": [ { "color": "#38414e" } ] }, { "featureType": "road", "elementType": "geometry.stroke", "stylers": [ { "color": "#212a37" } ] }, { "featureType": "road", "elementType": "labels.text.fill", "stylers": [ { "color": "#9ca5b3" } ] }, { "featureType": "road.highway", "elementType": "geometry", "stylers": [ { "color": "#746855" } ] }, { "featureType": "road.highway", "elementType": "geometry.stroke", "stylers": [ { "color": "#1f2835" } ] }, { "featureType": "road.highway", "elementType": "labels.text.fill", "stylers": [ { "color": "#f3d19c" } ] }, { "featureType": "transit", "elementType": "geometry", "stylers": [ { "color": "#2f3948" } ] }, { "featureType": "transit.station", "elementType": "labels.text.fill", "stylers": [ { "color": "#d59563" } ] }, { "featureType": "water", "elementType": "geometry", "stylers": [ { "color": "#17263c" } ] }, { "featureType": "water", "elementType": "labels.text.fill", "stylers": [ { "color": "#515c6d" } ] }, { "featureType": "water", "elementType": "labels.text.stroke", "stylers": [ { "color": "#17263c" } ] } ] ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/build.excerpt.yaml ================================================ targets: $default: sources: include: - lib/** # Some default includes that aren't really used here but will prevent # false-negative warnings: - $package$ - lib/$lib$ exclude: - '**/.*/**' # - '**/build/**' # builders: # code_excerpter|code_excerpter: # enabled: true ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:integration_test/integration_test.dart'; const LatLng _kInitialMapCenter = LatLng(0, 0); const double _kInitialZoomLevel = 5; const CameraPosition _kInitialCameraPosition = CameraPosition(target: _kInitialMapCenter, zoom: _kInitialZoomLevel); void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); GoogleMapsFlutterPlatform.instance.enableDebugInspection(); // Repeatedly checks an asynchronous value against a test condition, waiting // one frame between each check, returing the value if it passes the predicate // before [maxTries] is reached. // // Returns null if the predicate is never satisfied. // // This is useful for cases where the Maps SDK has some internally // asynchronous operation that we don't have visibility into (e.g., native UI // animations). Future waitForValueMatchingPredicate(WidgetTester tester, Future Function() getValue, bool Function(T) predicate, {int maxTries = 100}) async { for (int i = 0; i < maxTries; i++) { final T value = await getValue(); if (predicate(value)) { return value; } await tester.pump(); } return null; } testWidgets('testCompassToggle', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, compassEnabled: false, onMapCreated: (GoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool compassEnabled = await inspector.isCompassEnabled(mapId: mapId); expect(compassEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); compassEnabled = await inspector.isCompassEnabled(mapId: mapId); expect(compassEnabled, true); }); testWidgets('testMapToolbarToggle', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, mapToolbarEnabled: false, onMapCreated: (GoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool mapToolbarEnabled = await inspector.isMapToolbarEnabled(mapId: mapId); expect(mapToolbarEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); mapToolbarEnabled = await inspector.isMapToolbarEnabled(mapId: mapId); expect(mapToolbarEnabled, Platform.isAndroid); }); testWidgets('updateMinMaxZoomLevels', (WidgetTester tester) async { // The behaviors of setting min max zoom level on iOS and Android are different. // On iOS, when we get the min or max zoom level after setting the preference, the // min and max will be exactly the same as the value we set; on Android however, // the values we get do not equal to the value we set. // // Also, when we call zoomTo to set the zoom, on Android, it usually // honors the preferences that we set and the zoom cannot pass beyond the boundary. // On iOS, on the other hand, zoomTo seems to override the preferences. // // Thus we test iOS and Android a little differently here. final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); const MinMaxZoomPreference initialZoomLevel = MinMaxZoomPreference(4, 8); const MinMaxZoomPreference finalZoomLevel = MinMaxZoomPreference(6, 10); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, minMaxZoomPreference: initialZoomLevel, onMapCreated: (GoogleMapController c) async { controllerCompleter.complete(c); }, ), )); final GoogleMapController controller = await controllerCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; if (Platform.isIOS) { final MinMaxZoomPreference zoomLevel = await inspector.getMinMaxZoomLevels(mapId: controller.mapId); expect(zoomLevel, equals(initialZoomLevel)); } else if (Platform.isAndroid) { await controller.moveCamera(CameraUpdate.zoomTo(15)); await tester.pumpAndSettle(); double? zoomLevel = await controller.getZoomLevel(); expect(zoomLevel, equals(initialZoomLevel.maxZoom)); await controller.moveCamera(CameraUpdate.zoomTo(1)); await tester.pumpAndSettle(); zoomLevel = await controller.getZoomLevel(); expect(zoomLevel, equals(initialZoomLevel.minZoom)); } await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, minMaxZoomPreference: finalZoomLevel, onMapCreated: (GoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); if (Platform.isIOS) { final MinMaxZoomPreference zoomLevel = await inspector.getMinMaxZoomLevels(mapId: controller.mapId); expect(zoomLevel, equals(finalZoomLevel)); } else { await controller.moveCamera(CameraUpdate.zoomTo(15)); await tester.pumpAndSettle(); double? zoomLevel = await controller.getZoomLevel(); expect(zoomLevel, equals(finalZoomLevel.maxZoom)); await controller.moveCamera(CameraUpdate.zoomTo(1)); await tester.pumpAndSettle(); zoomLevel = await controller.getZoomLevel(); expect(zoomLevel, equals(finalZoomLevel.minZoom)); } }); testWidgets('testZoomGesturesEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, zoomGesturesEnabled: false, onMapCreated: (GoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool zoomGesturesEnabled = await inspector.areZoomGesturesEnabled(mapId: mapId); expect(zoomGesturesEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); zoomGesturesEnabled = await inspector.areZoomGesturesEnabled(mapId: mapId); expect(zoomGesturesEnabled, true); }); testWidgets('testZoomControlsEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool zoomControlsEnabled = await inspector.areZoomControlsEnabled(mapId: mapId); expect(zoomControlsEnabled, !Platform.isIOS); /// Zoom Controls functionality is not available on iOS at the moment. if (Platform.isAndroid) { await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, zoomControlsEnabled: false, onMapCreated: (GoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); zoomControlsEnabled = await inspector.areZoomControlsEnabled(mapId: mapId); expect(zoomControlsEnabled, false); } }); testWidgets('testLiteModeEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool liteModeEnabled = await inspector.isLiteModeEnabled(mapId: mapId); expect(liteModeEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, liteModeEnabled: true, onMapCreated: (GoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); liteModeEnabled = await inspector.isLiteModeEnabled(mapId: mapId); expect(liteModeEnabled, true); }, skip: !Platform.isAndroid); testWidgets('testRotateGesturesEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, rotateGesturesEnabled: false, onMapCreated: (GoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool rotateGesturesEnabled = await inspector.areRotateGesturesEnabled(mapId: mapId); expect(rotateGesturesEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); rotateGesturesEnabled = await inspector.areRotateGesturesEnabled(mapId: mapId); expect(rotateGesturesEnabled, true); }); testWidgets('testTiltGesturesEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, tiltGesturesEnabled: false, onMapCreated: (GoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool tiltGesturesEnabled = await inspector.areTiltGesturesEnabled(mapId: mapId); expect(tiltGesturesEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); tiltGesturesEnabled = await inspector.areTiltGesturesEnabled(mapId: mapId); expect(tiltGesturesEnabled, true); }); testWidgets('testScrollGesturesEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, scrollGesturesEnabled: false, onMapCreated: (GoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool scrollGesturesEnabled = await inspector.areScrollGesturesEnabled(mapId: mapId); expect(scrollGesturesEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); scrollGesturesEnabled = await inspector.areScrollGesturesEnabled(mapId: mapId); expect(scrollGesturesEnabled, true); }); testWidgets('testInitialCenterLocationAtCenter', (WidgetTester tester) async { await tester.binding.setSurfaceSize(const Size(800, 600)); final Completer mapControllerCompleter = Completer(); final Key key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { mapControllerCompleter.complete(controller); }, ), ), ); final GoogleMapController mapController = await mapControllerCompleter.future; await tester.pumpAndSettle(); // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen // in `mapRendered`. // https://github.com/flutter/flutter/issues/54758 await Future.delayed(const Duration(seconds: 1)); final ScreenCoordinate coordinate = await mapController.getScreenCoordinate(_kInitialCameraPosition.target); final Rect rect = tester.getRect(find.byKey(key)); if (Platform.isIOS) { // On iOS, the coordinate value from the GoogleMapSdk doesn't include the devicePixelRatio`. // So we don't need to do the conversion like we did below for other platforms. expect(coordinate.x, (rect.center.dx - rect.topLeft.dx).round()); expect(coordinate.y, (rect.center.dy - rect.topLeft.dy).round()); } else { expect( coordinate.x, ((rect.center.dx - rect.topLeft.dx) * tester.binding.window.devicePixelRatio) .round()); expect( coordinate.y, ((rect.center.dy - rect.topLeft.dy) * tester.binding.window.devicePixelRatio) .round()); } await tester.binding.setSurfaceSize(null); }); testWidgets('testGetVisibleRegion', (WidgetTester tester) async { final Key key = GlobalKey(); final LatLngBounds zeroLatLngBounds = LatLngBounds( southwest: const LatLng(0, 0), northeast: const LatLng(0, 0)); final Completer mapControllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { mapControllerCompleter.complete(controller); }, ), )); await tester.pumpAndSettle(); final GoogleMapController mapController = await mapControllerCompleter.future; // Wait for the visible region to be non-zero. final LatLngBounds firstVisibleRegion = await waitForValueMatchingPredicate( tester, () => mapController.getVisibleRegion(), (LatLngBounds bounds) => bounds != zeroLatLngBounds) ?? zeroLatLngBounds; expect(firstVisibleRegion, isNot(zeroLatLngBounds)); expect(firstVisibleRegion.contains(_kInitialMapCenter), isTrue); // Making a new `LatLngBounds` about (10, 10) distance south west to the `firstVisibleRegion`. // The size of the `LatLngBounds` is 10 by 10. final LatLng southWest = LatLng(firstVisibleRegion.southwest.latitude - 20, firstVisibleRegion.southwest.longitude - 20); final LatLng northEast = LatLng(firstVisibleRegion.southwest.latitude - 10, firstVisibleRegion.southwest.longitude - 10); final LatLng newCenter = LatLng( (northEast.latitude + southWest.latitude) / 2, (northEast.longitude + southWest.longitude) / 2, ); expect(firstVisibleRegion.contains(northEast), isFalse); expect(firstVisibleRegion.contains(southWest), isFalse); final LatLngBounds latLngBounds = LatLngBounds(southwest: southWest, northeast: northEast); // TODO(iskakaushik): non-zero padding is needed for some device configurations // https://github.com/flutter/flutter/issues/30575 const double padding = 0; await mapController .moveCamera(CameraUpdate.newLatLngBounds(latLngBounds, padding)); await tester.pumpAndSettle(const Duration(seconds: 3)); final LatLngBounds secondVisibleRegion = await mapController.getVisibleRegion(); expect(secondVisibleRegion, isNot(zeroLatLngBounds)); expect(firstVisibleRegion, isNot(secondVisibleRegion)); expect(secondVisibleRegion.contains(newCenter), isTrue); }); testWidgets('testTraffic', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, trafficEnabled: true, onMapCreated: (GoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool isTrafficEnabled = await inspector.isTrafficEnabled(mapId: mapId); expect(isTrafficEnabled, true); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); isTrafficEnabled = await inspector.isTrafficEnabled(mapId: mapId); expect(isTrafficEnabled, false); }); testWidgets('testBuildings', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; final bool isBuildingsEnabled = await inspector.areBuildingsEnabled(mapId: mapId); expect(isBuildingsEnabled, true); }); // Location button tests are skipped in Android because we don't have location permission to test. testWidgets('testMyLocationButtonToggle', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool myLocationButtonEnabled = await inspector.isMyLocationButtonEnabled(mapId: mapId); expect(myLocationButtonEnabled, true); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, myLocationButtonEnabled: false, onMapCreated: (GoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); myLocationButtonEnabled = await inspector.isMyLocationButtonEnabled(mapId: mapId); expect(myLocationButtonEnabled, false); }, skip: Platform.isAndroid); testWidgets('testMyLocationButton initial value false', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, myLocationButtonEnabled: false, onMapCreated: (GoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; final bool myLocationButtonEnabled = await inspector.isMyLocationButtonEnabled(mapId: mapId); expect(myLocationButtonEnabled, false); }, skip: Platform.isAndroid); testWidgets('testMyLocationButton initial value true', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; final bool myLocationButtonEnabled = await inspector.isMyLocationButtonEnabled(mapId: mapId); expect(myLocationButtonEnabled, true); }, skip: Platform.isAndroid); testWidgets('testSetMapStyle valid Json String', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { controllerCompleter.complete(controller); }, ), )); final GoogleMapController controller = await controllerCompleter.future; const String mapStyle = '[{"elementType":"geometry","stylers":[{"color":"#242f3e"}]}]'; await controller.setMapStyle(mapStyle); }); testWidgets('testSetMapStyle invalid Json String', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { controllerCompleter.complete(controller); }, ), )); final GoogleMapController controller = await controllerCompleter.future; try { await controller.setMapStyle('invalid_value'); fail('expected MapStyleException'); } on MapStyleException catch (e) { expect(e.cause, isNotNull); } }); testWidgets('testSetMapStyle null string', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { controllerCompleter.complete(controller); }, ), )); final GoogleMapController controller = await controllerCompleter.future; await controller.setMapStyle(null); }); testWidgets('testGetLatLng', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { controllerCompleter.complete(controller); }, ), )); final GoogleMapController controller = await controllerCompleter.future; await tester.pumpAndSettle(); // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen // in `mapRendered`. // https://github.com/flutter/flutter/issues/54758 await Future.delayed(const Duration(seconds: 1)); final LatLngBounds visibleRegion = await controller.getVisibleRegion(); final LatLng topLeft = await controller.getLatLng(const ScreenCoordinate(x: 0, y: 0)); final LatLng northWest = LatLng( visibleRegion.northeast.latitude, visibleRegion.southwest.longitude, ); expect(topLeft, northWest); }); testWidgets('testGetZoomLevel', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { controllerCompleter.complete(controller); }, ), )); final GoogleMapController controller = await controllerCompleter.future; await tester.pumpAndSettle(); // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen // in `mapRendered`. // https://github.com/flutter/flutter/issues/54758 await Future.delayed(const Duration(seconds: 1)); double zoom = await controller.getZoomLevel(); expect(zoom, _kInitialZoomLevel); await controller.moveCamera(CameraUpdate.zoomTo(7)); await tester.pumpAndSettle(); zoom = await controller.getZoomLevel(); expect(zoom, equals(7)); }); testWidgets('testScreenCoordinate', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { controllerCompleter.complete(controller); }, ), )); final GoogleMapController controller = await controllerCompleter.future; await tester.pumpAndSettle(); // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen // in `mapRendered`. // https://github.com/flutter/flutter/issues/54758 await Future.delayed(const Duration(seconds: 1)); final LatLngBounds visibleRegion = await controller.getVisibleRegion(); final LatLng northWest = LatLng( visibleRegion.northeast.latitude, visibleRegion.southwest.longitude, ); final ScreenCoordinate topLeft = await controller.getScreenCoordinate(northWest); expect(topLeft, const ScreenCoordinate(x: 0, y: 0)); }); testWidgets('testResizeWidget', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final GoogleMap map = GoogleMap( initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) async { controllerCompleter.complete(controller); }, ); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: MaterialApp( home: Scaffold( body: SizedBox(height: 100, width: 100, child: map))))); final GoogleMapController controller = await controllerCompleter.future; await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: MaterialApp( home: Scaffold( body: SizedBox(height: 400, width: 400, child: map))))); await tester.pumpAndSettle(); // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen // in `mapRendered`. // https://github.com/flutter/flutter/issues/54758 await Future.delayed(const Duration(seconds: 1)); // Simple call to make sure that the app hasn't crashed. final LatLngBounds bounds1 = await controller.getVisibleRegion(); final LatLngBounds bounds2 = await controller.getVisibleRegion(); expect(bounds1, bounds2); }); testWidgets('testToggleInfoWindow', (WidgetTester tester) async { const Marker marker = Marker( markerId: MarkerId('marker'), infoWindow: InfoWindow(title: 'InfoWindow')); final Set markers = {marker}; final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), markers: markers, onMapCreated: (GoogleMapController googleMapController) { controllerCompleter.complete(googleMapController); }, ), )); final GoogleMapController controller = await controllerCompleter.future; bool iwVisibleStatus = await controller.isMarkerInfoWindowShown(marker.markerId); expect(iwVisibleStatus, false); await controller.showMarkerInfoWindow(marker.markerId); // The Maps SDK doesn't always return true for whether it is shown // immediately after showing it, so wait for it to report as shown. iwVisibleStatus = await waitForValueMatchingPredicate( tester, () => controller.isMarkerInfoWindowShown(marker.markerId), (bool visible) => visible) ?? false; expect(iwVisibleStatus, true); await controller.hideMarkerInfoWindow(marker.markerId); iwVisibleStatus = await controller.isMarkerInfoWindowShown(marker.markerId); expect(iwVisibleStatus, false); }); testWidgets('fromAssetImage', (WidgetTester tester) async { const double pixelRatio = 2; const ImageConfiguration imageConfiguration = ImageConfiguration(devicePixelRatio: pixelRatio); final BitmapDescriptor mip = await BitmapDescriptor.fromAssetImage( imageConfiguration, 'red_square.png'); final BitmapDescriptor scaled = await BitmapDescriptor.fromAssetImage( imageConfiguration, 'red_square.png', mipmaps: false); expect((mip.toJson() as List)[2], 1); expect((scaled.toJson() as List)[2], 2); }); testWidgets('testTakeSnapshot', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { controllerCompleter.complete(controller); }, ), ), ); await tester.pumpAndSettle(const Duration(seconds: 3)); final GoogleMapController controller = await controllerCompleter.future; final Uint8List? bytes = await controller.takeSnapshot(); expect(bytes?.isNotEmpty, true); }, // TODO(cyanglaz): un-skip the test when we can test this on CI with API key enabled. // https://github.com/flutter/flutter/issues/57057 skip: Platform.isAndroid); testWidgets( 'set tileOverlay correctly', (WidgetTester tester) async { final Completer mapIdCompleter = Completer(); final TileOverlay tileOverlay1 = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_1'), tileProvider: _DebugTileProvider(), zIndex: 2, transparency: 0.2, ); final TileOverlay tileOverlay2 = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_2'), tileProvider: _DebugTileProvider(), zIndex: 1, visible: false, transparency: 0.3, fadeIn: false, ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: _kInitialCameraPosition, tileOverlays: {tileOverlay1, tileOverlay2}, onMapCreated: (GoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), ), ); await tester.pumpAndSettle(const Duration(seconds: 3)); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; final TileOverlay tileOverlayInfo1 = (await inspector .getTileOverlayInfo(tileOverlay1.mapsId, mapId: mapId))!; final TileOverlay tileOverlayInfo2 = (await inspector .getTileOverlayInfo(tileOverlay2.mapsId, mapId: mapId))!; expect(tileOverlayInfo1.visible, isTrue); expect(tileOverlayInfo1.fadeIn, isTrue); expect( tileOverlayInfo1.transparency, moreOrLessEquals(0.2, epsilon: 0.001)); expect(tileOverlayInfo1.zIndex, 2); expect(tileOverlayInfo2.visible, isFalse); expect(tileOverlayInfo2.fadeIn, isFalse); expect( tileOverlayInfo2.transparency, moreOrLessEquals(0.3, epsilon: 0.001)); expect(tileOverlayInfo2.zIndex, 1); }, ); testWidgets( 'update tileOverlays correctly', (WidgetTester tester) async { final Completer mapIdCompleter = Completer(); final Key key = GlobalKey(); final TileOverlay tileOverlay1 = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_1'), tileProvider: _DebugTileProvider(), zIndex: 2, transparency: 0.2, ); final TileOverlay tileOverlay2 = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_2'), tileProvider: _DebugTileProvider(), zIndex: 3, transparency: 0.5, ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, tileOverlays: {tileOverlay1, tileOverlay2}, onMapCreated: (GoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), ), ); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; final TileOverlay tileOverlay1New = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_1'), tileProvider: _DebugTileProvider(), zIndex: 1, visible: false, transparency: 0.3, fadeIn: false, ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, tileOverlays: {tileOverlay1New}, onMapCreated: (GoogleMapController controller) { fail('update: OnMapCreated should get called only once.'); }, ), ), ); await tester.pumpAndSettle(const Duration(seconds: 3)); final TileOverlay tileOverlayInfo1 = (await inspector .getTileOverlayInfo(tileOverlay1.mapsId, mapId: mapId))!; final TileOverlay? tileOverlayInfo2 = await inspector.getTileOverlayInfo(tileOverlay2.mapsId, mapId: mapId); expect(tileOverlayInfo1.visible, isFalse); expect(tileOverlayInfo1.fadeIn, isFalse); expect( tileOverlayInfo1.transparency, moreOrLessEquals(0.3, epsilon: 0.001)); expect(tileOverlayInfo1.zIndex, 1); expect(tileOverlayInfo2, isNull); }, ); testWidgets( 'remove tileOverlays correctly', (WidgetTester tester) async { final Completer mapIdCompleter = Completer(); final Key key = GlobalKey(); final TileOverlay tileOverlay1 = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_1'), tileProvider: _DebugTileProvider(), zIndex: 2, transparency: 0.2, ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, tileOverlays: {tileOverlay1}, onMapCreated: (GoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), ), ); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (GoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), ), ); await tester.pumpAndSettle(const Duration(seconds: 3)); final TileOverlay? tileOverlayInfo1 = await inspector.getTileOverlayInfo(tileOverlay1.mapsId, mapId: mapId); expect(tileOverlayInfo1, isNull); }, ); } class _DebugTileProvider implements TileProvider { _DebugTileProvider() { boxPaint.isAntiAlias = true; boxPaint.color = Colors.blue; boxPaint.strokeWidth = 2.0; boxPaint.style = PaintingStyle.stroke; } static const int width = 100; static const int height = 100; static final Paint boxPaint = Paint(); static const TextStyle textStyle = TextStyle( color: Colors.red, fontSize: 20, ); @override Future getTile(int x, int y, int? zoom) async { final ui.PictureRecorder recorder = ui.PictureRecorder(); final Canvas canvas = Canvas(recorder); final TextSpan textSpan = TextSpan( text: '$x,$y', style: textStyle, ); final TextPainter textPainter = TextPainter( text: textSpan, textDirection: TextDirection.ltr, ); textPainter.layout( maxWidth: width.toDouble(), ); textPainter.paint(canvas, Offset.zero); canvas.drawRect( Rect.fromLTRB(0, 0, width.toDouble(), width.toDouble()), boxPaint); final ui.Picture picture = recorder.endRecording(); final Uint8List byteData = await picture .toImage(width, height) .then((ui.Image image) => image.toByteData(format: ui.ImageByteFormat.png)) .then((ByteData? byteData) => byteData!.buffer.asUint8List()); return Tile(width, height, byteData); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 UIRequiredDeviceCapabilities arm64 MinimumOSVersion 9.0 ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/ios/Flutter/Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |build_configuration| build_configuration.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64 i386' end end end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "AppDelegate.h" #import "GeneratedPluginRegistrant.h" @import GoogleMaps; @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Provide the GoogleMaps API key. NSString *mapsApiKey = [[NSProcessInfo processInfo] environment][@"MAPS_API_KEY"]; if ([mapsApiKey length] == 0) { mapsApiKey = @"YOUR KEY HERE"; } [GMSServices provideAPIKey:mapsApiKey]; // Register Flutter plugins. [GeneratedPluginRegistrant registerWithRegistry:self]; return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "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" } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName google_maps_flutter_example CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS NSLocationWhenInUseUsageDescription This app needs your location to test the location feature of the Google Maps plugin. UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 4510D964F3B1259FEDD3ABA6 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */; }; 4A097997B7B27CE82FFC3AB8 /* libPods-RunnerUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DC8ED0578E8D540BBDA17645 /* libPods-RunnerUITests.a */; }; 6851F3562835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6851F3552835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m */; }; 68E4726A2836FF0C00BDDDAC /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 68E472692836FF0C00BDDDAC /* MapKit.framework */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 982F2A6C27BADE17003C81F4 /* PartiallyMockedMapView.m in Sources */ = {isa = PBXBuildFile; fileRef = 982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */; }; F7151F13265D7ED70028CB91 /* GoogleMapsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */; }; F7151F21265D7EE50028CB91 /* GoogleMapsUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F20265D7EE50028CB91 /* GoogleMapsUITests.m */; }; FC8F35FC8CD533B128950487 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F267F68029D1A4E2E4C572A7 /* libPods-RunnerTests.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ F7151F15265D7ED70028CB91 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; F7151F23265D7EE50028CB91 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 6851F3552835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTGoogleMapJSONConversionsConversionTests.m; sourceTree = ""; }; 68E472692836FF0C00BDDDAC /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk/System/iOSSupport/System/Library/Frameworks/MapKit.framework; sourceTree = DEVELOPER_DIR; }; 6AC1E6095B09DE4B02ECF64E /* Pods-RunnerUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerUITests/Pods-RunnerUITests.release.xcconfig"; sourceTree = ""; }; 733AFAB37683A9DA7512F09C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 982F2A6A27BADE17003C81F4 /* PartiallyMockedMapView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PartiallyMockedMapView.h; sourceTree = ""; }; 982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PartiallyMockedMapView.m; sourceTree = ""; }; B7AFC65E3DD5AC60D834D83D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; DC8ED0578E8D540BBDA17645 /* libPods-RunnerUITests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerUITests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; DDDAC1342ABDF2F125577581 /* Pods-RunnerUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerUITests/Pods-RunnerUITests.debug.xcconfig"; sourceTree = ""; }; E52C6A6210A56F027C582EF9 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; EA0E91726245EDC22B97E8B9 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; F267F68029D1A4E2E4C572A7 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; F7151F10265D7ED70028CB91 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GoogleMapsTests.m; sourceTree = ""; }; F7151F14265D7ED70028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F7151F1E265D7EE50028CB91 /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F7151F20265D7EE50028CB91 /* GoogleMapsUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GoogleMapsUITests.m; sourceTree = ""; }; F7151F22265D7EE50028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 4510D964F3B1259FEDD3ABA6 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F0D265D7ED70028CB91 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 68E4726A2836FF0C00BDDDAC /* MapKit.framework in Frameworks */, FC8F35FC8CD533B128950487 /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F1B265D7EE50028CB91 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 4A097997B7B27CE82FFC3AB8 /* libPods-RunnerUITests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 1E7CF0857EFC88FC263CF3B2 /* Frameworks */ = { isa = PBXGroup; children = ( 68E472692836FF0C00BDDDAC /* MapKit.framework */, 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */, F267F68029D1A4E2E4C572A7 /* libPods-RunnerTests.a */, DC8ED0578E8D540BBDA17645 /* libPods-RunnerUITests.a */, ); name = Frameworks; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, F7151F11265D7ED70028CB91 /* RunnerTests */, F7151F1F265D7EE50028CB91 /* RunnerUITests */, 97C146EF1CF9000F007C117D /* Products */, A189CFE5474BF8A07908B2E0 /* Pods */, 1E7CF0857EFC88FC263CF3B2 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, F7151F10265D7ED70028CB91 /* RunnerTests.xctest */, F7151F1E265D7EE50028CB91 /* RunnerUITests.xctest */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; A189CFE5474BF8A07908B2E0 /* Pods */ = { isa = PBXGroup; children = ( B7AFC65E3DD5AC60D834D83D /* Pods-Runner.debug.xcconfig */, EA0E91726245EDC22B97E8B9 /* Pods-Runner.release.xcconfig */, E52C6A6210A56F027C582EF9 /* Pods-RunnerTests.debug.xcconfig */, 733AFAB37683A9DA7512F09C /* Pods-RunnerTests.release.xcconfig */, DDDAC1342ABDF2F125577581 /* Pods-RunnerUITests.debug.xcconfig */, 6AC1E6095B09DE4B02ECF64E /* Pods-RunnerUITests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; F7151F11265D7ED70028CB91 /* RunnerTests */ = { isa = PBXGroup; children = ( 6851F3552835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m */, F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */, 982F2A6A27BADE17003C81F4 /* PartiallyMockedMapView.h */, 982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */, F7151F14265D7ED70028CB91 /* Info.plist */, ); path = RunnerTests; sourceTree = ""; }; F7151F1F265D7EE50028CB91 /* RunnerUITests */ = { isa = PBXGroup; children = ( F7151F20265D7EE50028CB91 /* GoogleMapsUITests.m */, F7151F22265D7EE50028CB91 /* Info.plist */, ); path = RunnerUITests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 74BF216DF17B0C7F983459BD /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, BB6BD9A1101E970BEF85B6D2 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; F7151F0F265D7ED70028CB91 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = F7151F19265D7ED70028CB91 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( D067548A17DC238B80D2BD12 /* [CP] Check Pods Manifest.lock */, F7151F0C265D7ED70028CB91 /* Sources */, F7151F0D265D7ED70028CB91 /* Frameworks */, F7151F0E265D7ED70028CB91 /* Resources */, ); buildRules = ( ); dependencies = ( F7151F16265D7ED70028CB91 /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = F7151F10265D7ED70028CB91 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; F7151F1D265D7EE50028CB91 /* RunnerUITests */ = { isa = PBXNativeTarget; buildConfigurationList = F7151F25265D7EE50028CB91 /* Build configuration list for PBXNativeTarget "RunnerUITests" */; buildPhases = ( BD39F60794E9A0264D5D3752 /* [CP] Check Pods Manifest.lock */, F7151F1A265D7EE50028CB91 /* Sources */, F7151F1B265D7EE50028CB91 /* Frameworks */, F7151F1C265D7EE50028CB91 /* Resources */, ); buildRules = ( ); dependencies = ( F7151F24265D7EE50028CB91 /* PBXTargetDependency */, ); name = RunnerUITests; productName = RunnerUITests; productReference = F7151F1E265D7EE50028CB91 /* RunnerUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1320; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; }; F7151F0F265D7ED70028CB91 = { CreatedOnToolsVersion = 12.5; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; F7151F1D265D7EE50028CB91 = { CreatedOnToolsVersion = 12.5; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, F7151F0F265D7ED70028CB91 /* RunnerTests */, F7151F1D265D7EE50028CB91 /* RunnerUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F0E265D7ED70028CB91 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; F7151F1C265D7EE50028CB91 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 74BF216DF17B0C7F983459BD /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; 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"; }; BB6BD9A1101E970BEF85B6D2 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", "${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMaps.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; showEnvVarsInLog = 0; }; BD39F60794E9A0264D5D3752 /* [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-RunnerUITests-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; }; D067548A17DC238B80D2BD12 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F0C265D7ED70028CB91 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F7151F13265D7ED70028CB91 /* GoogleMapsTests.m in Sources */, 6851F3562835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m in Sources */, 982F2A6C27BADE17003C81F4 /* PartiallyMockedMapView.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F1A265D7EE50028CB91 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F7151F21265D7EE50028CB91 /* GoogleMapsUITests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ F7151F16265D7ED70028CB91 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = F7151F15265D7ED70028CB91 /* PBXContainerItemProxy */; }; F7151F24265D7EE50028CB91 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = F7151F23265D7EE50028CB91 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 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++"; 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 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 = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ENABLE_BITCODE = NO; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.googleMobileMapsExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ENABLE_BITCODE = NO; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.googleMobileMapsExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; F7151F17265D7ED70028CB91 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = E52C6A6210A56F027C582EF9 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64"; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Debug; }; F7151F18265D7ED70028CB91 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 733AFAB37683A9DA7512F09C /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64"; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; }; F7151F26265D7EE50028CB91 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = DDDAC1342ABDF2F125577581 /* Pods-RunnerUITests.debug.xcconfig */; buildSettings = { CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = Runner; }; name = Debug; }; F7151F27265D7EE50028CB91 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 6AC1E6095B09DE4B02ECF64E /* Pods-RunnerUITests.release.xcconfig */; buildSettings = { CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = Runner; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F7151F19265D7ED70028CB91 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( F7151F17265D7ED70028CB91 /* Debug */, F7151F18265D7ED70028CB91 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F7151F25265D7EE50028CB91 /* Build configuration list for PBXNativeTarget "RunnerUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( F7151F26265D7EE50028CB91 /* Debug */, F7151F27265D7EE50028CB91 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/lib/animate_camera.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter/google_maps_flutter.dart'; import 'page.dart'; class AnimateCameraPage extends GoogleMapExampleAppPage { const AnimateCameraPage({Key? key}) : super(const Icon(Icons.map), 'Camera control, animated', key: key); @override Widget build(BuildContext context) { return const AnimateCamera(); } } class AnimateCamera extends StatefulWidget { const AnimateCamera({Key? key}) : super(key: key); @override State createState() => AnimateCameraState(); } class AnimateCameraState extends State { GoogleMapController? mapController; // ignore: use_setters_to_change_properties void _onMapCreated(GoogleMapController controller) { mapController = controller; } @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 300.0, height: 200.0, child: GoogleMap( onMapCreated: _onMapCreated, initialCameraPosition: const CameraPosition(target: LatLng(0.0, 0.0)), ), ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Column( children: [ TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.newCameraPosition( const CameraPosition( bearing: 270.0, target: LatLng(51.5160895, -0.1294527), tilt: 30.0, zoom: 17.0, ), ), ); }, child: const Text('newCameraPosition'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.newLatLng( const LatLng(56.1725505, 10.1850512), ), ); }, child: const Text('newLatLng'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.newLatLngBounds( LatLngBounds( southwest: const LatLng(-38.483935, 113.248673), northeast: const LatLng(-8.982446, 153.823821), ), 10.0, ), ); }, child: const Text('newLatLngBounds'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.newLatLngZoom( const LatLng(37.4231613, -122.087159), 11.0, ), ); }, child: const Text('newLatLngZoom'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.scrollBy(150.0, -225.0), ); }, child: const Text('scrollBy'), ), ], ), Column( children: [ TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.zoomBy( -0.5, const Offset(30.0, 20.0), ), ); }, child: const Text('zoomBy with focus'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.zoomBy(-0.5), ); }, child: const Text('zoomBy'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.zoomIn(), ); }, child: const Text('zoomIn'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.zoomOut(), ); }, child: const Text('zoomOut'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.zoomTo(16.0), ); }, child: const Text('zoomTo'), ), ], ), ], ) ], ); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/lib/lite_mode.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter/google_maps_flutter.dart'; import 'page.dart'; const CameraPosition _kInitialPosition = CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0); class LiteModePage extends GoogleMapExampleAppPage { const LiteModePage({Key? key}) : super(const Icon(Icons.map), 'Lite mode', key: key); @override Widget build(BuildContext context) { return const _LiteModeBody(); } } class _LiteModeBody extends StatelessWidget { const _LiteModeBody(); @override Widget build(BuildContext context) { return const Card( child: Padding( padding: EdgeInsets.symmetric(vertical: 30.0), child: Center( child: SizedBox( width: 300.0, height: 300.0, child: GoogleMap( initialCameraPosition: _kInitialPosition, liteModeEnabled: true, ), ), ), ), ); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:google_maps_flutter_android/google_maps_flutter_android.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'animate_camera.dart'; import 'lite_mode.dart'; import 'map_click.dart'; import 'map_coordinates.dart'; import 'map_ui.dart'; import 'marker_icons.dart'; import 'move_camera.dart'; import 'padding.dart'; import 'page.dart'; import 'place_circle.dart'; import 'place_marker.dart'; import 'place_polygon.dart'; import 'place_polyline.dart'; import 'scrolling_map.dart'; import 'snapshot.dart'; import 'tile_overlay.dart'; final List _allPages = [ const MapUiPage(), const MapCoordinatesPage(), const MapClickPage(), const AnimateCameraPage(), const MoveCameraPage(), const PlaceMarkerPage(), const MarkerIconsPage(), const ScrollingMapPage(), const PlacePolylinePage(), const PlacePolygonPage(), const PlaceCirclePage(), const PaddingPage(), const SnapshotPage(), const LiteModePage(), const TileOverlayPage(), ]; /// MapsDemo is the Main Application. class MapsDemo extends StatelessWidget { /// Default Constructor const MapsDemo({Key? key}) : super(key: key); void _pushPage(BuildContext context, GoogleMapExampleAppPage page) { Navigator.of(context).push(MaterialPageRoute( builder: (_) => Scaffold( appBar: AppBar(title: Text(page.title)), body: page, ))); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('GoogleMaps examples')), body: ListView.builder( itemCount: _allPages.length, itemBuilder: (_, int index) => ListTile( leading: _allPages[index].leading, title: Text(_allPages[index].title), onTap: () => _pushPage(context, _allPages[index]), ), ), ); } } void main() { final GoogleMapsFlutterPlatform mapsImplementation = GoogleMapsFlutterPlatform.instance; if (mapsImplementation is GoogleMapsFlutterAndroid) { mapsImplementation.useAndroidViewSurface = true; } runApp(const MaterialApp(home: MapsDemo())); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/lib/map_click.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter/google_maps_flutter.dart'; import 'page.dart'; const CameraPosition _kInitialPosition = CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0); class MapClickPage extends GoogleMapExampleAppPage { const MapClickPage({Key? key}) : super(const Icon(Icons.mouse), 'Map click', key: key); @override Widget build(BuildContext context) { return const _MapClickBody(); } } class _MapClickBody extends StatefulWidget { const _MapClickBody(); @override State createState() => _MapClickBodyState(); } class _MapClickBodyState extends State<_MapClickBody> { _MapClickBodyState(); GoogleMapController? mapController; LatLng? _lastTap; LatLng? _lastLongPress; @override Widget build(BuildContext context) { final GoogleMap googleMap = GoogleMap( onMapCreated: onMapCreated, initialCameraPosition: _kInitialPosition, onTap: (LatLng pos) { setState(() { _lastTap = pos; }); }, onLongPress: (LatLng pos) { setState(() { _lastLongPress = pos; }); }, ); final List columnChildren = [ Padding( padding: const EdgeInsets.all(10.0), child: Center( child: SizedBox( width: 300.0, height: 200.0, child: googleMap, ), ), ), ]; if (mapController != null) { final String lastTap = 'Tap:\n${_lastTap ?? ""}\n'; final String lastLongPress = 'Long press:\n${_lastLongPress ?? ""}'; columnChildren.add(Center( child: Text( lastTap, textAlign: TextAlign.center, ))); columnChildren.add(Center( child: Text( _lastTap != null ? 'Tapped' : '', textAlign: TextAlign.center, ))); columnChildren.add(Center( child: Text( lastLongPress, textAlign: TextAlign.center, ))); columnChildren.add(Center( child: Text( _lastLongPress != null ? 'Long pressed' : '', textAlign: TextAlign.center, ))); } return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: columnChildren, ); } Future onMapCreated(GoogleMapController controller) async { setState(() { mapController = controller; }); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/lib/map_coordinates.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter/google_maps_flutter.dart'; import 'page.dart'; const CameraPosition _kInitialPosition = CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0); class MapCoordinatesPage extends GoogleMapExampleAppPage { const MapCoordinatesPage({Key? key}) : super(const Icon(Icons.map), 'Map coordinates', key: key); @override Widget build(BuildContext context) { return const _MapCoordinatesBody(); } } class _MapCoordinatesBody extends StatefulWidget { const _MapCoordinatesBody(); @override State createState() => _MapCoordinatesBodyState(); } class _MapCoordinatesBodyState extends State<_MapCoordinatesBody> { _MapCoordinatesBodyState(); GoogleMapController? mapController; LatLngBounds _visibleRegion = LatLngBounds( southwest: const LatLng(0, 0), northeast: const LatLng(0, 0), ); @override Widget build(BuildContext context) { final GoogleMap googleMap = GoogleMap( onMapCreated: onMapCreated, initialCameraPosition: _kInitialPosition, onCameraIdle: _updateVisibleRegion, // https://github.com/flutter/flutter/issues/54758 ); return NotificationListener( onNotification: (ScrollNotification scrollState) { _updateVisibleRegion(); return true; }, child: ListView( children: [ Padding( padding: const EdgeInsets.all(10.0), child: Center( child: SizedBox( width: 300.0, height: 200.0, child: googleMap, ), ), ), if (mapController != null) Center( child: Text('VisibleRegion:' '\nnortheast: ${_visibleRegion.northeast},' '\nsouthwest: ${_visibleRegion.southwest}'), ), // Add a block at the bottom of this list to allow validation that the visible region of the map // does not change when scrolled under the safe view on iOS. // https://github.com/flutter/flutter/issues/107913 const SizedBox( width: 300, height: 1000, ), ], ), ); } Future onMapCreated(GoogleMapController controller) async { final LatLngBounds visibleRegion = await controller.getVisibleRegion(); setState(() { mapController = controller; _visibleRegion = visibleRegion; }); } Future _updateVisibleRegion() async { final LatLngBounds visibleRegion = await mapController!.getVisibleRegion(); setState(() { _visibleRegion = visibleRegion; }); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/lib/map_ui.dart ================================================ // Copyright 2013 The Flutter 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:flutter/services.dart' show rootBundle; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'page.dart'; final LatLngBounds sydneyBounds = LatLngBounds( southwest: const LatLng(-34.022631, 150.620685), northeast: const LatLng(-33.571835, 151.325952), ); class MapUiPage extends GoogleMapExampleAppPage { const MapUiPage({Key? key}) : super(const Icon(Icons.map), 'User interface', key: key); @override Widget build(BuildContext context) { return const MapUiBody(); } } class MapUiBody extends StatefulWidget { const MapUiBody({Key? key}) : super(key: key); @override State createState() => MapUiBodyState(); } class MapUiBodyState extends State { MapUiBodyState(); static const CameraPosition _kInitialPosition = CameraPosition( target: LatLng(-33.852, 151.211), zoom: 11.0, ); CameraPosition _position = _kInitialPosition; bool _isMapCreated = false; final bool _isMoving = false; bool _compassEnabled = true; bool _mapToolbarEnabled = true; CameraTargetBounds _cameraTargetBounds = CameraTargetBounds.unbounded; MinMaxZoomPreference _minMaxZoomPreference = MinMaxZoomPreference.unbounded; MapType _mapType = MapType.normal; bool _rotateGesturesEnabled = true; bool _scrollGesturesEnabled = true; bool _tiltGesturesEnabled = true; bool _zoomControlsEnabled = false; bool _zoomGesturesEnabled = true; bool _indoorViewEnabled = true; bool _myLocationEnabled = true; bool _myTrafficEnabled = false; bool _myLocationButtonEnabled = true; late GoogleMapController _controller; bool _nightMode = false; @override void initState() { super.initState(); } @override void dispose() { super.dispose(); } Widget _compassToggler() { return TextButton( child: Text('${_compassEnabled ? 'disable' : 'enable'} compass'), onPressed: () { setState(() { _compassEnabled = !_compassEnabled; }); }, ); } Widget _mapToolbarToggler() { return TextButton( child: Text('${_mapToolbarEnabled ? 'disable' : 'enable'} map toolbar'), onPressed: () { setState(() { _mapToolbarEnabled = !_mapToolbarEnabled; }); }, ); } Widget _latLngBoundsToggler() { return TextButton( child: Text( _cameraTargetBounds.bounds == null ? 'bound camera target' : 'release camera target', ), onPressed: () { setState(() { _cameraTargetBounds = _cameraTargetBounds.bounds == null ? CameraTargetBounds(sydneyBounds) : CameraTargetBounds.unbounded; }); }, ); } Widget _zoomBoundsToggler() { return TextButton( child: Text(_minMaxZoomPreference.minZoom == null ? 'bound zoom' : 'release zoom'), onPressed: () { setState(() { _minMaxZoomPreference = _minMaxZoomPreference.minZoom == null ? const MinMaxZoomPreference(12.0, 16.0) : MinMaxZoomPreference.unbounded; }); }, ); } Widget _mapTypeCycler() { final MapType nextType = MapType.values[(_mapType.index + 1) % MapType.values.length]; return TextButton( child: Text('change map type to $nextType'), onPressed: () { setState(() { _mapType = nextType; }); }, ); } Widget _rotateToggler() { return TextButton( child: Text('${_rotateGesturesEnabled ? 'disable' : 'enable'} rotate'), onPressed: () { setState(() { _rotateGesturesEnabled = !_rotateGesturesEnabled; }); }, ); } Widget _scrollToggler() { return TextButton( child: Text('${_scrollGesturesEnabled ? 'disable' : 'enable'} scroll'), onPressed: () { setState(() { _scrollGesturesEnabled = !_scrollGesturesEnabled; }); }, ); } Widget _tiltToggler() { return TextButton( child: Text('${_tiltGesturesEnabled ? 'disable' : 'enable'} tilt'), onPressed: () { setState(() { _tiltGesturesEnabled = !_tiltGesturesEnabled; }); }, ); } Widget _zoomToggler() { return TextButton( child: Text('${_zoomGesturesEnabled ? 'disable' : 'enable'} zoom'), onPressed: () { setState(() { _zoomGesturesEnabled = !_zoomGesturesEnabled; }); }, ); } Widget _zoomControlsToggler() { return TextButton( child: Text('${_zoomControlsEnabled ? 'disable' : 'enable'} zoom controls'), onPressed: () { setState(() { _zoomControlsEnabled = !_zoomControlsEnabled; }); }, ); } Widget _indoorViewToggler() { return TextButton( child: Text('${_indoorViewEnabled ? 'disable' : 'enable'} indoor'), onPressed: () { setState(() { _indoorViewEnabled = !_indoorViewEnabled; }); }, ); } Widget _myLocationToggler() { return TextButton( child: Text( '${_myLocationEnabled ? 'disable' : 'enable'} my location marker'), onPressed: () { setState(() { _myLocationEnabled = !_myLocationEnabled; }); }, ); } Widget _myLocationButtonToggler() { return TextButton( child: Text( '${_myLocationButtonEnabled ? 'disable' : 'enable'} my location button'), onPressed: () { setState(() { _myLocationButtonEnabled = !_myLocationButtonEnabled; }); }, ); } Widget _myTrafficToggler() { return TextButton( child: Text('${_myTrafficEnabled ? 'disable' : 'enable'} my traffic'), onPressed: () { setState(() { _myTrafficEnabled = !_myTrafficEnabled; }); }, ); } Future _getFileData(String path) async { return rootBundle.loadString(path); } void _setMapStyle(String mapStyle) { setState(() { _nightMode = true; _controller.setMapStyle(mapStyle); }); } // Should only be called if _isMapCreated is true. Widget _nightModeToggler() { assert(_isMapCreated); return TextButton( child: Text('${_nightMode ? 'disable' : 'enable'} night mode'), onPressed: () { if (_nightMode) { setState(() { _nightMode = false; _controller.setMapStyle(null); }); } else { _getFileData('assets/night_mode.json').then(_setMapStyle); } }, ); } @override Widget build(BuildContext context) { final GoogleMap googleMap = GoogleMap( onMapCreated: onMapCreated, initialCameraPosition: _kInitialPosition, compassEnabled: _compassEnabled, mapToolbarEnabled: _mapToolbarEnabled, cameraTargetBounds: _cameraTargetBounds, minMaxZoomPreference: _minMaxZoomPreference, mapType: _mapType, rotateGesturesEnabled: _rotateGesturesEnabled, scrollGesturesEnabled: _scrollGesturesEnabled, tiltGesturesEnabled: _tiltGesturesEnabled, zoomGesturesEnabled: _zoomGesturesEnabled, zoomControlsEnabled: _zoomControlsEnabled, indoorViewEnabled: _indoorViewEnabled, myLocationEnabled: _myLocationEnabled, myLocationButtonEnabled: _myLocationButtonEnabled, trafficEnabled: _myTrafficEnabled, onCameraMove: _updateCameraPosition, ); final List columnChildren = [ Padding( padding: const EdgeInsets.all(10.0), child: Center( child: SizedBox( width: 300.0, height: 200.0, child: googleMap, ), ), ), ]; if (_isMapCreated) { columnChildren.add( Expanded( child: ListView( children: [ Text('camera bearing: ${_position.bearing}'), Text( 'camera target: ${_position.target.latitude.toStringAsFixed(4)},' '${_position.target.longitude.toStringAsFixed(4)}'), Text('camera zoom: ${_position.zoom}'), Text('camera tilt: ${_position.tilt}'), Text(_isMoving ? '(Camera moving)' : '(Camera idle)'), _compassToggler(), _mapToolbarToggler(), _latLngBoundsToggler(), _mapTypeCycler(), _zoomBoundsToggler(), _rotateToggler(), _scrollToggler(), _tiltToggler(), _zoomToggler(), _zoomControlsToggler(), _indoorViewToggler(), _myLocationToggler(), _myLocationButtonToggler(), _myTrafficToggler(), _nightModeToggler(), ], ), ), ); } return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: columnChildren, ); } void _updateCameraPosition(CameraPosition position) { setState(() { _position = position; }); } void onMapCreated(GoogleMapController controller) { setState(() { _controller = controller; _isMapCreated = true; }); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/lib/marker_icons.dart ================================================ // Copyright 2013 The Flutter 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 // ignore_for_file: unawaited_futures import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'page.dart'; class MarkerIconsPage extends GoogleMapExampleAppPage { const MarkerIconsPage({Key? key}) : super(const Icon(Icons.image), 'Marker icons', key: key); @override Widget build(BuildContext context) { return const MarkerIconsBody(); } } class MarkerIconsBody extends StatefulWidget { const MarkerIconsBody({Key? key}) : super(key: key); @override State createState() => MarkerIconsBodyState(); } const LatLng _kMapCenter = LatLng(52.4478, -3.5402); class MarkerIconsBodyState extends State { GoogleMapController? controller; BitmapDescriptor? _markerIcon; @override Widget build(BuildContext context) { _createMarkerImageFromAsset(context); return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 350.0, height: 300.0, child: GoogleMap( initialCameraPosition: const CameraPosition( target: _kMapCenter, zoom: 7.0, ), markers: {_createMarker()}, onMapCreated: _onMapCreated, ), ), ) ], ); } Marker _createMarker() { if (_markerIcon != null) { return Marker( markerId: const MarkerId('marker_1'), position: _kMapCenter, icon: _markerIcon!, ); } else { return const Marker( markerId: MarkerId('marker_1'), position: _kMapCenter, ); } } Future _createMarkerImageFromAsset(BuildContext context) async { if (_markerIcon == null) { final ImageConfiguration imageConfiguration = createLocalImageConfiguration(context, size: const Size.square(48)); BitmapDescriptor.fromAssetImage( imageConfiguration, 'assets/red_square.png') .then(_updateBitmap); } } void _updateBitmap(BitmapDescriptor bitmap) { setState(() { _markerIcon = bitmap; }); } void _onMapCreated(GoogleMapController controllerParam) { setState(() { controller = controllerParam; }); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/lib/move_camera.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter/google_maps_flutter.dart'; import 'page.dart'; class MoveCameraPage extends GoogleMapExampleAppPage { const MoveCameraPage({Key? key}) : super(const Icon(Icons.map), 'Camera control', key: key); @override Widget build(BuildContext context) { return const MoveCamera(); } } class MoveCamera extends StatefulWidget { const MoveCamera({Key? key}) : super(key: key); @override State createState() => MoveCameraState(); } class MoveCameraState extends State { GoogleMapController? mapController; // ignore: use_setters_to_change_properties void _onMapCreated(GoogleMapController controller) { mapController = controller; } @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 300.0, height: 200.0, child: GoogleMap( onMapCreated: _onMapCreated, initialCameraPosition: const CameraPosition(target: LatLng(0.0, 0.0)), ), ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Column( children: [ TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.newCameraPosition( const CameraPosition( bearing: 270.0, target: LatLng(51.5160895, -0.1294527), tilt: 30.0, zoom: 17.0, ), ), ); }, child: const Text('newCameraPosition'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.newLatLng( const LatLng(56.1725505, 10.1850512), ), ); }, child: const Text('newLatLng'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.newLatLngBounds( LatLngBounds( southwest: const LatLng(-38.483935, 113.248673), northeast: const LatLng(-8.982446, 153.823821), ), 10.0, ), ); }, child: const Text('newLatLngBounds'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.newLatLngZoom( const LatLng(37.4231613, -122.087159), 11.0, ), ); }, child: const Text('newLatLngZoom'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.scrollBy(150.0, -225.0), ); }, child: const Text('scrollBy'), ), ], ), Column( children: [ TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.zoomBy( -0.5, const Offset(30.0, 20.0), ), ); }, child: const Text('zoomBy with focus'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.zoomBy(-0.5), ); }, child: const Text('zoomBy'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.zoomIn(), ); }, child: const Text('zoomIn'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.zoomOut(), ); }, child: const Text('zoomOut'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.zoomTo(16.0), ); }, child: const Text('zoomTo'), ), ], ), ], ) ], ); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/lib/padding.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter/google_maps_flutter.dart'; import 'page.dart'; class PaddingPage extends GoogleMapExampleAppPage { const PaddingPage({Key? key}) : super(const Icon(Icons.map), 'Add padding to the map', key: key); @override Widget build(BuildContext context) { return const MarkerIconsBody(); } } class MarkerIconsBody extends StatefulWidget { const MarkerIconsBody({Key? key}) : super(key: key); @override State createState() => MarkerIconsBodyState(); } const LatLng _kMapCenter = LatLng(52.4478, -3.5402); class MarkerIconsBodyState extends State { GoogleMapController? controller; EdgeInsets _padding = EdgeInsets.zero; @override Widget build(BuildContext context) { final GoogleMap googleMap = GoogleMap( onMapCreated: _onMapCreated, initialCameraPosition: const CameraPosition( target: _kMapCenter, zoom: 7.0, ), padding: _padding, ); final List columnChildren = [ Padding( padding: const EdgeInsets.all(10.0), child: Center( child: SizedBox( width: 300.0, height: 200.0, child: googleMap, ), ), ), const Padding( padding: EdgeInsets.only(top: 20), child: Center( child: Text( 'Enter Padding Below', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), ), ]; columnChildren.addAll([_paddingInput(), _buttons()]); return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: columnChildren, ); } void _onMapCreated(GoogleMapController controllerParam) { setState(() { controller = controllerParam; }); } final TextEditingController _topController = TextEditingController(); final TextEditingController _bottomController = TextEditingController(); final TextEditingController _leftController = TextEditingController(); final TextEditingController _rightController = TextEditingController(); Widget _paddingInput() { return Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ Flexible( flex: 2, child: TextField( controller: _topController, keyboardType: TextInputType.number, textAlign: TextAlign.center, decoration: const InputDecoration( hintText: 'Top', ), ), ), const Spacer(), Flexible( flex: 2, child: TextField( controller: _bottomController, keyboardType: TextInputType.number, textAlign: TextAlign.center, decoration: const InputDecoration( hintText: 'Bottom', ), ), ), const Spacer(), Flexible( flex: 2, child: TextField( controller: _leftController, keyboardType: TextInputType.number, textAlign: TextAlign.center, decoration: const InputDecoration( hintText: 'Left', ), ), ), const Spacer(), Flexible( flex: 2, child: TextField( controller: _rightController, keyboardType: TextInputType.number, textAlign: TextAlign.center, decoration: const InputDecoration( hintText: 'Right', ), ), ), ], ), ); } Widget _buttons() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ TextButton( child: const Text('Set Padding'), onPressed: () { setState(() { _padding = EdgeInsets.fromLTRB( double.tryParse(_leftController.value.text) ?? 0, double.tryParse(_topController.value.text) ?? 0, double.tryParse(_rightController.value.text) ?? 0, double.tryParse(_bottomController.value.text) ?? 0); }); }, ), TextButton( child: const Text('Reset Padding'), onPressed: () { setState(() { _topController.clear(); _bottomController.clear(); _leftController.clear(); _rightController.clear(); _padding = EdgeInsets.zero; }); }, ) ], ), ); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/lib/page.dart ================================================ // Copyright 2013 The Flutter 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'; abstract class GoogleMapExampleAppPage extends StatelessWidget { const GoogleMapExampleAppPage(this.leading, this.title, {Key? key}) : super(key: key); final Widget leading; final String title; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/lib/place_circle.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter/google_maps_flutter.dart'; import 'page.dart'; class PlaceCirclePage extends GoogleMapExampleAppPage { const PlaceCirclePage({Key? key}) : super(const Icon(Icons.linear_scale), 'Place circle', key: key); @override Widget build(BuildContext context) { return const PlaceCircleBody(); } } class PlaceCircleBody extends StatefulWidget { const PlaceCircleBody({Key? key}) : super(key: key); @override State createState() => PlaceCircleBodyState(); } class PlaceCircleBodyState extends State { PlaceCircleBodyState(); GoogleMapController? controller; Map circles = {}; int _circleIdCounter = 1; CircleId? selectedCircle; // Values when toggling circle color int fillColorsIndex = 0; int strokeColorsIndex = 0; List colors = [ Colors.purple, Colors.red, Colors.green, Colors.pink, ]; // Values when toggling circle stroke width int widthsIndex = 0; List widths = [10, 20, 5]; // ignore: use_setters_to_change_properties void _onMapCreated(GoogleMapController controller) { this.controller = controller; } @override void dispose() { super.dispose(); } void _onCircleTapped(CircleId circleId) { setState(() { selectedCircle = circleId; }); } void _remove(CircleId circleId) { setState(() { if (circles.containsKey(circleId)) { circles.remove(circleId); } if (circleId == selectedCircle) { selectedCircle = null; } }); } void _add() { final int circleCount = circles.length; if (circleCount == 12) { return; } final String circleIdVal = 'circle_id_$_circleIdCounter'; _circleIdCounter++; final CircleId circleId = CircleId(circleIdVal); final Circle circle = Circle( circleId: circleId, consumeTapEvents: true, strokeColor: Colors.orange, fillColor: Colors.green, strokeWidth: 5, center: _createCenter(), radius: 50000, onTap: () { _onCircleTapped(circleId); }, ); setState(() { circles[circleId] = circle; }); } void _toggleVisible(CircleId circleId) { final Circle circle = circles[circleId]!; setState(() { circles[circleId] = circle.copyWith( visibleParam: !circle.visible, ); }); } void _changeFillColor(CircleId circleId) { final Circle circle = circles[circleId]!; setState(() { circles[circleId] = circle.copyWith( fillColorParam: colors[++fillColorsIndex % colors.length], ); }); } void _changeStrokeColor(CircleId circleId) { final Circle circle = circles[circleId]!; setState(() { circles[circleId] = circle.copyWith( strokeColorParam: colors[++strokeColorsIndex % colors.length], ); }); } void _changeStrokeWidth(CircleId circleId) { final Circle circle = circles[circleId]!; setState(() { circles[circleId] = circle.copyWith( strokeWidthParam: widths[++widthsIndex % widths.length], ); }); } @override Widget build(BuildContext context) { final CircleId? selectedId = selectedCircle; return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 350.0, height: 300.0, child: GoogleMap( initialCameraPosition: const CameraPosition( target: LatLng(52.4478, -3.5402), zoom: 7.0, ), circles: Set.of(circles.values), onMapCreated: _onMapCreated, ), ), ), Expanded( child: SingleChildScrollView( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row( children: [ Column( children: [ TextButton( onPressed: _add, child: const Text('add'), ), TextButton( onPressed: (selectedId == null) ? null : () => _remove(selectedId), child: const Text('remove'), ), TextButton( onPressed: (selectedId == null) ? null : () => _toggleVisible(selectedId), child: const Text('toggle visible'), ), ], ), Column( children: [ TextButton( onPressed: (selectedId == null) ? null : () => _changeStrokeWidth(selectedId), child: const Text('change stroke width'), ), TextButton( onPressed: (selectedId == null) ? null : () => _changeStrokeColor(selectedId), child: const Text('change stroke color'), ), TextButton( onPressed: (selectedId == null) ? null : () => _changeFillColor(selectedId), child: const Text('change fill color'), ), ], ) ], ) ], ), ), ), ], ); } LatLng _createCenter() { final double offset = _circleIdCounter.ceilToDouble(); return _createLatLng(51.4816 + offset * 0.2, -3.1791); } LatLng _createLatLng(double lat, double lng) { return LatLng(lat, lng); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/lib/place_marker.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'dart:math'; import 'dart:typed_data'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'page.dart'; class PlaceMarkerPage extends GoogleMapExampleAppPage { const PlaceMarkerPage({Key? key}) : super(const Icon(Icons.place), 'Place marker', key: key); @override Widget build(BuildContext context) { return const PlaceMarkerBody(); } } class PlaceMarkerBody extends StatefulWidget { const PlaceMarkerBody({Key? key}) : super(key: key); @override State createState() => PlaceMarkerBodyState(); } typedef MarkerUpdateAction = Marker Function(Marker marker); class PlaceMarkerBodyState extends State { PlaceMarkerBodyState(); static const LatLng center = LatLng(-33.86711, 151.1947171); GoogleMapController? controller; Map markers = {}; MarkerId? selectedMarker; int _markerIdCounter = 1; LatLng? markerPosition; // ignore: use_setters_to_change_properties void _onMapCreated(GoogleMapController controller) { this.controller = controller; } @override void dispose() { super.dispose(); } void _onMarkerTapped(MarkerId markerId) { final Marker? tappedMarker = markers[markerId]; if (tappedMarker != null) { setState(() { final MarkerId? previousMarkerId = selectedMarker; if (previousMarkerId != null && markers.containsKey(previousMarkerId)) { final Marker resetOld = markers[previousMarkerId]! .copyWith(iconParam: BitmapDescriptor.defaultMarker); markers[previousMarkerId] = resetOld; } selectedMarker = markerId; final Marker newMarker = tappedMarker.copyWith( iconParam: BitmapDescriptor.defaultMarkerWithHue( BitmapDescriptor.hueGreen, ), ); markers[markerId] = newMarker; markerPosition = null; }); } } Future _onMarkerDrag(MarkerId markerId, LatLng newPosition) async { setState(() { markerPosition = newPosition; }); } Future _onMarkerDragEnd(MarkerId markerId, LatLng newPosition) async { final Marker? tappedMarker = markers[markerId]; if (tappedMarker != null) { setState(() { markerPosition = null; }); await showDialog( context: context, builder: (BuildContext context) { return AlertDialog( actions: [ TextButton( child: const Text('OK'), onPressed: () => Navigator.of(context).pop(), ) ], content: Padding( padding: const EdgeInsets.symmetric(vertical: 66), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('Old position: ${tappedMarker.position}'), Text('New position: $newPosition'), ], ))); }); } } void _add() { final int markerCount = markers.length; if (markerCount == 12) { return; } final String markerIdVal = 'marker_id_$_markerIdCounter'; _markerIdCounter++; final MarkerId markerId = MarkerId(markerIdVal); final Marker marker = Marker( markerId: markerId, position: LatLng( center.latitude + sin(_markerIdCounter * pi / 6.0) / 20.0, center.longitude + cos(_markerIdCounter * pi / 6.0) / 20.0, ), infoWindow: InfoWindow(title: markerIdVal, snippet: '*'), onTap: () => _onMarkerTapped(markerId), onDragEnd: (LatLng position) => _onMarkerDragEnd(markerId, position), onDrag: (LatLng position) => _onMarkerDrag(markerId, position), ); setState(() { markers[markerId] = marker; }); } void _remove(MarkerId markerId) { setState(() { if (markers.containsKey(markerId)) { markers.remove(markerId); } }); } void _changePosition(MarkerId markerId) { final Marker marker = markers[markerId]!; final LatLng current = marker.position; final Offset offset = Offset( center.latitude - current.latitude, center.longitude - current.longitude, ); setState(() { markers[markerId] = marker.copyWith( positionParam: LatLng( center.latitude + offset.dy, center.longitude + offset.dx, ), ); }); } void _changeAnchor(MarkerId markerId) { final Marker marker = markers[markerId]!; final Offset currentAnchor = marker.anchor; final Offset newAnchor = Offset(1.0 - currentAnchor.dy, currentAnchor.dx); setState(() { markers[markerId] = marker.copyWith( anchorParam: newAnchor, ); }); } Future _changeInfoAnchor(MarkerId markerId) async { final Marker marker = markers[markerId]!; final Offset currentAnchor = marker.infoWindow.anchor; final Offset newAnchor = Offset(1.0 - currentAnchor.dy, currentAnchor.dx); setState(() { markers[markerId] = marker.copyWith( infoWindowParam: marker.infoWindow.copyWith( anchorParam: newAnchor, ), ); }); } Future _toggleDraggable(MarkerId markerId) async { final Marker marker = markers[markerId]!; setState(() { markers[markerId] = marker.copyWith( draggableParam: !marker.draggable, ); }); } Future _toggleFlat(MarkerId markerId) async { final Marker marker = markers[markerId]!; setState(() { markers[markerId] = marker.copyWith( flatParam: !marker.flat, ); }); } Future _changeInfo(MarkerId markerId) async { final Marker marker = markers[markerId]!; final String newSnippet = '${marker.infoWindow.snippet!}*'; setState(() { markers[markerId] = marker.copyWith( infoWindowParam: marker.infoWindow.copyWith( snippetParam: newSnippet, ), ); }); } Future _changeAlpha(MarkerId markerId) async { final Marker marker = markers[markerId]!; final double current = marker.alpha; setState(() { markers[markerId] = marker.copyWith( alphaParam: current < 0.1 ? 1.0 : current * 0.75, ); }); } Future _changeRotation(MarkerId markerId) async { final Marker marker = markers[markerId]!; final double current = marker.rotation; setState(() { markers[markerId] = marker.copyWith( rotationParam: current == 330.0 ? 0.0 : current + 30.0, ); }); } Future _toggleVisible(MarkerId markerId) async { final Marker marker = markers[markerId]!; setState(() { markers[markerId] = marker.copyWith( visibleParam: !marker.visible, ); }); } Future _changeZIndex(MarkerId markerId) async { final Marker marker = markers[markerId]!; final double current = marker.zIndex; setState(() { markers[markerId] = marker.copyWith( zIndexParam: current == 12.0 ? 0.0 : current + 1.0, ); }); } void _setMarkerIcon(MarkerId markerId, BitmapDescriptor assetIcon) { final Marker marker = markers[markerId]!; setState(() { markers[markerId] = marker.copyWith( iconParam: assetIcon, ); }); } Future _getAssetIcon(BuildContext context) async { final Completer bitmapIcon = Completer(); final ImageConfiguration config = createLocalImageConfiguration(context); const AssetImage('assets/red_square.png') .resolve(config) .addListener(ImageStreamListener((ImageInfo image, bool sync) async { final ByteData? bytes = await image.image.toByteData(format: ImageByteFormat.png); if (bytes == null) { bitmapIcon.completeError(Exception('Unable to encode icon')); return; } final BitmapDescriptor bitmap = BitmapDescriptor.fromBytes(bytes.buffer.asUint8List()); bitmapIcon.complete(bitmap); })); return bitmapIcon.future; } @override Widget build(BuildContext context) { final MarkerId? selectedId = selectedMarker; return Stack(children: [ Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded( child: GoogleMap( onMapCreated: _onMapCreated, initialCameraPosition: const CameraPosition( target: LatLng(-33.852, 151.211), zoom: 11.0, ), markers: Set.of(markers.values), ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ TextButton( onPressed: _add, child: const Text('Add'), ), TextButton( onPressed: selectedId == null ? null : () => _remove(selectedId), child: const Text('Remove'), ), ], ), Wrap( alignment: WrapAlignment.spaceEvenly, children: [ TextButton( onPressed: selectedId == null ? null : () => _changeInfo(selectedId), child: const Text('change info'), ), TextButton( onPressed: selectedId == null ? null : () => _changeInfoAnchor(selectedId), child: const Text('change info anchor'), ), TextButton( onPressed: selectedId == null ? null : () => _changeAlpha(selectedId), child: const Text('change alpha'), ), TextButton( onPressed: selectedId == null ? null : () => _changeAnchor(selectedId), child: const Text('change anchor'), ), TextButton( onPressed: selectedId == null ? null : () => _toggleDraggable(selectedId), child: const Text('toggle draggable'), ), TextButton( onPressed: selectedId == null ? null : () => _toggleFlat(selectedId), child: const Text('toggle flat'), ), TextButton( onPressed: selectedId == null ? null : () => _changePosition(selectedId), child: const Text('change position'), ), TextButton( onPressed: selectedId == null ? null : () => _changeRotation(selectedId), child: const Text('change rotation'), ), TextButton( onPressed: selectedId == null ? null : () => _toggleVisible(selectedId), child: const Text('toggle visible'), ), TextButton( onPressed: selectedId == null ? null : () => _changeZIndex(selectedId), child: const Text('change zIndex'), ), TextButton( onPressed: selectedId == null ? null : () { _getAssetIcon(context).then( (BitmapDescriptor icon) { _setMarkerIcon(selectedId, icon); }, ); }, child: const Text('set marker icon'), ), ], ), ], ), Visibility( visible: markerPosition != null, child: Container( color: Colors.white70, height: 30, padding: const EdgeInsets.only(left: 12, right: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ if (markerPosition == null) Container() else Expanded(child: Text('lat: ${markerPosition!.latitude}')), if (markerPosition == null) Container() else Expanded(child: Text('lng: ${markerPosition!.longitude}')), ], ), ), ), ]); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/lib/place_polygon.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter/google_maps_flutter.dart'; import 'page.dart'; class PlacePolygonPage extends GoogleMapExampleAppPage { const PlacePolygonPage({Key? key}) : super(const Icon(Icons.linear_scale), 'Place polygon', key: key); @override Widget build(BuildContext context) { return const PlacePolygonBody(); } } class PlacePolygonBody extends StatefulWidget { const PlacePolygonBody({Key? key}) : super(key: key); @override State createState() => PlacePolygonBodyState(); } class PlacePolygonBodyState extends State { PlacePolygonBodyState(); GoogleMapController? controller; Map polygons = {}; Map polygonOffsets = {}; int _polygonIdCounter = 0; PolygonId? selectedPolygon; // Values when toggling polygon color int strokeColorsIndex = 0; int fillColorsIndex = 0; List colors = [ Colors.purple, Colors.red, Colors.green, Colors.pink, ]; // Values when toggling polygon width int widthsIndex = 0; List widths = [10, 20, 5]; // ignore: use_setters_to_change_properties void _onMapCreated(GoogleMapController controller) { this.controller = controller; } @override void dispose() { super.dispose(); } void _onPolygonTapped(PolygonId polygonId) { setState(() { selectedPolygon = polygonId; }); } void _remove(PolygonId polygonId) { setState(() { if (polygons.containsKey(polygonId)) { polygons.remove(polygonId); } selectedPolygon = null; }); } void _add() { final int polygonCount = polygons.length; if (polygonCount == 12) { return; } final String polygonIdVal = 'polygon_id_$_polygonIdCounter'; final PolygonId polygonId = PolygonId(polygonIdVal); final Polygon polygon = Polygon( polygonId: polygonId, consumeTapEvents: true, strokeColor: Colors.orange, strokeWidth: 5, fillColor: Colors.green, points: _createPoints(), onTap: () { _onPolygonTapped(polygonId); }, ); setState(() { polygons[polygonId] = polygon; polygonOffsets[polygonId] = _polygonIdCounter.ceilToDouble(); // increment _polygonIdCounter to have unique polygon id each time _polygonIdCounter++; }); } void _toggleGeodesic(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith( geodesicParam: !polygon.geodesic, ); }); } void _toggleVisible(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith( visibleParam: !polygon.visible, ); }); } void _changeStrokeColor(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith( strokeColorParam: colors[++strokeColorsIndex % colors.length], ); }); } void _changeFillColor(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith( fillColorParam: colors[++fillColorsIndex % colors.length], ); }); } void _changeWidth(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith( strokeWidthParam: widths[++widthsIndex % widths.length], ); }); } void _addHoles(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith(holesParam: _createHoles(polygonId)); }); } void _removeHoles(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith( holesParam: >[], ); }); } @override Widget build(BuildContext context) { final PolygonId? selectedId = selectedPolygon; return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 350.0, height: 300.0, child: GoogleMap( initialCameraPosition: const CameraPosition( target: LatLng(52.4478, -3.5402), zoom: 7.0, ), polygons: Set.of(polygons.values), onMapCreated: _onMapCreated, ), ), ), Expanded( child: SingleChildScrollView( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row( children: [ Column( children: [ TextButton( onPressed: _add, child: const Text('add'), ), TextButton( onPressed: (selectedId == null) ? null : () => _remove(selectedId), child: const Text('remove'), ), TextButton( onPressed: (selectedId == null) ? null : () => _toggleVisible(selectedId), child: const Text('toggle visible'), ), TextButton( onPressed: (selectedId == null) ? null : () => _toggleGeodesic(selectedId), child: const Text('toggle geodesic'), ), ], ), Column( children: [ TextButton( onPressed: (selectedId == null) ? null : ((polygons[selectedId]!.holes.isNotEmpty) ? null : () => _addHoles(selectedId)), child: const Text('add holes'), ), TextButton( onPressed: (selectedId == null) ? null : ((polygons[selectedId]!.holes.isEmpty) ? null : () => _removeHoles(selectedId)), child: const Text('remove holes'), ), TextButton( onPressed: (selectedId == null) ? null : () => _changeWidth(selectedId), child: const Text('change stroke width'), ), TextButton( onPressed: (selectedId == null) ? null : () => _changeStrokeColor(selectedId), child: const Text('change stroke color'), ), TextButton( onPressed: (selectedId == null) ? null : () => _changeFillColor(selectedId), child: const Text('change fill color'), ), ], ) ], ) ], ), ), ), ], ); } List _createPoints() { final List points = []; final double offset = _polygonIdCounter.ceilToDouble(); points.add(_createLatLng(51.2395 + offset, -3.4314)); points.add(_createLatLng(53.5234 + offset, -3.5314)); points.add(_createLatLng(52.4351 + offset, -4.5235)); points.add(_createLatLng(52.1231 + offset, -5.0829)); return points; } List> _createHoles(PolygonId polygonId) { final List> holes = >[]; final double offset = polygonOffsets[polygonId]!; final List hole1 = []; hole1.add(_createLatLng(51.8395 + offset, -3.8814)); hole1.add(_createLatLng(52.0234 + offset, -3.9914)); hole1.add(_createLatLng(52.1351 + offset, -4.4435)); hole1.add(_createLatLng(52.0231 + offset, -4.5829)); holes.add(hole1); final List hole2 = []; hole2.add(_createLatLng(52.2395 + offset, -3.6814)); hole2.add(_createLatLng(52.4234 + offset, -3.7914)); hole2.add(_createLatLng(52.5351 + offset, -4.2435)); hole2.add(_createLatLng(52.4231 + offset, -4.3829)); holes.add(hole2); return holes; } LatLng _createLatLng(double lat, double lng) { return LatLng(lat, lng); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/lib/place_polyline.dart ================================================ // Copyright 2013 The Flutter 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/foundation.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'page.dart'; class PlacePolylinePage extends GoogleMapExampleAppPage { const PlacePolylinePage({Key? key}) : super(const Icon(Icons.linear_scale), 'Place polyline', key: key); @override Widget build(BuildContext context) { return const PlacePolylineBody(); } } class PlacePolylineBody extends StatefulWidget { const PlacePolylineBody({Key? key}) : super(key: key); @override State createState() => PlacePolylineBodyState(); } class PlacePolylineBodyState extends State { PlacePolylineBodyState(); GoogleMapController? controller; Map polylines = {}; int _polylineIdCounter = 0; PolylineId? selectedPolyline; // Values when toggling polyline color int colorsIndex = 0; List colors = [ Colors.purple, Colors.red, Colors.green, Colors.pink, ]; // Values when toggling polyline width int widthsIndex = 0; List widths = [10, 20, 5]; int jointTypesIndex = 0; List jointTypes = [ JointType.mitered, JointType.bevel, JointType.round ]; // Values when toggling polyline end cap type int endCapsIndex = 0; List endCaps = [Cap.buttCap, Cap.squareCap, Cap.roundCap]; // Values when toggling polyline start cap type int startCapsIndex = 0; List startCaps = [Cap.buttCap, Cap.squareCap, Cap.roundCap]; // Values when toggling polyline pattern int patternsIndex = 0; List> patterns = >[ [], [ PatternItem.dash(30.0), PatternItem.gap(20.0), PatternItem.dot, PatternItem.gap(20.0) ], [PatternItem.dash(30.0), PatternItem.gap(20.0)], [PatternItem.dot, PatternItem.gap(10.0)], ]; // ignore: use_setters_to_change_properties void _onMapCreated(GoogleMapController controller) { this.controller = controller; } @override void dispose() { super.dispose(); } void _onPolylineTapped(PolylineId polylineId) { setState(() { selectedPolyline = polylineId; }); } void _remove(PolylineId polylineId) { setState(() { if (polylines.containsKey(polylineId)) { polylines.remove(polylineId); } selectedPolyline = null; }); } void _add() { final int polylineCount = polylines.length; if (polylineCount == 12) { return; } final String polylineIdVal = 'polyline_id_$_polylineIdCounter'; _polylineIdCounter++; final PolylineId polylineId = PolylineId(polylineIdVal); final Polyline polyline = Polyline( polylineId: polylineId, consumeTapEvents: true, color: Colors.orange, width: 5, points: _createPoints(), onTap: () { _onPolylineTapped(polylineId); }, ); setState(() { polylines[polylineId] = polyline; }); } void _toggleGeodesic(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( geodesicParam: !polyline.geodesic, ); }); } void _toggleVisible(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( visibleParam: !polyline.visible, ); }); } void _changeColor(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( colorParam: colors[++colorsIndex % colors.length], ); }); } void _changeWidth(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( widthParam: widths[++widthsIndex % widths.length], ); }); } void _changeJointType(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( jointTypeParam: jointTypes[++jointTypesIndex % jointTypes.length], ); }); } void _changeEndCap(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( endCapParam: endCaps[++endCapsIndex % endCaps.length], ); }); } void _changeStartCap(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( startCapParam: startCaps[++startCapsIndex % startCaps.length], ); }); } void _changePattern(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( patternsParam: patterns[++patternsIndex % patterns.length], ); }); } @override Widget build(BuildContext context) { final bool isIOS = !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS; final PolylineId? selectedId = selectedPolyline; return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 350.0, height: 300.0, child: GoogleMap( initialCameraPosition: const CameraPosition( target: LatLng(53.1721, -3.5402), zoom: 7.0, ), polylines: Set.of(polylines.values), onMapCreated: _onMapCreated, ), ), ), Expanded( child: SingleChildScrollView( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row( children: [ Column( children: [ TextButton( onPressed: _add, child: const Text('add'), ), TextButton( onPressed: (selectedId == null) ? null : () => _remove(selectedId), child: const Text('remove'), ), TextButton( onPressed: (selectedId == null) ? null : () => _toggleVisible(selectedId), child: const Text('toggle visible'), ), TextButton( onPressed: (selectedId == null) ? null : () => _toggleGeodesic(selectedId), child: const Text('toggle geodesic'), ), ], ), Column( children: [ TextButton( onPressed: (selectedId == null) ? null : () => _changeWidth(selectedId), child: const Text('change width'), ), TextButton( onPressed: (selectedId == null) ? null : () => _changeColor(selectedId), child: const Text('change color'), ), TextButton( onPressed: isIOS || (selectedId == null) ? null : () => _changeStartCap(selectedId), child: const Text('change start cap [Android only]'), ), TextButton( onPressed: isIOS || (selectedId == null) ? null : () => _changeEndCap(selectedId), child: const Text('change end cap [Android only]'), ), TextButton( onPressed: isIOS || (selectedId == null) ? null : () => _changeJointType(selectedId), child: const Text('change joint type [Android only]'), ), TextButton( onPressed: isIOS || (selectedId == null) ? null : () => _changePattern(selectedId), child: const Text('change pattern [Android only]'), ), ], ) ], ) ], ), ), ), ], ); } List _createPoints() { final List points = []; final double offset = _polylineIdCounter.ceilToDouble(); points.add(_createLatLng(51.4816 + offset, -3.1791)); points.add(_createLatLng(53.0430 + offset, -2.9925)); points.add(_createLatLng(53.1396 + offset, -4.2739)); points.add(_createLatLng(52.4153 + offset, -4.0829)); return points; } LatLng _createLatLng(double lat, double lng) { return LatLng(lat, lng); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/lib/readme_sample.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.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: 'Flutter Google Maps Demo', home: MapSample(), ); } } // #docregion MapSample class MapSample extends StatefulWidget { const MapSample({Key? key}) : super(key: key); @override State createState() => MapSampleState(); } class MapSampleState extends State { final Completer _controller = Completer(); static const CameraPosition _kGooglePlex = CameraPosition( target: LatLng(37.42796133580664, -122.085749655962), zoom: 14.4746, ); static const CameraPosition _kLake = CameraPosition( bearing: 192.8334901395799, target: LatLng(37.43296265331129, -122.08832357078792), tilt: 59.440717697143555, zoom: 19.151926040649414); @override Widget build(BuildContext context) { return Scaffold( body: GoogleMap( mapType: MapType.hybrid, initialCameraPosition: _kGooglePlex, onMapCreated: (GoogleMapController controller) { _controller.complete(controller); }, ), floatingActionButton: FloatingActionButton.extended( onPressed: _goToTheLake, label: const Text('To the lake!'), icon: const Icon(Icons.directions_boat), ), ); } Future _goToTheLake() async { final GoogleMapController controller = await _controller.future; controller.animateCamera(CameraUpdate.newCameraPosition(_kLake)); } } // #enddocregion MapSample ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/lib/scrolling_map.dart ================================================ // Copyright 2013 The Flutter 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/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'page.dart'; const LatLng _center = LatLng(32.080664, 34.9563837); class ScrollingMapPage extends GoogleMapExampleAppPage { const ScrollingMapPage({Key? key}) : super(const Icon(Icons.map), 'Scrolling map', key: key); @override Widget build(BuildContext context) { return const ScrollingMapBody(); } } class ScrollingMapBody extends StatelessWidget { const ScrollingMapBody({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return ListView( children: [ Card( child: Padding( padding: const EdgeInsets.symmetric(vertical: 30.0), child: Column( children: [ const Padding( padding: EdgeInsets.only(bottom: 12.0), child: Text('This map consumes all touch events.'), ), Center( child: SizedBox( width: 300.0, height: 300.0, child: GoogleMap( initialCameraPosition: const CameraPosition( target: _center, zoom: 11.0, ), gestureRecognizers: // >{ Factory( () => EagerGestureRecognizer(), ), }, ), ), ), ], ), ), ), Card( child: Padding( padding: const EdgeInsets.symmetric(vertical: 30.0), child: Column( children: [ const Text("This map doesn't consume the vertical drags."), const Padding( padding: EdgeInsets.only(bottom: 12.0), child: Text('It still gets other gestures (e.g scale or tap).'), ), Center( child: SizedBox( width: 300.0, height: 300.0, child: GoogleMap( initialCameraPosition: const CameraPosition( target: _center, zoom: 11.0, ), markers: { Marker( markerId: const MarkerId('test_marker_id'), position: LatLng( _center.latitude, _center.longitude, ), infoWindow: const InfoWindow( title: 'An interesting location', snippet: '*', ), ), }, gestureRecognizers: < Factory>{ Factory( () => ScaleGestureRecognizer(), ), }, ), ), ), ], ), ), ), ], ); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/lib/snapshot.dart ================================================ // Copyright 2013 The Flutter 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 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'page.dart'; const CameraPosition _kInitialPosition = CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0); class SnapshotPage extends GoogleMapExampleAppPage { const SnapshotPage({Key? key}) : super(const Icon(Icons.camera_alt), 'Take a snapshot of the map', key: key); @override Widget build(BuildContext context) { return _SnapshotBody(); } } class _SnapshotBody extends StatefulWidget { @override _SnapshotBodyState createState() => _SnapshotBodyState(); } class _SnapshotBodyState extends State<_SnapshotBody> { GoogleMapController? _mapController; Uint8List? _imageBytes; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ SizedBox( height: 180, child: GoogleMap( onMapCreated: onMapCreated, initialCameraPosition: _kInitialPosition, ), ), TextButton( child: const Text('Take a snapshot'), onPressed: () async { final Uint8List? imageBytes = await _mapController?.takeSnapshot(); setState(() { _imageBytes = imageBytes; }); }, ), Container( decoration: BoxDecoration(color: Colors.blueGrey[50]), height: 180, child: _imageBytes != null ? Image.memory(_imageBytes!) : null, ), ], ), ); } // ignore: use_setters_to_change_properties void onMapCreated(GoogleMapController controller) { _mapController = controller; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/lib/tile_overlay.dart ================================================ // Copyright 2013 The Flutter 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 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'page.dart'; class TileOverlayPage extends GoogleMapExampleAppPage { const TileOverlayPage({Key? key}) : super(const Icon(Icons.map), 'Tile overlay', key: key); @override Widget build(BuildContext context) { return const TileOverlayBody(); } } class TileOverlayBody extends StatefulWidget { const TileOverlayBody({Key? key}) : super(key: key); @override State createState() => TileOverlayBodyState(); } class TileOverlayBodyState extends State { TileOverlayBodyState(); GoogleMapController? controller; TileOverlay? _tileOverlay; // ignore: use_setters_to_change_properties void _onMapCreated(GoogleMapController controller) { this.controller = controller; } @override void dispose() { super.dispose(); } void _removeTileOverlay() { setState(() { _tileOverlay = null; }); } void _addTileOverlay() { final TileOverlay tileOverlay = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_1'), tileProvider: _DebugTileProvider(), ); setState(() { _tileOverlay = tileOverlay; }); } void _clearTileCache() { if (_tileOverlay != null && controller != null) { controller!.clearTileCache(_tileOverlay!.tileOverlayId); } } @override Widget build(BuildContext context) { final Set overlays = { if (_tileOverlay != null) _tileOverlay!, }; return Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 350.0, height: 300.0, child: GoogleMap( initialCameraPosition: const CameraPosition( target: LatLng(59.935460, 30.325177), zoom: 7.0, ), tileOverlays: overlays, onMapCreated: _onMapCreated, ), ), ), TextButton( onPressed: _addTileOverlay, child: const Text('Add tile overlay'), ), TextButton( onPressed: _removeTileOverlay, child: const Text('Remove tile overlay'), ), TextButton( onPressed: _clearTileCache, child: const Text('Clear tile cache'), ), ], ); } } class _DebugTileProvider implements TileProvider { _DebugTileProvider() { boxPaint.isAntiAlias = true; boxPaint.color = Colors.blue; boxPaint.strokeWidth = 2.0; boxPaint.style = PaintingStyle.stroke; } static const int width = 100; static const int height = 100; static final Paint boxPaint = Paint(); static const TextStyle textStyle = TextStyle( color: Colors.red, fontSize: 20, ); @override Future getTile(int x, int y, int? zoom) async { final ui.PictureRecorder recorder = ui.PictureRecorder(); final Canvas canvas = Canvas(recorder); final TextSpan textSpan = TextSpan( text: '$x,$y', style: textStyle, ); final TextPainter textPainter = TextPainter( text: textSpan, textDirection: TextDirection.ltr, ); textPainter.layout( maxWidth: width.toDouble(), ); textPainter.paint(canvas, Offset.zero); canvas.drawRect( Rect.fromLTRB(0, 0, width.toDouble(), width.toDouble()), boxPaint); final ui.Picture picture = recorder.endRecording(); final Uint8List byteData = await picture .toImage(width, height) .then((ui.Image image) => image.toByteData(format: ui.ImageByteFormat.png)) .then((ByteData? byteData) => byteData!.buffer.asUint8List()); return Tile(width, height, byteData); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/pubspec.yaml ================================================ name: google_maps_flutter_example description: Demonstrates how to use the google_maps_flutter plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: cupertino_icons: ^1.0.5 flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.1 google_maps_flutter: # When depending on this package from a real application you should use: # google_maps_flutter: ^x.y.z # See https://dart.dev/tools/pub/dependencies#version-constraints # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ google_maps_flutter_android: ^2.1.10 google_maps_flutter_platform_interface: ^2.2.1 dev_dependencies: build_runner: ^2.1.10 espresso: ^0.2.0 flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true assets: - assets/ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. library google_maps_flutter; import 'dart:async'; import 'dart:io'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter_android/google_maps_flutter_android.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; export 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart' show ArgumentCallbacks, ArgumentCallback, BitmapDescriptor, CameraPosition, CameraPositionCallback, CameraTargetBounds, CameraUpdate, Cap, Circle, CircleId, InfoWindow, JointType, LatLng, LatLngBounds, MapStyleException, MapType, Marker, MarkerId, MinMaxZoomPreference, PatternItem, Polygon, PolygonId, Polyline, PolylineId, ScreenCoordinate, Tile, TileOverlayId, TileOverlay, TileProvider; part 'src/controller.dart'; part 'src/google_map.dart'; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart ================================================ // Copyright 2013 The Flutter 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: library_private_types_in_public_api part of google_maps_flutter; /// Controller for a single GoogleMap instance running on the host platform. class GoogleMapController { GoogleMapController._( this._googleMapState, { required this.mapId, }) { _connectStreams(mapId); } /// The mapId for this controller final int mapId; /// Initialize control of a [GoogleMap] with [id]. /// /// Mainly for internal use when instantiating a [GoogleMapController] passed /// in [GoogleMap.onMapCreated] callback. static Future init( int id, CameraPosition initialCameraPosition, _GoogleMapState googleMapState, ) async { assert(id != null); await GoogleMapsFlutterPlatform.instance.init(id); return GoogleMapController._( googleMapState, mapId: id, ); } final _GoogleMapState _googleMapState; void _connectStreams(int mapId) { if (_googleMapState.widget.onCameraMoveStarted != null) { GoogleMapsFlutterPlatform.instance .onCameraMoveStarted(mapId: mapId) .listen((_) => _googleMapState.widget.onCameraMoveStarted!()); } if (_googleMapState.widget.onCameraMove != null) { GoogleMapsFlutterPlatform.instance.onCameraMove(mapId: mapId).listen( (CameraMoveEvent e) => _googleMapState.widget.onCameraMove!(e.value)); } if (_googleMapState.widget.onCameraIdle != null) { GoogleMapsFlutterPlatform.instance .onCameraIdle(mapId: mapId) .listen((_) => _googleMapState.widget.onCameraIdle!()); } GoogleMapsFlutterPlatform.instance .onMarkerTap(mapId: mapId) .listen((MarkerTapEvent e) => _googleMapState.onMarkerTap(e.value)); GoogleMapsFlutterPlatform.instance.onMarkerDragStart(mapId: mapId).listen( (MarkerDragStartEvent e) => _googleMapState.onMarkerDragStart(e.value, e.position)); GoogleMapsFlutterPlatform.instance.onMarkerDrag(mapId: mapId).listen( (MarkerDragEvent e) => _googleMapState.onMarkerDrag(e.value, e.position)); GoogleMapsFlutterPlatform.instance.onMarkerDragEnd(mapId: mapId).listen( (MarkerDragEndEvent e) => _googleMapState.onMarkerDragEnd(e.value, e.position)); GoogleMapsFlutterPlatform.instance.onInfoWindowTap(mapId: mapId).listen( (InfoWindowTapEvent e) => _googleMapState.onInfoWindowTap(e.value)); GoogleMapsFlutterPlatform.instance .onPolylineTap(mapId: mapId) .listen((PolylineTapEvent e) => _googleMapState.onPolylineTap(e.value)); GoogleMapsFlutterPlatform.instance .onPolygonTap(mapId: mapId) .listen((PolygonTapEvent e) => _googleMapState.onPolygonTap(e.value)); GoogleMapsFlutterPlatform.instance .onCircleTap(mapId: mapId) .listen((CircleTapEvent e) => _googleMapState.onCircleTap(e.value)); GoogleMapsFlutterPlatform.instance .onTap(mapId: mapId) .listen((MapTapEvent e) => _googleMapState.onTap(e.position)); GoogleMapsFlutterPlatform.instance.onLongPress(mapId: mapId).listen( (MapLongPressEvent e) => _googleMapState.onLongPress(e.position)); } /// Updates configuration options of the map user interface. /// /// Change listeners are notified once the update has been made on the /// platform side. /// /// The returned [Future] completes after listeners have been notified. Future _updateMapConfiguration(MapConfiguration update) { return GoogleMapsFlutterPlatform.instance .updateMapConfiguration(update, mapId: mapId); } /// Updates marker configuration. /// /// Change listeners are notified once the update has been made on the /// platform side. /// /// The returned [Future] completes after listeners have been notified. Future _updateMarkers(MarkerUpdates markerUpdates) { assert(markerUpdates != null); return GoogleMapsFlutterPlatform.instance .updateMarkers(markerUpdates, mapId: mapId); } /// Updates polygon configuration. /// /// Change listeners are notified once the update has been made on the /// platform side. /// /// The returned [Future] completes after listeners have been notified. Future _updatePolygons(PolygonUpdates polygonUpdates) { assert(polygonUpdates != null); return GoogleMapsFlutterPlatform.instance .updatePolygons(polygonUpdates, mapId: mapId); } /// Updates polyline configuration. /// /// Change listeners are notified once the update has been made on the /// platform side. /// /// The returned [Future] completes after listeners have been notified. Future _updatePolylines(PolylineUpdates polylineUpdates) { assert(polylineUpdates != null); return GoogleMapsFlutterPlatform.instance .updatePolylines(polylineUpdates, mapId: mapId); } /// Updates circle configuration. /// /// Change listeners are notified once the update has been made on the /// platform side. /// /// The returned [Future] completes after listeners have been notified. Future _updateCircles(CircleUpdates circleUpdates) { assert(circleUpdates != null); return GoogleMapsFlutterPlatform.instance .updateCircles(circleUpdates, mapId: mapId); } /// Updates tile overlays configuration. /// /// Change listeners are notified once the update has been made on the /// platform side. /// /// The returned [Future] completes after listeners have been notified. Future _updateTileOverlays(Set newTileOverlays) { return GoogleMapsFlutterPlatform.instance .updateTileOverlays(newTileOverlays: newTileOverlays, mapId: mapId); } /// Clears the tile cache so that all tiles will be requested again from the /// [TileProvider]. /// /// The current tiles from this tile overlay will also be /// cleared from the map after calling this method. The API maintains a small /// in-memory cache of tiles. If you want to cache tiles for longer, you /// should implement an on-disk cache. Future clearTileCache(TileOverlayId tileOverlayId) async { assert(tileOverlayId != null); return GoogleMapsFlutterPlatform.instance .clearTileCache(tileOverlayId, mapId: mapId); } /// Starts an animated change of the map camera position. /// /// The returned [Future] completes after the change has been started on the /// platform side. Future animateCamera(CameraUpdate cameraUpdate) { return GoogleMapsFlutterPlatform.instance .animateCamera(cameraUpdate, mapId: mapId); } /// Changes the map camera position. /// /// The returned [Future] completes after the change has been made on the /// platform side. Future moveCamera(CameraUpdate cameraUpdate) { return GoogleMapsFlutterPlatform.instance .moveCamera(cameraUpdate, mapId: mapId); } /// Sets the styling of the base map. /// /// Set to `null` to clear any previous custom styling. /// /// If problems were detected with the [mapStyle], including un-parsable /// styling JSON, unrecognized feature type, unrecognized element type, or /// invalid styler keys: [MapStyleException] is thrown and the current /// style is left unchanged. /// /// The style string can be generated using [map style tool](https://mapstyle.withgoogle.com/). /// Also, refer [iOS](https://developers.google.com/maps/documentation/ios-sdk/style-reference) /// and [Android](https://developers.google.com/maps/documentation/android-sdk/style-reference) /// style reference for more information regarding the supported styles. Future setMapStyle(String? mapStyle) { return GoogleMapsFlutterPlatform.instance .setMapStyle(mapStyle, mapId: mapId); } /// Return [LatLngBounds] defining the region that is visible in a map. Future getVisibleRegion() { return GoogleMapsFlutterPlatform.instance.getVisibleRegion(mapId: mapId); } /// Return [ScreenCoordinate] of the [LatLng] in the current map view. /// /// A projection is used to translate between on screen location and geographic coordinates. /// Screen location is in screen pixels (not display pixels) with respect to the top left corner /// of the map, not necessarily of the whole screen. Future getScreenCoordinate(LatLng latLng) { return GoogleMapsFlutterPlatform.instance .getScreenCoordinate(latLng, mapId: mapId); } /// Returns [LatLng] corresponding to the [ScreenCoordinate] in the current map view. /// /// Returned [LatLng] corresponds to a screen location. The screen location is specified in screen /// pixels (not display pixels) relative to the top left of the map, not top left of the whole screen. Future getLatLng(ScreenCoordinate screenCoordinate) { return GoogleMapsFlutterPlatform.instance .getLatLng(screenCoordinate, mapId: mapId); } /// Programmatically show the Info Window for a [Marker]. /// /// The `markerId` must match one of the markers on the map. /// An invalid `markerId` triggers an "Invalid markerId" error. /// /// * See also: /// * [hideMarkerInfoWindow] to hide the Info Window. /// * [isMarkerInfoWindowShown] to check if the Info Window is showing. Future showMarkerInfoWindow(MarkerId markerId) { assert(markerId != null); return GoogleMapsFlutterPlatform.instance .showMarkerInfoWindow(markerId, mapId: mapId); } /// Programmatically hide the Info Window for a [Marker]. /// /// The `markerId` must match one of the markers on the map. /// An invalid `markerId` triggers an "Invalid markerId" error. /// /// * See also: /// * [showMarkerInfoWindow] to show the Info Window. /// * [isMarkerInfoWindowShown] to check if the Info Window is showing. Future hideMarkerInfoWindow(MarkerId markerId) { assert(markerId != null); return GoogleMapsFlutterPlatform.instance .hideMarkerInfoWindow(markerId, mapId: mapId); } /// Returns `true` when the [InfoWindow] is showing, `false` otherwise. /// /// The `markerId` must match one of the markers on the map. /// An invalid `markerId` triggers an "Invalid markerId" error. /// /// * See also: /// * [showMarkerInfoWindow] to show the Info Window. /// * [hideMarkerInfoWindow] to hide the Info Window. Future isMarkerInfoWindowShown(MarkerId markerId) { assert(markerId != null); return GoogleMapsFlutterPlatform.instance .isMarkerInfoWindowShown(markerId, mapId: mapId); } /// Returns the current zoom level of the map Future getZoomLevel() { return GoogleMapsFlutterPlatform.instance.getZoomLevel(mapId: mapId); } /// Returns the image bytes of the map Future takeSnapshot() { return GoogleMapsFlutterPlatform.instance.takeSnapshot(mapId: mapId); } /// Disposes of the platform resources void dispose() { GoogleMapsFlutterPlatform.instance.dispose(mapId: mapId); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. part of google_maps_flutter; /// Callback method for when the map is ready to be used. /// /// Pass to [GoogleMap.onMapCreated] to receive a [GoogleMapController] when the /// map is created. typedef MapCreatedCallback = void Function(GoogleMapController controller); // This counter is used to provide a stable "constant" initialization id // to the buildView function, so the web implementation can use it as a // cache key. This needs to be provided from the outside, because web // views seem to re-render much more often that mobile platform views. int _nextMapCreationId = 0; /// Error thrown when an unknown map object ID is provided to a method. class UnknownMapObjectIdError extends Error { /// Creates an assertion error with the provided [message]. UnknownMapObjectIdError(this.objectType, this.objectId, [this.context]); /// The name of the map object whose ID is unknown. final String objectType; /// The unknown maps object ID. final MapsObjectId objectId; /// The context where the error occurred. final String? context; @override String toString() { if (context != null) { return 'Unknown $objectType ID "${objectId.value}" in $context'; } return 'Unknown $objectType ID "${objectId.value}"'; } } /// Android specific settings for [GoogleMap]. @Deprecated( 'See https://pub.dev/packages/google_maps_flutter_android#display-mode') class AndroidGoogleMapsFlutter { @Deprecated( 'See https://pub.dev/packages/google_maps_flutter_android#display-mode') AndroidGoogleMapsFlutter._(); /// Whether to render [GoogleMap] with a [AndroidViewSurface] to build the Google Maps widget. /// /// This implementation uses hybrid composition to render the Google Maps /// Widget on Android. This comes at the cost of some performance on Android /// versions below 10. See /// https://flutter.dev/docs/development/platform-integration/platform-views#performance for more /// information. @Deprecated( 'See https://pub.dev/packages/google_maps_flutter_android#display-mode') static bool get useAndroidViewSurface { final GoogleMapsFlutterPlatform platform = GoogleMapsFlutterPlatform.instance; if (platform is GoogleMapsFlutterAndroid) { return platform.useAndroidViewSurface; } return false; } /// Set whether to render [GoogleMap] with a [AndroidViewSurface] to build the Google Maps widget. /// /// This implementation uses hybrid composition to render the Google Maps /// Widget on Android. This comes at the cost of some performance on Android /// versions below 10. See /// https://flutter.dev/docs/development/platform-integration/platform-views#performance for more /// information. @Deprecated( 'See https://pub.dev/packages/google_maps_flutter_android#display-mode') static set useAndroidViewSurface(bool useAndroidViewSurface) { final GoogleMapsFlutterPlatform platform = GoogleMapsFlutterPlatform.instance; if (platform is GoogleMapsFlutterAndroid) { platform.useAndroidViewSurface = useAndroidViewSurface; } } } /// A widget which displays a map with data obtained from the Google Maps service. class GoogleMap extends StatefulWidget { /// Creates a widget displaying data from Google Maps services. /// /// [AssertionError] will be thrown if [initialCameraPosition] is null; const GoogleMap({ Key? key, required this.initialCameraPosition, this.onMapCreated, this.gestureRecognizers = const >{}, this.compassEnabled = true, this.mapToolbarEnabled = true, this.cameraTargetBounds = CameraTargetBounds.unbounded, this.mapType = MapType.normal, this.minMaxZoomPreference = MinMaxZoomPreference.unbounded, this.rotateGesturesEnabled = true, this.scrollGesturesEnabled = true, this.zoomControlsEnabled = true, this.zoomGesturesEnabled = true, this.liteModeEnabled = false, this.tiltGesturesEnabled = true, this.myLocationEnabled = false, this.myLocationButtonEnabled = true, this.layoutDirection, /// If no padding is specified default padding will be 0. this.padding = EdgeInsets.zero, this.indoorViewEnabled = false, this.trafficEnabled = false, this.buildingsEnabled = true, this.markers = const {}, this.polygons = const {}, this.polylines = const {}, this.circles = const {}, this.onCameraMoveStarted, this.tileOverlays = const {}, this.onCameraMove, this.onCameraIdle, this.onTap, this.onLongPress, }) : assert(initialCameraPosition != null), super(key: key); /// Callback method for when the map is ready to be used. /// /// Used to receive a [GoogleMapController] for this [GoogleMap]. final MapCreatedCallback? onMapCreated; /// The initial position of the map's camera. final CameraPosition initialCameraPosition; /// True if the map should show a compass when rotated. final bool compassEnabled; /// True if the map should show a toolbar when you interact with the map. Android only. final bool mapToolbarEnabled; /// Geographical bounding box for the camera target. final CameraTargetBounds cameraTargetBounds; /// Type of map tiles to be rendered. final MapType mapType; /// The layout direction to use for the embedded view. /// /// If this is null, the ambient [Directionality] is used instead. If there is /// no ambient [Directionality], [TextDirection.ltr] is used. final TextDirection? layoutDirection; /// Preferred bounds for the camera zoom level. /// /// Actual bounds depend on map data and device. final MinMaxZoomPreference minMaxZoomPreference; /// True if the map view should respond to rotate gestures. final bool rotateGesturesEnabled; /// True if the map view should respond to scroll gestures. final bool scrollGesturesEnabled; /// True if the map view should show zoom controls. This includes two buttons /// to zoom in and zoom out. The default value is to show zoom controls. /// /// This is only supported on Android. And this field is silently ignored on iOS. final bool zoomControlsEnabled; /// True if the map view should respond to zoom gestures. final bool zoomGesturesEnabled; /// True if the map view should be in lite mode. Android only. /// /// See https://developers.google.com/maps/documentation/android-sdk/lite#overview_of_lite_mode for more details. final bool liteModeEnabled; /// True if the map view should respond to tilt gestures. final bool tiltGesturesEnabled; /// Padding to be set on map. See https://developers.google.com/maps/documentation/android-sdk/map#map_padding for more details. final EdgeInsets padding; /// Markers to be placed on the map. final Set markers; /// Polygons to be placed on the map. final Set polygons; /// Polylines to be placed on the map. final Set polylines; /// Circles to be placed on the map. final Set circles; /// Tile overlays to be placed on the map. final Set tileOverlays; /// Called when the camera starts moving. /// /// This can be initiated by the following: /// 1. Non-gesture animation initiated in response to user actions. /// For example: zoom buttons, my location button, or marker clicks. /// 2. Programmatically initiated animation. /// 3. Camera motion initiated in response to user gestures on the map. /// For example: pan, tilt, pinch to zoom, or rotate. final VoidCallback? onCameraMoveStarted; /// Called repeatedly as the camera continues to move after an /// onCameraMoveStarted call. /// /// This may be called as often as once every frame and should /// not perform expensive operations. final CameraPositionCallback? onCameraMove; /// Called when camera movement has ended, there are no pending /// animations and the user has stopped interacting with the map. final VoidCallback? onCameraIdle; /// Called every time a [GoogleMap] is tapped. final ArgumentCallback? onTap; /// Called every time a [GoogleMap] is long pressed. final ArgumentCallback? onLongPress; /// True if a "My Location" layer should be shown on the map. /// /// This layer includes a location indicator at the current device location, /// as well as a My Location button. /// * The indicator is a small blue dot if the device is stationary, or a /// chevron if the device is moving. /// * The My Location button animates to focus on the user's current location /// if the user's location is currently known. /// /// Enabling this feature requires adding location permissions to both native /// platforms of your app. /// * On Android add either /// `` /// or `` /// to your `AndroidManifest.xml` file. `ACCESS_COARSE_LOCATION` returns a /// location with an accuracy approximately equivalent to a city block, while /// `ACCESS_FINE_LOCATION` returns as precise a location as possible, although /// it consumes more battery power. You will also need to request these /// permissions during run-time. If they are not granted, the My Location /// feature will fail silently. /// * On iOS add a `NSLocationWhenInUseUsageDescription` key to your /// `Info.plist` file. This will automatically prompt the user for permissions /// when the map tries to turn on the My Location layer. final bool myLocationEnabled; /// Enables or disables the my-location button. /// /// The my-location button causes the camera to move such that the user's /// location is in the center of the map. If the button is enabled, it is /// only shown when the my-location layer is enabled. /// /// By default, the my-location button is enabled (and hence shown when the /// my-location layer is enabled). /// /// See also: /// * [myLocationEnabled] parameter. final bool myLocationButtonEnabled; /// Enables or disables the indoor view from the map final bool indoorViewEnabled; /// Enables or disables the traffic layer of the map final bool trafficEnabled; /// Enables or disables showing 3D buildings where available final bool buildingsEnabled; /// Which gestures should be consumed by the map. /// /// It is possible for other gesture recognizers to be competing with the map on pointer /// events, e.g if the map is inside a [ListView] the [ListView] will want to handle /// vertical drags. The map will claim gestures that are recognized by any of the /// recognizers on this list. /// /// When this set is empty, the map will only handle pointer events for gestures that /// were not claimed by any other gesture recognizer. final Set> gestureRecognizers; /// Creates a [State] for this [GoogleMap]. @override State createState() => _GoogleMapState(); } class _GoogleMapState extends State { final int _mapId = _nextMapCreationId++; final Completer _controller = Completer(); Map _markers = {}; Map _polygons = {}; Map _polylines = {}; Map _circles = {}; late MapConfiguration _mapConfiguration; @override Widget build(BuildContext context) { return GoogleMapsFlutterPlatform.instance.buildViewWithConfiguration( _mapId, onPlatformViewCreated, widgetConfiguration: MapWidgetConfiguration( textDirection: widget.layoutDirection ?? Directionality.maybeOf(context) ?? TextDirection.ltr, initialCameraPosition: widget.initialCameraPosition, gestureRecognizers: widget.gestureRecognizers, ), mapObjects: MapObjects( markers: widget.markers, polygons: widget.polygons, polylines: widget.polylines, circles: widget.circles, ), mapConfiguration: _mapConfiguration, ); } @override void initState() { super.initState(); _mapConfiguration = _configurationFromMapWidget(widget); _markers = keyByMarkerId(widget.markers); _polygons = keyByPolygonId(widget.polygons); _polylines = keyByPolylineId(widget.polylines); _circles = keyByCircleId(widget.circles); } @override void dispose() { _disposeController(); super.dispose(); } Future _disposeController() async { final GoogleMapController controller = await _controller.future; controller.dispose(); } @override void didUpdateWidget(GoogleMap oldWidget) { super.didUpdateWidget(oldWidget); _updateOptions(); _updateMarkers(); _updatePolygons(); _updatePolylines(); _updateCircles(); _updateTileOverlays(); } Future _updateOptions() async { final MapConfiguration newConfig = _configurationFromMapWidget(widget); final MapConfiguration updates = newConfig.diffFrom(_mapConfiguration); if (updates.isEmpty) { return; } final GoogleMapController controller = await _controller.future; // ignore: unawaited_futures controller._updateMapConfiguration(updates); _mapConfiguration = newConfig; } Future _updateMarkers() async { final GoogleMapController controller = await _controller.future; // ignore: unawaited_futures controller._updateMarkers( MarkerUpdates.from(_markers.values.toSet(), widget.markers)); _markers = keyByMarkerId(widget.markers); } Future _updatePolygons() async { final GoogleMapController controller = await _controller.future; // ignore: unawaited_futures controller._updatePolygons( PolygonUpdates.from(_polygons.values.toSet(), widget.polygons)); _polygons = keyByPolygonId(widget.polygons); } Future _updatePolylines() async { final GoogleMapController controller = await _controller.future; // ignore: unawaited_futures controller._updatePolylines( PolylineUpdates.from(_polylines.values.toSet(), widget.polylines)); _polylines = keyByPolylineId(widget.polylines); } Future _updateCircles() async { final GoogleMapController controller = await _controller.future; // ignore: unawaited_futures controller._updateCircles( CircleUpdates.from(_circles.values.toSet(), widget.circles)); _circles = keyByCircleId(widget.circles); } Future _updateTileOverlays() async { final GoogleMapController controller = await _controller.future; // ignore: unawaited_futures controller._updateTileOverlays(widget.tileOverlays); } Future onPlatformViewCreated(int id) async { final GoogleMapController controller = await GoogleMapController.init( id, widget.initialCameraPosition, this, ); _controller.complete(controller); _updateTileOverlays(); final MapCreatedCallback? onMapCreated = widget.onMapCreated; if (onMapCreated != null) { onMapCreated(controller); } } void onMarkerTap(MarkerId markerId) { assert(markerId != null); final Marker? marker = _markers[markerId]; if (marker == null) { throw UnknownMapObjectIdError('marker', markerId, 'onTap'); } final VoidCallback? onTap = marker.onTap; if (onTap != null) { onTap(); } } void onMarkerDragStart(MarkerId markerId, LatLng position) { assert(markerId != null); final Marker? marker = _markers[markerId]; if (marker == null) { throw UnknownMapObjectIdError('marker', markerId, 'onDragStart'); } final ValueChanged? onDragStart = marker.onDragStart; if (onDragStart != null) { onDragStart(position); } } void onMarkerDrag(MarkerId markerId, LatLng position) { assert(markerId != null); final Marker? marker = _markers[markerId]; if (marker == null) { throw UnknownMapObjectIdError('marker', markerId, 'onDrag'); } final ValueChanged? onDrag = marker.onDrag; if (onDrag != null) { onDrag(position); } } void onMarkerDragEnd(MarkerId markerId, LatLng position) { assert(markerId != null); final Marker? marker = _markers[markerId]; if (marker == null) { throw UnknownMapObjectIdError('marker', markerId, 'onDragEnd'); } final ValueChanged? onDragEnd = marker.onDragEnd; if (onDragEnd != null) { onDragEnd(position); } } void onPolygonTap(PolygonId polygonId) { assert(polygonId != null); final Polygon? polygon = _polygons[polygonId]; if (polygon == null) { throw UnknownMapObjectIdError('polygon', polygonId, 'onTap'); } final VoidCallback? onTap = polygon.onTap; if (onTap != null) { onTap(); } } void onPolylineTap(PolylineId polylineId) { assert(polylineId != null); final Polyline? polyline = _polylines[polylineId]; if (polyline == null) { throw UnknownMapObjectIdError('polyline', polylineId, 'onTap'); } final VoidCallback? onTap = polyline.onTap; if (onTap != null) { onTap(); } } void onCircleTap(CircleId circleId) { assert(circleId != null); final Circle? circle = _circles[circleId]; if (circle == null) { throw UnknownMapObjectIdError('marker', circleId, 'onTap'); } final VoidCallback? onTap = circle.onTap; if (onTap != null) { onTap(); } } void onInfoWindowTap(MarkerId markerId) { assert(markerId != null); final Marker? marker = _markers[markerId]; if (marker == null) { throw UnknownMapObjectIdError('marker', markerId, 'InfoWindow onTap'); } final VoidCallback? onTap = marker.infoWindow.onTap; if (onTap != null) { onTap(); } } void onTap(LatLng position) { assert(position != null); final ArgumentCallback? onTap = widget.onTap; if (onTap != null) { onTap(position); } } void onLongPress(LatLng position) { assert(position != null); final ArgumentCallback? onLongPress = widget.onLongPress; if (onLongPress != null) { onLongPress(position); } } } /// Builds a [MapConfiguration] from the given [map]. MapConfiguration _configurationFromMapWidget(GoogleMap map) { assert(!map.liteModeEnabled || Platform.isAndroid); return MapConfiguration( compassEnabled: map.compassEnabled, mapToolbarEnabled: map.mapToolbarEnabled, cameraTargetBounds: map.cameraTargetBounds, mapType: map.mapType, minMaxZoomPreference: map.minMaxZoomPreference, rotateGesturesEnabled: map.rotateGesturesEnabled, scrollGesturesEnabled: map.scrollGesturesEnabled, tiltGesturesEnabled: map.tiltGesturesEnabled, trackCameraPosition: map.onCameraMove != null, zoomControlsEnabled: map.zoomControlsEnabled, zoomGesturesEnabled: map.zoomGesturesEnabled, liteModeEnabled: map.liteModeEnabled, myLocationEnabled: map.myLocationEnabled, myLocationButtonEnabled: map.myLocationButtonEnabled, padding: map.padding, indoorViewEnabled: map.indoorViewEnabled, trafficEnabled: map.trafficEnabled, buildingsEnabled: map.buildingsEnabled, ); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/pubspec.yaml ================================================ 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.3 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: platforms: android: default_package: google_maps_flutter_android ios: default_package: google_maps_flutter_ios dependencies: flutter: sdk: flutter google_maps_flutter_android: ^2.1.10 google_maps_flutter_ios: ^2.1.10 google_maps_flutter_platform_interface: ^2.2.1 dev_dependencies: flutter_test: sdk: flutter plugin_platform_interface: ^2.0.0 stream_transform: ^2.0.0 ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/test/circle_updates_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'fake_maps_controllers.dart'; Widget _mapWithCircles(Set circles) { return Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), circles: circles, ), ); } void main() { TestWidgetsFlutterBinding.ensureInitialized(); final FakePlatformViewsController fakePlatformViewsController = FakePlatformViewsController(); setUpAll(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( SystemChannels.platform_views, fakePlatformViewsController.fakePlatformViewsMethodHandler, ); }); setUp(() { fakePlatformViewsController.reset(); }); testWidgets('Initializing a circle', (WidgetTester tester) async { const Circle c1 = Circle(circleId: CircleId('circle_1')); await tester.pumpWidget(_mapWithCircles({c1})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.circlesToAdd.length, 1); final Circle initializedCircle = platformGoogleMap.circlesToAdd.first; expect(initializedCircle, equals(c1)); expect(platformGoogleMap.circleIdsToRemove.isEmpty, true); expect(platformGoogleMap.circlesToChange.isEmpty, true); }); testWidgets('Adding a circle', (WidgetTester tester) async { const Circle c1 = Circle(circleId: CircleId('circle_1')); const Circle c2 = Circle(circleId: CircleId('circle_2')); await tester.pumpWidget(_mapWithCircles({c1})); await tester.pumpWidget(_mapWithCircles({c1, c2})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.circlesToAdd.length, 1); final Circle addedCircle = platformGoogleMap.circlesToAdd.first; expect(addedCircle, equals(c2)); expect(platformGoogleMap.circleIdsToRemove.isEmpty, true); expect(platformGoogleMap.circlesToChange.isEmpty, true); }); testWidgets('Removing a circle', (WidgetTester tester) async { const Circle c1 = Circle(circleId: CircleId('circle_1')); await tester.pumpWidget(_mapWithCircles({c1})); await tester.pumpWidget(_mapWithCircles({})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.circleIdsToRemove.length, 1); expect(platformGoogleMap.circleIdsToRemove.first, equals(c1.circleId)); expect(platformGoogleMap.circlesToChange.isEmpty, true); expect(platformGoogleMap.circlesToAdd.isEmpty, true); }); testWidgets('Updating a circle', (WidgetTester tester) async { const Circle c1 = Circle(circleId: CircleId('circle_1')); const Circle c2 = Circle(circleId: CircleId('circle_1'), radius: 10); await tester.pumpWidget(_mapWithCircles({c1})); await tester.pumpWidget(_mapWithCircles({c2})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.circlesToChange.length, 1); expect(platformGoogleMap.circlesToChange.first, equals(c2)); expect(platformGoogleMap.circleIdsToRemove.isEmpty, true); expect(platformGoogleMap.circlesToAdd.isEmpty, true); }); testWidgets('Updating a circle', (WidgetTester tester) async { const Circle c1 = Circle(circleId: CircleId('circle_1')); const Circle c2 = Circle(circleId: CircleId('circle_1'), radius: 10); await tester.pumpWidget(_mapWithCircles({c1})); await tester.pumpWidget(_mapWithCircles({c2})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.circlesToChange.length, 1); final Circle update = platformGoogleMap.circlesToChange.first; expect(update, equals(c2)); expect(update.radius, 10); }); testWidgets('Multi Update', (WidgetTester tester) async { Circle c1 = const Circle(circleId: CircleId('circle_1')); Circle c2 = const Circle(circleId: CircleId('circle_2')); final Set prev = {c1, c2}; c1 = const Circle(circleId: CircleId('circle_1'), visible: false); c2 = const Circle(circleId: CircleId('circle_2'), radius: 10); final Set cur = {c1, c2}; await tester.pumpWidget(_mapWithCircles(prev)); await tester.pumpWidget(_mapWithCircles(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.circlesToChange, cur); expect(platformGoogleMap.circleIdsToRemove.isEmpty, true); expect(platformGoogleMap.circlesToAdd.isEmpty, true); }); testWidgets('Multi Update', (WidgetTester tester) async { Circle c2 = const Circle(circleId: CircleId('circle_2')); const Circle c3 = Circle(circleId: CircleId('circle_3')); final Set prev = {c2, c3}; // c1 is added, c2 is updated, c3 is removed. const Circle c1 = Circle(circleId: CircleId('circle_1')); c2 = const Circle(circleId: CircleId('circle_2'), radius: 10); final Set cur = {c1, c2}; await tester.pumpWidget(_mapWithCircles(prev)); await tester.pumpWidget(_mapWithCircles(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.circlesToChange.length, 1); expect(platformGoogleMap.circlesToAdd.length, 1); expect(platformGoogleMap.circleIdsToRemove.length, 1); expect(platformGoogleMap.circlesToChange.first, equals(c2)); expect(platformGoogleMap.circlesToAdd.first, equals(c1)); expect(platformGoogleMap.circleIdsToRemove.first, equals(c3.circleId)); }); testWidgets('Partial Update', (WidgetTester tester) async { const Circle c1 = Circle(circleId: CircleId('circle_1')); const Circle c2 = Circle(circleId: CircleId('circle_2')); Circle c3 = const Circle(circleId: CircleId('circle_3')); final Set prev = {c1, c2, c3}; c3 = const Circle(circleId: CircleId('circle_3'), radius: 10); final Set cur = {c1, c2, c3}; await tester.pumpWidget(_mapWithCircles(prev)); await tester.pumpWidget(_mapWithCircles(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.circlesToChange, {c3}); expect(platformGoogleMap.circleIdsToRemove.isEmpty, true); expect(platformGoogleMap.circlesToAdd.isEmpty, true); }); testWidgets('Update non platform related attr', (WidgetTester tester) async { Circle c1 = const Circle(circleId: CircleId('circle_1')); final Set prev = {c1}; c1 = Circle(circleId: const CircleId('circle_1'), onTap: () {}); final Set cur = {c1}; await tester.pumpWidget(_mapWithCircles(prev)); await tester.pumpWidget(_mapWithCircles(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.circlesToChange.isEmpty, true); expect(platformGoogleMap.circleIdsToRemove.isEmpty, true); expect(platformGoogleMap.circlesToAdd.isEmpty, true); }); } /// 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; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:typed_data'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; class FakePlatformGoogleMap { FakePlatformGoogleMap(int id, Map params) : cameraPosition = CameraPosition.fromMap(params['initialCameraPosition']), channel = MethodChannel('plugins.flutter.io/google_maps_$id') { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, onMethodCall); updateOptions(params['options'] as Map); updateMarkers(params); updatePolygons(params); updatePolylines(params); updateCircles(params); updateTileOverlays(Map.castFrom(params)); } MethodChannel channel; CameraPosition? cameraPosition; bool? compassEnabled; bool? mapToolbarEnabled; CameraTargetBounds? cameraTargetBounds; MapType? mapType; MinMaxZoomPreference? minMaxZoomPreference; bool? rotateGesturesEnabled; bool? scrollGesturesEnabled; bool? tiltGesturesEnabled; bool? zoomGesturesEnabled; bool? zoomControlsEnabled; bool? liteModeEnabled; bool? trackCameraPosition; bool? myLocationEnabled; bool? trafficEnabled; bool? buildingsEnabled; bool? myLocationButtonEnabled; List? padding; Set markerIdsToRemove = {}; Set markersToAdd = {}; Set markersToChange = {}; Set polygonIdsToRemove = {}; Set polygonsToAdd = {}; Set polygonsToChange = {}; Set polylineIdsToRemove = {}; Set polylinesToAdd = {}; Set polylinesToChange = {}; Set circleIdsToRemove = {}; Set circlesToAdd = {}; Set circlesToChange = {}; Set tileOverlayIdsToRemove = {}; Set tileOverlaysToAdd = {}; Set tileOverlaysToChange = {}; Future onMethodCall(MethodCall call) { switch (call.method) { case 'map#update': final Map arguments = (call.arguments as Map).cast(); updateOptions(arguments['options']! as Map); return Future.sync(() {}); case 'markers#update': updateMarkers(call.arguments as Map?); return Future.sync(() {}); case 'polygons#update': updatePolygons(call.arguments as Map?); return Future.sync(() {}); case 'polylines#update': updatePolylines(call.arguments as Map?); return Future.sync(() {}); case 'tileOverlays#update': updateTileOverlays(Map.castFrom( call.arguments as Map)); return Future.sync(() {}); case 'circles#update': updateCircles(call.arguments as Map?); return Future.sync(() {}); default: return Future.sync(() {}); } } void updateMarkers(Map? markerUpdates) { if (markerUpdates == null) { return; } markersToAdd = _deserializeMarkers(markerUpdates['markersToAdd']); markerIdsToRemove = _deserializeMarkerIds( markerUpdates['markerIdsToRemove'] as List?); markersToChange = _deserializeMarkers(markerUpdates['markersToChange']); } Set _deserializeMarkerIds(List? markerIds) { if (markerIds == null) { return {}; } return markerIds .map((dynamic markerId) => MarkerId(markerId as String)) .toSet(); } Set _deserializeMarkers(dynamic markers) { if (markers == null) { return {}; } final List markersData = markers as List; final Set result = {}; for (final Map markerData in markersData.cast>()) { final String markerId = markerData['markerId'] as String; final double alpha = markerData['alpha'] as double; final bool draggable = markerData['draggable'] as bool; final bool visible = markerData['visible'] as bool; final dynamic infoWindowData = markerData['infoWindow']; InfoWindow infoWindow = InfoWindow.noText; if (infoWindowData != null) { final Map infoWindowMap = infoWindowData as Map; infoWindow = InfoWindow( title: infoWindowMap['title'] as String?, snippet: infoWindowMap['snippet'] as String?, ); } result.add(Marker( markerId: MarkerId(markerId), draggable: draggable, visible: visible, infoWindow: infoWindow, alpha: alpha, )); } return result; } void updatePolygons(Map? polygonUpdates) { if (polygonUpdates == null) { return; } polygonsToAdd = _deserializePolygons(polygonUpdates['polygonsToAdd']); polygonIdsToRemove = _deserializePolygonIds( polygonUpdates['polygonIdsToRemove'] as List?); polygonsToChange = _deserializePolygons(polygonUpdates['polygonsToChange']); } Set _deserializePolygonIds(List? polygonIds) { if (polygonIds == null) { return {}; } return polygonIds .map((dynamic polygonId) => PolygonId(polygonId as String)) .toSet(); } Set _deserializePolygons(dynamic polygons) { if (polygons == null) { return {}; } final List polygonsData = polygons as List; final Set result = {}; for (final Map polygonData in polygonsData.cast>()) { final String polygonId = polygonData['polygonId'] as String; final bool visible = polygonData['visible'] as bool; final bool geodesic = polygonData['geodesic'] as bool; final List points = _deserializePoints(polygonData['points'] as List); final List> holes = _deserializeHoles(polygonData['holes'] as List); result.add(Polygon( polygonId: PolygonId(polygonId), visible: visible, geodesic: geodesic, points: points, holes: holes, )); } 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 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 _deserializePoints(hole as List); }).toList(); } void updatePolylines(Map? polylineUpdates) { if (polylineUpdates == null) { return; } polylinesToAdd = _deserializePolylines(polylineUpdates['polylinesToAdd']); polylineIdsToRemove = _deserializePolylineIds( polylineUpdates['polylineIdsToRemove'] as List?); polylinesToChange = _deserializePolylines(polylineUpdates['polylinesToChange']); } Set _deserializePolylineIds(List? polylineIds) { if (polylineIds == null) { return {}; } return polylineIds .map((dynamic polylineId) => PolylineId(polylineId as String)) .toSet(); } Set _deserializePolylines(dynamic polylines) { if (polylines == null) { return {}; } final List polylinesData = polylines as List; final Set result = {}; for (final Map polylineData in polylinesData.cast>()) { final String polylineId = polylineData['polylineId'] as String; final bool visible = polylineData['visible'] as bool; final bool geodesic = polylineData['geodesic'] as bool; final List points = _deserializePoints(polylineData['points'] as List); result.add(Polyline( polylineId: PolylineId(polylineId), visible: visible, geodesic: geodesic, points: points, )); } return result; } void updateCircles(Map? circleUpdates) { if (circleUpdates == null) { return; } circlesToAdd = _deserializeCircles(circleUpdates['circlesToAdd']); circleIdsToRemove = _deserializeCircleIds( circleUpdates['circleIdsToRemove'] as List?); circlesToChange = _deserializeCircles(circleUpdates['circlesToChange']); } void updateTileOverlays(Map updateTileOverlayUpdates) { if (updateTileOverlayUpdates == null) { return; } final List>? tileOverlaysToAddList = updateTileOverlayUpdates['tileOverlaysToAdd'] != null ? List.castFrom>( updateTileOverlayUpdates['tileOverlaysToAdd'] as List) : null; final List? tileOverlayIdsToRemoveList = updateTileOverlayUpdates['tileOverlayIdsToRemove'] != null ? List.castFrom( updateTileOverlayUpdates['tileOverlayIdsToRemove'] as List) : null; final List>? tileOverlaysToChangeList = updateTileOverlayUpdates['tileOverlaysToChange'] != null ? List.castFrom>( updateTileOverlayUpdates['tileOverlaysToChange'] as List) : null; tileOverlaysToAdd = _deserializeTileOverlays(tileOverlaysToAddList); tileOverlayIdsToRemove = _deserializeTileOverlayIds(tileOverlayIdsToRemoveList); tileOverlaysToChange = _deserializeTileOverlays(tileOverlaysToChangeList); } Set _deserializeCircleIds(List? circleIds) { if (circleIds == null) { return {}; } return circleIds .map((dynamic circleId) => CircleId(circleId as String)) .toSet(); } Set _deserializeCircles(dynamic circles) { if (circles == null) { return {}; } final List circlesData = circles as List; final Set result = {}; for (final Map circleData in circlesData.cast>()) { final String circleId = circleData['circleId'] as String; final bool visible = circleData['visible'] as bool; final double radius = circleData['radius'] as double; result.add(Circle( circleId: CircleId(circleId), visible: visible, radius: radius, )); } return result; } Set _deserializeTileOverlayIds(List? tileOverlayIds) { if (tileOverlayIds == null || tileOverlayIds.isEmpty) { return {}; } return tileOverlayIds .map((String tileOverlayId) => TileOverlayId(tileOverlayId)) .toSet(); } Set _deserializeTileOverlays( List>? tileOverlays) { if (tileOverlays == null || tileOverlays.isEmpty) { return {}; } final Set result = {}; for (final Map tileOverlayData in tileOverlays) { final String tileOverlayId = tileOverlayData['tileOverlayId'] as String; final bool fadeIn = tileOverlayData['fadeIn'] as bool; final double transparency = tileOverlayData['transparency'] as double; final int zIndex = tileOverlayData['zIndex'] as int; final bool visible = tileOverlayData['visible'] as bool; result.add(TileOverlay( tileOverlayId: TileOverlayId(tileOverlayId), fadeIn: fadeIn, transparency: transparency, zIndex: zIndex, visible: visible, )); } return result; } void updateOptions(Map options) { if (options.containsKey('compassEnabled')) { compassEnabled = options['compassEnabled'] as bool?; } if (options.containsKey('mapToolbarEnabled')) { mapToolbarEnabled = options['mapToolbarEnabled'] as bool?; } if (options.containsKey('cameraTargetBounds')) { final List boundsList = options['cameraTargetBounds'] as List; cameraTargetBounds = boundsList[0] == null ? CameraTargetBounds.unbounded : CameraTargetBounds(LatLngBounds.fromList(boundsList[0])); } if (options.containsKey('mapType')) { mapType = MapType.values[options['mapType'] as int]; } if (options.containsKey('minMaxZoomPreference')) { final List minMaxZoomList = options['minMaxZoomPreference'] as List; minMaxZoomPreference = MinMaxZoomPreference( minMaxZoomList[0] as double?, minMaxZoomList[1] as double?); } if (options.containsKey('rotateGesturesEnabled')) { rotateGesturesEnabled = options['rotateGesturesEnabled'] as bool?; } if (options.containsKey('scrollGesturesEnabled')) { scrollGesturesEnabled = options['scrollGesturesEnabled'] as bool?; } if (options.containsKey('tiltGesturesEnabled')) { tiltGesturesEnabled = options['tiltGesturesEnabled'] as bool?; } if (options.containsKey('trackCameraPosition')) { trackCameraPosition = options['trackCameraPosition'] as bool?; } if (options.containsKey('zoomGesturesEnabled')) { zoomGesturesEnabled = options['zoomGesturesEnabled'] as bool?; } if (options.containsKey('zoomControlsEnabled')) { zoomControlsEnabled = options['zoomControlsEnabled'] as bool?; } if (options.containsKey('liteModeEnabled')) { liteModeEnabled = options['liteModeEnabled'] as bool?; } if (options.containsKey('myLocationEnabled')) { myLocationEnabled = options['myLocationEnabled'] as bool?; } if (options.containsKey('myLocationButtonEnabled')) { myLocationButtonEnabled = options['myLocationButtonEnabled'] as bool?; } if (options.containsKey('trafficEnabled')) { trafficEnabled = options['trafficEnabled'] as bool?; } if (options.containsKey('buildingsEnabled')) { buildingsEnabled = options['buildingsEnabled'] as bool?; } if (options.containsKey('padding')) { padding = options['padding'] as List?; } } } class FakePlatformViewsController { FakePlatformGoogleMap? lastCreatedView; Future fakePlatformViewsMethodHandler(MethodCall call) { switch (call.method) { case 'create': final Map args = call.arguments as Map; final Map params = _decodeParams(args['params'] as Uint8List)!; lastCreatedView = FakePlatformGoogleMap( args['id'] as int, params, ); return Future.sync(() => 1); default: return Future.sync(() {}); } } void reset() { lastCreatedView = null; } } Map? _decodeParams(Uint8List paramsMessage) { final ByteBuffer buffer = paramsMessage.buffer; final ByteData messageBytes = buffer.asByteData( paramsMessage.offsetInBytes, paramsMessage.lengthInBytes, ); return const StandardMessageCodec().decodeMessage(messageBytes) as Map?; } /// 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; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'fake_maps_controllers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); final FakePlatformViewsController fakePlatformViewsController = FakePlatformViewsController(); setUpAll(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( SystemChannels.platform_views, fakePlatformViewsController.fakePlatformViewsMethodHandler, ); }); setUp(() { fakePlatformViewsController.reset(); }); testWidgets('Initial camera position', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.cameraPosition, const CameraPosition(target: LatLng(10.0, 15.0))); }); testWidgets('Initial camera position change is a no-op', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), ), ), ); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 16.0)), ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.cameraPosition, const CameraPosition(target: LatLng(10.0, 15.0))); }); testWidgets('Can update compassEnabled', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), compassEnabled: false, ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.compassEnabled, false); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), ), ), ); expect(platformGoogleMap.compassEnabled, true); }); testWidgets('Can update mapToolbarEnabled', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), mapToolbarEnabled: false, ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.mapToolbarEnabled, false); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), ), ), ); expect(platformGoogleMap.mapToolbarEnabled, true); }); testWidgets('Can update cameraTargetBounds', (WidgetTester tester) async { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), cameraTargetBounds: CameraTargetBounds( LatLngBounds( southwest: const LatLng(10.0, 20.0), northeast: const LatLng(30.0, 40.0), ), ), ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect( platformGoogleMap.cameraTargetBounds, CameraTargetBounds( LatLngBounds( southwest: const LatLng(10.0, 20.0), northeast: const LatLng(30.0, 40.0), ), )); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), cameraTargetBounds: CameraTargetBounds( LatLngBounds( southwest: const LatLng(16.0, 20.0), northeast: const LatLng(30.0, 40.0), ), ), ), ), ); expect( platformGoogleMap.cameraTargetBounds, CameraTargetBounds( LatLngBounds( southwest: const LatLng(16.0, 20.0), northeast: const LatLng(30.0, 40.0), ), )); }); testWidgets('Can update mapType', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), mapType: MapType.hybrid, ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.mapType, MapType.hybrid); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), mapType: MapType.satellite, ), ), ); expect(platformGoogleMap.mapType, MapType.satellite); }); testWidgets('Can update minMaxZoom', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), minMaxZoomPreference: MinMaxZoomPreference(1.0, 3.0), ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.minMaxZoomPreference, const MinMaxZoomPreference(1.0, 3.0)); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), ), ), ); expect( platformGoogleMap.minMaxZoomPreference, MinMaxZoomPreference.unbounded); }); testWidgets('Can update rotateGesturesEnabled', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), rotateGesturesEnabled: false, ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.rotateGesturesEnabled, false); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), ), ), ); expect(platformGoogleMap.rotateGesturesEnabled, true); }); testWidgets('Can update scrollGesturesEnabled', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), scrollGesturesEnabled: false, ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.scrollGesturesEnabled, false); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), ), ), ); expect(platformGoogleMap.scrollGesturesEnabled, true); }); testWidgets('Can update tiltGesturesEnabled', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), tiltGesturesEnabled: false, ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.tiltGesturesEnabled, false); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), ), ), ); expect(platformGoogleMap.tiltGesturesEnabled, true); }); testWidgets('Can update trackCameraPosition', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.trackCameraPosition, false); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), onCameraMove: (CameraPosition position) {}, ), ), ); expect(platformGoogleMap.trackCameraPosition, true); }); testWidgets('Can update zoomGesturesEnabled', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), zoomGesturesEnabled: false, ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.zoomGesturesEnabled, false); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), ), ), ); expect(platformGoogleMap.zoomGesturesEnabled, true); }); testWidgets('Can update zoomControlsEnabled', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), zoomControlsEnabled: false, ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.zoomControlsEnabled, false); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), ), ), ); expect(platformGoogleMap.zoomControlsEnabled, true); }); testWidgets('Can update myLocationEnabled', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.myLocationEnabled, false); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), myLocationEnabled: true, ), ), ); expect(platformGoogleMap.myLocationEnabled, true); }); testWidgets('Can update myLocationButtonEnabled', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.myLocationButtonEnabled, true); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), myLocationButtonEnabled: false, ), ), ); expect(platformGoogleMap.myLocationButtonEnabled, false); }); testWidgets('Is default padding 0', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.padding, [0, 0, 0, 0]); }); testWidgets('Can update padding', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.padding, [0, 0, 0, 0]); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), padding: EdgeInsets.fromLTRB(10, 20, 30, 40), ), ), ); expect(platformGoogleMap.padding, [20, 10, 40, 30]); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), padding: EdgeInsets.fromLTRB(50, 60, 70, 80), ), ), ); expect(platformGoogleMap.padding, [60, 50, 80, 70]); }); testWidgets('Can update traffic', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.trafficEnabled, false); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), trafficEnabled: true, ), ), ); expect(platformGoogleMap.trafficEnabled, true); }); testWidgets('Can update buildings', (WidgetTester tester) async { await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), buildingsEnabled: false, ), ), ); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.buildingsEnabled, false); await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)), ), ), ); expect(platformGoogleMap.buildingsEnabled, true); }); } /// 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; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/test/map_creation_test.dart ================================================ // Copyright 2013 The Flutter 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'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:stream_transform/stream_transform.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); late TestGoogleMapsFlutterPlatform platform; setUp(() { // Use a mock platform so we never need to hit the MethodChannel code. platform = TestGoogleMapsFlutterPlatform(); GoogleMapsFlutterPlatform.instance = platform; }); testWidgets('_webOnlyMapCreationId increments with each GoogleMap widget', ( WidgetTester tester, ) 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( initialCameraPosition: CameraPosition( target: LatLng(43.362, -5.849), ), ), GoogleMap( initialCameraPosition: CameraPosition( target: LatLng(47.649, -122.350), ), ), ], ), ), ); // Verify that each one was created with a different _webOnlyMapCreationId. expect(platform.createdIds.length, 2); expect(platform.createdIds[0], 0); expect(platform.createdIds[1], 1); }); testWidgets('Calls platform.dispose when GoogleMap is disposed of', ( WidgetTester tester, ) async { await tester.pumpWidget(const GoogleMap( initialCameraPosition: CameraPosition( target: LatLng(43.3608, -5.8702), ), )); // Now dispose of the map... await tester.pumpWidget(Container()); expect(platform.disposed, true); }); } // A dummy implementation of the platform interface for tests. class TestGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform { TestGoogleMapsFlutterPlatform(); // The IDs passed to each call to buildView, in call order. List createdIds = []; // Whether `dispose` has been called. bool disposed = false; // Stream controller to inject events for testing. final StreamController> mapEventStreamController = StreamController>.broadcast(); @override Future init(int mapId) async {} @override Future updateMapConfiguration( MapConfiguration update, { required int mapId, }) async {} @override Future updateMarkers( MarkerUpdates markerUpdates, { required int mapId, }) async {} @override Future updatePolygons( PolygonUpdates polygonUpdates, { required int mapId, }) async {} @override Future updatePolylines( PolylineUpdates polylineUpdates, { required int mapId, }) async {} @override Future updateCircles( CircleUpdates circleUpdates, { required int mapId, }) async {} @override Future updateTileOverlays({ required Set newTileOverlays, required int mapId, }) async {} @override Future clearTileCache( TileOverlayId tileOverlayId, { required int mapId, }) async {} @override Future animateCamera( CameraUpdate cameraUpdate, { required int mapId, }) async {} @override Future moveCamera( CameraUpdate cameraUpdate, { required int mapId, }) async {} @override Future setMapStyle( String? mapStyle, { required int mapId, }) async {} @override Future getVisibleRegion({ required int mapId, }) async { return LatLngBounds( southwest: const LatLng(0, 0), northeast: const LatLng(0, 0)); } @override Future getScreenCoordinate( LatLng latLng, { required int mapId, }) async { return const ScreenCoordinate(x: 0, y: 0); } @override Future getLatLng( ScreenCoordinate screenCoordinate, { required int mapId, }) async { return const LatLng(0, 0); } @override Future showMarkerInfoWindow( MarkerId markerId, { required int mapId, }) async {} @override Future hideMarkerInfoWindow( MarkerId markerId, { required int mapId, }) async {} @override Future isMarkerInfoWindowShown( MarkerId markerId, { required int mapId, }) async { return false; } @override Future getZoomLevel({ required int mapId, }) async { return 0.0; } @override Future takeSnapshot({ required int mapId, }) async { return null; } @override Stream onCameraMoveStarted({required int mapId}) { return mapEventStreamController.stream.whereType(); } @override Stream onCameraMove({required int mapId}) { return mapEventStreamController.stream.whereType(); } @override Stream onCameraIdle({required int mapId}) { return mapEventStreamController.stream.whereType(); } @override Stream onMarkerTap({required int mapId}) { return mapEventStreamController.stream.whereType(); } @override Stream onInfoWindowTap({required int mapId}) { return mapEventStreamController.stream.whereType(); } @override Stream onMarkerDragStart({required int mapId}) { return mapEventStreamController.stream.whereType(); } @override Stream onMarkerDrag({required int mapId}) { return mapEventStreamController.stream.whereType(); } @override Stream onMarkerDragEnd({required int mapId}) { return mapEventStreamController.stream.whereType(); } @override Stream onPolylineTap({required int mapId}) { return mapEventStreamController.stream.whereType(); } @override Stream onPolygonTap({required int mapId}) { return mapEventStreamController.stream.whereType(); } @override Stream onCircleTap({required int mapId}) { return mapEventStreamController.stream.whereType(); } @override Stream onTap({required int mapId}) { return mapEventStreamController.stream.whereType(); } @override Stream onLongPress({required int mapId}) { return mapEventStreamController.stream.whereType(); } @override void dispose({required int mapId}) { disposed = true; } @override Widget buildViewWithConfiguration( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { required MapWidgetConfiguration widgetConfiguration, MapObjects mapObjects = const MapObjects(), MapConfiguration mapConfiguration = const MapConfiguration(), }) { onPlatformViewCreated(0); createdIds.add(creationId); return Container(); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/test/marker_updates_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'fake_maps_controllers.dart'; Widget _mapWithMarkers(Set markers) { return Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), markers: markers, ), ); } void main() { TestWidgetsFlutterBinding.ensureInitialized(); final FakePlatformViewsController fakePlatformViewsController = FakePlatformViewsController(); setUpAll(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( SystemChannels.platform_views, fakePlatformViewsController.fakePlatformViewsMethodHandler, ); }); setUp(() { fakePlatformViewsController.reset(); }); testWidgets('Initializing a marker', (WidgetTester tester) async { const Marker m1 = Marker(markerId: MarkerId('marker_1')); await tester.pumpWidget(_mapWithMarkers({m1})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.markersToAdd.length, 1); final Marker initializedMarker = platformGoogleMap.markersToAdd.first; expect(initializedMarker, equals(m1)); expect(platformGoogleMap.markerIdsToRemove.isEmpty, true); expect(platformGoogleMap.markersToChange.isEmpty, true); }); testWidgets('Adding a marker', (WidgetTester tester) async { const Marker m1 = Marker(markerId: MarkerId('marker_1')); const Marker m2 = Marker(markerId: MarkerId('marker_2')); await tester.pumpWidget(_mapWithMarkers({m1})); await tester.pumpWidget(_mapWithMarkers({m1, m2})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.markersToAdd.length, 1); final Marker addedMarker = platformGoogleMap.markersToAdd.first; expect(addedMarker, equals(m2)); expect(platformGoogleMap.markerIdsToRemove.isEmpty, true); expect(platformGoogleMap.markersToChange.isEmpty, true); }); testWidgets('Removing a marker', (WidgetTester tester) async { const Marker m1 = Marker(markerId: MarkerId('marker_1')); await tester.pumpWidget(_mapWithMarkers({m1})); await tester.pumpWidget(_mapWithMarkers({})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.markerIdsToRemove.length, 1); expect(platformGoogleMap.markerIdsToRemove.first, equals(m1.markerId)); expect(platformGoogleMap.markersToChange.isEmpty, true); expect(platformGoogleMap.markersToAdd.isEmpty, true); }); testWidgets('Updating a marker', (WidgetTester tester) async { const Marker m1 = Marker(markerId: MarkerId('marker_1')); const Marker m2 = Marker(markerId: MarkerId('marker_1'), alpha: 0.5); await tester.pumpWidget(_mapWithMarkers({m1})); await tester.pumpWidget(_mapWithMarkers({m2})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.markersToChange.length, 1); expect(platformGoogleMap.markersToChange.first, equals(m2)); expect(platformGoogleMap.markerIdsToRemove.isEmpty, true); expect(platformGoogleMap.markersToAdd.isEmpty, true); }); testWidgets('Updating a marker', (WidgetTester tester) async { const Marker m1 = Marker(markerId: MarkerId('marker_1')); const Marker m2 = Marker( markerId: MarkerId('marker_1'), infoWindow: InfoWindow(snippet: 'changed'), ); await tester.pumpWidget(_mapWithMarkers({m1})); await tester.pumpWidget(_mapWithMarkers({m2})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.markersToChange.length, 1); final Marker update = platformGoogleMap.markersToChange.first; expect(update, equals(m2)); expect(update.infoWindow.snippet, 'changed'); }); testWidgets('Multi Update', (WidgetTester tester) async { Marker m1 = const Marker(markerId: MarkerId('marker_1')); Marker m2 = const Marker(markerId: MarkerId('marker_2')); final Set prev = {m1, m2}; m1 = const Marker(markerId: MarkerId('marker_1'), visible: false); m2 = const Marker(markerId: MarkerId('marker_2'), draggable: true); final Set cur = {m1, m2}; await tester.pumpWidget(_mapWithMarkers(prev)); await tester.pumpWidget(_mapWithMarkers(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.markersToChange, cur); expect(platformGoogleMap.markerIdsToRemove.isEmpty, true); expect(platformGoogleMap.markersToAdd.isEmpty, true); }); testWidgets('Multi Update', (WidgetTester tester) async { Marker m2 = const Marker(markerId: MarkerId('marker_2')); const Marker m3 = Marker(markerId: MarkerId('marker_3')); final Set prev = {m2, m3}; // m1 is added, m2 is updated, m3 is removed. const Marker m1 = Marker(markerId: MarkerId('marker_1')); m2 = const Marker(markerId: MarkerId('marker_2'), draggable: true); final Set cur = {m1, m2}; await tester.pumpWidget(_mapWithMarkers(prev)); await tester.pumpWidget(_mapWithMarkers(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.markersToChange.length, 1); expect(platformGoogleMap.markersToAdd.length, 1); expect(platformGoogleMap.markerIdsToRemove.length, 1); expect(platformGoogleMap.markersToChange.first, equals(m2)); expect(platformGoogleMap.markersToAdd.first, equals(m1)); expect(platformGoogleMap.markerIdsToRemove.first, equals(m3.markerId)); }); testWidgets('Partial Update', (WidgetTester tester) async { const Marker m1 = Marker(markerId: MarkerId('marker_1')); const Marker m2 = Marker(markerId: MarkerId('marker_2')); Marker m3 = const Marker(markerId: MarkerId('marker_3')); final Set prev = {m1, m2, m3}; m3 = const Marker(markerId: MarkerId('marker_3'), draggable: true); final Set cur = {m1, m2, m3}; await tester.pumpWidget(_mapWithMarkers(prev)); await tester.pumpWidget(_mapWithMarkers(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.markersToChange, {m3}); expect(platformGoogleMap.markerIdsToRemove.isEmpty, true); expect(platformGoogleMap.markersToAdd.isEmpty, true); }); testWidgets('Update non platform related attr', (WidgetTester tester) async { Marker m1 = const Marker(markerId: MarkerId('marker_1')); final Set prev = {m1}; m1 = Marker( markerId: const MarkerId('marker_1'), onTap: () {}, onDragEnd: (LatLng latLng) {}); final Set cur = {m1}; await tester.pumpWidget(_mapWithMarkers(prev)); await tester.pumpWidget(_mapWithMarkers(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.markersToChange.isEmpty, true); expect(platformGoogleMap.markerIdsToRemove.isEmpty, true); expect(platformGoogleMap.markersToAdd.isEmpty, true); }); } /// 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; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/test/polygon_updates_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'fake_maps_controllers.dart'; Widget _mapWithPolygons(Set polygons) { return Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), polygons: polygons, ), ); } List _rectPoints({ required double size, LatLng center = const LatLng(0, 0), }) { final double halfSize = size / 2; return [ LatLng(center.latitude + halfSize, center.longitude + halfSize), LatLng(center.latitude - halfSize, center.longitude + halfSize), LatLng(center.latitude - halfSize, center.longitude - halfSize), LatLng(center.latitude + halfSize, center.longitude - halfSize), ]; } Polygon _polygonWithPointsAndHole(PolygonId polygonId) { _rectPoints(size: 1); return Polygon( polygonId: polygonId, points: _rectPoints(size: 1), holes: >[_rectPoints(size: 0.5)], ); } void main() { TestWidgetsFlutterBinding.ensureInitialized(); final FakePlatformViewsController fakePlatformViewsController = FakePlatformViewsController(); setUpAll(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( SystemChannels.platform_views, fakePlatformViewsController.fakePlatformViewsMethodHandler, ); }); setUp(() { fakePlatformViewsController.reset(); }); testWidgets('Initializing a polygon', (WidgetTester tester) async { const Polygon p1 = Polygon(polygonId: PolygonId('polygon_1')); await tester.pumpWidget(_mapWithPolygons({p1})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polygonsToAdd.length, 1); final Polygon initializedPolygon = platformGoogleMap.polygonsToAdd.first; expect(initializedPolygon, equals(p1)); expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); expect(platformGoogleMap.polygonsToChange.isEmpty, true); }); testWidgets('Adding a polygon', (WidgetTester tester) async { const Polygon p1 = Polygon(polygonId: PolygonId('polygon_1')); const Polygon p2 = Polygon(polygonId: PolygonId('polygon_2')); await tester.pumpWidget(_mapWithPolygons({p1})); await tester.pumpWidget(_mapWithPolygons({p1, p2})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polygonsToAdd.length, 1); final Polygon addedPolygon = platformGoogleMap.polygonsToAdd.first; expect(addedPolygon, equals(p2)); expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); expect(platformGoogleMap.polygonsToChange.isEmpty, true); }); testWidgets('Removing a polygon', (WidgetTester tester) async { const Polygon p1 = Polygon(polygonId: PolygonId('polygon_1')); await tester.pumpWidget(_mapWithPolygons({p1})); await tester.pumpWidget(_mapWithPolygons({})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polygonIdsToRemove.length, 1); expect(platformGoogleMap.polygonIdsToRemove.first, equals(p1.polygonId)); expect(platformGoogleMap.polygonsToChange.isEmpty, true); expect(platformGoogleMap.polygonsToAdd.isEmpty, true); }); testWidgets('Updating a polygon', (WidgetTester tester) async { const Polygon p1 = Polygon(polygonId: PolygonId('polygon_1')); const Polygon p2 = Polygon(polygonId: PolygonId('polygon_1'), geodesic: true); await tester.pumpWidget(_mapWithPolygons({p1})); await tester.pumpWidget(_mapWithPolygons({p2})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polygonsToChange.length, 1); expect(platformGoogleMap.polygonsToChange.first, equals(p2)); expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); expect(platformGoogleMap.polygonsToAdd.isEmpty, true); }); testWidgets('Mutate a polygon', (WidgetTester tester) async { final List points = [const LatLng(0.0, 0.0)]; final Polygon p1 = Polygon( polygonId: const PolygonId('polygon_1'), points: points, ); await tester.pumpWidget(_mapWithPolygons({p1})); p1.points.add(const LatLng(1.0, 1.0)); await tester.pumpWidget(_mapWithPolygons({p1})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polygonsToChange.length, 1); expect(platformGoogleMap.polygonsToChange.first, equals(p1)); expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); expect(platformGoogleMap.polygonsToAdd.isEmpty, true); }); testWidgets('Multi Update', (WidgetTester tester) async { Polygon p1 = const Polygon(polygonId: PolygonId('polygon_1')); Polygon p2 = const Polygon(polygonId: PolygonId('polygon_2')); final Set prev = {p1, p2}; p1 = const Polygon(polygonId: PolygonId('polygon_1'), visible: false); p2 = const Polygon(polygonId: PolygonId('polygon_2'), geodesic: true); final Set cur = {p1, p2}; await tester.pumpWidget(_mapWithPolygons(prev)); await tester.pumpWidget(_mapWithPolygons(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polygonsToChange, cur); expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); expect(platformGoogleMap.polygonsToAdd.isEmpty, true); }); testWidgets('Multi Update', (WidgetTester tester) async { Polygon p2 = const Polygon(polygonId: PolygonId('polygon_2')); const Polygon p3 = Polygon(polygonId: PolygonId('polygon_3')); final Set prev = {p2, p3}; // p1 is added, p2 is updated, p3 is removed. const Polygon p1 = Polygon(polygonId: PolygonId('polygon_1')); p2 = const Polygon(polygonId: PolygonId('polygon_2'), geodesic: true); final Set cur = {p1, p2}; await tester.pumpWidget(_mapWithPolygons(prev)); await tester.pumpWidget(_mapWithPolygons(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polygonsToChange.length, 1); expect(platformGoogleMap.polygonsToAdd.length, 1); expect(platformGoogleMap.polygonIdsToRemove.length, 1); expect(platformGoogleMap.polygonsToChange.first, equals(p2)); expect(platformGoogleMap.polygonsToAdd.first, equals(p1)); expect(platformGoogleMap.polygonIdsToRemove.first, equals(p3.polygonId)); }); testWidgets('Partial Update', (WidgetTester tester) async { const Polygon p1 = Polygon(polygonId: PolygonId('polygon_1')); const Polygon p2 = Polygon(polygonId: PolygonId('polygon_2')); Polygon p3 = const Polygon(polygonId: PolygonId('polygon_3')); final Set prev = {p1, p2, p3}; p3 = const Polygon(polygonId: PolygonId('polygon_3'), geodesic: true); final Set cur = {p1, p2, p3}; await tester.pumpWidget(_mapWithPolygons(prev)); await tester.pumpWidget(_mapWithPolygons(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polygonsToChange, {p3}); expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); expect(platformGoogleMap.polygonsToAdd.isEmpty, true); }); testWidgets('Update non platform related attr', (WidgetTester tester) async { Polygon p1 = const Polygon(polygonId: PolygonId('polygon_1')); final Set prev = {p1}; p1 = Polygon(polygonId: const PolygonId('polygon_1'), onTap: () {}); final Set cur = {p1}; await tester.pumpWidget(_mapWithPolygons(prev)); await tester.pumpWidget(_mapWithPolygons(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polygonsToChange.isEmpty, true); expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); expect(platformGoogleMap.polygonsToAdd.isEmpty, true); }); testWidgets('Initializing a polygon with points and hole', (WidgetTester tester) async { final Polygon p1 = _polygonWithPointsAndHole(const PolygonId('polygon_1')); await tester.pumpWidget(_mapWithPolygons({p1})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polygonsToAdd.length, 1); final Polygon initializedPolygon = platformGoogleMap.polygonsToAdd.first; expect(initializedPolygon, equals(p1)); expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); expect(platformGoogleMap.polygonsToChange.isEmpty, true); }); testWidgets('Adding a polygon with points and hole', (WidgetTester tester) async { const Polygon p1 = Polygon(polygonId: PolygonId('polygon_1')); final Polygon p2 = _polygonWithPointsAndHole(const PolygonId('polygon_2')); await tester.pumpWidget(_mapWithPolygons({p1})); await tester.pumpWidget(_mapWithPolygons({p1, p2})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polygonsToAdd.length, 1); final Polygon addedPolygon = platformGoogleMap.polygonsToAdd.first; expect(addedPolygon, equals(p2)); expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); expect(platformGoogleMap.polygonsToChange.isEmpty, true); }); testWidgets('Removing a polygon with points and hole', (WidgetTester tester) async { final Polygon p1 = _polygonWithPointsAndHole(const PolygonId('polygon_1')); await tester.pumpWidget(_mapWithPolygons({p1})); await tester.pumpWidget(_mapWithPolygons({})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polygonIdsToRemove.length, 1); expect(platformGoogleMap.polygonIdsToRemove.first, equals(p1.polygonId)); expect(platformGoogleMap.polygonsToChange.isEmpty, true); expect(platformGoogleMap.polygonsToAdd.isEmpty, true); }); testWidgets('Updating a polygon by adding points and hole', (WidgetTester tester) async { const Polygon p1 = Polygon(polygonId: PolygonId('polygon_1')); final Polygon p2 = _polygonWithPointsAndHole(const PolygonId('polygon_1')); await tester.pumpWidget(_mapWithPolygons({p1})); await tester.pumpWidget(_mapWithPolygons({p2})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polygonsToChange.length, 1); expect(platformGoogleMap.polygonsToChange.first, equals(p2)); expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); expect(platformGoogleMap.polygonsToAdd.isEmpty, true); }); testWidgets('Mutate a polygon with points and holes', (WidgetTester tester) async { final Polygon p1 = Polygon( polygonId: const PolygonId('polygon_1'), points: _rectPoints(size: 1), holes: >[_rectPoints(size: 0.5)], ); await tester.pumpWidget(_mapWithPolygons({p1})); p1.points ..clear() ..addAll(_rectPoints(size: 2)); p1.holes ..clear() ..addAll(>[_rectPoints(size: 1)]); await tester.pumpWidget(_mapWithPolygons({p1})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polygonsToChange.length, 1); expect(platformGoogleMap.polygonsToChange.first, equals(p1)); expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); expect(platformGoogleMap.polygonsToAdd.isEmpty, true); }); testWidgets('Multi Update polygons with points and hole', (WidgetTester tester) async { Polygon p1 = const Polygon(polygonId: PolygonId('polygon_1')); Polygon p2 = Polygon( polygonId: const PolygonId('polygon_2'), points: _rectPoints(size: 2), holes: >[_rectPoints(size: 1)], ); final Set prev = {p1, p2}; p1 = const Polygon(polygonId: PolygonId('polygon_1'), visible: false); p2 = p2.copyWith( pointsParam: _rectPoints(size: 5), holesParam: >[_rectPoints(size: 2)], ); final Set cur = {p1, p2}; await tester.pumpWidget(_mapWithPolygons(prev)); await tester.pumpWidget(_mapWithPolygons(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polygonsToChange, cur); expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); expect(platformGoogleMap.polygonsToAdd.isEmpty, true); }); testWidgets('Multi Update polygons with points and hole', (WidgetTester tester) async { Polygon p2 = Polygon( polygonId: const PolygonId('polygon_2'), points: _rectPoints(size: 2), holes: >[_rectPoints(size: 1)], ); const Polygon p3 = Polygon(polygonId: PolygonId('polygon_3')); final Set prev = {p2, p3}; // p1 is added, p2 is updated, p3 is removed. final Polygon p1 = _polygonWithPointsAndHole(const PolygonId('polygon_1')); p2 = p2.copyWith( pointsParam: _rectPoints(size: 5), holesParam: >[_rectPoints(size: 3)], ); final Set cur = {p1, p2}; await tester.pumpWidget(_mapWithPolygons(prev)); await tester.pumpWidget(_mapWithPolygons(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polygonsToChange.length, 1); expect(platformGoogleMap.polygonsToAdd.length, 1); expect(platformGoogleMap.polygonIdsToRemove.length, 1); expect(platformGoogleMap.polygonsToChange.first, equals(p2)); expect(platformGoogleMap.polygonsToAdd.first, equals(p1)); expect(platformGoogleMap.polygonIdsToRemove.first, equals(p3.polygonId)); }); testWidgets('Partial Update polygons with points and hole', (WidgetTester tester) async { final Polygon p1 = _polygonWithPointsAndHole(const PolygonId('polygon_1')); const Polygon p2 = Polygon(polygonId: PolygonId('polygon_2')); Polygon p3 = Polygon( polygonId: const PolygonId('polygon_3'), points: _rectPoints(size: 2), holes: >[_rectPoints(size: 1)], ); final Set prev = {p1, p2, p3}; p3 = p3.copyWith( pointsParam: _rectPoints(size: 5), holesParam: >[_rectPoints(size: 3)], ); final Set cur = {p1, p2, p3}; await tester.pumpWidget(_mapWithPolygons(prev)); await tester.pumpWidget(_mapWithPolygons(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polygonsToChange, {p3}); expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); expect(platformGoogleMap.polygonsToAdd.isEmpty, true); }); } /// 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; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/test/polyline_updates_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'fake_maps_controllers.dart'; Widget _mapWithPolylines(Set polylines) { return Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), polylines: polylines, ), ); } void main() { TestWidgetsFlutterBinding.ensureInitialized(); final FakePlatformViewsController fakePlatformViewsController = FakePlatformViewsController(); setUpAll(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( SystemChannels.platform_views, fakePlatformViewsController.fakePlatformViewsMethodHandler, ); }); setUp(() { fakePlatformViewsController.reset(); }); testWidgets('Initializing a polyline', (WidgetTester tester) async { const Polyline p1 = Polyline(polylineId: PolylineId('polyline_1')); await tester.pumpWidget(_mapWithPolylines({p1})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polylinesToAdd.length, 1); final Polyline initializedPolyline = platformGoogleMap.polylinesToAdd.first; expect(initializedPolyline, equals(p1)); expect(platformGoogleMap.polylineIdsToRemove.isEmpty, true); expect(platformGoogleMap.polylinesToChange.isEmpty, true); }); testWidgets('Adding a polyline', (WidgetTester tester) async { const Polyline p1 = Polyline(polylineId: PolylineId('polyline_1')); const Polyline p2 = Polyline(polylineId: PolylineId('polyline_2')); await tester.pumpWidget(_mapWithPolylines({p1})); await tester.pumpWidget(_mapWithPolylines({p1, p2})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polylinesToAdd.length, 1); final Polyline addedPolyline = platformGoogleMap.polylinesToAdd.first; expect(addedPolyline, equals(p2)); expect(platformGoogleMap.polylineIdsToRemove.isEmpty, true); expect(platformGoogleMap.polylinesToChange.isEmpty, true); }); testWidgets('Removing a polyline', (WidgetTester tester) async { const Polyline p1 = Polyline(polylineId: PolylineId('polyline_1')); await tester.pumpWidget(_mapWithPolylines({p1})); await tester.pumpWidget(_mapWithPolylines({})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polylineIdsToRemove.length, 1); expect(platformGoogleMap.polylineIdsToRemove.first, equals(p1.polylineId)); expect(platformGoogleMap.polylinesToChange.isEmpty, true); expect(platformGoogleMap.polylinesToAdd.isEmpty, true); }); testWidgets('Updating a polyline', (WidgetTester tester) async { const Polyline p1 = Polyline(polylineId: PolylineId('polyline_1')); const Polyline p2 = Polyline(polylineId: PolylineId('polyline_1'), geodesic: true); await tester.pumpWidget(_mapWithPolylines({p1})); await tester.pumpWidget(_mapWithPolylines({p2})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polylinesToChange.length, 1); expect(platformGoogleMap.polylinesToChange.first, equals(p2)); expect(platformGoogleMap.polylineIdsToRemove.isEmpty, true); expect(platformGoogleMap.polylinesToAdd.isEmpty, true); }); testWidgets('Updating a polyline', (WidgetTester tester) async { const Polyline p1 = Polyline(polylineId: PolylineId('polyline_1')); const Polyline p2 = Polyline(polylineId: PolylineId('polyline_1'), geodesic: true); await tester.pumpWidget(_mapWithPolylines({p1})); await tester.pumpWidget(_mapWithPolylines({p2})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polylinesToChange.length, 1); final Polyline update = platformGoogleMap.polylinesToChange.first; expect(update, equals(p2)); expect(update.geodesic, true); }); testWidgets('Mutate a polyline', (WidgetTester tester) async { final List points = [const LatLng(0.0, 0.0)]; final Polyline p1 = Polyline( polylineId: const PolylineId('polyline_1'), points: points, ); await tester.pumpWidget(_mapWithPolylines({p1})); p1.points.add(const LatLng(1.0, 1.0)); await tester.pumpWidget(_mapWithPolylines({p1})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polylinesToChange.length, 1); expect(platformGoogleMap.polylinesToChange.first, equals(p1)); expect(platformGoogleMap.polylineIdsToRemove.isEmpty, true); expect(platformGoogleMap.polylinesToAdd.isEmpty, true); }); testWidgets('Multi Update', (WidgetTester tester) async { Polyline p1 = const Polyline(polylineId: PolylineId('polyline_1')); Polyline p2 = const Polyline(polylineId: PolylineId('polyline_2')); final Set prev = {p1, p2}; p1 = const Polyline(polylineId: PolylineId('polyline_1'), visible: false); p2 = const Polyline(polylineId: PolylineId('polyline_2'), geodesic: true); final Set cur = {p1, p2}; await tester.pumpWidget(_mapWithPolylines(prev)); await tester.pumpWidget(_mapWithPolylines(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polylinesToChange, cur); expect(platformGoogleMap.polylineIdsToRemove.isEmpty, true); expect(platformGoogleMap.polylinesToAdd.isEmpty, true); }); testWidgets('Multi Update', (WidgetTester tester) async { Polyline p2 = const Polyline(polylineId: PolylineId('polyline_2')); const Polyline p3 = Polyline(polylineId: PolylineId('polyline_3')); final Set prev = {p2, p3}; // p1 is added, p2 is updated, p3 is removed. const Polyline p1 = Polyline(polylineId: PolylineId('polyline_1')); p2 = const Polyline(polylineId: PolylineId('polyline_2'), geodesic: true); final Set cur = {p1, p2}; await tester.pumpWidget(_mapWithPolylines(prev)); await tester.pumpWidget(_mapWithPolylines(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polylinesToChange.length, 1); expect(platformGoogleMap.polylinesToAdd.length, 1); expect(platformGoogleMap.polylineIdsToRemove.length, 1); expect(platformGoogleMap.polylinesToChange.first, equals(p2)); expect(platformGoogleMap.polylinesToAdd.first, equals(p1)); expect(platformGoogleMap.polylineIdsToRemove.first, equals(p3.polylineId)); }); testWidgets('Partial Update', (WidgetTester tester) async { const Polyline p1 = Polyline(polylineId: PolylineId('polyline_1')); const Polyline p2 = Polyline(polylineId: PolylineId('polyline_2')); Polyline p3 = const Polyline(polylineId: PolylineId('polyline_3')); final Set prev = {p1, p2, p3}; p3 = const Polyline(polylineId: PolylineId('polyline_3'), geodesic: true); final Set cur = {p1, p2, p3}; await tester.pumpWidget(_mapWithPolylines(prev)); await tester.pumpWidget(_mapWithPolylines(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polylinesToChange, {p3}); expect(platformGoogleMap.polylineIdsToRemove.isEmpty, true); expect(platformGoogleMap.polylinesToAdd.isEmpty, true); }); testWidgets('Update non platform related attr', (WidgetTester tester) async { Polyline p1 = const Polyline(polylineId: PolylineId('polyline_1')); final Set prev = {p1}; p1 = Polyline(polylineId: const PolylineId('polyline_1'), onTap: () {}); final Set cur = {p1}; await tester.pumpWidget(_mapWithPolylines(prev)); await tester.pumpWidget(_mapWithPolylines(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.polylinesToChange.isEmpty, true); expect(platformGoogleMap.polylineIdsToRemove.isEmpty, true); expect(platformGoogleMap.polylinesToAdd.isEmpty, true); }); } /// 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; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'fake_maps_controllers.dart'; Widget _mapWithTileOverlays(Set tileOverlays) { return Directionality( textDirection: TextDirection.ltr, child: GoogleMap( initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), tileOverlays: tileOverlays, ), ); } void main() { final FakePlatformViewsController fakePlatformViewsController = FakePlatformViewsController(); setUpAll(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( SystemChannels.platform_views, fakePlatformViewsController.fakePlatformViewsMethodHandler, ); }); setUp(() { fakePlatformViewsController.reset(); }); testWidgets('Initializing a tile overlay', (WidgetTester tester) async { const TileOverlay t1 = TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_1')); await tester.pumpWidget(_mapWithTileOverlays({t1})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.tileOverlaysToAdd.length, 1); final TileOverlay initializedTileOverlay = platformGoogleMap.tileOverlaysToAdd.first; expect(initializedTileOverlay, equals(t1)); expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true); expect(platformGoogleMap.tileOverlaysToChange.isEmpty, true); }); testWidgets('Adding a tile overlay', (WidgetTester tester) async { const TileOverlay t1 = TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_1')); const TileOverlay t2 = TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_2')); await tester.pumpWidget(_mapWithTileOverlays({t1})); await tester.pumpWidget(_mapWithTileOverlays({t1, t2})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.tileOverlaysToAdd.length, 1); final TileOverlay addedTileOverlay = platformGoogleMap.tileOverlaysToAdd.first; expect(addedTileOverlay, equals(t2)); expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true); expect(platformGoogleMap.tileOverlaysToChange.isEmpty, true); }); testWidgets('Removing a tile overlay', (WidgetTester tester) async { const TileOverlay t1 = TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_1')); await tester.pumpWidget(_mapWithTileOverlays({t1})); await tester.pumpWidget(_mapWithTileOverlays({})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.tileOverlayIdsToRemove.length, 1); expect(platformGoogleMap.tileOverlayIdsToRemove.first, equals(t1.tileOverlayId)); expect(platformGoogleMap.tileOverlaysToChange.isEmpty, true); expect(platformGoogleMap.tileOverlaysToAdd.isEmpty, true); }); testWidgets('Updating a tile overlay', (WidgetTester tester) async { const TileOverlay t1 = TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_1')); const TileOverlay t2 = TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_1'), zIndex: 10); await tester.pumpWidget(_mapWithTileOverlays({t1})); await tester.pumpWidget(_mapWithTileOverlays({t2})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.tileOverlaysToChange.length, 1); expect(platformGoogleMap.tileOverlaysToChange.first, equals(t2)); expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true); expect(platformGoogleMap.tileOverlaysToAdd.isEmpty, true); }); testWidgets('Updating a tile overlay', (WidgetTester tester) async { const TileOverlay t1 = TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_1')); const TileOverlay t2 = TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_1'), zIndex: 10); await tester.pumpWidget(_mapWithTileOverlays({t1})); await tester.pumpWidget(_mapWithTileOverlays({t2})); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.tileOverlaysToChange.length, 1); final TileOverlay update = platformGoogleMap.tileOverlaysToChange.first; expect(update, equals(t2)); expect(update.zIndex, 10); }); testWidgets('Multi Update', (WidgetTester tester) async { TileOverlay t1 = const TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_1')); TileOverlay t2 = const TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_2')); final Set prev = {t1, t2}; t1 = const TileOverlay( tileOverlayId: TileOverlayId('tile_overlay_1'), visible: false); t2 = const TileOverlay( tileOverlayId: TileOverlayId('tile_overlay_2'), zIndex: 10); final Set cur = {t1, t2}; await tester.pumpWidget(_mapWithTileOverlays(prev)); await tester.pumpWidget(_mapWithTileOverlays(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.tileOverlaysToChange, cur); expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true); expect(platformGoogleMap.tileOverlaysToAdd.isEmpty, true); }); testWidgets('Multi Update', (WidgetTester tester) async { TileOverlay t2 = const TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_2')); const TileOverlay t3 = TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_3')); final Set prev = {t2, t3}; // t1 is added, t2 is updated, t3 is removed. const TileOverlay t1 = TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_1')); t2 = const TileOverlay( tileOverlayId: TileOverlayId('tile_overlay_2'), zIndex: 10); final Set cur = {t1, t2}; await tester.pumpWidget(_mapWithTileOverlays(prev)); await tester.pumpWidget(_mapWithTileOverlays(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.tileOverlaysToChange.length, 1); expect(platformGoogleMap.tileOverlaysToAdd.length, 1); expect(platformGoogleMap.tileOverlayIdsToRemove.length, 1); expect(platformGoogleMap.tileOverlaysToChange.first, equals(t2)); expect(platformGoogleMap.tileOverlaysToAdd.first, equals(t1)); expect(platformGoogleMap.tileOverlayIdsToRemove.first, equals(t3.tileOverlayId)); }); testWidgets('Partial Update', (WidgetTester tester) async { const TileOverlay t1 = TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_1')); const TileOverlay t2 = TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_2')); TileOverlay t3 = const TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_3')); final Set prev = {t1, t2, t3}; t3 = const TileOverlay( tileOverlayId: TileOverlayId('tile_overlay_3'), zIndex: 10); final Set cur = {t1, t2, t3}; await tester.pumpWidget(_mapWithTileOverlays(prev)); await tester.pumpWidget(_mapWithTileOverlays(cur)); final FakePlatformGoogleMap platformGoogleMap = fakePlatformViewsController.lastCreatedView!; expect(platformGoogleMap.tileOverlaysToChange, {t3}); expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true); expect(platformGoogleMap.tileOverlaysToAdd.isEmpty, true); }); } /// 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; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/AUTHORS ================================================ # 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> Taha Tesser ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md ================================================ ## 2.4.5 * Fixes Initial padding not working when map has not been created yet. ## 2.4.4 * Fixes Points losing precision when converting to LatLng. * 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. * Updates code for new analysis options. ## 2.3.3 * Update android gradle plugin to 7.3.1. ## 2.3.2 * Update `com.google.android.gms:play-services-maps` to 18.1.0. ## 2.3.1 * Updates imports for `prefer_relative_imports`. ## 2.3.0 * Switches the default for `useAndroidViewSurface` to true, and adds information about the current mode behaviors to the README. * Updates minimum Flutter version to 2.10. ## 2.2.0 * Updates `useAndroidViewSurface` to require Hybrid Composition, making the selection work again in Flutter 3.0+. Earlier versions of Flutter are no longer supported. * Fixes violations of new analysis option use_named_constants. * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 2.1.10 * Splits Android implementation out of `google_maps_flutter` as a federated implementation. ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/LICENSE ================================================ 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. ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/README.md ================================================ # google\_maps\_flutter\_android The Android implementation of [`google_maps_flutter`][1]. ## Usage This package is [endorsed][2], which means you can simply use `google_maps_flutter` normally. This package will be automatically included in your app when you do. ## Display Mode This plugin supports two different [platform view display modes][3]. The default display mode is subject to change in the future, and will not be considered a breaking change, so if you want to ensure a specific mode you can set it explicitly: ```dart import 'package:google_maps_flutter_android/google_maps_flutter_android.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; void main() { // Require Hybrid Composition mode on Android. final GoogleMapsFlutterPlatform mapsImplementation = GoogleMapsFlutterPlatform.instance; if (mapsImplementation is GoogleMapsFlutterAndroid) { mapsImplementation.useAndroidViewSurface = true; } // ··· } ``` ### Hybrid Composition This is the current default mode, and corresponds to `useAndroidViewSurface = true`. It ensures that the map display will work as expected, at the cost of some performance. ### Texture Layer Hybrid Composition This is a new display mode used by most plugins starting with Flutter 3.0, and corresponds to `useAndroidViewSurface = false`. This is more performant than Hybrid Composition, but currently [misses certain map updates][4]. This mode will likely become the default in future versions if/when the missed updates issue can be resolved. ## Map renderer This plugin supports the option to request a specific [map renderer][5]. The renderer must be requested before creating GoogleMap instances, as the renderer can be initialized only once per application context. ```dart AndroidMapRenderer mapRenderer = AndroidMapRenderer.platformDefault; // ··· final GoogleMapsFlutterPlatform mapsImplementation = GoogleMapsFlutterPlatform.instance; if (mapsImplementation is GoogleMapsFlutterAndroid) { WidgetsFlutterBinding.ensureInitialized(); mapRenderer = await mapsImplementation .initializeWithRenderer(AndroidMapRenderer.latest); } ``` Available values are `AndroidMapRenderer.latest`, `AndroidMapRenderer.legacy`, `AndroidMapRenderer.platformDefault`. Note that getting the requested renderer as a response is not guaranteed. [1]: https://pub.dev/packages/google_maps_flutter [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin [3]: https://docs.flutter.dev/development/platform-integration/android/platform-views [4]: https://github.com/flutter/flutter/issues/103686 [5]: https://developers.google.com/maps/documentation/android-sdk/renderer ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle ================================================ group 'io.flutter.plugins.googlemaps' version '1.0-SNAPSHOT' buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.3.1' } } rootProject.allprojects { repositories { google() mavenCentral() } } apply plugin: 'com.android.library' android { compileSdkVersion 31 defaultConfig { minSdkVersion 20 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } dependencies { implementation "androidx.annotation:annotation:1.1.0" 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.5.1' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:5.1.1' testImplementation 'androidx.test:core:1.2.0' testImplementation "org.robolectric:robolectric:4.3.1" } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } testOptions { unitTests.includeAndroidResources = true unitTests.returnDefaultValues = true unitTests.all { testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" outputs.upToDateWhen {false} showStandardStreams = true } } } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/settings.gradle ================================================ rootProject.name = 'google_maps_flutter_android' ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/CircleBuilder.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.model.CircleOptions; import com.google.android.gms.maps.model.LatLng; class CircleBuilder implements CircleOptionsSink { private final CircleOptions circleOptions; private final float density; private boolean consumeTapEvents; CircleBuilder(float density) { this.circleOptions = new CircleOptions(); this.density = density; } CircleOptions build() { return circleOptions; } boolean consumeTapEvents() { return consumeTapEvents; } @Override public void setFillColor(int color) { circleOptions.fillColor(color); } @Override public void setStrokeColor(int color) { circleOptions.strokeColor(color); } @Override public void setCenter(LatLng center) { circleOptions.center(center); } @Override public void setRadius(double radius) { circleOptions.radius(radius); } @Override public void setConsumeTapEvents(boolean consumeTapEvents) { this.consumeTapEvents = consumeTapEvents; circleOptions.clickable(consumeTapEvents); } @Override public void setVisible(boolean visible) { circleOptions.visible(visible); } @Override public void setStrokeWidth(float strokeWidth) { circleOptions.strokeWidth(strokeWidth * density); } @Override public void setZIndex(float zIndex) { circleOptions.zIndex(zIndex); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/CircleController.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.model.Circle; import com.google.android.gms.maps.model.LatLng; /** Controller of a single Circle on the map. */ class CircleController implements CircleOptionsSink { private final Circle circle; private final String googleMapsCircleId; private final float density; private boolean consumeTapEvents; CircleController(Circle circle, boolean consumeTapEvents, float density) { this.circle = circle; this.consumeTapEvents = consumeTapEvents; this.density = density; this.googleMapsCircleId = circle.getId(); } void remove() { circle.remove(); } @Override public void setConsumeTapEvents(boolean consumeTapEvents) { this.consumeTapEvents = consumeTapEvents; circle.setClickable(consumeTapEvents); } @Override public void setStrokeColor(int strokeColor) { circle.setStrokeColor(strokeColor); } @Override public void setFillColor(int fillColor) { circle.setFillColor(fillColor); } @Override public void setCenter(LatLng center) { circle.setCenter(center); } @Override public void setRadius(double radius) { circle.setRadius(radius); } @Override public void setVisible(boolean visible) { circle.setVisible(visible); } @Override public void setStrokeWidth(float strokeWidth) { circle.setStrokeWidth(strokeWidth * density); } @Override public void setZIndex(float zIndex) { circle.setZIndex(zIndex); } String getGoogleMapsCircleId() { return googleMapsCircleId; } boolean consumeTapEvents() { return consumeTapEvents; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/CircleOptionsSink.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.model.LatLng; /** Receiver of Circle configuration options. */ interface CircleOptionsSink { void setConsumeTapEvents(boolean consumetapEvents); void setStrokeColor(int strokeColor); void setFillColor(int fillColor); void setCenter(LatLng center); void setRadius(double radius); void setVisible(boolean visible); void setStrokeWidth(float strokeWidth); void setZIndex(float zIndex); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/CirclesController.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.Circle; import com.google.android.gms.maps.model.CircleOptions; import io.flutter.plugin.common.MethodChannel; import java.util.HashMap; import java.util.List; import java.util.Map; class CirclesController { private final Map circleIdToController; private final Map googleMapsCircleIdToDartCircleId; private final MethodChannel methodChannel; private final float density; private GoogleMap googleMap; CirclesController(MethodChannel methodChannel, float density) { this.circleIdToController = new HashMap<>(); this.googleMapsCircleIdToDartCircleId = new HashMap<>(); this.methodChannel = methodChannel; this.density = density; } void setGoogleMap(GoogleMap googleMap) { this.googleMap = googleMap; } void addCircles(List circlesToAdd) { if (circlesToAdd != null) { for (Object circleToAdd : circlesToAdd) { addCircle(circleToAdd); } } } void changeCircles(List circlesToChange) { if (circlesToChange != null) { for (Object circleToChange : circlesToChange) { changeCircle(circleToChange); } } } void removeCircles(List circleIdsToRemove) { if (circleIdsToRemove == null) { return; } for (Object rawCircleId : circleIdsToRemove) { if (rawCircleId == null) { continue; } String circleId = (String) rawCircleId; final CircleController circleController = circleIdToController.remove(circleId); if (circleController != null) { circleController.remove(); googleMapsCircleIdToDartCircleId.remove(circleController.getGoogleMapsCircleId()); } } } boolean onCircleTap(String googleCircleId) { String circleId = googleMapsCircleIdToDartCircleId.get(googleCircleId); if (circleId == null) { return false; } methodChannel.invokeMethod("circle#onTap", Convert.circleIdToJson(circleId)); CircleController circleController = circleIdToController.get(circleId); if (circleController != null) { return circleController.consumeTapEvents(); } return false; } private void addCircle(Object circle) { if (circle == null) { return; } CircleBuilder circleBuilder = new CircleBuilder(density); String circleId = Convert.interpretCircleOptions(circle, circleBuilder); CircleOptions options = circleBuilder.build(); addCircle(circleId, options, circleBuilder.consumeTapEvents()); } private void addCircle(String circleId, CircleOptions circleOptions, boolean consumeTapEvents) { final Circle circle = googleMap.addCircle(circleOptions); CircleController controller = new CircleController(circle, consumeTapEvents, density); circleIdToController.put(circleId, controller); googleMapsCircleIdToDartCircleId.put(circle.getId(), circleId); } private void changeCircle(Object circle) { if (circle == null) { return; } String circleId = getCircleId(circle); CircleController circleController = circleIdToController.get(circleId); if (circleController != null) { Convert.interpretCircleOptions(circle, circleController); } } @SuppressWarnings("unchecked") private static String getCircleId(Object circle) { Map circleMap = (Map) circle; return (String) circleMap.get("circleId"); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Point; import androidx.annotation.VisibleForTesting; import com.google.android.gms.maps.CameraUpdate; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.model.BitmapDescriptor; import com.google.android.gms.maps.model.BitmapDescriptorFactory; import com.google.android.gms.maps.model.ButtCap; import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.Cap; import com.google.android.gms.maps.model.CustomCap; import com.google.android.gms.maps.model.Dash; import com.google.android.gms.maps.model.Dot; import com.google.android.gms.maps.model.Gap; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.LatLngBounds; import com.google.android.gms.maps.model.PatternItem; import com.google.android.gms.maps.model.RoundCap; import com.google.android.gms.maps.model.SquareCap; import com.google.android.gms.maps.model.Tile; import io.flutter.view.FlutterMain; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; /** Conversions between JSON-like values and GoogleMaps data types. */ class Convert { // TODO(hamdikahloun): FlutterMain has been deprecated and should be replaced with FlutterLoader // when it's available in Stable channel: https://github.com/flutter/flutter/issues/70923. @SuppressWarnings("deprecation") private static BitmapDescriptor toBitmapDescriptor(Object o) { final List data = toList(o); switch (toString(data.get(0))) { case "defaultMarker": if (data.size() == 1) { return BitmapDescriptorFactory.defaultMarker(); } else { return BitmapDescriptorFactory.defaultMarker(toFloat(data.get(1))); } case "fromAsset": if (data.size() == 2) { return BitmapDescriptorFactory.fromAsset( FlutterMain.getLookupKeyForAsset(toString(data.get(1)))); } else { return BitmapDescriptorFactory.fromAsset( FlutterMain.getLookupKeyForAsset(toString(data.get(1)), toString(data.get(2)))); } case "fromAssetImage": if (data.size() == 3) { return BitmapDescriptorFactory.fromAsset( FlutterMain.getLookupKeyForAsset(toString(data.get(1)))); } else { throw new IllegalArgumentException( "'fromAssetImage' Expected exactly 3 arguments, got: " + data.size()); } case "fromBytes": return getBitmapFromBytes(data); default: throw new IllegalArgumentException("Cannot interpret " + o + " as BitmapDescriptor"); } } private static BitmapDescriptor getBitmapFromBytes(List data) { if (data.size() == 2) { try { Bitmap bitmap = toBitmap(data.get(1)); return BitmapDescriptorFactory.fromBitmap(bitmap); } catch (Exception e) { throw new IllegalArgumentException("Unable to interpret bytes as a valid image.", e); } } else { throw new IllegalArgumentException( "fromBytes should have exactly one argument, interpretTileOverlayOptions the bytes. Got: " + data.size()); } } private static boolean toBoolean(Object o) { return (Boolean) o; } static CameraPosition toCameraPosition(Object o) { final Map data = toMap(o); final CameraPosition.Builder builder = CameraPosition.builder(); builder.bearing(toFloat(data.get("bearing"))); builder.target(toLatLng(data.get("target"))); builder.tilt(toFloat(data.get("tilt"))); builder.zoom(toFloat(data.get("zoom"))); return builder.build(); } static CameraUpdate toCameraUpdate(Object o, float density) { final List data = toList(o); switch (toString(data.get(0))) { case "newCameraPosition": return CameraUpdateFactory.newCameraPosition(toCameraPosition(data.get(1))); case "newLatLng": return CameraUpdateFactory.newLatLng(toLatLng(data.get(1))); case "newLatLngBounds": return CameraUpdateFactory.newLatLngBounds( toLatLngBounds(data.get(1)), toPixels(data.get(2), density)); case "newLatLngZoom": return CameraUpdateFactory.newLatLngZoom(toLatLng(data.get(1)), toFloat(data.get(2))); case "scrollBy": return CameraUpdateFactory.scrollBy( // toFractionalPixels(data.get(1), density), // toFractionalPixels(data.get(2), density)); case "zoomBy": if (data.size() == 2) { return CameraUpdateFactory.zoomBy(toFloat(data.get(1))); } else { return CameraUpdateFactory.zoomBy(toFloat(data.get(1)), toPoint(data.get(2), density)); } case "zoomIn": return CameraUpdateFactory.zoomIn(); case "zoomOut": return CameraUpdateFactory.zoomOut(); case "zoomTo": return CameraUpdateFactory.zoomTo(toFloat(data.get(1))); default: throw new IllegalArgumentException("Cannot interpret " + o + " as CameraUpdate"); } } private static double toDouble(Object o) { return ((Number) o).doubleValue(); } private static float toFloat(Object o) { return ((Number) o).floatValue(); } private static Float toFloatWrapper(Object o) { return (o == null) ? null : toFloat(o); } private static int toInt(Object o) { return ((Number) o).intValue(); } static Object cameraPositionToJson(CameraPosition position) { if (position == null) { return null; } final Map data = new HashMap<>(); data.put("bearing", position.bearing); data.put("target", latLngToJson(position.target)); data.put("tilt", position.tilt); data.put("zoom", position.zoom); return data; } static Object latlngBoundsToJson(LatLngBounds latLngBounds) { final Map arguments = new HashMap<>(2); arguments.put("southwest", latLngToJson(latLngBounds.southwest)); arguments.put("northeast", latLngToJson(latLngBounds.northeast)); return arguments; } static Object markerIdToJson(String markerId) { if (markerId == null) { return null; } final Map data = new HashMap<>(1); data.put("markerId", markerId); return data; } static Object polygonIdToJson(String polygonId) { if (polygonId == null) { return null; } final Map data = new HashMap<>(1); data.put("polygonId", polygonId); return data; } static Object polylineIdToJson(String polylineId) { if (polylineId == null) { return null; } final Map data = new HashMap<>(1); data.put("polylineId", polylineId); return data; } static Object circleIdToJson(String circleId) { if (circleId == null) { return null; } final Map data = new HashMap<>(1); data.put("circleId", circleId); return data; } static Map tileOverlayArgumentsToJson( String tileOverlayId, int x, int y, int zoom) { if (tileOverlayId == null) { return null; } final Map data = new HashMap<>(4); data.put("tileOverlayId", tileOverlayId); data.put("x", x); data.put("y", y); data.put("zoom", zoom); return data; } static Object latLngToJson(LatLng latLng) { return Arrays.asList(latLng.latitude, latLng.longitude); } static LatLng toLatLng(Object o) { final List data = toList(o); return new LatLng(toDouble(data.get(0)), toDouble(data.get(1))); } static Point toPoint(Object o) { Object x = toMap(o).get("x"); Object y = toMap(o).get("y"); return new Point((int) x, (int) y); } static Map pointToJson(Point point) { final Map data = new HashMap<>(2); data.put("x", point.x); data.put("y", point.y); return data; } private static LatLngBounds toLatLngBounds(Object o) { if (o == null) { return null; } final List data = toList(o); return new LatLngBounds(toLatLng(data.get(0)), toLatLng(data.get(1))); } private static List toList(Object o) { return (List) o; } private static Map toMap(Object o) { return (Map) o; } private static Map toObjectMap(Object o) { Map hashMap = new HashMap<>(); Map map = (Map) o; for (Object key : map.keySet()) { Object object = map.get(key); if (object != null) { hashMap.put((String) key, object); } } return hashMap; } private static float toFractionalPixels(Object o, float density) { return toFloat(o) * density; } private static int toPixels(Object o, float density) { return (int) toFractionalPixels(o, density); } private static Bitmap toBitmap(Object o) { byte[] bmpData = (byte[]) o; Bitmap bitmap = BitmapFactory.decodeByteArray(bmpData, 0, bmpData.length); if (bitmap == null) { throw new IllegalArgumentException("Unable to decode bytes as a valid bitmap."); } else { return bitmap; } } private static Point toPoint(Object o, float density) { final List data = toList(o); return new Point(toPixels(data.get(0), density), toPixels(data.get(1), density)); } private static String toString(Object o) { return (String) o; } static void interpretGoogleMapOptions(Object o, GoogleMapOptionsSink sink) { final Map data = toMap(o); final Object cameraTargetBounds = data.get("cameraTargetBounds"); if (cameraTargetBounds != null) { final List targetData = toList(cameraTargetBounds); sink.setCameraTargetBounds(toLatLngBounds(targetData.get(0))); } final Object compassEnabled = data.get("compassEnabled"); if (compassEnabled != null) { sink.setCompassEnabled(toBoolean(compassEnabled)); } final Object mapToolbarEnabled = data.get("mapToolbarEnabled"); if (mapToolbarEnabled != null) { sink.setMapToolbarEnabled(toBoolean(mapToolbarEnabled)); } final Object mapType = data.get("mapType"); if (mapType != null) { sink.setMapType(toInt(mapType)); } final Object minMaxZoomPreference = data.get("minMaxZoomPreference"); if (minMaxZoomPreference != null) { final List zoomPreferenceData = toList(minMaxZoomPreference); sink.setMinMaxZoomPreference( // toFloatWrapper(zoomPreferenceData.get(0)), // toFloatWrapper(zoomPreferenceData.get(1))); } final Object padding = data.get("padding"); if (padding != null) { final List paddingData = toList(padding); sink.setPadding( toFloat(paddingData.get(0)), toFloat(paddingData.get(1)), toFloat(paddingData.get(2)), toFloat(paddingData.get(3))); } final Object rotateGesturesEnabled = data.get("rotateGesturesEnabled"); if (rotateGesturesEnabled != null) { sink.setRotateGesturesEnabled(toBoolean(rotateGesturesEnabled)); } final Object scrollGesturesEnabled = data.get("scrollGesturesEnabled"); if (scrollGesturesEnabled != null) { sink.setScrollGesturesEnabled(toBoolean(scrollGesturesEnabled)); } final Object tiltGesturesEnabled = data.get("tiltGesturesEnabled"); if (tiltGesturesEnabled != null) { sink.setTiltGesturesEnabled(toBoolean(tiltGesturesEnabled)); } final Object trackCameraPosition = data.get("trackCameraPosition"); if (trackCameraPosition != null) { sink.setTrackCameraPosition(toBoolean(trackCameraPosition)); } final Object zoomGesturesEnabled = data.get("zoomGesturesEnabled"); if (zoomGesturesEnabled != null) { sink.setZoomGesturesEnabled(toBoolean(zoomGesturesEnabled)); } final Object liteModeEnabled = data.get("liteModeEnabled"); if (liteModeEnabled != null) { sink.setLiteModeEnabled(toBoolean(liteModeEnabled)); } final Object myLocationEnabled = data.get("myLocationEnabled"); if (myLocationEnabled != null) { sink.setMyLocationEnabled(toBoolean(myLocationEnabled)); } final Object zoomControlsEnabled = data.get("zoomControlsEnabled"); if (zoomControlsEnabled != null) { sink.setZoomControlsEnabled(toBoolean(zoomControlsEnabled)); } final Object myLocationButtonEnabled = data.get("myLocationButtonEnabled"); if (myLocationButtonEnabled != null) { sink.setMyLocationButtonEnabled(toBoolean(myLocationButtonEnabled)); } final Object indoorEnabled = data.get("indoorEnabled"); if (indoorEnabled != null) { sink.setIndoorEnabled(toBoolean(indoorEnabled)); } final Object trafficEnabled = data.get("trafficEnabled"); if (trafficEnabled != null) { sink.setTrafficEnabled(toBoolean(trafficEnabled)); } final Object buildingsEnabled = data.get("buildingsEnabled"); if (buildingsEnabled != null) { sink.setBuildingsEnabled(toBoolean(buildingsEnabled)); } } /** Returns the dartMarkerId of the interpreted marker. */ static String interpretMarkerOptions(Object o, MarkerOptionsSink sink) { final Map data = toMap(o); final Object alpha = data.get("alpha"); if (alpha != null) { sink.setAlpha(toFloat(alpha)); } final Object anchor = data.get("anchor"); if (anchor != null) { final List anchorData = toList(anchor); sink.setAnchor(toFloat(anchorData.get(0)), toFloat(anchorData.get(1))); } final Object consumeTapEvents = data.get("consumeTapEvents"); if (consumeTapEvents != null) { sink.setConsumeTapEvents(toBoolean(consumeTapEvents)); } final Object draggable = data.get("draggable"); if (draggable != null) { sink.setDraggable(toBoolean(draggable)); } final Object flat = data.get("flat"); if (flat != null) { sink.setFlat(toBoolean(flat)); } final Object icon = data.get("icon"); if (icon != null) { sink.setIcon(toBitmapDescriptor(icon)); } final Object infoWindow = data.get("infoWindow"); if (infoWindow != null) { interpretInfoWindowOptions(sink, toObjectMap(infoWindow)); } final Object position = data.get("position"); if (position != null) { sink.setPosition(toLatLng(position)); } final Object rotation = data.get("rotation"); if (rotation != null) { sink.setRotation(toFloat(rotation)); } final Object visible = data.get("visible"); if (visible != null) { sink.setVisible(toBoolean(visible)); } final Object zIndex = data.get("zIndex"); if (zIndex != null) { sink.setZIndex(toFloat(zIndex)); } final String markerId = (String) data.get("markerId"); if (markerId == null) { throw new IllegalArgumentException("markerId was null"); } else { return markerId; } } private static void interpretInfoWindowOptions( MarkerOptionsSink sink, Map infoWindow) { String title = (String) infoWindow.get("title"); String snippet = (String) infoWindow.get("snippet"); // snippet is nullable. if (title != null) { sink.setInfoWindowText(title, snippet); } Object infoWindowAnchor = infoWindow.get("anchor"); if (infoWindowAnchor != null) { final List anchorData = toList(infoWindowAnchor); sink.setInfoWindowAnchor(toFloat(anchorData.get(0)), toFloat(anchorData.get(1))); } } static String interpretPolygonOptions(Object o, PolygonOptionsSink sink) { final Map data = toMap(o); final Object consumeTapEvents = data.get("consumeTapEvents"); if (consumeTapEvents != null) { sink.setConsumeTapEvents(toBoolean(consumeTapEvents)); } final Object geodesic = data.get("geodesic"); if (geodesic != null) { sink.setGeodesic(toBoolean(geodesic)); } final Object visible = data.get("visible"); if (visible != null) { sink.setVisible(toBoolean(visible)); } final Object fillColor = data.get("fillColor"); if (fillColor != null) { sink.setFillColor(toInt(fillColor)); } final Object strokeColor = data.get("strokeColor"); if (strokeColor != null) { sink.setStrokeColor(toInt(strokeColor)); } final Object strokeWidth = data.get("strokeWidth"); if (strokeWidth != null) { sink.setStrokeWidth(toInt(strokeWidth)); } final Object zIndex = data.get("zIndex"); if (zIndex != null) { sink.setZIndex(toFloat(zIndex)); } final Object points = data.get("points"); if (points != null) { sink.setPoints(toPoints(points)); } final Object holes = data.get("holes"); if (holes != null) { sink.setHoles(toHoles(holes)); } final String polygonId = (String) data.get("polygonId"); if (polygonId == null) { throw new IllegalArgumentException("polygonId was null"); } else { return polygonId; } } static String interpretPolylineOptions(Object o, PolylineOptionsSink sink) { final Map data = toMap(o); final Object consumeTapEvents = data.get("consumeTapEvents"); if (consumeTapEvents != null) { sink.setConsumeTapEvents(toBoolean(consumeTapEvents)); } final Object color = data.get("color"); if (color != null) { sink.setColor(toInt(color)); } final Object endCap = data.get("endCap"); if (endCap != null) { sink.setEndCap(toCap(endCap)); } final Object geodesic = data.get("geodesic"); if (geodesic != null) { sink.setGeodesic(toBoolean(geodesic)); } final Object jointType = data.get("jointType"); if (jointType != null) { sink.setJointType(toInt(jointType)); } final Object startCap = data.get("startCap"); if (startCap != null) { sink.setStartCap(toCap(startCap)); } final Object visible = data.get("visible"); if (visible != null) { sink.setVisible(toBoolean(visible)); } final Object width = data.get("width"); if (width != null) { sink.setWidth(toInt(width)); } final Object zIndex = data.get("zIndex"); if (zIndex != null) { sink.setZIndex(toFloat(zIndex)); } final Object points = data.get("points"); if (points != null) { sink.setPoints(toPoints(points)); } final Object pattern = data.get("pattern"); if (pattern != null) { sink.setPattern(toPattern(pattern)); } final String polylineId = (String) data.get("polylineId"); if (polylineId == null) { throw new IllegalArgumentException("polylineId was null"); } else { return polylineId; } } static String interpretCircleOptions(Object o, CircleOptionsSink sink) { final Map data = toMap(o); final Object consumeTapEvents = data.get("consumeTapEvents"); if (consumeTapEvents != null) { sink.setConsumeTapEvents(toBoolean(consumeTapEvents)); } final Object fillColor = data.get("fillColor"); if (fillColor != null) { sink.setFillColor(toInt(fillColor)); } final Object strokeColor = data.get("strokeColor"); if (strokeColor != null) { sink.setStrokeColor(toInt(strokeColor)); } final Object visible = data.get("visible"); if (visible != null) { sink.setVisible(toBoolean(visible)); } final Object strokeWidth = data.get("strokeWidth"); if (strokeWidth != null) { sink.setStrokeWidth(toInt(strokeWidth)); } final Object zIndex = data.get("zIndex"); if (zIndex != null) { sink.setZIndex(toFloat(zIndex)); } final Object center = data.get("center"); if (center != null) { sink.setCenter(toLatLng(center)); } final Object radius = data.get("radius"); if (radius != null) { sink.setRadius(toDouble(radius)); } final String circleId = (String) data.get("circleId"); if (circleId == null) { throw new IllegalArgumentException("circleId was null"); } else { return circleId; } } @VisibleForTesting static List toPoints(Object o) { final List data = toList(o); final List points = new ArrayList<>(data.size()); for (Object rawPoint : data) { final List point = toList(rawPoint); points.add(new LatLng(toDouble(point.get(0)), toDouble(point.get(1)))); } return points; } private static List> toHoles(Object o) { final List data = toList(o); final List> holes = new ArrayList<>(data.size()); for (Object rawHole : data) { holes.add(toPoints(rawHole)); } return holes; } private static List toPattern(Object o) { final List data = toList(o); if (data.isEmpty()) { return null; } final List pattern = new ArrayList<>(data.size()); for (Object ob : data) { final List patternItem = toList(ob); switch (toString(patternItem.get(0))) { case "dot": pattern.add(new Dot()); break; case "dash": pattern.add(new Dash(toFloat(patternItem.get(1)))); break; case "gap": pattern.add(new Gap(toFloat(patternItem.get(1)))); break; default: throw new IllegalArgumentException("Cannot interpret " + pattern + " as PatternItem"); } } return pattern; } private static Cap toCap(Object o) { final List data = toList(o); switch (toString(data.get(0))) { case "buttCap": return new ButtCap(); case "roundCap": return new RoundCap(); case "squareCap": return new SquareCap(); case "customCap": if (data.size() == 2) { return new CustomCap(toBitmapDescriptor(data.get(1))); } else { return new CustomCap(toBitmapDescriptor(data.get(1)), toFloat(data.get(2))); } default: throw new IllegalArgumentException("Cannot interpret " + o + " as Cap"); } } static String interpretTileOverlayOptions(Map data, TileOverlaySink sink) { final Object fadeIn = data.get("fadeIn"); if (fadeIn != null) { sink.setFadeIn(toBoolean(fadeIn)); } final Object transparency = data.get("transparency"); if (transparency != null) { sink.setTransparency(toFloat(transparency)); } final Object zIndex = data.get("zIndex"); if (zIndex != null) { sink.setZIndex(toFloat(zIndex)); } final Object visible = data.get("visible"); if (visible != null) { sink.setVisible(toBoolean(visible)); } final String tileOverlayId = (String) data.get("tileOverlayId"); if (tileOverlayId == null) { throw new IllegalArgumentException("tileOverlayId was null"); } else { return tileOverlayId; } } static Tile interpretTile(Map data) { int width = toInt(data.get("width")); int height = toInt(data.get("height")); byte[] dataArray = null; if (data.get("data") != null) { dataArray = (byte[]) data.get("data"); } return new Tile(width, height, dataArray); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import android.content.Context; import android.graphics.Rect; import com.google.android.gms.maps.GoogleMapOptions; import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.LatLngBounds; import io.flutter.plugin.common.BinaryMessenger; import java.util.List; import java.util.Map; class GoogleMapBuilder implements GoogleMapOptionsSink { private final GoogleMapOptions options = new GoogleMapOptions(); private boolean trackCameraPosition = false; private boolean myLocationEnabled = false; private boolean myLocationButtonEnabled = false; private boolean indoorEnabled = true; private boolean trafficEnabled = false; private boolean buildingsEnabled = true; private Object initialMarkers; private Object initialPolygons; private Object initialPolylines; private Object initialCircles; private List> initialTileOverlays; private Rect padding = new Rect(0, 0, 0, 0); GoogleMapController build( int id, Context context, BinaryMessenger binaryMessenger, LifecycleProvider lifecycleProvider) { final GoogleMapController controller = new GoogleMapController(id, context, binaryMessenger, lifecycleProvider, options); controller.init(); controller.setMyLocationEnabled(myLocationEnabled); controller.setMyLocationButtonEnabled(myLocationButtonEnabled); controller.setIndoorEnabled(indoorEnabled); controller.setTrafficEnabled(trafficEnabled); controller.setBuildingsEnabled(buildingsEnabled); controller.setTrackCameraPosition(trackCameraPosition); controller.setInitialMarkers(initialMarkers); controller.setInitialPolygons(initialPolygons); controller.setInitialPolylines(initialPolylines); controller.setInitialCircles(initialCircles); controller.setPadding(padding.top, padding.left, padding.bottom, padding.right); controller.setInitialTileOverlays(initialTileOverlays); return controller; } void setInitialCameraPosition(CameraPosition position) { options.camera(position); } @Override public void setCompassEnabled(boolean compassEnabled) { options.compassEnabled(compassEnabled); } @Override public void setMapToolbarEnabled(boolean setMapToolbarEnabled) { options.mapToolbarEnabled(setMapToolbarEnabled); } @Override public void setCameraTargetBounds(LatLngBounds bounds) { options.latLngBoundsForCameraTarget(bounds); } @Override public void setMapType(int mapType) { options.mapType(mapType); } @Override public void setMinMaxZoomPreference(Float min, Float max) { if (min != null) { options.minZoomPreference(min); } if (max != null) { options.maxZoomPreference(max); } } @Override public void setPadding(float top, float left, float bottom, float right) { this.padding = new Rect((int) left, (int) top, (int) right, (int) bottom); } @Override public void setTrackCameraPosition(boolean trackCameraPosition) { this.trackCameraPosition = trackCameraPosition; } @Override public void setRotateGesturesEnabled(boolean rotateGesturesEnabled) { options.rotateGesturesEnabled(rotateGesturesEnabled); } @Override public void setScrollGesturesEnabled(boolean scrollGesturesEnabled) { options.scrollGesturesEnabled(scrollGesturesEnabled); } @Override public void setTiltGesturesEnabled(boolean tiltGesturesEnabled) { options.tiltGesturesEnabled(tiltGesturesEnabled); } @Override public void setZoomGesturesEnabled(boolean zoomGesturesEnabled) { options.zoomGesturesEnabled(zoomGesturesEnabled); } @Override public void setLiteModeEnabled(boolean liteModeEnabled) { options.liteMode(liteModeEnabled); } @Override public void setIndoorEnabled(boolean indoorEnabled) { this.indoorEnabled = indoorEnabled; } @Override public void setTrafficEnabled(boolean trafficEnabled) { this.trafficEnabled = trafficEnabled; } @Override public void setBuildingsEnabled(boolean buildingsEnabled) { this.buildingsEnabled = buildingsEnabled; } @Override public void setMyLocationEnabled(boolean myLocationEnabled) { this.myLocationEnabled = myLocationEnabled; } @Override public void setZoomControlsEnabled(boolean zoomControlsEnabled) { options.zoomControlsEnabled(zoomControlsEnabled); } @Override public void setMyLocationButtonEnabled(boolean myLocationButtonEnabled) { this.myLocationButtonEnabled = myLocationButtonEnabled; } @Override public void setInitialMarkers(Object initialMarkers) { this.initialMarkers = initialMarkers; } @Override public void setInitialPolygons(Object initialPolygons) { this.initialPolygons = initialPolygons; } @Override public void setInitialPolylines(Object initialPolylines) { this.initialPolylines = initialPolylines; } @Override public void setInitialCircles(Object initialCircles) { this.initialCircles = initialCircles; } @Override public void setInitialTileOverlays(List> initialTileOverlays) { this.initialTileOverlays = initialTileOverlays; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import android.Manifest; import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.Point; import android.os.Bundle; import android.util.Log; import android.view.Choreographer; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; import com.google.android.gms.maps.CameraUpdate; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.GoogleMap.SnapshotReadyCallback; import com.google.android.gms.maps.GoogleMapOptions; import com.google.android.gms.maps.MapView; import com.google.android.gms.maps.OnMapReadyCallback; import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.Circle; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.LatLngBounds; import com.google.android.gms.maps.model.MapStyleOptions; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.Polygon; import com.google.android.gms.maps.model.Polyline; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.platform.PlatformView; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** Controller of a single GoogleMaps MapView instance. */ final class GoogleMapController implements DefaultLifecycleObserver, ActivityPluginBinding.OnSaveInstanceStateListener, GoogleMapOptionsSink, MethodChannel.MethodCallHandler, OnMapReadyCallback, GoogleMapListener, PlatformView { private static final String TAG = "GoogleMapController"; private final int id; private final MethodChannel methodChannel; private final GoogleMapOptions options; @Nullable private MapView mapView; @Nullable private GoogleMap googleMap; private boolean trackCameraPosition = false; private boolean myLocationEnabled = false; private boolean myLocationButtonEnabled = false; private boolean zoomControlsEnabled = true; private boolean indoorEnabled = true; private boolean trafficEnabled = false; private boolean buildingsEnabled = true; private boolean disposed = false; @VisibleForTesting final float density; private MethodChannel.Result mapReadyResult; private final Context context; private final LifecycleProvider lifecycleProvider; private final MarkersController markersController; private final PolygonsController polygonsController; private final PolylinesController polylinesController; private final CirclesController circlesController; private final TileOverlaysController tileOverlaysController; private List initialMarkers; private List initialPolygons; private List initialPolylines; private List initialCircles; private List> initialTileOverlays; @VisibleForTesting List initialPadding; GoogleMapController( int id, Context context, BinaryMessenger binaryMessenger, LifecycleProvider lifecycleProvider, GoogleMapOptions options) { this.id = id; this.context = context; this.options = options; this.mapView = new MapView(context, options); this.density = context.getResources().getDisplayMetrics().density; methodChannel = new MethodChannel(binaryMessenger, "plugins.flutter.dev/google_maps_android_" + id); methodChannel.setMethodCallHandler(this); this.lifecycleProvider = lifecycleProvider; this.markersController = new MarkersController(methodChannel); this.polygonsController = new PolygonsController(methodChannel, density); this.polylinesController = new PolylinesController(methodChannel, density); this.circlesController = new CirclesController(methodChannel, density); this.tileOverlaysController = new TileOverlaysController(methodChannel); } @Override public View getView() { return mapView; } @VisibleForTesting /*package*/ void setView(MapView view) { mapView = view; } void init() { lifecycleProvider.getLifecycle().addObserver(this); mapView.getMapAsync(this); } private void moveCamera(CameraUpdate cameraUpdate) { googleMap.moveCamera(cameraUpdate); } private void animateCamera(CameraUpdate cameraUpdate) { googleMap.animateCamera(cameraUpdate); } private CameraPosition getCameraPosition() { return trackCameraPosition ? googleMap.getCameraPosition() : null; } private boolean loadedCallbackPending = false; /** * Invalidates the map view after the map has finished rendering. * *

gmscore GL renderer uses a {@link android.view.TextureView}. Android platform views that are * displayed as a texture after Flutter v3.0.0. require that the view hierarchy is notified after * all drawing operations have been flushed. * *

Since the GL renderer doesn't use standard Android views, and instead uses GL directly, we * notify the view hierarchy by invalidating the view. * *

Unfortunately, when {@link GoogleMap.OnMapLoadedCallback} is fired, the texture may not have * been updated yet. * *

To workaround this limitation, wait two frames. This ensures that at least the frame budget * (16.66ms at 60hz) have passed since the drawing operation was issued. */ private void invalidateMapIfNeeded() { if (googleMap == null || loadedCallbackPending) { return; } loadedCallbackPending = true; googleMap.setOnMapLoadedCallback( new GoogleMap.OnMapLoadedCallback() { @Override public void onMapLoaded() { loadedCallbackPending = false; postFrameCallback( () -> { postFrameCallback( () -> { if (mapView != null) { mapView.invalidate(); } }); }); } }); } private static void postFrameCallback(Runnable f) { Choreographer.getInstance() .postFrameCallback( new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { f.run(); } }); } @Override public void onMapReady(GoogleMap googleMap) { this.googleMap = googleMap; this.googleMap.setIndoorEnabled(this.indoorEnabled); this.googleMap.setTrafficEnabled(this.trafficEnabled); this.googleMap.setBuildingsEnabled(this.buildingsEnabled); googleMap.setOnInfoWindowClickListener(this); if (mapReadyResult != null) { mapReadyResult.success(null); mapReadyResult = null; } setGoogleMapListener(this); updateMyLocationSettings(); markersController.setGoogleMap(googleMap); polygonsController.setGoogleMap(googleMap); polylinesController.setGoogleMap(googleMap); circlesController.setGoogleMap(googleMap); tileOverlaysController.setGoogleMap(googleMap); updateInitialMarkers(); updateInitialPolygons(); updateInitialPolylines(); updateInitialCircles(); updateInitialTileOverlays(); if (initialPadding != null && initialPadding.size() == 4) { setPadding( initialPadding.get(0), initialPadding.get(1), initialPadding.get(2), initialPadding.get(3)); } } @Override public void onMethodCall(MethodCall call, MethodChannel.Result result) { switch (call.method) { case "map#waitForMap": if (googleMap != null) { result.success(null); return; } mapReadyResult = result; break; case "map#update": { Convert.interpretGoogleMapOptions(call.argument("options"), this); result.success(Convert.cameraPositionToJson(getCameraPosition())); break; } case "map#getVisibleRegion": { if (googleMap != null) { LatLngBounds latLngBounds = googleMap.getProjection().getVisibleRegion().latLngBounds; result.success(Convert.latlngBoundsToJson(latLngBounds)); } else { result.error( "GoogleMap uninitialized", "getVisibleRegion called prior to map initialization", null); } break; } case "map#getScreenCoordinate": { if (googleMap != null) { LatLng latLng = Convert.toLatLng(call.arguments); Point screenLocation = googleMap.getProjection().toScreenLocation(latLng); result.success(Convert.pointToJson(screenLocation)); } else { result.error( "GoogleMap uninitialized", "getScreenCoordinate called prior to map initialization", null); } break; } case "map#getLatLng": { if (googleMap != null) { Point point = Convert.toPoint(call.arguments); LatLng latLng = googleMap.getProjection().fromScreenLocation(point); result.success(Convert.latLngToJson(latLng)); } else { result.error( "GoogleMap uninitialized", "getLatLng called prior to map initialization", null); } break; } case "map#takeSnapshot": { if (googleMap != null) { final MethodChannel.Result _result = result; googleMap.snapshot( new SnapshotReadyCallback() { @Override public void onSnapshotReady(Bitmap bitmap) { ByteArrayOutputStream stream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); byte[] byteArray = stream.toByteArray(); bitmap.recycle(); _result.success(byteArray); } }); } else { result.error("GoogleMap uninitialized", "takeSnapshot", null); } break; } case "camera#move": { final CameraUpdate cameraUpdate = Convert.toCameraUpdate(call.argument("cameraUpdate"), density); moveCamera(cameraUpdate); result.success(null); break; } case "camera#animate": { final CameraUpdate cameraUpdate = Convert.toCameraUpdate(call.argument("cameraUpdate"), density); animateCamera(cameraUpdate); result.success(null); break; } case "markers#update": { invalidateMapIfNeeded(); List markersToAdd = call.argument("markersToAdd"); markersController.addMarkers(markersToAdd); List markersToChange = call.argument("markersToChange"); markersController.changeMarkers(markersToChange); List markerIdsToRemove = call.argument("markerIdsToRemove"); markersController.removeMarkers(markerIdsToRemove); result.success(null); break; } case "markers#showInfoWindow": { Object markerId = call.argument("markerId"); markersController.showMarkerInfoWindow((String) markerId, result); break; } case "markers#hideInfoWindow": { Object markerId = call.argument("markerId"); markersController.hideMarkerInfoWindow((String) markerId, result); break; } case "markers#isInfoWindowShown": { Object markerId = call.argument("markerId"); markersController.isInfoWindowShown((String) markerId, result); break; } case "polygons#update": { invalidateMapIfNeeded(); List polygonsToAdd = call.argument("polygonsToAdd"); polygonsController.addPolygons(polygonsToAdd); List polygonsToChange = call.argument("polygonsToChange"); polygonsController.changePolygons(polygonsToChange); List polygonIdsToRemove = call.argument("polygonIdsToRemove"); polygonsController.removePolygons(polygonIdsToRemove); result.success(null); break; } case "polylines#update": { invalidateMapIfNeeded(); List polylinesToAdd = call.argument("polylinesToAdd"); polylinesController.addPolylines(polylinesToAdd); List polylinesToChange = call.argument("polylinesToChange"); polylinesController.changePolylines(polylinesToChange); List polylineIdsToRemove = call.argument("polylineIdsToRemove"); polylinesController.removePolylines(polylineIdsToRemove); result.success(null); break; } case "circles#update": { invalidateMapIfNeeded(); List circlesToAdd = call.argument("circlesToAdd"); circlesController.addCircles(circlesToAdd); List circlesToChange = call.argument("circlesToChange"); circlesController.changeCircles(circlesToChange); List circleIdsToRemove = call.argument("circleIdsToRemove"); circlesController.removeCircles(circleIdsToRemove); result.success(null); break; } case "map#isCompassEnabled": { result.success(googleMap.getUiSettings().isCompassEnabled()); break; } case "map#isMapToolbarEnabled": { result.success(googleMap.getUiSettings().isMapToolbarEnabled()); break; } case "map#getMinMaxZoomLevels": { List zoomLevels = new ArrayList<>(2); zoomLevels.add(googleMap.getMinZoomLevel()); zoomLevels.add(googleMap.getMaxZoomLevel()); result.success(zoomLevels); break; } case "map#isZoomGesturesEnabled": { result.success(googleMap.getUiSettings().isZoomGesturesEnabled()); break; } case "map#isLiteModeEnabled": { result.success(options.getLiteMode()); break; } case "map#isZoomControlsEnabled": { result.success(googleMap.getUiSettings().isZoomControlsEnabled()); break; } case "map#isScrollGesturesEnabled": { result.success(googleMap.getUiSettings().isScrollGesturesEnabled()); break; } case "map#isTiltGesturesEnabled": { result.success(googleMap.getUiSettings().isTiltGesturesEnabled()); break; } case "map#isRotateGesturesEnabled": { result.success(googleMap.getUiSettings().isRotateGesturesEnabled()); break; } case "map#isMyLocationButtonEnabled": { result.success(googleMap.getUiSettings().isMyLocationButtonEnabled()); break; } case "map#isTrafficEnabled": { result.success(googleMap.isTrafficEnabled()); break; } case "map#isBuildingsEnabled": { result.success(googleMap.isBuildingsEnabled()); break; } case "map#getZoomLevel": { result.success(googleMap.getCameraPosition().zoom); break; } case "map#setStyle": { invalidateMapIfNeeded(); boolean mapStyleSet; if (call.arguments instanceof String) { String mapStyle = (String) call.arguments; if (mapStyle == null) { mapStyleSet = googleMap.setMapStyle(null); } else { mapStyleSet = googleMap.setMapStyle(new MapStyleOptions(mapStyle)); } } else { mapStyleSet = googleMap.setMapStyle(null); } ArrayList mapStyleResult = new ArrayList<>(2); mapStyleResult.add(mapStyleSet); if (!mapStyleSet) { mapStyleResult.add( "Unable to set the map style. Please check console logs for errors."); } result.success(mapStyleResult); break; } case "tileOverlays#update": { invalidateMapIfNeeded(); List> tileOverlaysToAdd = call.argument("tileOverlaysToAdd"); tileOverlaysController.addTileOverlays(tileOverlaysToAdd); List> tileOverlaysToChange = call.argument("tileOverlaysToChange"); tileOverlaysController.changeTileOverlays(tileOverlaysToChange); List tileOverlaysToRemove = call.argument("tileOverlayIdsToRemove"); tileOverlaysController.removeTileOverlays(tileOverlaysToRemove); result.success(null); break; } case "tileOverlays#clearTileCache": { invalidateMapIfNeeded(); String tileOverlayId = call.argument("tileOverlayId"); tileOverlaysController.clearTileCache(tileOverlayId); result.success(null); break; } case "map#getTileOverlayInfo": { String tileOverlayId = call.argument("tileOverlayId"); result.success(tileOverlaysController.getTileOverlayInfo(tileOverlayId)); break; } default: result.notImplemented(); } } @Override public void onMapClick(LatLng latLng) { final Map arguments = new HashMap<>(2); arguments.put("position", Convert.latLngToJson(latLng)); methodChannel.invokeMethod("map#onTap", arguments); } @Override public void onMapLongClick(LatLng latLng) { final Map arguments = new HashMap<>(2); arguments.put("position", Convert.latLngToJson(latLng)); methodChannel.invokeMethod("map#onLongPress", arguments); } @Override public void onCameraMoveStarted(int reason) { final Map arguments = new HashMap<>(2); boolean isGesture = reason == GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE; arguments.put("isGesture", isGesture); methodChannel.invokeMethod("camera#onMoveStarted", arguments); } @Override public void onInfoWindowClick(Marker marker) { markersController.onInfoWindowTap(marker.getId()); } @Override public void onCameraMove() { if (!trackCameraPosition) { return; } final Map arguments = new HashMap<>(2); arguments.put("position", Convert.cameraPositionToJson(googleMap.getCameraPosition())); methodChannel.invokeMethod("camera#onMove", arguments); } @Override public void onCameraIdle() { methodChannel.invokeMethod("camera#onIdle", Collections.singletonMap("map", id)); } @Override public boolean onMarkerClick(Marker marker) { return markersController.onMarkerTap(marker.getId()); } @Override public void onMarkerDragStart(Marker marker) { markersController.onMarkerDragStart(marker.getId(), marker.getPosition()); } @Override public void onMarkerDrag(Marker marker) { markersController.onMarkerDrag(marker.getId(), marker.getPosition()); } @Override public void onMarkerDragEnd(Marker marker) { markersController.onMarkerDragEnd(marker.getId(), marker.getPosition()); } @Override public void onPolygonClick(Polygon polygon) { polygonsController.onPolygonTap(polygon.getId()); } @Override public void onPolylineClick(Polyline polyline) { polylinesController.onPolylineTap(polyline.getId()); } @Override public void onCircleClick(Circle circle) { circlesController.onCircleTap(circle.getId()); } @Override public void dispose() { if (disposed) { return; } disposed = true; methodChannel.setMethodCallHandler(null); setGoogleMapListener(null); destroyMapViewIfNecessary(); Lifecycle lifecycle = lifecycleProvider.getLifecycle(); if (lifecycle != null) { lifecycle.removeObserver(this); } } private void setGoogleMapListener(@Nullable GoogleMapListener listener) { if (googleMap == null) { Log.v(TAG, "Controller was disposed before GoogleMap was ready."); return; } googleMap.setOnCameraMoveStartedListener(listener); googleMap.setOnCameraMoveListener(listener); googleMap.setOnCameraIdleListener(listener); googleMap.setOnMarkerClickListener(listener); googleMap.setOnMarkerDragListener(listener); googleMap.setOnPolygonClickListener(listener); googleMap.setOnPolylineClickListener(listener); googleMap.setOnCircleClickListener(listener); googleMap.setOnMapClickListener(listener); googleMap.setOnMapLongClickListener(listener); } // @Override // The minimum supported version of Flutter doesn't have this method on the PlatformView interface, but the maximum // does. This will override it when available even with the annotation commented out. public void onInputConnectionLocked() { // TODO(mklim): Remove this empty override once https://github.com/flutter/flutter/issues/40126 is fixed in stable. } // @Override // The minimum supported version of Flutter doesn't have this method on the PlatformView interface, but the maximum // does. This will override it when available even with the annotation commented out. public void onInputConnectionUnlocked() { // TODO(mklim): Remove this empty override once https://github.com/flutter/flutter/issues/40126 is fixed in stable. } // DefaultLifecycleObserver @Override public void onCreate(@NonNull LifecycleOwner owner) { if (disposed) { return; } mapView.onCreate(null); } @Override public void onStart(@NonNull LifecycleOwner owner) { if (disposed) { return; } mapView.onStart(); } @Override public void onResume(@NonNull LifecycleOwner owner) { if (disposed) { return; } mapView.onResume(); } @Override public void onPause(@NonNull LifecycleOwner owner) { if (disposed) { return; } mapView.onResume(); } @Override public void onStop(@NonNull LifecycleOwner owner) { if (disposed) { return; } mapView.onStop(); } @Override public void onDestroy(@NonNull LifecycleOwner owner) { owner.getLifecycle().removeObserver(this); if (disposed) { return; } destroyMapViewIfNecessary(); } @Override public void onRestoreInstanceState(Bundle bundle) { if (disposed) { return; } mapView.onCreate(bundle); } @Override public void onSaveInstanceState(Bundle bundle) { if (disposed) { return; } mapView.onSaveInstanceState(bundle); } // GoogleMapOptionsSink methods @Override public void setCameraTargetBounds(LatLngBounds bounds) { googleMap.setLatLngBoundsForCameraTarget(bounds); } @Override public void setCompassEnabled(boolean compassEnabled) { googleMap.getUiSettings().setCompassEnabled(compassEnabled); } @Override public void setMapToolbarEnabled(boolean mapToolbarEnabled) { googleMap.getUiSettings().setMapToolbarEnabled(mapToolbarEnabled); } @Override public void setMapType(int mapType) { googleMap.setMapType(mapType); } @Override public void setTrackCameraPosition(boolean trackCameraPosition) { this.trackCameraPosition = trackCameraPosition; } @Override public void setRotateGesturesEnabled(boolean rotateGesturesEnabled) { googleMap.getUiSettings().setRotateGesturesEnabled(rotateGesturesEnabled); } @Override public void setScrollGesturesEnabled(boolean scrollGesturesEnabled) { googleMap.getUiSettings().setScrollGesturesEnabled(scrollGesturesEnabled); } @Override public void setTiltGesturesEnabled(boolean tiltGesturesEnabled) { googleMap.getUiSettings().setTiltGesturesEnabled(tiltGesturesEnabled); } @Override public void setMinMaxZoomPreference(Float min, Float max) { googleMap.resetMinMaxZoomPreference(); if (min != null) { googleMap.setMinZoomPreference(min); } if (max != null) { googleMap.setMaxZoomPreference(max); } } @Override public void setPadding(float top, float left, float bottom, float right) { if (googleMap != null) { googleMap.setPadding( (int) (left * density), (int) (top * density), (int) (right * density), (int) (bottom * density)); } else { setInitialPadding(top, left, bottom, right); } } @VisibleForTesting void setInitialPadding(float top, float left, float bottom, float right) { if (initialPadding == null) { initialPadding = new ArrayList<>(); } else { initialPadding.clear(); } initialPadding.add(top); initialPadding.add(left); initialPadding.add(bottom); initialPadding.add(right); } @Override public void setZoomGesturesEnabled(boolean zoomGesturesEnabled) { googleMap.getUiSettings().setZoomGesturesEnabled(zoomGesturesEnabled); } /** This call will have no effect on already created map */ @Override public void setLiteModeEnabled(boolean liteModeEnabled) { options.liteMode(liteModeEnabled); } @Override public void setMyLocationEnabled(boolean myLocationEnabled) { if (this.myLocationEnabled == myLocationEnabled) { return; } this.myLocationEnabled = myLocationEnabled; if (googleMap != null) { updateMyLocationSettings(); } } @Override public void setMyLocationButtonEnabled(boolean myLocationButtonEnabled) { if (this.myLocationButtonEnabled == myLocationButtonEnabled) { return; } this.myLocationButtonEnabled = myLocationButtonEnabled; if (googleMap != null) { updateMyLocationSettings(); } } @Override public void setZoomControlsEnabled(boolean zoomControlsEnabled) { if (this.zoomControlsEnabled == zoomControlsEnabled) { return; } this.zoomControlsEnabled = zoomControlsEnabled; if (googleMap != null) { googleMap.getUiSettings().setZoomControlsEnabled(zoomControlsEnabled); } } @Override public void setInitialMarkers(Object initialMarkers) { ArrayList markers = (ArrayList) initialMarkers; this.initialMarkers = markers != null ? new ArrayList<>(markers) : null; if (googleMap != null) { updateInitialMarkers(); } } private void updateInitialMarkers() { markersController.addMarkers(initialMarkers); } @Override public void setInitialPolygons(Object initialPolygons) { ArrayList polygons = (ArrayList) initialPolygons; this.initialPolygons = polygons != null ? new ArrayList<>(polygons) : null; if (googleMap != null) { updateInitialPolygons(); } } private void updateInitialPolygons() { polygonsController.addPolygons(initialPolygons); } @Override public void setInitialPolylines(Object initialPolylines) { ArrayList polylines = (ArrayList) initialPolylines; this.initialPolylines = polylines != null ? new ArrayList<>(polylines) : null; if (googleMap != null) { updateInitialPolylines(); } } private void updateInitialPolylines() { polylinesController.addPolylines(initialPolylines); } @Override public void setInitialCircles(Object initialCircles) { ArrayList circles = (ArrayList) initialCircles; this.initialCircles = circles != null ? new ArrayList<>(circles) : null; if (googleMap != null) { updateInitialCircles(); } } private void updateInitialCircles() { circlesController.addCircles(initialCircles); } @Override public void setInitialTileOverlays(List> initialTileOverlays) { this.initialTileOverlays = initialTileOverlays; if (googleMap != null) { updateInitialTileOverlays(); } } private void updateInitialTileOverlays() { tileOverlaysController.addTileOverlays(initialTileOverlays); } @SuppressLint("MissingPermission") private void updateMyLocationSettings() { if (hasLocationPermission()) { // The plugin doesn't add the location permission by default so that apps that don't need // the feature won't require the permission. // Gradle is doing a static check for missing permission and in some configurations will // fail the build if the permission is missing. The following disables the Gradle lint. //noinspection ResourceType googleMap.setMyLocationEnabled(myLocationEnabled); googleMap.getUiSettings().setMyLocationButtonEnabled(myLocationButtonEnabled); } else { // TODO(amirh): Make the options update fail. // https://github.com/flutter/flutter/issues/24327 Log.e(TAG, "Cannot enable MyLocation layer as location permissions are not granted"); } } private boolean hasLocationPermission() { return checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED; } private int checkSelfPermission(String permission) { if (permission == null) { throw new IllegalArgumentException("permission is null"); } return context.checkPermission( permission, android.os.Process.myPid(), android.os.Process.myUid()); } private void destroyMapViewIfNecessary() { if (mapView == null) { return; } mapView.onDestroy(); mapView = null; } public void setIndoorEnabled(boolean indoorEnabled) { this.indoorEnabled = indoorEnabled; } public void setTrafficEnabled(boolean trafficEnabled) { this.trafficEnabled = trafficEnabled; if (googleMap == null) { return; } googleMap.setTrafficEnabled(trafficEnabled); } public void setBuildingsEnabled(boolean buildingsEnabled) { this.buildingsEnabled = buildingsEnabled; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import android.content.Context; import com.google.android.gms.maps.model.CameraPosition; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.StandardMessageCodec; import io.flutter.plugin.platform.PlatformView; import io.flutter.plugin.platform.PlatformViewFactory; import java.util.List; import java.util.Map; public class GoogleMapFactory extends PlatformViewFactory { private final BinaryMessenger binaryMessenger; private final LifecycleProvider lifecycleProvider; private final GoogleMapInitializer googleMapInitializer; GoogleMapFactory( BinaryMessenger binaryMessenger, Context context, LifecycleProvider lifecycleProvider) { super(StandardMessageCodec.INSTANCE); this.binaryMessenger = binaryMessenger; this.lifecycleProvider = lifecycleProvider; this.googleMapInitializer = new GoogleMapInitializer(context, binaryMessenger); } @SuppressWarnings("unchecked") @Override public PlatformView create(Context context, int id, Object args) { Map params = (Map) args; final GoogleMapBuilder builder = new GoogleMapBuilder(); Convert.interpretGoogleMapOptions(params.get("options"), builder); if (params.containsKey("initialCameraPosition")) { CameraPosition position = Convert.toCameraPosition(params.get("initialCameraPosition")); builder.setInitialCameraPosition(position); } if (params.containsKey("markersToAdd")) { builder.setInitialMarkers(params.get("markersToAdd")); } if (params.containsKey("polygonsToAdd")) { builder.setInitialPolygons(params.get("polygonsToAdd")); } if (params.containsKey("polylinesToAdd")) { builder.setInitialPolylines(params.get("polylinesToAdd")); } if (params.containsKey("circlesToAdd")) { builder.setInitialCircles(params.get("circlesToAdd")); } if (params.containsKey("tileOverlaysToAdd")) { builder.setInitialTileOverlays((List>) params.get("tileOverlaysToAdd")); } return builder.build(id, context, binaryMessenger, lifecycleProvider); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapInitializer.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import android.content.Context; import androidx.annotation.VisibleForTesting; import com.google.android.gms.maps.MapsInitializer; import com.google.android.gms.maps.MapsInitializer.Renderer; import com.google.android.gms.maps.OnMapsSdkInitializedCallback; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; /** GoogleMaps initializer used to initialize the Google Maps SDK with preferred settings. */ final class GoogleMapInitializer implements OnMapsSdkInitializedCallback, MethodChannel.MethodCallHandler { private final MethodChannel methodChannel; private final Context context; private static MethodChannel.Result initializationResult; private boolean rendererInitialized = false; GoogleMapInitializer(Context context, BinaryMessenger binaryMessenger) { this.context = context; methodChannel = new MethodChannel(binaryMessenger, "plugins.flutter.dev/google_maps_android_initializer"); methodChannel.setMethodCallHandler(this); } @Override public void onMethodCall(MethodCall call, MethodChannel.Result result) { switch (call.method) { case "initializer#preferRenderer": { String preferredRenderer = (String) call.argument("value"); initializeWithPreferredRenderer(preferredRenderer, result); break; } default: result.notImplemented(); } } /** * Initializes map renderer to with preferred renderer type. Renderer can be initialized only once * per application context. * *

Supported renderer types are "latest", "legacy" and "default". */ private void initializeWithPreferredRenderer( String preferredRenderer, MethodChannel.Result result) { if (rendererInitialized || initializationResult != null) { result.error( "Renderer already initialized", "Renderer initialization called multiple times", null); } else { initializationResult = result; switch (preferredRenderer) { case "latest": initializeWithRendererRequest(Renderer.LATEST); break; case "legacy": initializeWithRendererRequest(Renderer.LEGACY); break; case "default": initializeWithRendererRequest(null); break; default: initializationResult.error( "Invalid renderer type", "Renderer initialization called with invalid renderer type", null); initializationResult = null; } } } /** * Initializes map renderer to with preferred renderer type. * *

This method is visible for testing purposes only and should never be used outside this * class. */ @VisibleForTesting public void initializeWithRendererRequest(MapsInitializer.Renderer renderer) { MapsInitializer.initialize(context, renderer, this); } /** Is called by Google Maps SDK to determine which version of the renderer was initialized. */ @Override public void onMapsSdkInitialized(MapsInitializer.Renderer renderer) { rendererInitialized = true; if (initializationResult != null) { switch (renderer) { case LATEST: initializationResult.success("latest"); break; case LEGACY: initializationResult.success("legacy"); break; default: initializationResult.error( "Unknown renderer type", "Initialized with unknown renderer type", null); } initializationResult = null; } } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapListener.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.GoogleMap; interface GoogleMapListener extends GoogleMap.OnCameraIdleListener, GoogleMap.OnCameraMoveListener, GoogleMap.OnCameraMoveStartedListener, GoogleMap.OnInfoWindowClickListener, GoogleMap.OnMarkerClickListener, GoogleMap.OnPolygonClickListener, GoogleMap.OnPolylineClickListener, GoogleMap.OnCircleClickListener, GoogleMap.OnMapClickListener, GoogleMap.OnMapLongClickListener, GoogleMap.OnMarkerDragListener {} ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.model.LatLngBounds; import java.util.List; import java.util.Map; /** Receiver of GoogleMap configuration options. */ interface GoogleMapOptionsSink { void setCameraTargetBounds(LatLngBounds bounds); void setCompassEnabled(boolean compassEnabled); void setMapToolbarEnabled(boolean setMapToolbarEnabled); void setMapType(int mapType); void setMinMaxZoomPreference(Float min, Float max); void setPadding(float top, float left, float bottom, float right); void setRotateGesturesEnabled(boolean rotateGesturesEnabled); void setScrollGesturesEnabled(boolean scrollGesturesEnabled); void setTiltGesturesEnabled(boolean tiltGesturesEnabled); void setTrackCameraPosition(boolean trackCameraPosition); void setZoomGesturesEnabled(boolean zoomGesturesEnabled); void setLiteModeEnabled(boolean liteModeEnabled); void setMyLocationEnabled(boolean myLocationEnabled); void setZoomControlsEnabled(boolean zoomControlsEnabled); void setMyLocationButtonEnabled(boolean myLocationButtonEnabled); void setIndoorEnabled(boolean indoorEnabled); void setTrafficEnabled(boolean trafficEnabled); void setBuildingsEnabled(boolean buildingsEnabled); void setInitialMarkers(Object initialMarkers); void setInitialPolygons(Object initialPolygons); void setInitialPolylines(Object initialPolylines); void setInitialCircles(Object initialCircles); void setInitialTileOverlays(List> initialTileOverlays); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import android.app.Activity; import android.app.Application.ActivityLifecycleCallbacks; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.Lifecycle.Event; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; 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.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter; /** * Plugin for controlling a set of GoogleMap views to be shown as overlays on top of the Flutter * view. The overlay should be hidden during transformations or while Flutter is rendering on top of * the map. A Texture drawn using GoogleMap bitmap snapshots can then be shown instead of the * overlay. */ public class GoogleMapsPlugin implements FlutterPlugin, ActivityAware { @Nullable private Lifecycle lifecycle; private static final String VIEW_TYPE = "plugins.flutter.dev/google_maps_android"; @SuppressWarnings("deprecation") public static void registerWith( final io.flutter.plugin.common.PluginRegistry.Registrar registrar) { final Activity activity = registrar.activity(); if (activity == null) { // When a background flutter view tries to register the plugin, the registrar has no activity. // We stop the registration process as this plugin is foreground only. return; } if (activity instanceof LifecycleOwner) { registrar .platformViewRegistry() .registerViewFactory( VIEW_TYPE, new GoogleMapFactory( registrar.messenger(), registrar.context(), new LifecycleProvider() { @Override public Lifecycle getLifecycle() { return ((LifecycleOwner) activity).getLifecycle(); } })); } else { registrar .platformViewRegistry() .registerViewFactory( VIEW_TYPE, new GoogleMapFactory( registrar.messenger(), registrar.context(), new ProxyLifecycleProvider(activity))); } } public GoogleMapsPlugin() {} // FlutterPlugin @Override public void onAttachedToEngine(FlutterPluginBinding binding) { binding .getPlatformViewRegistry() .registerViewFactory( VIEW_TYPE, new GoogleMapFactory( binding.getBinaryMessenger(), binding.getApplicationContext(), new LifecycleProvider() { @Nullable @Override public Lifecycle getLifecycle() { return lifecycle; } })); } @Override public void onDetachedFromEngine(FlutterPluginBinding binding) {} // ActivityAware @Override public void onAttachedToActivity(ActivityPluginBinding binding) { lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); } @Override public void onDetachedFromActivity() { lifecycle = null; } @Override public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { onAttachedToActivity(binding); } @Override public void onDetachedFromActivityForConfigChanges() { onDetachedFromActivity(); } /** * This class provides a {@link LifecycleOwner} for the activity driven by {@link * ActivityLifecycleCallbacks}. * *

This is used in the case where a direct Lifecycle/Owner is not available. */ private static final class ProxyLifecycleProvider implements ActivityLifecycleCallbacks, LifecycleOwner, LifecycleProvider { private final LifecycleRegistry lifecycle = new LifecycleRegistry(this); private final int registrarActivityHashCode; private ProxyLifecycleProvider(Activity activity) { this.registrarActivityHashCode = activity.hashCode(); activity.getApplication().registerActivityLifecycleCallbacks(this); } @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { if (activity.hashCode() != registrarActivityHashCode) { return; } lifecycle.handleLifecycleEvent(Event.ON_CREATE); } @Override public void onActivityStarted(Activity activity) { if (activity.hashCode() != registrarActivityHashCode) { return; } lifecycle.handleLifecycleEvent(Event.ON_START); } @Override public void onActivityResumed(Activity activity) { if (activity.hashCode() != registrarActivityHashCode) { return; } lifecycle.handleLifecycleEvent(Event.ON_RESUME); } @Override public void onActivityPaused(Activity activity) { if (activity.hashCode() != registrarActivityHashCode) { return; } lifecycle.handleLifecycleEvent(Event.ON_PAUSE); } @Override public void onActivityStopped(Activity activity) { if (activity.hashCode() != registrarActivityHashCode) { return; } lifecycle.handleLifecycleEvent(Event.ON_STOP); } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} @Override public void onActivityDestroyed(Activity activity) { if (activity.hashCode() != registrarActivityHashCode) { return; } activity.getApplication().unregisterActivityLifecycleCallbacks(this); lifecycle.handleLifecycleEvent(Event.ON_DESTROY); } @NonNull @Override public Lifecycle getLifecycle() { return lifecycle; } } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/LifecycleProvider.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import androidx.annotation.Nullable; import androidx.lifecycle.Lifecycle; interface LifecycleProvider { @Nullable Lifecycle getLifecycle(); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkerBuilder.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.model.BitmapDescriptor; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.MarkerOptions; class MarkerBuilder implements MarkerOptionsSink { private final MarkerOptions markerOptions; private boolean consumeTapEvents; MarkerBuilder() { this.markerOptions = new MarkerOptions(); } MarkerOptions build() { return markerOptions; } boolean consumeTapEvents() { return consumeTapEvents; } @Override public void setAlpha(float alpha) { markerOptions.alpha(alpha); } @Override public void setAnchor(float u, float v) { markerOptions.anchor(u, v); } @Override public void setConsumeTapEvents(boolean consumeTapEvents) { this.consumeTapEvents = consumeTapEvents; } @Override public void setDraggable(boolean draggable) { markerOptions.draggable(draggable); } @Override public void setFlat(boolean flat) { markerOptions.flat(flat); } @Override public void setIcon(BitmapDescriptor bitmapDescriptor) { markerOptions.icon(bitmapDescriptor); } @Override public void setInfoWindowAnchor(float u, float v) { markerOptions.infoWindowAnchor(u, v); } @Override public void setInfoWindowText(String title, String snippet) { markerOptions.title(title); markerOptions.snippet(snippet); } @Override public void setPosition(LatLng position) { markerOptions.position(position); } @Override public void setRotation(float rotation) { markerOptions.rotation(rotation); } @Override public void setVisible(boolean visible) { markerOptions.visible(visible); } @Override public void setZIndex(float zIndex) { markerOptions.zIndex(zIndex); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkerController.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.model.BitmapDescriptor; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.Marker; /** Controller of a single Marker on the map. */ class MarkerController implements MarkerOptionsSink { private final Marker marker; private final String googleMapsMarkerId; private boolean consumeTapEvents; MarkerController(Marker marker, boolean consumeTapEvents) { this.marker = marker; this.consumeTapEvents = consumeTapEvents; this.googleMapsMarkerId = marker.getId(); } void remove() { marker.remove(); } @Override public void setAlpha(float alpha) { marker.setAlpha(alpha); } @Override public void setAnchor(float u, float v) { marker.setAnchor(u, v); } @Override public void setConsumeTapEvents(boolean consumeTapEvents) { this.consumeTapEvents = consumeTapEvents; } @Override public void setDraggable(boolean draggable) { marker.setDraggable(draggable); } @Override public void setFlat(boolean flat) { marker.setFlat(flat); } @Override public void setIcon(BitmapDescriptor bitmapDescriptor) { marker.setIcon(bitmapDescriptor); } @Override public void setInfoWindowAnchor(float u, float v) { marker.setInfoWindowAnchor(u, v); } @Override public void setInfoWindowText(String title, String snippet) { marker.setTitle(title); marker.setSnippet(snippet); } @Override public void setPosition(LatLng position) { marker.setPosition(position); } @Override public void setRotation(float rotation) { marker.setRotation(rotation); } @Override public void setVisible(boolean visible) { marker.setVisible(visible); } @Override public void setZIndex(float zIndex) { marker.setZIndex(zIndex); } String getGoogleMapsMarkerId() { return googleMapsMarkerId; } boolean consumeTapEvents() { return consumeTapEvents; } public void showInfoWindow() { marker.showInfoWindow(); } public void hideInfoWindow() { marker.hideInfoWindow(); } public boolean isInfoWindowShown() { return marker.isInfoWindowShown(); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkerOptionsSink.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.model.BitmapDescriptor; import com.google.android.gms.maps.model.LatLng; /** Receiver of Marker configuration options. */ interface MarkerOptionsSink { void setAlpha(float alpha); void setAnchor(float u, float v); void setConsumeTapEvents(boolean consumeTapEvents); void setDraggable(boolean draggable); void setFlat(boolean flat); void setIcon(BitmapDescriptor bitmapDescriptor); void setInfoWindowAnchor(float u, float v); void setInfoWindowText(String title, String snippet); void setPosition(LatLng position); void setRotation(float rotation); void setVisible(boolean visible); void setZIndex(float zIndex); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkersController.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; import io.flutter.plugin.common.MethodChannel; import java.util.HashMap; import java.util.List; import java.util.Map; class MarkersController { private final Map markerIdToController; private final Map googleMapsMarkerIdToDartMarkerId; private final MethodChannel methodChannel; private GoogleMap googleMap; MarkersController(MethodChannel methodChannel) { this.markerIdToController = new HashMap<>(); this.googleMapsMarkerIdToDartMarkerId = new HashMap<>(); this.methodChannel = methodChannel; } void setGoogleMap(GoogleMap googleMap) { this.googleMap = googleMap; } void addMarkers(List markersToAdd) { if (markersToAdd != null) { for (Object markerToAdd : markersToAdd) { addMarker(markerToAdd); } } } void changeMarkers(List markersToChange) { if (markersToChange != null) { for (Object markerToChange : markersToChange) { changeMarker(markerToChange); } } } void removeMarkers(List markerIdsToRemove) { if (markerIdsToRemove == null) { return; } for (Object rawMarkerId : markerIdsToRemove) { if (rawMarkerId == null) { continue; } String markerId = (String) rawMarkerId; final MarkerController markerController = markerIdToController.remove(markerId); if (markerController != null) { markerController.remove(); googleMapsMarkerIdToDartMarkerId.remove(markerController.getGoogleMapsMarkerId()); } } } void showMarkerInfoWindow(String markerId, MethodChannel.Result result) { MarkerController markerController = markerIdToController.get(markerId); if (markerController != null) { markerController.showInfoWindow(); result.success(null); } else { result.error("Invalid markerId", "showInfoWindow called with invalid markerId", null); } } void hideMarkerInfoWindow(String markerId, MethodChannel.Result result) { MarkerController markerController = markerIdToController.get(markerId); if (markerController != null) { markerController.hideInfoWindow(); result.success(null); } else { result.error("Invalid markerId", "hideInfoWindow called with invalid markerId", null); } } void isInfoWindowShown(String markerId, MethodChannel.Result result) { MarkerController markerController = markerIdToController.get(markerId); if (markerController != null) { result.success(markerController.isInfoWindowShown()); } else { result.error("Invalid markerId", "isInfoWindowShown called with invalid markerId", null); } } boolean onMarkerTap(String googleMarkerId) { String markerId = googleMapsMarkerIdToDartMarkerId.get(googleMarkerId); if (markerId == null) { return false; } methodChannel.invokeMethod("marker#onTap", Convert.markerIdToJson(markerId)); MarkerController markerController = markerIdToController.get(markerId); if (markerController != null) { return markerController.consumeTapEvents(); } return false; } void onMarkerDragStart(String googleMarkerId, LatLng latLng) { String markerId = googleMapsMarkerIdToDartMarkerId.get(googleMarkerId); if (markerId == null) { return; } final Map data = new HashMap<>(); data.put("markerId", markerId); data.put("position", Convert.latLngToJson(latLng)); methodChannel.invokeMethod("marker#onDragStart", data); } void onMarkerDrag(String googleMarkerId, LatLng latLng) { String markerId = googleMapsMarkerIdToDartMarkerId.get(googleMarkerId); if (markerId == null) { return; } final Map data = new HashMap<>(); data.put("markerId", markerId); data.put("position", Convert.latLngToJson(latLng)); methodChannel.invokeMethod("marker#onDrag", data); } void onMarkerDragEnd(String googleMarkerId, LatLng latLng) { String markerId = googleMapsMarkerIdToDartMarkerId.get(googleMarkerId); if (markerId == null) { return; } final Map data = new HashMap<>(); data.put("markerId", markerId); data.put("position", Convert.latLngToJson(latLng)); methodChannel.invokeMethod("marker#onDragEnd", data); } void onInfoWindowTap(String googleMarkerId) { String markerId = googleMapsMarkerIdToDartMarkerId.get(googleMarkerId); if (markerId == null) { return; } methodChannel.invokeMethod("infoWindow#onTap", Convert.markerIdToJson(markerId)); } private void addMarker(Object marker) { if (marker == null) { return; } MarkerBuilder markerBuilder = new MarkerBuilder(); String markerId = Convert.interpretMarkerOptions(marker, markerBuilder); MarkerOptions options = markerBuilder.build(); addMarker(markerId, options, markerBuilder.consumeTapEvents()); } private void addMarker(String markerId, MarkerOptions markerOptions, boolean consumeTapEvents) { final Marker marker = googleMap.addMarker(markerOptions); MarkerController controller = new MarkerController(marker, consumeTapEvents); markerIdToController.put(markerId, controller); googleMapsMarkerIdToDartMarkerId.put(marker.getId(), markerId); } private void changeMarker(Object marker) { if (marker == null) { return; } String markerId = getMarkerId(marker); MarkerController markerController = markerIdToController.get(markerId); if (markerController != null) { Convert.interpretMarkerOptions(marker, markerController); } } @SuppressWarnings("unchecked") private static String getMarkerId(Object marker) { Map markerMap = (Map) marker; return (String) markerMap.get("markerId"); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolygonBuilder.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.PolygonOptions; import java.util.List; class PolygonBuilder implements PolygonOptionsSink { private final PolygonOptions polygonOptions; private final float density; private boolean consumeTapEvents; PolygonBuilder(float density) { this.polygonOptions = new PolygonOptions(); this.density = density; } PolygonOptions build() { return polygonOptions; } boolean consumeTapEvents() { return consumeTapEvents; } @Override public void setFillColor(int color) { polygonOptions.fillColor(color); } @Override public void setStrokeColor(int color) { polygonOptions.strokeColor(color); } @Override public void setPoints(List points) { polygonOptions.addAll(points); } @Override public void setHoles(List> holes) { for (List hole : holes) { polygonOptions.addHole(hole); } } @Override public void setConsumeTapEvents(boolean consumeTapEvents) { this.consumeTapEvents = consumeTapEvents; polygonOptions.clickable(consumeTapEvents); } @Override public void setGeodesic(boolean geodisc) { polygonOptions.geodesic(geodisc); } @Override public void setVisible(boolean visible) { polygonOptions.visible(visible); } @Override public void setStrokeWidth(float width) { polygonOptions.strokeWidth(width * density); } @Override public void setZIndex(float zIndex) { polygonOptions.zIndex(zIndex); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolygonController.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.Polygon; import java.util.List; /** Controller of a single Polygon on the map. */ class PolygonController implements PolygonOptionsSink { private final Polygon polygon; private final String googleMapsPolygonId; private final float density; private boolean consumeTapEvents; PolygonController(Polygon polygon, boolean consumeTapEvents, float density) { this.polygon = polygon; this.density = density; this.consumeTapEvents = consumeTapEvents; this.googleMapsPolygonId = polygon.getId(); } void remove() { polygon.remove(); } @Override public void setConsumeTapEvents(boolean consumeTapEvents) { this.consumeTapEvents = consumeTapEvents; polygon.setClickable(consumeTapEvents); } @Override public void setFillColor(int color) { polygon.setFillColor(color); } @Override public void setStrokeColor(int color) { polygon.setStrokeColor(color); } @Override public void setGeodesic(boolean geodesic) { polygon.setGeodesic(geodesic); } @Override public void setPoints(List points) { polygon.setPoints(points); } public void setHoles(List> holes) { polygon.setHoles(holes); } @Override public void setVisible(boolean visible) { polygon.setVisible(visible); } @Override public void setStrokeWidth(float width) { polygon.setStrokeWidth(width * density); } @Override public void setZIndex(float zIndex) { polygon.setZIndex(zIndex); } String getGoogleMapsPolygonId() { return googleMapsPolygonId; } boolean consumeTapEvents() { return consumeTapEvents; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolygonOptionsSink.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.model.LatLng; import java.util.List; /** Receiver of Polygon configuration options. */ interface PolygonOptionsSink { void setConsumeTapEvents(boolean consumetapEvents); void setFillColor(int color); void setStrokeColor(int color); void setGeodesic(boolean geodesic); void setPoints(List points); void setHoles(List> holes); void setVisible(boolean visible); void setStrokeWidth(float width); void setZIndex(float zIndex); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolygonsController.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.Polygon; import com.google.android.gms.maps.model.PolygonOptions; import io.flutter.plugin.common.MethodChannel; import java.util.HashMap; import java.util.List; import java.util.Map; class PolygonsController { private final Map polygonIdToController; private final Map googleMapsPolygonIdToDartPolygonId; private final MethodChannel methodChannel; private final float density; private GoogleMap googleMap; PolygonsController(MethodChannel methodChannel, float density) { this.polygonIdToController = new HashMap<>(); this.googleMapsPolygonIdToDartPolygonId = new HashMap<>(); this.methodChannel = methodChannel; this.density = density; } void setGoogleMap(GoogleMap googleMap) { this.googleMap = googleMap; } void addPolygons(List polygonsToAdd) { if (polygonsToAdd != null) { for (Object polygonToAdd : polygonsToAdd) { addPolygon(polygonToAdd); } } } void changePolygons(List polygonsToChange) { if (polygonsToChange != null) { for (Object polygonToChange : polygonsToChange) { changePolygon(polygonToChange); } } } void removePolygons(List polygonIdsToRemove) { if (polygonIdsToRemove == null) { return; } for (Object rawPolygonId : polygonIdsToRemove) { if (rawPolygonId == null) { continue; } String polygonId = (String) rawPolygonId; final PolygonController polygonController = polygonIdToController.remove(polygonId); if (polygonController != null) { polygonController.remove(); googleMapsPolygonIdToDartPolygonId.remove(polygonController.getGoogleMapsPolygonId()); } } } boolean onPolygonTap(String googlePolygonId) { String polygonId = googleMapsPolygonIdToDartPolygonId.get(googlePolygonId); if (polygonId == null) { return false; } methodChannel.invokeMethod("polygon#onTap", Convert.polygonIdToJson(polygonId)); PolygonController polygonController = polygonIdToController.get(polygonId); if (polygonController != null) { return polygonController.consumeTapEvents(); } return false; } private void addPolygon(Object polygon) { if (polygon == null) { return; } PolygonBuilder polygonBuilder = new PolygonBuilder(density); String polygonId = Convert.interpretPolygonOptions(polygon, polygonBuilder); PolygonOptions options = polygonBuilder.build(); addPolygon(polygonId, options, polygonBuilder.consumeTapEvents()); } private void addPolygon( String polygonId, PolygonOptions polygonOptions, boolean consumeTapEvents) { final Polygon polygon = googleMap.addPolygon(polygonOptions); PolygonController controller = new PolygonController(polygon, consumeTapEvents, density); polygonIdToController.put(polygonId, controller); googleMapsPolygonIdToDartPolygonId.put(polygon.getId(), polygonId); } private void changePolygon(Object polygon) { if (polygon == null) { return; } String polygonId = getPolygonId(polygon); PolygonController polygonController = polygonIdToController.get(polygonId); if (polygonController != null) { Convert.interpretPolygonOptions(polygon, polygonController); } } @SuppressWarnings("unchecked") private static String getPolygonId(Object polygon) { Map polygonMap = (Map) polygon; return (String) polygonMap.get("polygonId"); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolylineBuilder.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.model.Cap; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.PatternItem; import com.google.android.gms.maps.model.PolylineOptions; import java.util.List; class PolylineBuilder implements PolylineOptionsSink { private final PolylineOptions polylineOptions; private boolean consumeTapEvents; private final float density; PolylineBuilder(float density) { this.polylineOptions = new PolylineOptions(); this.density = density; } PolylineOptions build() { return polylineOptions; } boolean consumeTapEvents() { return consumeTapEvents; } @Override public void setColor(int color) { polylineOptions.color(color); } @Override public void setEndCap(Cap endCap) { polylineOptions.endCap(endCap); } @Override public void setJointType(int jointType) { polylineOptions.jointType(jointType); } @Override public void setPattern(List pattern) { polylineOptions.pattern(pattern); } @Override public void setPoints(List points) { polylineOptions.addAll(points); } @Override public void setConsumeTapEvents(boolean consumeTapEvents) { this.consumeTapEvents = consumeTapEvents; polylineOptions.clickable(consumeTapEvents); } @Override public void setGeodesic(boolean geodisc) { polylineOptions.geodesic(geodisc); } @Override public void setStartCap(Cap startCap) { polylineOptions.startCap(startCap); } @Override public void setVisible(boolean visible) { polylineOptions.visible(visible); } @Override public void setWidth(float width) { polylineOptions.width(width * density); } @Override public void setZIndex(float zIndex) { polylineOptions.zIndex(zIndex); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolylineController.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.model.Cap; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.PatternItem; import com.google.android.gms.maps.model.Polyline; import java.util.List; /** Controller of a single Polyline on the map. */ class PolylineController implements PolylineOptionsSink { private final Polyline polyline; private final String googleMapsPolylineId; private boolean consumeTapEvents; private final float density; PolylineController(Polyline polyline, boolean consumeTapEvents, float density) { this.polyline = polyline; this.consumeTapEvents = consumeTapEvents; this.density = density; this.googleMapsPolylineId = polyline.getId(); } void remove() { polyline.remove(); } @Override public void setConsumeTapEvents(boolean consumeTapEvents) { this.consumeTapEvents = consumeTapEvents; polyline.setClickable(consumeTapEvents); } @Override public void setColor(int color) { polyline.setColor(color); } @Override public void setEndCap(Cap endCap) { polyline.setEndCap(endCap); } @Override public void setGeodesic(boolean geodesic) { polyline.setGeodesic(geodesic); } @Override public void setJointType(int jointType) { polyline.setJointType(jointType); } @Override public void setPattern(List pattern) { polyline.setPattern(pattern); } @Override public void setPoints(List points) { polyline.setPoints(points); } @Override public void setStartCap(Cap startCap) { polyline.setStartCap(startCap); } @Override public void setVisible(boolean visible) { polyline.setVisible(visible); } @Override public void setWidth(float width) { polyline.setWidth(width * density); } @Override public void setZIndex(float zIndex) { polyline.setZIndex(zIndex); } String getGoogleMapsPolylineId() { return googleMapsPolylineId; } boolean consumeTapEvents() { return consumeTapEvents; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolylineOptionsSink.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.model.Cap; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.PatternItem; import java.util.List; /** Receiver of Polyline configuration options. */ interface PolylineOptionsSink { void setConsumeTapEvents(boolean consumetapEvents); void setColor(int color); void setEndCap(Cap endCap); void setGeodesic(boolean geodesic); void setJointType(int jointType); void setPattern(List pattern); void setPoints(List points); void setStartCap(Cap startCap); void setVisible(boolean visible); void setWidth(float width); void setZIndex(float zIndex); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolylinesController.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.Polyline; import com.google.android.gms.maps.model.PolylineOptions; import io.flutter.plugin.common.MethodChannel; import java.util.HashMap; import java.util.List; import java.util.Map; class PolylinesController { private final Map polylineIdToController; private final Map googleMapsPolylineIdToDartPolylineId; private final MethodChannel methodChannel; private GoogleMap googleMap; private final float density; PolylinesController(MethodChannel methodChannel, float density) { this.polylineIdToController = new HashMap<>(); this.googleMapsPolylineIdToDartPolylineId = new HashMap<>(); this.methodChannel = methodChannel; this.density = density; } void setGoogleMap(GoogleMap googleMap) { this.googleMap = googleMap; } void addPolylines(List polylinesToAdd) { if (polylinesToAdd != null) { for (Object polylineToAdd : polylinesToAdd) { addPolyline(polylineToAdd); } } } void changePolylines(List polylinesToChange) { if (polylinesToChange != null) { for (Object polylineToChange : polylinesToChange) { changePolyline(polylineToChange); } } } void removePolylines(List polylineIdsToRemove) { if (polylineIdsToRemove == null) { return; } for (Object rawPolylineId : polylineIdsToRemove) { if (rawPolylineId == null) { continue; } String polylineId = (String) rawPolylineId; final PolylineController polylineController = polylineIdToController.remove(polylineId); if (polylineController != null) { polylineController.remove(); googleMapsPolylineIdToDartPolylineId.remove(polylineController.getGoogleMapsPolylineId()); } } } boolean onPolylineTap(String googlePolylineId) { String polylineId = googleMapsPolylineIdToDartPolylineId.get(googlePolylineId); if (polylineId == null) { return false; } methodChannel.invokeMethod("polyline#onTap", Convert.polylineIdToJson(polylineId)); PolylineController polylineController = polylineIdToController.get(polylineId); if (polylineController != null) { return polylineController.consumeTapEvents(); } return false; } private void addPolyline(Object polyline) { if (polyline == null) { return; } PolylineBuilder polylineBuilder = new PolylineBuilder(density); String polylineId = Convert.interpretPolylineOptions(polyline, polylineBuilder); PolylineOptions options = polylineBuilder.build(); addPolyline(polylineId, options, polylineBuilder.consumeTapEvents()); } private void addPolyline( String polylineId, PolylineOptions polylineOptions, boolean consumeTapEvents) { final Polyline polyline = googleMap.addPolyline(polylineOptions); PolylineController controller = new PolylineController(polyline, consumeTapEvents, density); polylineIdToController.put(polylineId, controller); googleMapsPolylineIdToDartPolylineId.put(polyline.getId(), polylineId); } private void changePolyline(Object polyline) { if (polyline == null) { return; } String polylineId = getPolylineId(polyline); PolylineController polylineController = polylineIdToController.get(polylineId); if (polylineController != null) { Convert.interpretPolylineOptions(polyline, polylineController); } } @SuppressWarnings("unchecked") private static String getPolylineId(Object polyline) { Map polylineMap = (Map) polyline; return (String) polylineMap.get("polylineId"); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayBuilder.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.model.TileOverlayOptions; import com.google.android.gms.maps.model.TileProvider; class TileOverlayBuilder implements TileOverlaySink { private final TileOverlayOptions tileOverlayOptions; TileOverlayBuilder() { this.tileOverlayOptions = new TileOverlayOptions(); } TileOverlayOptions build() { return tileOverlayOptions; } @Override public void setFadeIn(boolean fadeIn) { tileOverlayOptions.fadeIn(fadeIn); } @Override public void setTransparency(float transparency) { tileOverlayOptions.transparency(transparency); } @Override public void setZIndex(float zIndex) { tileOverlayOptions.zIndex(zIndex); } @Override public void setVisible(boolean visible) { tileOverlayOptions.visible(visible); } @Override public void setTileProvider(TileProvider tileProvider) { tileOverlayOptions.tileProvider(tileProvider); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.model.TileOverlay; import com.google.android.gms.maps.model.TileProvider; import java.util.HashMap; import java.util.Map; class TileOverlayController implements TileOverlaySink { private final TileOverlay tileOverlay; TileOverlayController(TileOverlay tileOverlay) { this.tileOverlay = tileOverlay; } void remove() { tileOverlay.remove(); } void clearTileCache() { tileOverlay.clearTileCache(); } Map getTileOverlayInfo() { Map tileOverlayInfo = new HashMap<>(); tileOverlayInfo.put("fadeIn", tileOverlay.getFadeIn()); tileOverlayInfo.put("transparency", tileOverlay.getTransparency()); tileOverlayInfo.put("id", tileOverlay.getId()); tileOverlayInfo.put("zIndex", tileOverlay.getZIndex()); tileOverlayInfo.put("visible", tileOverlay.isVisible()); return tileOverlayInfo; } @Override public void setFadeIn(boolean fadeIn) { tileOverlay.setFadeIn(fadeIn); } @Override public void setTransparency(float transparency) { tileOverlay.setTransparency(transparency); } @Override public void setZIndex(float zIndex) { tileOverlay.setZIndex(zIndex); } @Override public void setVisible(boolean visible) { tileOverlay.setVisible(visible); } @Override public void setTileProvider(TileProvider tileProvider) { // You can not change tile provider after creation } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaySink.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.model.TileProvider; /** Receiver of TileOverlayOptions configuration. */ interface TileOverlaySink { void setFadeIn(boolean fadeIn); void setTransparency(float transparency); void setZIndex(float zIndex); void setVisible(boolean visible); void setTileProvider(TileProvider tileProvider); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.TileOverlay; import com.google.android.gms.maps.model.TileOverlayOptions; import io.flutter.plugin.common.MethodChannel; import java.util.HashMap; import java.util.List; import java.util.Map; class TileOverlaysController { private final Map tileOverlayIdToController; private final MethodChannel methodChannel; private GoogleMap googleMap; TileOverlaysController(MethodChannel methodChannel) { this.tileOverlayIdToController = new HashMap<>(); this.methodChannel = methodChannel; } void setGoogleMap(GoogleMap googleMap) { this.googleMap = googleMap; } void addTileOverlays(List> tileOverlaysToAdd) { if (tileOverlaysToAdd == null) { return; } for (Map tileOverlayToAdd : tileOverlaysToAdd) { addTileOverlay(tileOverlayToAdd); } } void changeTileOverlays(List> tileOverlaysToChange) { if (tileOverlaysToChange == null) { return; } for (Map tileOverlayToChange : tileOverlaysToChange) { changeTileOverlay(tileOverlayToChange); } } void removeTileOverlays(List tileOverlayIdsToRemove) { if (tileOverlayIdsToRemove == null) { return; } for (String tileOverlayId : tileOverlayIdsToRemove) { if (tileOverlayId == null) { continue; } removeTileOverlay(tileOverlayId); } } void clearTileCache(String tileOverlayId) { if (tileOverlayId == null) { return; } TileOverlayController tileOverlayController = tileOverlayIdToController.get(tileOverlayId); if (tileOverlayController != null) { tileOverlayController.clearTileCache(); } } Map getTileOverlayInfo(String tileOverlayId) { if (tileOverlayId == null) { return null; } TileOverlayController tileOverlayController = tileOverlayIdToController.get(tileOverlayId); if (tileOverlayController == null) { return null; } return tileOverlayController.getTileOverlayInfo(); } private void addTileOverlay(Map tileOverlayOptions) { if (tileOverlayOptions == null) { return; } TileOverlayBuilder tileOverlayOptionsBuilder = new TileOverlayBuilder(); String tileOverlayId = Convert.interpretTileOverlayOptions(tileOverlayOptions, tileOverlayOptionsBuilder); TileProviderController tileProviderController = new TileProviderController(methodChannel, tileOverlayId); tileOverlayOptionsBuilder.setTileProvider(tileProviderController); TileOverlayOptions options = tileOverlayOptionsBuilder.build(); TileOverlay tileOverlay = googleMap.addTileOverlay(options); TileOverlayController tileOverlayController = new TileOverlayController(tileOverlay); tileOverlayIdToController.put(tileOverlayId, tileOverlayController); } private void changeTileOverlay(Map tileOverlayOptions) { if (tileOverlayOptions == null) { return; } String tileOverlayId = getTileOverlayId(tileOverlayOptions); TileOverlayController tileOverlayController = tileOverlayIdToController.get(tileOverlayId); if (tileOverlayController != null) { Convert.interpretTileOverlayOptions(tileOverlayOptions, tileOverlayController); } } private void removeTileOverlay(String tileOverlayId) { TileOverlayController tileOverlayController = tileOverlayIdToController.get(tileOverlayId); if (tileOverlayController != null) { tileOverlayController.remove(); tileOverlayIdToController.remove(tileOverlayId); } } @SuppressWarnings("unchecked") private static String getTileOverlayId(Map tileOverlay) { return (String) tileOverlay.get("tileOverlayId"); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileProviderController.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import android.os.Handler; import android.os.Looper; import android.util.Log; import androidx.annotation.NonNull; import com.google.android.gms.maps.model.Tile; import com.google.android.gms.maps.model.TileProvider; import io.flutter.plugin.common.MethodChannel; import java.util.Map; import java.util.concurrent.CountDownLatch; class TileProviderController implements TileProvider { private static final String TAG = "TileProviderController"; private final String tileOverlayId; private final MethodChannel methodChannel; private final Handler handler = new Handler(Looper.getMainLooper()); TileProviderController(MethodChannel methodChannel, String tileOverlayId) { this.tileOverlayId = tileOverlayId; this.methodChannel = methodChannel; } @Override public Tile getTile(final int x, final int y, final int zoom) { Worker worker = new Worker(x, y, zoom); return worker.getTile(); } private final class Worker implements MethodChannel.Result { private final CountDownLatch countDownLatch = new CountDownLatch(1); private final int x; private final int y; private final int zoom; private Map result; Worker(int x, int y, int zoom) { this.x = x; this.y = y; this.zoom = zoom; } @NonNull Tile getTile() { handler.post( () -> methodChannel.invokeMethod( "tileOverlay#getTile", Convert.tileOverlayArgumentsToJson(tileOverlayId, x, y, zoom), this)); try { // Because `methodChannel.invokeMethod` is async, we use a `countDownLatch` make it synchronized. countDownLatch.await(); } catch (InterruptedException e) { Log.e( TAG, String.format("countDownLatch: can't get tile: x = %d, y= %d, zoom = %d", x, y, zoom), e); return TileProvider.NO_TILE; } try { return Convert.interpretTile(result); } catch (Exception e) { Log.e(TAG, "Can't parse tile data", e); return TileProvider.NO_TILE; } } @Override @SuppressWarnings("unchecked") public void success(Object data) { result = (Map) data; countDownLatch.countDown(); } @Override public void error(String errorCode, String errorMessage, Object data) { Log.e( TAG, String.format( "Can't get tile: errorCode = %s, errorMessage = %s, date = %s", errorCode, errorCode, data)); result = null; countDownLatch.countDown(); } @Override public void notImplemented() { Log.e(TAG, "Can't get tile: notImplemented"); result = null; countDownLatch.countDown(); } } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/CircleBuilderTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import static junit.framework.TestCase.assertEquals; import com.google.android.gms.maps.model.CircleOptions; import org.junit.Test; public class CircleBuilderTest { @Test public void density_AppliesToStrokeWidth() { final float density = 5; final float strokeWidth = 3; final CircleBuilder builder = new CircleBuilder(density); builder.setStrokeWidth(strokeWidth); final CircleOptions options = builder.build(); final float width = options.getStrokeWidth(); assertEquals(density * strokeWidth, width); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/CircleControllerTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import com.google.android.gms.internal.maps.zzl; import com.google.android.gms.maps.model.Circle; import org.junit.Test; import org.mockito.Mockito; public class CircleControllerTest { @Test public void controller_SetsStrokeDensity() { final zzl z = mock(zzl.class); final Circle circle = spy(new Circle(z)); final float density = 5; final float strokeWidth = 3; final CircleController controller = new CircleController(circle, false, density); controller.setStrokeWidth(strokeWidth); Mockito.verify(circle).setStrokeWidth(density * strokeWidth); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/ConvertTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import com.google.android.gms.maps.model.LatLng; import java.util.ArrayList; import java.util.List; import org.junit.Assert; import org.junit.Test; public class ConvertTest { @Test public void ConvertToPointsConvertsThePointsWithFullPrecision() { double latitude = 43.03725568057; double longitude = -87.90466904649; ArrayList point = new ArrayList(); point.add(latitude); point.add(longitude); ArrayList> pointsList = new ArrayList<>(); pointsList.add(point); List latLngs = Convert.toPoints(pointsList); LatLng latLng = latLngs.get(0); Assert.assertEquals(latitude, latLng.latitude, 1e-15); Assert.assertEquals(longitude, latLng.longitude, 1e-15); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.Context; import android.os.Build; import androidx.activity.ComponentActivity; import androidx.test.core.app.ApplicationProvider; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.MapView; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import java.util.HashMap; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) @Config(sdk = Build.VERSION_CODES.P) public class GoogleMapControllerTest { private Context context; private ComponentActivity activity; private GoogleMapController googleMapController; @Mock BinaryMessenger mockMessenger; @Mock GoogleMap mockGoogleMap; @Before public void before() { MockitoAnnotations.initMocks(this); context = ApplicationProvider.getApplicationContext(); activity = Robolectric.setupActivity(ComponentActivity.class); googleMapController = new GoogleMapController(0, context, mockMessenger, activity::getLifecycle, null); googleMapController.init(); } @Test public void DisposeReleaseTheMap() throws InterruptedException { googleMapController.onMapReady(mockGoogleMap); assertTrue(googleMapController != null); googleMapController.dispose(); assertNull(googleMapController.getView()); } @Test public void OnDestroyReleaseTheMap() throws InterruptedException { googleMapController.onMapReady(mockGoogleMap); assertTrue(googleMapController != null); googleMapController.onDestroy(activity); assertNull(googleMapController.getView()); } @Test public void InvalidateMapAfterMethodCalls() throws InterruptedException { String[] methodsThatTriggerInvalidation = { "markers#update", "polygons#update", "polylines#update", "circles#update", "map#setStyle", "tileOverlays#update", "tileOverlays#clearTileCache" }; for (String methodName : methodsThatTriggerInvalidation) { googleMapController = new GoogleMapController(0, context, mockMessenger, activity::getLifecycle, null); googleMapController.init(); mockGoogleMap = mock(GoogleMap.class); googleMapController.onMapReady(mockGoogleMap); MethodChannel.Result result = mock(MethodChannel.Result.class); System.out.println(methodName); googleMapController.onMethodCall( new MethodCall(methodName, new HashMap()), result); ArgumentCaptor argument = ArgumentCaptor.forClass(GoogleMap.OnMapLoadedCallback.class); verify(mockGoogleMap).setOnMapLoadedCallback(argument.capture()); MapView mapView = mock(MapView.class); googleMapController.setView(mapView); verify(mapView, never()).invalidate(); argument.getValue().onMapLoaded(); verify(mapView).invalidate(); } } @Test public void InvalidateMapOnceAfterMethodCall() throws InterruptedException { googleMapController.onMapReady(mockGoogleMap); MethodChannel.Result result = mock(MethodChannel.Result.class); googleMapController.onMethodCall( new MethodCall("markers#update", new HashMap()), result); googleMapController.onMethodCall( new MethodCall("polygons#update", new HashMap()), result); ArgumentCaptor argument = ArgumentCaptor.forClass(GoogleMap.OnMapLoadedCallback.class); verify(mockGoogleMap).setOnMapLoadedCallback(argument.capture()); MapView mapView = mock(MapView.class); googleMapController.setView(mapView); verify(mapView, never()).invalidate(); argument.getValue().onMapLoaded(); verify(mapView).invalidate(); } @Test public void MethodCalledAfterControllerIsDestroyed() throws InterruptedException { googleMapController.onMapReady(mockGoogleMap); MethodChannel.Result result = mock(MethodChannel.Result.class); googleMapController.onMethodCall( new MethodCall("markers#update", new HashMap()), result); ArgumentCaptor argument = ArgumentCaptor.forClass(GoogleMap.OnMapLoadedCallback.class); verify(mockGoogleMap).setOnMapLoadedCallback(argument.capture()); MapView mapView = mock(MapView.class); googleMapController.setView(mapView); googleMapController.onDestroy(activity); argument.getValue().onMapLoaded(); verify(mapView, never()).invalidate(); } @Test public void OnMapReadySetsPaddingIfInitialPaddingIsThere() { float padding = 10f; int paddingWithDensity = (int) (padding * googleMapController.density); googleMapController.setInitialPadding(padding, padding, padding, padding); googleMapController.onMapReady(mockGoogleMap); verify(mockGoogleMap, times(1)) .setPadding(paddingWithDensity, paddingWithDensity, paddingWithDensity, paddingWithDensity); } @Test public void SetPaddingStoresThePaddingValuesInInInitialPaddingWhenGoogleMapIsNull() { assertNull(googleMapController.initialPadding); googleMapController.setPadding(0f, 0f, 0f, 0f); assertNotNull(googleMapController.initialPadding); Assert.assertEquals(4, googleMapController.initialPadding.size()); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapInitializerTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.Context; import android.os.Build; import androidx.test.core.app.ApplicationProvider; import com.google.android.gms.maps.MapsInitializer.Renderer; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import java.util.HashMap; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) @Config(sdk = Build.VERSION_CODES.P) public class GoogleMapInitializerTest { private GoogleMapInitializer googleMapInitializer; @Mock BinaryMessenger mockMessenger; @Before public void before() { MockitoAnnotations.openMocks(this); Context context = ApplicationProvider.getApplicationContext(); googleMapInitializer = spy(new GoogleMapInitializer(context, mockMessenger)); } @Test public void initializer_OnMapsSdkInitializedWithLatestRenderer() { doNothing().when(googleMapInitializer).initializeWithRendererRequest(Renderer.LATEST); MethodChannel.Result result = mock(MethodChannel.Result.class); googleMapInitializer.onMethodCall( new MethodCall( "initializer#preferRenderer", new HashMap() { { put("value", "latest"); } }), result); googleMapInitializer.onMapsSdkInitialized(Renderer.LATEST); verify(result, times(1)).success("latest"); verify(result, never()).error(any(), any(), any()); } @Test public void initializer_OnMapsSdkInitializedWithLegacyRenderer() { doNothing().when(googleMapInitializer).initializeWithRendererRequest(Renderer.LEGACY); MethodChannel.Result result = mock(MethodChannel.Result.class); googleMapInitializer.onMethodCall( new MethodCall( "initializer#preferRenderer", new HashMap() { { put("value", "legacy"); } }), result); googleMapInitializer.onMapsSdkInitialized(Renderer.LEGACY); verify(result, times(1)).success("legacy"); verify(result, never()).error(any(), any(), any()); } @Test public void initializer_onMethodCallWithUnknownRenderer() { doNothing().when(googleMapInitializer).initializeWithRendererRequest(Renderer.LEGACY); MethodChannel.Result result = mock(MethodChannel.Result.class); googleMapInitializer.onMethodCall( new MethodCall( "initializer#preferRenderer", new HashMap() { { put("value", "wrong_renderer"); } }), result); verify(result, never()).success(any()); verify(result, times(1)).error(eq("Invalid renderer type"), any(), any()); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/MarkersControllerTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodCodec; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.junit.Test; import org.mockito.Mockito; public class MarkersControllerTest { @Test public void controller_OnMarkerDragStart() { final MethodChannel methodChannel = spy(new MethodChannel(mock(BinaryMessenger.class), "no-name", mock(MethodCodec.class))); final MarkersController controller = new MarkersController(methodChannel); final GoogleMap googleMap = mock(GoogleMap.class); controller.setGoogleMap(googleMap); final Marker marker = mock(Marker.class); final String googleMarkerId = "abc123"; when(marker.getId()).thenReturn(googleMarkerId); when(googleMap.addMarker(any(MarkerOptions.class))).thenReturn(marker); final LatLng latLng = new LatLng(1.1, 2.2); final Map markerOptions = new HashMap(); markerOptions.put("markerId", googleMarkerId); final List markers = Arrays.asList(markerOptions); controller.addMarkers(markers); controller.onMarkerDragStart(googleMarkerId, latLng); final List points = new ArrayList(); points.add(latLng.latitude); points.add(latLng.longitude); final Map data = new HashMap<>(); data.put("markerId", googleMarkerId); data.put("position", points); Mockito.verify(methodChannel).invokeMethod("marker#onDragStart", data); } @Test public void controller_OnMarkerDragEnd() { final MethodChannel methodChannel = spy(new MethodChannel(mock(BinaryMessenger.class), "no-name", mock(MethodCodec.class))); final MarkersController controller = new MarkersController(methodChannel); final GoogleMap googleMap = mock(GoogleMap.class); controller.setGoogleMap(googleMap); final Marker marker = mock(Marker.class); final String googleMarkerId = "abc123"; when(marker.getId()).thenReturn(googleMarkerId); when(googleMap.addMarker(any(MarkerOptions.class))).thenReturn(marker); final LatLng latLng = new LatLng(1.1, 2.2); final Map markerOptions = new HashMap(); markerOptions.put("markerId", googleMarkerId); final List markers = Arrays.asList(markerOptions); controller.addMarkers(markers); controller.onMarkerDragEnd(googleMarkerId, latLng); final List points = new ArrayList(); points.add(latLng.latitude); points.add(latLng.longitude); final Map data = new HashMap<>(); data.put("markerId", googleMarkerId); data.put("position", points); Mockito.verify(methodChannel).invokeMethod("marker#onDragEnd", data); } @Test public void controller_OnMarkerDrag() { final MethodChannel methodChannel = spy(new MethodChannel(mock(BinaryMessenger.class), "no-name", mock(MethodCodec.class))); final MarkersController controller = new MarkersController(methodChannel); final GoogleMap googleMap = mock(GoogleMap.class); controller.setGoogleMap(googleMap); final Marker marker = mock(Marker.class); final String googleMarkerId = "abc123"; when(marker.getId()).thenReturn(googleMarkerId); when(googleMap.addMarker(any(MarkerOptions.class))).thenReturn(marker); final LatLng latLng = new LatLng(1.1, 2.2); final Map markerOptions = new HashMap(); markerOptions.put("markerId", googleMarkerId); final List markers = Arrays.asList(markerOptions); controller.addMarkers(markers); controller.onMarkerDrag(googleMarkerId, latLng); final List points = new ArrayList(); points.add(latLng.latitude); points.add(latLng.longitude); final Map data = new HashMap<>(); data.put("markerId", googleMarkerId); data.put("position", points); Mockito.verify(methodChannel).invokeMethod("marker#onDrag", data); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolygonBuilderTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import static junit.framework.TestCase.assertEquals; import com.google.android.gms.maps.model.PolygonOptions; import org.junit.Test; public class PolygonBuilderTest { @Test public void density_AppliesToStrokeWidth() { final float density = 5; final float strokeWidth = 3; final PolygonBuilder builder = new PolygonBuilder(density); builder.setStrokeWidth(strokeWidth); final PolygonOptions options = builder.build(); final float width = options.getStrokeWidth(); assertEquals(density * strokeWidth, width); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolygonControllerTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import com.google.android.gms.internal.maps.zzad; import com.google.android.gms.maps.model.Polygon; import org.junit.Test; import org.mockito.Mockito; public class PolygonControllerTest { @Test public void controller_SetsStrokeDensity() { final zzad z = mock(zzad.class); final Polygon polygon = spy(new Polygon(z)); final float density = 5; final float strokeWidth = 3; final PolygonController controller = new PolygonController(polygon, false, density); controller.setStrokeWidth(strokeWidth); Mockito.verify(polygon).setStrokeWidth(density * strokeWidth); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolylineBuilderTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import static junit.framework.TestCase.assertEquals; import com.google.android.gms.maps.model.PolylineOptions; import org.junit.Test; public class PolylineBuilderTest { @Test public void density_AppliesToStrokeWidth() { final float density = 5; final float strokeWidth = 3; final PolylineBuilder builder = new PolylineBuilder(density); builder.setWidth(strokeWidth); final PolylineOptions options = builder.build(); final float width = options.getWidth(); assertEquals(density * strokeWidth, width); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolylineControllerTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import com.google.android.gms.internal.maps.zzag; import com.google.android.gms.maps.model.Polyline; import org.junit.Test; import org.mockito.Mockito; public class PolylineControllerTest { @Test public void controller_SetsStrokeDensity() { final zzag z = mock(zzag.class); final Polyline polyline = spy(new Polyline(z)); final float density = 5; final float strokeWidth = 3; final PolylineController controller = new PolylineController(polyline, false, density); controller.setWidth(strokeWidth); Mockito.verify(polyline).setWidth(density * strokeWidth); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/android/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker ================================================ mock-maker-inline ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/.metadata ================================================ # 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: 3ea4d06340a97a1e9d7cae97567c64e0569dcaa2 channel: beta ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/README.md ================================================ # 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. ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 lintOptions { disable 'InvalidPackage' } defaultConfig { applicationId "io.flutter.plugins.googlemapsexample" minSdkVersion 20 targetSdkVersion 28 multiDexEnabled true versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } defaultConfig { manifestPlaceholders = [mapsApiKey: "$System.env.MAPS_API_KEY"] } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } testOptions { unitTests { includeAndroidResources = true } } dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' api 'androidx.test:core:1.2.0' testImplementation 'com.google.android.gms:play-services-maps:17.0.0' } } flutter { source '../..' } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/googlemapsexample/GoogleMapsTest.java ================================================ // Copyright 2013 The Flutter 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.googlemapsexample; import static org.junit.Assert.assertTrue; import androidx.test.core.app.ActivityScenario; import io.flutter.plugins.googlemaps.GoogleMapsPlugin; import org.junit.Test; public class GoogleMapsTest { @Test public void googleMapsPluginIsAdded() { final ActivityScenario scenario = ActivityScenario.launch(GoogleMapsTestActivity.class); scenario.onActivity( activity -> { assertTrue(activity.engine.getPlugins().has(GoogleMapsPlugin.class)); }); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/googlemapsexample/MainActivityTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.googlemaps; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class MainActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/debug/java/io/flutter/plugins/googlemapsexample/GoogleMapsTestActivity.java ================================================ // Copyright 2013 The Flutter 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.googlemapsexample; import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; // Makes the FlutterEngine accessible for testing. public class GoogleMapsTestActivity extends FlutterActivity { public FlutterEngine engine; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); engine = flutterEngine; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/assets/night_mode.json ================================================ [ { "elementType": "geometry", "stylers": [ { "color": "#242f3e" } ] }, { "elementType": "labels.text.fill", "stylers": [ { "color": "#746855" } ] }, { "elementType": "labels.text.stroke", "stylers": [ { "color": "#242f3e" } ] }, { "featureType": "administrative.locality", "elementType": "labels.text.fill", "stylers": [ { "color": "#d59563" } ] }, { "featureType": "poi", "elementType": "labels.text.fill", "stylers": [ { "color": "#d59563" } ] }, { "featureType": "poi.park", "elementType": "geometry", "stylers": [ { "color": "#263c3f" } ] }, { "featureType": "poi.park", "elementType": "labels.text.fill", "stylers": [ { "color": "#6b9a76" } ] }, { "featureType": "road", "elementType": "geometry", "stylers": [ { "color": "#38414e" } ] }, { "featureType": "road", "elementType": "geometry.stroke", "stylers": [ { "color": "#212a37" } ] }, { "featureType": "road", "elementType": "labels.text.fill", "stylers": [ { "color": "#9ca5b3" } ] }, { "featureType": "road.highway", "elementType": "geometry", "stylers": [ { "color": "#746855" } ] }, { "featureType": "road.highway", "elementType": "geometry.stroke", "stylers": [ { "color": "#1f2835" } ] }, { "featureType": "road.highway", "elementType": "labels.text.fill", "stylers": [ { "color": "#f3d19c" } ] }, { "featureType": "transit", "elementType": "geometry", "stylers": [ { "color": "#2f3948" } ] }, { "featureType": "transit.station", "elementType": "labels.text.fill", "stylers": [ { "color": "#d59563" } ] }, { "featureType": "water", "elementType": "geometry", "stylers": [ { "color": "#17263c" } ] }, { "featureType": "water", "elementType": "labels.text.fill", "stylers": [ { "color": "#515c6d" } ] }, { "featureType": "water", "elementType": "labels.text.stroke", "stylers": [ { "color": "#17263c" } ] } ] ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/build.excerpt.yaml ================================================ targets: $default: sources: include: - lib/** # Some default includes that aren't really used here but will prevent # false-negative warnings: - $package$ - lib/$lib$ exclude: - '**/.*/**' - '**/build/**' builders: code_excerpter|code_excerpter: enabled: true ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_tests.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_android/google_maps_flutter_android.dart'; import 'package:google_maps_flutter_example/example_google_map.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; const LatLng _kInitialMapCenter = LatLng(0, 0); const double _kInitialZoomLevel = 5; const CameraPosition _kInitialCameraPosition = CameraPosition(target: _kInitialMapCenter, zoom: _kInitialZoomLevel); void googleMapsTests() { GoogleMapsFlutterPlatform.instance.enableDebugInspection(); // Repeatedly checks an asynchronous value against a test condition, waiting // on frame between each check, returing the value if it passes the predicate // before [maxTries] is reached. // // Returns null if the predicate is never satisfied. // // This is useful for cases where the Maps SDK has some internally // asynchronous operation that we don't have visibility into (e.g., native UI // animations). Future waitForValueMatchingPredicate(WidgetTester tester, Future Function() getValue, bool Function(T) predicate, {int maxTries = 100}) async { for (int i = 0; i < maxTries; i++) { final T value = await getValue(); if (predicate(value)) { return value; } await tester.pump(); } return null; } testWidgets('uses surface view', (WidgetTester tester) async { final GoogleMapsFlutterAndroid instance = GoogleMapsFlutterPlatform.instance as GoogleMapsFlutterAndroid; final bool previousUseAndroidViewSurfaceValue = instance.useAndroidViewSurface; instance.useAndroidViewSurface = true; final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, compassEnabled: false, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); await mapIdCompleter.future; // Wait for the placeholder to be replaced by the actual view. while (!tester.any(find.byType(AndroidViewSurface)) && !tester.any(find.byType(AndroidView))) { await tester.pump(); } instance.useAndroidViewSurface = previousUseAndroidViewSurfaceValue; expect(tester.any(find.byType(AndroidViewSurface)), true); }); testWidgets('testCompassToggle', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, compassEnabled: false, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool compassEnabled = await inspector.isCompassEnabled(mapId: mapId); expect(compassEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); compassEnabled = await inspector.isCompassEnabled(mapId: mapId); expect(compassEnabled, true); }); testWidgets('testMapToolbarToggle', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, mapToolbarEnabled: false, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool mapToolbarEnabled = await inspector.isMapToolbarEnabled(mapId: mapId); expect(mapToolbarEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); mapToolbarEnabled = await inspector.isMapToolbarEnabled(mapId: mapId); expect(mapToolbarEnabled, true); }); testWidgets('updateMinMaxZoomLevels', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); const MinMaxZoomPreference initialZoomLevel = MinMaxZoomPreference(4, 8); const MinMaxZoomPreference finalZoomLevel = MinMaxZoomPreference(6, 10); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, minMaxZoomPreference: initialZoomLevel, onMapCreated: (ExampleGoogleMapController c) async { controllerCompleter.complete(c); }, ), )); final ExampleGoogleMapController controller = await controllerCompleter.future; // On Android, zooming with zoomTo is constrained by the min/max. await controller.moveCamera(CameraUpdate.zoomTo(15)); await tester.pumpAndSettle(); double? zoomLevel = await controller.getZoomLevel(); expect(zoomLevel, equals(initialZoomLevel.maxZoom)); await controller.moveCamera(CameraUpdate.zoomTo(1)); await tester.pumpAndSettle(); zoomLevel = await controller.getZoomLevel(); expect(zoomLevel, equals(initialZoomLevel.minZoom)); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, minMaxZoomPreference: finalZoomLevel, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); await controller.moveCamera(CameraUpdate.zoomTo(15)); await tester.pumpAndSettle(); zoomLevel = await controller.getZoomLevel(); expect(zoomLevel, equals(finalZoomLevel.maxZoom)); await controller.moveCamera(CameraUpdate.zoomTo(1)); await tester.pumpAndSettle(); zoomLevel = await controller.getZoomLevel(); expect(zoomLevel, equals(finalZoomLevel.minZoom)); }); testWidgets('testZoomGesturesEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, zoomGesturesEnabled: false, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool zoomGesturesEnabled = await inspector.areZoomGesturesEnabled(mapId: mapId); expect(zoomGesturesEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); zoomGesturesEnabled = await inspector.areZoomGesturesEnabled(mapId: mapId); expect(zoomGesturesEnabled, true); }); testWidgets('testZoomControlsEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool zoomControlsEnabled = await inspector.areZoomControlsEnabled(mapId: mapId); expect(zoomControlsEnabled, true); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, zoomControlsEnabled: false, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); zoomControlsEnabled = await inspector.areZoomControlsEnabled(mapId: mapId); expect(zoomControlsEnabled, false); }); testWidgets('testLiteModeEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool liteModeEnabled = await inspector.isLiteModeEnabled(mapId: mapId); expect(liteModeEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, liteModeEnabled: true, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); liteModeEnabled = await inspector.isLiteModeEnabled(mapId: mapId); expect(liteModeEnabled, true); }); testWidgets('testRotateGesturesEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, rotateGesturesEnabled: false, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool rotateGesturesEnabled = await inspector.areRotateGesturesEnabled(mapId: mapId); expect(rotateGesturesEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); rotateGesturesEnabled = await inspector.areRotateGesturesEnabled(mapId: mapId); expect(rotateGesturesEnabled, true); }); testWidgets('testTiltGesturesEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, tiltGesturesEnabled: false, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool tiltGesturesEnabled = await inspector.areTiltGesturesEnabled(mapId: mapId); expect(tiltGesturesEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); tiltGesturesEnabled = await inspector.areTiltGesturesEnabled(mapId: mapId); expect(tiltGesturesEnabled, true); }); testWidgets('testScrollGesturesEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, scrollGesturesEnabled: false, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool scrollGesturesEnabled = await inspector.areScrollGesturesEnabled(mapId: mapId); expect(scrollGesturesEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); scrollGesturesEnabled = await inspector.areScrollGesturesEnabled(mapId: mapId); expect(scrollGesturesEnabled, true); }); testWidgets('testInitialCenterLocationAtCenter', (WidgetTester tester) async { await tester.binding.setSurfaceSize(const Size(800, 600)); final Completer mapControllerCompleter = Completer(); final Key key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { mapControllerCompleter.complete(controller); }, ), ), ); final ExampleGoogleMapController mapController = await mapControllerCompleter.future; await tester.pumpAndSettle(); // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and // `mapControllerCompleter.complete(controller)` above should happen in // `mapRendered`. // https://github.com/flutter/flutter/issues/54758 await Future.delayed(const Duration(seconds: 1)); final ScreenCoordinate coordinate = await mapController.getScreenCoordinate(_kInitialCameraPosition.target); final Rect rect = tester.getRect(find.byKey(key)); expect( coordinate.x, ((rect.center.dx - rect.topLeft.dx) * tester.binding.window.devicePixelRatio) .round()); expect( coordinate.y, ((rect.center.dy - rect.topLeft.dy) * tester.binding.window.devicePixelRatio) .round()); await tester.binding.setSurfaceSize(null); }); testWidgets('testGetVisibleRegion', (WidgetTester tester) async { final Key key = GlobalKey(); final LatLngBounds zeroLatLngBounds = LatLngBounds( southwest: const LatLng(0, 0), northeast: const LatLng(0, 0)); final Completer mapControllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { mapControllerCompleter.complete(controller); }, ), )); await tester.pumpAndSettle(); final ExampleGoogleMapController mapController = await mapControllerCompleter.future; // Wait for the visible region to be non-zero. final LatLngBounds firstVisibleRegion = await waitForValueMatchingPredicate( tester, () => mapController.getVisibleRegion(), (LatLngBounds bounds) => bounds != zeroLatLngBounds && bounds.northeast != bounds.southwest) ?? zeroLatLngBounds; expect(firstVisibleRegion, isNot(zeroLatLngBounds)); expect(firstVisibleRegion.contains(_kInitialMapCenter), isTrue); // Making a new `LatLngBounds` about (10, 10) distance south west to the `firstVisibleRegion`. // The size of the `LatLngBounds` is 10 by 10. final LatLng southWest = LatLng(firstVisibleRegion.southwest.latitude - 20, firstVisibleRegion.southwest.longitude - 20); final LatLng northEast = LatLng(firstVisibleRegion.southwest.latitude - 10, firstVisibleRegion.southwest.longitude - 10); final LatLng newCenter = LatLng( (northEast.latitude + southWest.latitude) / 2, (northEast.longitude + southWest.longitude) / 2, ); expect(firstVisibleRegion.contains(northEast), isFalse); expect(firstVisibleRegion.contains(southWest), isFalse); final LatLngBounds latLngBounds = LatLngBounds(southwest: southWest, northeast: northEast); // TODO(iskakaushik): non-zero padding is needed for some device configurations // https://github.com/flutter/flutter/issues/30575 const double padding = 0; await mapController .moveCamera(CameraUpdate.newLatLngBounds(latLngBounds, padding)); await tester.pumpAndSettle(const Duration(seconds: 3)); final LatLngBounds secondVisibleRegion = await mapController.getVisibleRegion(); expect(secondVisibleRegion, isNot(zeroLatLngBounds)); expect(firstVisibleRegion, isNot(secondVisibleRegion)); expect(secondVisibleRegion.contains(newCenter), isTrue); }); testWidgets('testTraffic', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, trafficEnabled: true, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool isTrafficEnabled = await inspector.isTrafficEnabled(mapId: mapId); expect(isTrafficEnabled, true); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); isTrafficEnabled = await inspector.isTrafficEnabled(mapId: mapId); expect(isTrafficEnabled, false); }); testWidgets('testBuildings', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; final bool isBuildingsEnabled = await inspector.areBuildingsEnabled(mapId: mapId); expect(isBuildingsEnabled, true); }); testWidgets('testMyLocationButtonToggle', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool myLocationButtonEnabled = await inspector.isMyLocationButtonEnabled(mapId: mapId); expect(myLocationButtonEnabled, true); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, myLocationButtonEnabled: false, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); myLocationButtonEnabled = await inspector.isMyLocationButtonEnabled(mapId: mapId); expect(myLocationButtonEnabled, false); }, // Location button tests are skipped in Android because we don't have location permission to test. skip: true); testWidgets('testMyLocationButton initial value false', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, myLocationButtonEnabled: false, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; final bool myLocationButtonEnabled = await inspector.isMyLocationButtonEnabled(mapId: mapId); expect(myLocationButtonEnabled, false); }, // Location button tests are skipped in Android because we don't have location permission to test. skip: true); testWidgets('testMyLocationButton initial value true', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; final bool myLocationButtonEnabled = await inspector.isMyLocationButtonEnabled(mapId: mapId); expect(myLocationButtonEnabled, true); }, // Location button tests are skipped in Android because we don't have location permission to test. skip: true); testWidgets('testSetMapStyle valid Json String', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { controllerCompleter.complete(controller); }, ), )); final ExampleGoogleMapController controller = await controllerCompleter.future; const String mapStyle = '[{"elementType":"geometry","stylers":[{"color":"#242f3e"}]}]'; await controller.setMapStyle(mapStyle); }); testWidgets('testSetMapStyle invalid Json String', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { controllerCompleter.complete(controller); }, ), )); final ExampleGoogleMapController controller = await controllerCompleter.future; try { await controller.setMapStyle('invalid_value'); fail('expected MapStyleException'); } on MapStyleException catch (e) { expect(e.cause, isNotNull); } }); testWidgets('testSetMapStyle null string', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { controllerCompleter.complete(controller); }, ), )); final ExampleGoogleMapController controller = await controllerCompleter.future; await controller.setMapStyle(null); }); testWidgets('testGetLatLng', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { controllerCompleter.complete(controller); }, ), )); final ExampleGoogleMapController controller = await controllerCompleter.future; await tester.pumpAndSettle(); // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen // in `mapRendered`. // https://github.com/flutter/flutter/issues/54758 await Future.delayed(const Duration(seconds: 1)); final LatLngBounds visibleRegion = await controller.getVisibleRegion(); final LatLng topLeft = await controller.getLatLng(const ScreenCoordinate(x: 0, y: 0)); final LatLng northWest = LatLng( visibleRegion.northeast.latitude, visibleRegion.southwest.longitude, ); expect(topLeft, northWest); }); testWidgets('testGetZoomLevel', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { controllerCompleter.complete(controller); }, ), )); final ExampleGoogleMapController controller = await controllerCompleter.future; await tester.pumpAndSettle(); // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen // in `mapRendered`. // https://github.com/flutter/flutter/issues/54758 await Future.delayed(const Duration(seconds: 1)); double zoom = await controller.getZoomLevel(); expect(zoom, _kInitialZoomLevel); await controller.moveCamera(CameraUpdate.zoomTo(7)); await tester.pumpAndSettle(); zoom = await controller.getZoomLevel(); expect(zoom, equals(7)); }); testWidgets('testScreenCoordinate', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { controllerCompleter.complete(controller); }, ), )); final ExampleGoogleMapController controller = await controllerCompleter.future; await tester.pumpAndSettle(); // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen // in `mapRendered`. // https://github.com/flutter/flutter/issues/54758 await Future.delayed(const Duration(seconds: 1)); final LatLngBounds visibleRegion = await controller.getVisibleRegion(); final LatLng northWest = LatLng( visibleRegion.northeast.latitude, visibleRegion.southwest.longitude, ); final ScreenCoordinate topLeft = await controller.getScreenCoordinate(northWest); expect(topLeft, const ScreenCoordinate(x: 0, y: 0)); }); testWidgets('testResizeWidget', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final ExampleGoogleMap map = ExampleGoogleMap( initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) async { controllerCompleter.complete(controller); }, ); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: MaterialApp( home: Scaffold( body: SizedBox(height: 100, width: 100, child: map))))); final ExampleGoogleMapController controller = await controllerCompleter.future; await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: MaterialApp( home: Scaffold( body: SizedBox(height: 400, width: 400, child: map))))); await tester.pumpAndSettle(); // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen // in `mapRendered`. // https://github.com/flutter/flutter/issues/54758 await Future.delayed(const Duration(seconds: 1)); // Simple call to make sure that the app hasn't crashed. final LatLngBounds bounds1 = await controller.getVisibleRegion(); final LatLngBounds bounds2 = await controller.getVisibleRegion(); expect(bounds1, bounds2); }); testWidgets('testToggleInfoWindow', (WidgetTester tester) async { const Marker marker = Marker( markerId: MarkerId('marker'), infoWindow: InfoWindow(title: 'InfoWindow')); final Set markers = {marker}; final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), markers: markers, onMapCreated: (ExampleGoogleMapController googleMapController) { controllerCompleter.complete(googleMapController); }, ), )); final ExampleGoogleMapController controller = await controllerCompleter.future; bool iwVisibleStatus = await controller.isMarkerInfoWindowShown(marker.markerId); expect(iwVisibleStatus, false); await controller.showMarkerInfoWindow(marker.markerId); // The Maps SDK doesn't always return true for whether it is shown // immediately after showing it, so wait for it to report as shown. iwVisibleStatus = await waitForValueMatchingPredicate( tester, () => controller.isMarkerInfoWindowShown(marker.markerId), (bool visible) => visible) ?? false; expect(iwVisibleStatus, true); await controller.hideMarkerInfoWindow(marker.markerId); iwVisibleStatus = await controller.isMarkerInfoWindowShown(marker.markerId); expect(iwVisibleStatus, false); }); testWidgets('fromAssetImage', (WidgetTester tester) async { const double pixelRatio = 2; const ImageConfiguration imageConfiguration = ImageConfiguration(devicePixelRatio: pixelRatio); final BitmapDescriptor mip = await BitmapDescriptor.fromAssetImage( imageConfiguration, 'red_square.png'); final BitmapDescriptor scaled = await BitmapDescriptor.fromAssetImage( imageConfiguration, 'red_square.png', mipmaps: false); expect((mip.toJson() as List)[2], 1); expect((scaled.toJson() as List)[2], 2); }); testWidgets('testTakeSnapshot', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { controllerCompleter.complete(controller); }, ), ), ); await tester.pumpAndSettle(const Duration(seconds: 3)); final ExampleGoogleMapController controller = await controllerCompleter.future; final Uint8List? bytes = await controller.takeSnapshot(); expect(bytes?.isNotEmpty, true); }, // TODO(cyanglaz): un-skip the test when we can test this on CI with API key enabled. // https://github.com/flutter/flutter/issues/57057 skip: Platform.isAndroid); testWidgets( 'set tileOverlay correctly', (WidgetTester tester) async { final Completer mapIdCompleter = Completer(); final TileOverlay tileOverlay1 = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_1'), tileProvider: _DebugTileProvider(), zIndex: 2, transparency: 0.2, ); final TileOverlay tileOverlay2 = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_2'), tileProvider: _DebugTileProvider(), zIndex: 1, visible: false, transparency: 0.3, fadeIn: false, ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( initialCameraPosition: _kInitialCameraPosition, tileOverlays: {tileOverlay1, tileOverlay2}, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), ), ); await tester.pumpAndSettle(const Duration(seconds: 3)); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; final TileOverlay tileOverlayInfo1 = (await inspector .getTileOverlayInfo(tileOverlay1.mapsId, mapId: mapId))!; final TileOverlay tileOverlayInfo2 = (await inspector .getTileOverlayInfo(tileOverlay2.mapsId, mapId: mapId))!; expect(tileOverlayInfo1.visible, isTrue); expect(tileOverlayInfo1.fadeIn, isTrue); expect( tileOverlayInfo1.transparency, moreOrLessEquals(0.2, epsilon: 0.001)); expect(tileOverlayInfo1.zIndex, 2); expect(tileOverlayInfo2.visible, isFalse); expect(tileOverlayInfo2.fadeIn, isFalse); expect( tileOverlayInfo2.transparency, moreOrLessEquals(0.3, epsilon: 0.001)); expect(tileOverlayInfo2.zIndex, 1); }, ); testWidgets( 'update tileOverlays correctly', (WidgetTester tester) async { final Completer mapIdCompleter = Completer(); final Key key = GlobalKey(); final TileOverlay tileOverlay1 = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_1'), tileProvider: _DebugTileProvider(), zIndex: 2, transparency: 0.2, ); final TileOverlay tileOverlay2 = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_2'), tileProvider: _DebugTileProvider(), zIndex: 3, transparency: 0.5, ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, tileOverlays: {tileOverlay1, tileOverlay2}, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), ), ); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; final TileOverlay tileOverlay1New = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_1'), tileProvider: _DebugTileProvider(), zIndex: 1, visible: false, transparency: 0.3, fadeIn: false, ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, tileOverlays: {tileOverlay1New}, onMapCreated: (ExampleGoogleMapController controller) { fail('update: OnMapCreated should get called only once.'); }, ), ), ); await tester.pumpAndSettle(const Duration(seconds: 3)); final TileOverlay tileOverlayInfo1 = (await inspector .getTileOverlayInfo(tileOverlay1.mapsId, mapId: mapId))!; final TileOverlay? tileOverlayInfo2 = await inspector.getTileOverlayInfo(tileOverlay2.mapsId, mapId: mapId); expect(tileOverlayInfo1.visible, isFalse); expect(tileOverlayInfo1.fadeIn, isFalse); expect( tileOverlayInfo1.transparency, moreOrLessEquals(0.3, epsilon: 0.001)); expect(tileOverlayInfo1.zIndex, 1); expect(tileOverlayInfo2, isNull); }, ); testWidgets( 'remove tileOverlays correctly', (WidgetTester tester) async { final Completer mapIdCompleter = Completer(); final Key key = GlobalKey(); final TileOverlay tileOverlay1 = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_1'), tileProvider: _DebugTileProvider(), zIndex: 2, transparency: 0.2, ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, tileOverlays: {tileOverlay1}, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), ), ); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), ), ); await tester.pumpAndSettle(const Duration(seconds: 3)); final TileOverlay? tileOverlayInfo1 = await inspector.getTileOverlayInfo(tileOverlay1.mapsId, mapId: mapId); expect(tileOverlayInfo1, isNull); }, ); } class _DebugTileProvider implements TileProvider { _DebugTileProvider() { boxPaint.isAntiAlias = true; boxPaint.color = Colors.blue; boxPaint.strokeWidth = 2.0; boxPaint.style = PaintingStyle.stroke; } static const int width = 100; static const int height = 100; static final Paint boxPaint = Paint(); static const TextStyle textStyle = TextStyle( color: Colors.red, fontSize: 20, ); @override Future getTile(int x, int y, int? zoom) async { final ui.PictureRecorder recorder = ui.PictureRecorder(); final Canvas canvas = Canvas(recorder); final TextSpan textSpan = TextSpan( text: '$x,$y', style: textStyle, ); final TextPainter textPainter = TextPainter( text: textSpan, textDirection: TextDirection.ltr, ); textPainter.layout( maxWidth: width.toDouble(), ); textPainter.paint(canvas, Offset.zero); canvas.drawRect( Rect.fromLTRB(0, 0, width.toDouble(), width.toDouble()), boxPaint); final ui.Picture picture = recorder.endRecording(); final Uint8List byteData = await picture .toImage(width, height) .then((ui.Image image) => image.toByteData(format: ui.ImageByteFormat.png)) .then((ByteData? byteData) => byteData!.buffer.asUint8List()); return Tile(width, height, byteData); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/latest_renderer_test.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter_android/google_maps_flutter_android.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:integration_test/integration_test.dart'; import 'google_maps_tests.dart' show googleMapsTests; void main() { late AndroidMapRenderer initializedRenderer; IntegrationTestWidgetsFlutterBinding.ensureInitialized(); setUpAll(() async { final GoogleMapsFlutterAndroid instance = GoogleMapsFlutterPlatform.instance as GoogleMapsFlutterAndroid; initializedRenderer = await instance.initializeWithRenderer(AndroidMapRenderer.latest); }); testWidgets('initialized with latest renderer', (WidgetTester _) async { expect(initializedRenderer, AndroidMapRenderer.latest); }); testWidgets('throws PlatformException on multiple renderer initializations', (WidgetTester _) async { final GoogleMapsFlutterAndroid instance = GoogleMapsFlutterPlatform.instance as GoogleMapsFlutterAndroid; expect( () async => instance.initializeWithRenderer(AndroidMapRenderer.latest), throwsA(isA().having((PlatformException e) => e.code, 'code', 'Renderer already initialized'))); }); // Run tests. googleMapsTests(); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/legacy_renderer_test.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter_android/google_maps_flutter_android.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:integration_test/integration_test.dart'; import 'google_maps_tests.dart' show googleMapsTests; void main() { late AndroidMapRenderer initializedRenderer; IntegrationTestWidgetsFlutterBinding.ensureInitialized(); setUpAll(() async { final GoogleMapsFlutterAndroid instance = GoogleMapsFlutterPlatform.instance as GoogleMapsFlutterAndroid; initializedRenderer = await instance.initializeWithRenderer(AndroidMapRenderer.legacy); }); testWidgets('initialized with legacy renderer', (WidgetTester _) async { expect(initializedRenderer, AndroidMapRenderer.legacy); }); testWidgets('throws PlatformException on multiple renderer initializations', (WidgetTester _) async { final GoogleMapsFlutterAndroid instance = GoogleMapsFlutterPlatform.instance as GoogleMapsFlutterAndroid; expect( () async => instance.initializeWithRenderer(AndroidMapRenderer.legacy), throwsA(isA().having((PlatformException e) => e.code, 'code', 'Renderer already initialized'))); }); // Run tests. googleMapsTests(); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/animate_camera.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; class AnimateCameraPage extends GoogleMapExampleAppPage { const AnimateCameraPage({Key? key}) : super(const Icon(Icons.map), 'Camera control, animated', key: key); @override Widget build(BuildContext context) { return const AnimateCamera(); } } class AnimateCamera extends StatefulWidget { const AnimateCamera({Key? key}) : super(key: key); @override State createState() => AnimateCameraState(); } class AnimateCameraState extends State { ExampleGoogleMapController? mapController; // ignore: use_setters_to_change_properties void _onMapCreated(ExampleGoogleMapController controller) { mapController = controller; } @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 300.0, height: 200.0, child: ExampleGoogleMap( onMapCreated: _onMapCreated, initialCameraPosition: const CameraPosition(target: LatLng(0.0, 0.0)), ), ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Column( children: [ TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.newCameraPosition( const CameraPosition( bearing: 270.0, target: LatLng(51.5160895, -0.1294527), tilt: 30.0, zoom: 17.0, ), ), ); }, child: const Text('newCameraPosition'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.newLatLng( const LatLng(56.1725505, 10.1850512), ), ); }, child: const Text('newLatLng'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.newLatLngBounds( LatLngBounds( southwest: const LatLng(-38.483935, 113.248673), northeast: const LatLng(-8.982446, 153.823821), ), 10.0, ), ); }, child: const Text('newLatLngBounds'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.newLatLngZoom( const LatLng(37.4231613, -122.087159), 11.0, ), ); }, child: const Text('newLatLngZoom'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.scrollBy(150.0, -225.0), ); }, child: const Text('scrollBy'), ), ], ), Column( children: [ TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.zoomBy( -0.5, const Offset(30.0, 20.0), ), ); }, child: const Text('zoomBy with focus'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.zoomBy(-0.5), ); }, child: const Text('zoomBy'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.zoomIn(), ); }, child: const Text('zoomIn'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.zoomOut(), ); }, child: const Text('zoomOut'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.zoomTo(16.0), ); }, child: const Text('zoomTo'), ), ], ), ], ) ], ); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/example_google_map.dart ================================================ // Copyright 2013 The Flutter 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'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; // This is a pared down version of the Dart code from the app-facing package, // to allow running the same examples for package-local testing. // TODO(stuartmorgan): Consider extracting this to a shared package. See also // https://github.com/flutter/flutter/issues/46716. /// Controller for a single ExampleGoogleMap instance running on the host platform. class ExampleGoogleMapController { ExampleGoogleMapController._( this._googleMapState, { required this.mapId, }) { _connectStreams(mapId); } /// The mapId for this controller final int mapId; /// Initialize control of a [ExampleGoogleMap] with [id]. /// /// Mainly for internal use when instantiating a [ExampleGoogleMapController] passed /// in [ExampleGoogleMap.onMapCreated] callback. static Future _init( int id, CameraPosition initialCameraPosition, _ExampleGoogleMapState googleMapState, ) async { await GoogleMapsFlutterPlatform.instance.init(id); return ExampleGoogleMapController._( googleMapState, mapId: id, ); } final _ExampleGoogleMapState _googleMapState; void _connectStreams(int mapId) { if (_googleMapState.widget.onCameraMoveStarted != null) { GoogleMapsFlutterPlatform.instance .onCameraMoveStarted(mapId: mapId) .listen((_) => _googleMapState.widget.onCameraMoveStarted!()); } if (_googleMapState.widget.onCameraMove != null) { GoogleMapsFlutterPlatform.instance.onCameraMove(mapId: mapId).listen( (CameraMoveEvent e) => _googleMapState.widget.onCameraMove!(e.value)); } if (_googleMapState.widget.onCameraIdle != null) { GoogleMapsFlutterPlatform.instance .onCameraIdle(mapId: mapId) .listen((_) => _googleMapState.widget.onCameraIdle!()); } GoogleMapsFlutterPlatform.instance .onMarkerTap(mapId: mapId) .listen((MarkerTapEvent e) => _googleMapState.onMarkerTap(e.value)); GoogleMapsFlutterPlatform.instance.onMarkerDragStart(mapId: mapId).listen( (MarkerDragStartEvent e) => _googleMapState.onMarkerDragStart(e.value, e.position)); GoogleMapsFlutterPlatform.instance.onMarkerDrag(mapId: mapId).listen( (MarkerDragEvent e) => _googleMapState.onMarkerDrag(e.value, e.position)); GoogleMapsFlutterPlatform.instance.onMarkerDragEnd(mapId: mapId).listen( (MarkerDragEndEvent e) => _googleMapState.onMarkerDragEnd(e.value, e.position)); GoogleMapsFlutterPlatform.instance.onInfoWindowTap(mapId: mapId).listen( (InfoWindowTapEvent e) => _googleMapState.onInfoWindowTap(e.value)); GoogleMapsFlutterPlatform.instance .onPolylineTap(mapId: mapId) .listen((PolylineTapEvent e) => _googleMapState.onPolylineTap(e.value)); GoogleMapsFlutterPlatform.instance .onPolygonTap(mapId: mapId) .listen((PolygonTapEvent e) => _googleMapState.onPolygonTap(e.value)); GoogleMapsFlutterPlatform.instance .onCircleTap(mapId: mapId) .listen((CircleTapEvent e) => _googleMapState.onCircleTap(e.value)); GoogleMapsFlutterPlatform.instance .onTap(mapId: mapId) .listen((MapTapEvent e) => _googleMapState.onTap(e.position)); GoogleMapsFlutterPlatform.instance.onLongPress(mapId: mapId).listen( (MapLongPressEvent e) => _googleMapState.onLongPress(e.position)); } /// Updates configuration options of the map user interface. Future _updateMapConfiguration(MapConfiguration update) { return GoogleMapsFlutterPlatform.instance .updateMapConfiguration(update, mapId: mapId); } /// Updates marker configuration. Future _updateMarkers(MarkerUpdates markerUpdates) { return GoogleMapsFlutterPlatform.instance .updateMarkers(markerUpdates, mapId: mapId); } /// Updates polygon configuration. Future _updatePolygons(PolygonUpdates polygonUpdates) { return GoogleMapsFlutterPlatform.instance .updatePolygons(polygonUpdates, mapId: mapId); } /// Updates polyline configuration. Future _updatePolylines(PolylineUpdates polylineUpdates) { return GoogleMapsFlutterPlatform.instance .updatePolylines(polylineUpdates, mapId: mapId); } /// Updates circle configuration. Future _updateCircles(CircleUpdates circleUpdates) { return GoogleMapsFlutterPlatform.instance .updateCircles(circleUpdates, mapId: mapId); } /// Updates tile overlays configuration. Future _updateTileOverlays(Set newTileOverlays) { return GoogleMapsFlutterPlatform.instance .updateTileOverlays(newTileOverlays: newTileOverlays, mapId: mapId); } /// Clears the tile cache so that all tiles will be requested again from the /// [TileProvider]. Future clearTileCache(TileOverlayId tileOverlayId) async { return GoogleMapsFlutterPlatform.instance .clearTileCache(tileOverlayId, mapId: mapId); } /// Starts an animated change of the map camera position. Future animateCamera(CameraUpdate cameraUpdate) { return GoogleMapsFlutterPlatform.instance .animateCamera(cameraUpdate, mapId: mapId); } /// Changes the map camera position. Future moveCamera(CameraUpdate cameraUpdate) { return GoogleMapsFlutterPlatform.instance .moveCamera(cameraUpdate, mapId: mapId); } /// Sets the styling of the base map. Future setMapStyle(String? mapStyle) { return GoogleMapsFlutterPlatform.instance .setMapStyle(mapStyle, mapId: mapId); } /// Return [LatLngBounds] defining the region that is visible in a map. Future getVisibleRegion() { return GoogleMapsFlutterPlatform.instance.getVisibleRegion(mapId: mapId); } /// Return [ScreenCoordinate] of the [LatLng] in the current map view. Future getScreenCoordinate(LatLng latLng) { return GoogleMapsFlutterPlatform.instance .getScreenCoordinate(latLng, mapId: mapId); } /// Returns [LatLng] corresponding to the [ScreenCoordinate] in the current map view. Future getLatLng(ScreenCoordinate screenCoordinate) { return GoogleMapsFlutterPlatform.instance .getLatLng(screenCoordinate, mapId: mapId); } /// Programmatically show the Info Window for a [Marker]. Future showMarkerInfoWindow(MarkerId markerId) { return GoogleMapsFlutterPlatform.instance .showMarkerInfoWindow(markerId, mapId: mapId); } /// Programmatically hide the Info Window for a [Marker]. Future hideMarkerInfoWindow(MarkerId markerId) { return GoogleMapsFlutterPlatform.instance .hideMarkerInfoWindow(markerId, mapId: mapId); } /// Returns `true` when the [InfoWindow] is showing, `false` otherwise. Future isMarkerInfoWindowShown(MarkerId markerId) { return GoogleMapsFlutterPlatform.instance .isMarkerInfoWindowShown(markerId, mapId: mapId); } /// Returns the current zoom level of the map Future getZoomLevel() { return GoogleMapsFlutterPlatform.instance.getZoomLevel(mapId: mapId); } /// Returns the image bytes of the map Future takeSnapshot() { return GoogleMapsFlutterPlatform.instance.takeSnapshot(mapId: mapId); } /// Disposes of the platform resources void dispose() { GoogleMapsFlutterPlatform.instance.dispose(mapId: mapId); } } // The next map ID to create. int _nextMapCreationId = 0; /// A widget which displays a map with data obtained from the Google Maps service. class ExampleGoogleMap extends StatefulWidget { /// Creates a widget displaying data from Google Maps services. /// /// [AssertionError] will be thrown if [initialCameraPosition] is null; const ExampleGoogleMap({ Key? key, required this.initialCameraPosition, this.onMapCreated, this.gestureRecognizers = const >{}, this.compassEnabled = true, this.mapToolbarEnabled = true, this.cameraTargetBounds = CameraTargetBounds.unbounded, this.mapType = MapType.normal, this.minMaxZoomPreference = MinMaxZoomPreference.unbounded, this.rotateGesturesEnabled = true, this.scrollGesturesEnabled = true, this.zoomControlsEnabled = true, this.zoomGesturesEnabled = true, this.liteModeEnabled = false, this.tiltGesturesEnabled = true, this.myLocationEnabled = false, this.myLocationButtonEnabled = true, this.layoutDirection, /// If no padding is specified default padding will be 0. this.padding = EdgeInsets.zero, this.indoorViewEnabled = false, this.trafficEnabled = false, this.buildingsEnabled = true, this.markers = const {}, this.polygons = const {}, this.polylines = const {}, this.circles = const {}, this.onCameraMoveStarted, this.tileOverlays = const {}, this.onCameraMove, this.onCameraIdle, this.onTap, this.onLongPress, }) : super(key: key); /// Callback method for when the map is ready to be used. /// /// Used to receive a [ExampleGoogleMapController] for this [ExampleGoogleMap]. final void Function(ExampleGoogleMapController controller)? onMapCreated; /// The initial position of the map's camera. final CameraPosition initialCameraPosition; /// True if the map should show a compass when rotated. final bool compassEnabled; /// True if the map should show a toolbar when you interact with the map. Android only. final bool mapToolbarEnabled; /// Geographical bounding box for the camera target. final CameraTargetBounds cameraTargetBounds; /// Type of map tiles to be rendered. final MapType mapType; /// The layout direction to use for the embedded view. final TextDirection? layoutDirection; /// Preferred bounds for the camera zoom level. /// /// Actual bounds depend on map data and device. final MinMaxZoomPreference minMaxZoomPreference; /// True if the map view should respond to rotate gestures. final bool rotateGesturesEnabled; /// True if the map view should respond to scroll gestures. final bool scrollGesturesEnabled; /// True if the map view should show zoom controls. This includes two buttons /// to zoom in and zoom out. The default value is to show zoom controls. final bool zoomControlsEnabled; /// True if the map view should respond to zoom gestures. final bool zoomGesturesEnabled; /// True if the map view should be in lite mode. Android only. final bool liteModeEnabled; /// True if the map view should respond to tilt gestures. final bool tiltGesturesEnabled; /// Padding to be set on map. final EdgeInsets padding; /// Markers to be placed on the map. final Set markers; /// Polygons to be placed on the map. final Set polygons; /// Polylines to be placed on the map. final Set polylines; /// Circles to be placed on the map. final Set circles; /// Tile overlays to be placed on the map. final Set tileOverlays; /// Called when the camera starts moving. final VoidCallback? onCameraMoveStarted; /// Called repeatedly as the camera continues to move after an /// onCameraMoveStarted call. final CameraPositionCallback? onCameraMove; /// Called when camera movement has ended, there are no pending /// animations and the user has stopped interacting with the map. final VoidCallback? onCameraIdle; /// Called every time a [ExampleGoogleMap] is tapped. final ArgumentCallback? onTap; /// Called every time a [ExampleGoogleMap] is long pressed. final ArgumentCallback? onLongPress; /// True if a "My Location" layer should be shown on the map. final bool myLocationEnabled; /// Enables or disables the my-location button. final bool myLocationButtonEnabled; /// Enables or disables the indoor view from the map final bool indoorViewEnabled; /// Enables or disables the traffic layer of the map final bool trafficEnabled; /// Enables or disables showing 3D buildings where available final bool buildingsEnabled; /// Which gestures should be consumed by the map. final Set> gestureRecognizers; /// Creates a [State] for this [ExampleGoogleMap]. @override State createState() => _ExampleGoogleMapState(); } class _ExampleGoogleMapState extends State { final int _mapId = _nextMapCreationId++; final Completer _controller = Completer(); Map _markers = {}; Map _polygons = {}; Map _polylines = {}; Map _circles = {}; late MapConfiguration _mapConfiguration; @override Widget build(BuildContext context) { return GoogleMapsFlutterPlatform.instance.buildViewWithConfiguration( _mapId, onPlatformViewCreated, widgetConfiguration: MapWidgetConfiguration( textDirection: widget.layoutDirection ?? Directionality.maybeOf(context) ?? TextDirection.ltr, initialCameraPosition: widget.initialCameraPosition, gestureRecognizers: widget.gestureRecognizers, ), mapObjects: MapObjects( markers: widget.markers, polygons: widget.polygons, polylines: widget.polylines, circles: widget.circles, ), mapConfiguration: _mapConfiguration, ); } @override void initState() { super.initState(); _mapConfiguration = _configurationFromMapWidget(widget); _markers = keyByMarkerId(widget.markers); _polygons = keyByPolygonId(widget.polygons); _polylines = keyByPolylineId(widget.polylines); _circles = keyByCircleId(widget.circles); } @override void dispose() { _controller.future .then((ExampleGoogleMapController controller) => controller.dispose()); super.dispose(); } @override void didUpdateWidget(ExampleGoogleMap oldWidget) { super.didUpdateWidget(oldWidget); _updateOptions(); _updateMarkers(); _updatePolygons(); _updatePolylines(); _updateCircles(); _updateTileOverlays(); } Future _updateOptions() async { final MapConfiguration newConfig = _configurationFromMapWidget(widget); final MapConfiguration updates = newConfig.diffFrom(_mapConfiguration); if (updates.isEmpty) { return; } final ExampleGoogleMapController controller = await _controller.future; controller._updateMapConfiguration(updates); _mapConfiguration = newConfig; } Future _updateMarkers() async { final ExampleGoogleMapController controller = await _controller.future; controller._updateMarkers( MarkerUpdates.from(_markers.values.toSet(), widget.markers)); _markers = keyByMarkerId(widget.markers); } Future _updatePolygons() async { final ExampleGoogleMapController controller = await _controller.future; controller._updatePolygons( PolygonUpdates.from(_polygons.values.toSet(), widget.polygons)); _polygons = keyByPolygonId(widget.polygons); } Future _updatePolylines() async { final ExampleGoogleMapController controller = await _controller.future; controller._updatePolylines( PolylineUpdates.from(_polylines.values.toSet(), widget.polylines)); _polylines = keyByPolylineId(widget.polylines); } Future _updateCircles() async { final ExampleGoogleMapController controller = await _controller.future; controller._updateCircles( CircleUpdates.from(_circles.values.toSet(), widget.circles)); _circles = keyByCircleId(widget.circles); } Future _updateTileOverlays() async { final ExampleGoogleMapController controller = await _controller.future; controller._updateTileOverlays(widget.tileOverlays); } Future onPlatformViewCreated(int id) async { final ExampleGoogleMapController controller = await ExampleGoogleMapController._init( id, widget.initialCameraPosition, this, ); _controller.complete(controller); _updateTileOverlays(); widget.onMapCreated?.call(controller); } void onMarkerTap(MarkerId markerId) { _markers[markerId]!.onTap?.call(); } void onMarkerDragStart(MarkerId markerId, LatLng position) { _markers[markerId]!.onDragStart?.call(position); } void onMarkerDrag(MarkerId markerId, LatLng position) { _markers[markerId]!.onDrag?.call(position); } void onMarkerDragEnd(MarkerId markerId, LatLng position) { _markers[markerId]!.onDragEnd?.call(position); } void onPolygonTap(PolygonId polygonId) { _polygons[polygonId]!.onTap?.call(); } void onPolylineTap(PolylineId polylineId) { _polylines[polylineId]!.onTap?.call(); } void onCircleTap(CircleId circleId) { _circles[circleId]!.onTap?.call(); } void onInfoWindowTap(MarkerId markerId) { _markers[markerId]!.infoWindow.onTap?.call(); } void onTap(LatLng position) { widget.onTap?.call(position); } void onLongPress(LatLng position) { widget.onLongPress?.call(position); } } /// Builds a [MapConfiguration] from the given [map]. MapConfiguration _configurationFromMapWidget(ExampleGoogleMap map) { return MapConfiguration( compassEnabled: map.compassEnabled, mapToolbarEnabled: map.mapToolbarEnabled, cameraTargetBounds: map.cameraTargetBounds, mapType: map.mapType, minMaxZoomPreference: map.minMaxZoomPreference, rotateGesturesEnabled: map.rotateGesturesEnabled, scrollGesturesEnabled: map.scrollGesturesEnabled, tiltGesturesEnabled: map.tiltGesturesEnabled, trackCameraPosition: map.onCameraMove != null, zoomControlsEnabled: map.zoomControlsEnabled, zoomGesturesEnabled: map.zoomGesturesEnabled, liteModeEnabled: map.liteModeEnabled, myLocationEnabled: map.myLocationEnabled, myLocationButtonEnabled: map.myLocationButtonEnabled, padding: map.padding, indoorViewEnabled: map.indoorViewEnabled, trafficEnabled: map.trafficEnabled, buildingsEnabled: map.buildingsEnabled, ); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/lite_mode.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; const CameraPosition _kInitialPosition = CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0); class LiteModePage extends GoogleMapExampleAppPage { const LiteModePage({Key? key}) : super(const Icon(Icons.map), 'Lite mode', key: key); @override Widget build(BuildContext context) { return const _LiteModeBody(); } } class _LiteModeBody extends StatelessWidget { const _LiteModeBody(); @override Widget build(BuildContext context) { return const Card( child: Padding( padding: EdgeInsets.symmetric(vertical: 30.0), child: Center( child: SizedBox( width: 300.0, height: 300.0, child: ExampleGoogleMap( initialCameraPosition: _kInitialPosition, liteModeEnabled: true, ), ), ), ), ); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:google_maps_flutter_android/google_maps_flutter_android.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'animate_camera.dart'; import 'lite_mode.dart'; import 'map_click.dart'; import 'map_coordinates.dart'; import 'map_ui.dart'; import 'marker_icons.dart'; import 'move_camera.dart'; import 'padding.dart'; import 'page.dart'; import 'place_circle.dart'; import 'place_marker.dart'; import 'place_polygon.dart'; import 'place_polyline.dart'; import 'scrolling_map.dart'; import 'snapshot.dart'; import 'tile_overlay.dart'; final List _allPages = [ const MapUiPage(), const MapCoordinatesPage(), const MapClickPage(), const AnimateCameraPage(), const MoveCameraPage(), const PlaceMarkerPage(), const MarkerIconsPage(), const ScrollingMapPage(), const PlacePolylinePage(), const PlacePolygonPage(), const PlaceCirclePage(), const PaddingPage(), const SnapshotPage(), const LiteModePage(), const TileOverlayPage(), ]; /// MapsDemo is the Main Application. class MapsDemo extends StatelessWidget { /// Default Constructor const MapsDemo({Key? key}) : super(key: key); void _pushPage(BuildContext context, GoogleMapExampleAppPage page) { Navigator.of(context).push(MaterialPageRoute( builder: (_) => Scaffold( appBar: AppBar(title: Text(page.title)), body: page, ))); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('GoogleMaps examples')), body: ListView.builder( itemCount: _allPages.length, itemBuilder: (_, int index) => ListTile( leading: _allPages[index].leading, title: Text(_allPages[index].title), onTap: () => _pushPage(context, _allPages[index]), ), ), ); } } void main() { final GoogleMapsFlutterPlatform platform = GoogleMapsFlutterPlatform.instance; // Default to Hybrid Composition for the example. (platform as GoogleMapsFlutterAndroid).useAndroidViewSurface = true; runApp(const MaterialApp(home: MapsDemo())); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_click.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; const CameraPosition _kInitialPosition = CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0); class MapClickPage extends GoogleMapExampleAppPage { const MapClickPage({Key? key}) : super(const Icon(Icons.mouse), 'Map click', key: key); @override Widget build(BuildContext context) { return const _MapClickBody(); } } class _MapClickBody extends StatefulWidget { const _MapClickBody(); @override State createState() => _MapClickBodyState(); } class _MapClickBodyState extends State<_MapClickBody> { _MapClickBodyState(); ExampleGoogleMapController? mapController; LatLng? _lastTap; LatLng? _lastLongPress; @override Widget build(BuildContext context) { final ExampleGoogleMap googleMap = ExampleGoogleMap( onMapCreated: onMapCreated, initialCameraPosition: _kInitialPosition, onTap: (LatLng pos) { setState(() { _lastTap = pos; }); }, onLongPress: (LatLng pos) { setState(() { _lastLongPress = pos; }); }, ); final List columnChildren = [ Padding( padding: const EdgeInsets.all(10.0), child: Center( child: SizedBox( width: 300.0, height: 200.0, child: googleMap, ), ), ), ]; if (mapController != null) { final String lastTap = 'Tap:\n${_lastTap ?? ""}\n'; final String lastLongPress = 'Long press:\n${_lastLongPress ?? ""}'; columnChildren.add(Center( child: Text( lastTap, textAlign: TextAlign.center, ))); columnChildren.add(Center( child: Text( _lastTap != null ? 'Tapped' : '', textAlign: TextAlign.center, ))); columnChildren.add(Center( child: Text( lastLongPress, textAlign: TextAlign.center, ))); columnChildren.add(Center( child: Text( _lastLongPress != null ? 'Long pressed' : '', textAlign: TextAlign.center, ))); } return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: columnChildren, ); } Future onMapCreated(ExampleGoogleMapController controller) async { setState(() { mapController = controller; }); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_coordinates.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; const CameraPosition _kInitialPosition = CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0); class MapCoordinatesPage extends GoogleMapExampleAppPage { const MapCoordinatesPage({Key? key}) : super(const Icon(Icons.map), 'Map coordinates', key: key); @override Widget build(BuildContext context) { return const _MapCoordinatesBody(); } } class _MapCoordinatesBody extends StatefulWidget { const _MapCoordinatesBody(); @override State createState() => _MapCoordinatesBodyState(); } class _MapCoordinatesBodyState extends State<_MapCoordinatesBody> { _MapCoordinatesBodyState(); ExampleGoogleMapController? mapController; LatLngBounds _visibleRegion = LatLngBounds( southwest: const LatLng(0, 0), northeast: const LatLng(0, 0), ); @override Widget build(BuildContext context) { final ExampleGoogleMap googleMap = ExampleGoogleMap( onMapCreated: onMapCreated, initialCameraPosition: _kInitialPosition, onCameraIdle: _updateVisibleRegion, // https://github.com/flutter/flutter/issues/54758 ); return NotificationListener( onNotification: (ScrollNotification scrollState) { _updateVisibleRegion(); return true; }, child: ListView( children: [ Padding( padding: const EdgeInsets.all(10.0), child: Center( child: SizedBox( width: 300.0, height: 200.0, child: googleMap, ), ), ), if (mapController != null) Center( child: Text('VisibleRegion:' '\nnortheast: ${_visibleRegion.northeast},' '\nsouthwest: ${_visibleRegion.southwest}'), ), // Add a block at the bottom of this list to allow validation that the visible region of the map // does not change when scrolled under the safe view on iOS. // https://github.com/flutter/flutter/issues/107913 const SizedBox( width: 300, height: 1000, ), ], ), ); } Future onMapCreated(ExampleGoogleMapController controller) async { final LatLngBounds visibleRegion = await controller.getVisibleRegion(); setState(() { mapController = controller; _visibleRegion = visibleRegion; }); } Future _updateVisibleRegion() async { final LatLngBounds visibleRegion = await mapController!.getVisibleRegion(); setState(() { _visibleRegion = visibleRegion; }); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_ui.dart ================================================ // Copyright 2013 The Flutter 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:flutter/services.dart' show rootBundle; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; final LatLngBounds sydneyBounds = LatLngBounds( southwest: const LatLng(-34.022631, 150.620685), northeast: const LatLng(-33.571835, 151.325952), ); class MapUiPage extends GoogleMapExampleAppPage { const MapUiPage({Key? key}) : super(const Icon(Icons.map), 'User interface', key: key); @override Widget build(BuildContext context) { return const MapUiBody(); } } class MapUiBody extends StatefulWidget { const MapUiBody({Key? key}) : super(key: key); @override State createState() => MapUiBodyState(); } class MapUiBodyState extends State { MapUiBodyState(); static const CameraPosition _kInitialPosition = CameraPosition( target: LatLng(-33.852, 151.211), zoom: 11.0, ); CameraPosition _position = _kInitialPosition; bool _isMapCreated = false; final bool _isMoving = false; bool _compassEnabled = true; bool _mapToolbarEnabled = true; CameraTargetBounds _cameraTargetBounds = CameraTargetBounds.unbounded; MinMaxZoomPreference _minMaxZoomPreference = MinMaxZoomPreference.unbounded; MapType _mapType = MapType.normal; bool _rotateGesturesEnabled = true; bool _scrollGesturesEnabled = true; bool _tiltGesturesEnabled = true; bool _zoomControlsEnabled = false; bool _zoomGesturesEnabled = true; bool _indoorViewEnabled = true; bool _myLocationEnabled = true; bool _myTrafficEnabled = false; bool _myLocationButtonEnabled = true; late ExampleGoogleMapController _controller; bool _nightMode = false; @override void initState() { super.initState(); } @override void dispose() { super.dispose(); } Widget _compassToggler() { return TextButton( child: Text('${_compassEnabled ? 'disable' : 'enable'} compass'), onPressed: () { setState(() { _compassEnabled = !_compassEnabled; }); }, ); } Widget _mapToolbarToggler() { return TextButton( child: Text('${_mapToolbarEnabled ? 'disable' : 'enable'} map toolbar'), onPressed: () { setState(() { _mapToolbarEnabled = !_mapToolbarEnabled; }); }, ); } Widget _latLngBoundsToggler() { return TextButton( child: Text( _cameraTargetBounds.bounds == null ? 'bound camera target' : 'release camera target', ), onPressed: () { setState(() { _cameraTargetBounds = _cameraTargetBounds.bounds == null ? CameraTargetBounds(sydneyBounds) : CameraTargetBounds.unbounded; }); }, ); } Widget _zoomBoundsToggler() { return TextButton( child: Text(_minMaxZoomPreference.minZoom == null ? 'bound zoom' : 'release zoom'), onPressed: () { setState(() { _minMaxZoomPreference = _minMaxZoomPreference.minZoom == null ? const MinMaxZoomPreference(12.0, 16.0) : MinMaxZoomPreference.unbounded; }); }, ); } Widget _mapTypeCycler() { final MapType nextType = MapType.values[(_mapType.index + 1) % MapType.values.length]; return TextButton( child: Text('change map type to $nextType'), onPressed: () { setState(() { _mapType = nextType; }); }, ); } Widget _rotateToggler() { return TextButton( child: Text('${_rotateGesturesEnabled ? 'disable' : 'enable'} rotate'), onPressed: () { setState(() { _rotateGesturesEnabled = !_rotateGesturesEnabled; }); }, ); } Widget _scrollToggler() { return TextButton( child: Text('${_scrollGesturesEnabled ? 'disable' : 'enable'} scroll'), onPressed: () { setState(() { _scrollGesturesEnabled = !_scrollGesturesEnabled; }); }, ); } Widget _tiltToggler() { return TextButton( child: Text('${_tiltGesturesEnabled ? 'disable' : 'enable'} tilt'), onPressed: () { setState(() { _tiltGesturesEnabled = !_tiltGesturesEnabled; }); }, ); } Widget _zoomToggler() { return TextButton( child: Text('${_zoomGesturesEnabled ? 'disable' : 'enable'} zoom'), onPressed: () { setState(() { _zoomGesturesEnabled = !_zoomGesturesEnabled; }); }, ); } Widget _zoomControlsToggler() { return TextButton( child: Text('${_zoomControlsEnabled ? 'disable' : 'enable'} zoom controls'), onPressed: () { setState(() { _zoomControlsEnabled = !_zoomControlsEnabled; }); }, ); } Widget _indoorViewToggler() { return TextButton( child: Text('${_indoorViewEnabled ? 'disable' : 'enable'} indoor'), onPressed: () { setState(() { _indoorViewEnabled = !_indoorViewEnabled; }); }, ); } Widget _myLocationToggler() { return TextButton( child: Text( '${_myLocationEnabled ? 'disable' : 'enable'} my location marker'), onPressed: () { setState(() { _myLocationEnabled = !_myLocationEnabled; }); }, ); } Widget _myLocationButtonToggler() { return TextButton( child: Text( '${_myLocationButtonEnabled ? 'disable' : 'enable'} my location button'), onPressed: () { setState(() { _myLocationButtonEnabled = !_myLocationButtonEnabled; }); }, ); } Widget _myTrafficToggler() { return TextButton( child: Text('${_myTrafficEnabled ? 'disable' : 'enable'} my traffic'), onPressed: () { setState(() { _myTrafficEnabled = !_myTrafficEnabled; }); }, ); } Future _getFileData(String path) async { return rootBundle.loadString(path); } void _setMapStyle(String mapStyle) { setState(() { _nightMode = true; _controller.setMapStyle(mapStyle); }); } // Should only be called if _isMapCreated is true. Widget _nightModeToggler() { assert(_isMapCreated); return TextButton( child: Text('${_nightMode ? 'disable' : 'enable'} night mode'), onPressed: () { if (_nightMode) { setState(() { _nightMode = false; _controller.setMapStyle(null); }); } else { _getFileData('assets/night_mode.json').then(_setMapStyle); } }, ); } @override Widget build(BuildContext context) { final ExampleGoogleMap googleMap = ExampleGoogleMap( onMapCreated: onMapCreated, initialCameraPosition: _kInitialPosition, compassEnabled: _compassEnabled, mapToolbarEnabled: _mapToolbarEnabled, cameraTargetBounds: _cameraTargetBounds, minMaxZoomPreference: _minMaxZoomPreference, mapType: _mapType, rotateGesturesEnabled: _rotateGesturesEnabled, scrollGesturesEnabled: _scrollGesturesEnabled, tiltGesturesEnabled: _tiltGesturesEnabled, zoomGesturesEnabled: _zoomGesturesEnabled, zoomControlsEnabled: _zoomControlsEnabled, indoorViewEnabled: _indoorViewEnabled, myLocationEnabled: _myLocationEnabled, myLocationButtonEnabled: _myLocationButtonEnabled, trafficEnabled: _myTrafficEnabled, onCameraMove: _updateCameraPosition, ); final List columnChildren = [ Padding( padding: const EdgeInsets.all(10.0), child: Center( child: SizedBox( width: 300.0, height: 200.0, child: googleMap, ), ), ), ]; if (_isMapCreated) { columnChildren.add( Expanded( child: ListView( children: [ Text('camera bearing: ${_position.bearing}'), Text( 'camera target: ${_position.target.latitude.toStringAsFixed(4)},' '${_position.target.longitude.toStringAsFixed(4)}'), Text('camera zoom: ${_position.zoom}'), Text('camera tilt: ${_position.tilt}'), Text(_isMoving ? '(Camera moving)' : '(Camera idle)'), _compassToggler(), _mapToolbarToggler(), _latLngBoundsToggler(), _mapTypeCycler(), _zoomBoundsToggler(), _rotateToggler(), _scrollToggler(), _tiltToggler(), _zoomToggler(), _zoomControlsToggler(), _indoorViewToggler(), _myLocationToggler(), _myLocationButtonToggler(), _myTrafficToggler(), _nightModeToggler(), ], ), ), ); } return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: columnChildren, ); } void _updateCameraPosition(CameraPosition position) { setState(() { _position = position; }); } void onMapCreated(ExampleGoogleMapController controller) { setState(() { _controller = controller; _isMapCreated = true; }); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/marker_icons.dart ================================================ // Copyright 2013 The Flutter 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 // ignore_for_file: unawaited_futures import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; class MarkerIconsPage extends GoogleMapExampleAppPage { const MarkerIconsPage({Key? key}) : super(const Icon(Icons.image), 'Marker icons', key: key); @override Widget build(BuildContext context) { return const MarkerIconsBody(); } } class MarkerIconsBody extends StatefulWidget { const MarkerIconsBody({Key? key}) : super(key: key); @override State createState() => MarkerIconsBodyState(); } const LatLng _kMapCenter = LatLng(52.4478, -3.5402); class MarkerIconsBodyState extends State { ExampleGoogleMapController? controller; BitmapDescriptor? _markerIcon; @override Widget build(BuildContext context) { _createMarkerImageFromAsset(context); return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 350.0, height: 300.0, child: ExampleGoogleMap( initialCameraPosition: const CameraPosition( target: _kMapCenter, zoom: 7.0, ), markers: {_createMarker()}, onMapCreated: _onMapCreated, ), ), ) ], ); } Marker _createMarker() { if (_markerIcon != null) { return Marker( markerId: const MarkerId('marker_1'), position: _kMapCenter, icon: _markerIcon!, ); } else { return const Marker( markerId: MarkerId('marker_1'), position: _kMapCenter, ); } } Future _createMarkerImageFromAsset(BuildContext context) async { if (_markerIcon == null) { final ImageConfiguration imageConfiguration = createLocalImageConfiguration(context, size: const Size.square(48)); BitmapDescriptor.fromAssetImage( imageConfiguration, 'assets/red_square.png') .then(_updateBitmap); } } void _updateBitmap(BitmapDescriptor bitmap) { setState(() { _markerIcon = bitmap; }); } void _onMapCreated(ExampleGoogleMapController controllerParam) { setState(() { controller = controllerParam; }); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/move_camera.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; class MoveCameraPage extends GoogleMapExampleAppPage { const MoveCameraPage({Key? key}) : super(const Icon(Icons.map), 'Camera control', key: key); @override Widget build(BuildContext context) { return const MoveCamera(); } } class MoveCamera extends StatefulWidget { const MoveCamera({Key? key}) : super(key: key); @override State createState() => MoveCameraState(); } class MoveCameraState extends State { ExampleGoogleMapController? mapController; // ignore: use_setters_to_change_properties void _onMapCreated(ExampleGoogleMapController controller) { mapController = controller; } @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 300.0, height: 200.0, child: ExampleGoogleMap( onMapCreated: _onMapCreated, initialCameraPosition: const CameraPosition(target: LatLng(0.0, 0.0)), ), ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Column( children: [ TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.newCameraPosition( const CameraPosition( bearing: 270.0, target: LatLng(51.5160895, -0.1294527), tilt: 30.0, zoom: 17.0, ), ), ); }, child: const Text('newCameraPosition'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.newLatLng( const LatLng(56.1725505, 10.1850512), ), ); }, child: const Text('newLatLng'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.newLatLngBounds( LatLngBounds( southwest: const LatLng(-38.483935, 113.248673), northeast: const LatLng(-8.982446, 153.823821), ), 10.0, ), ); }, child: const Text('newLatLngBounds'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.newLatLngZoom( const LatLng(37.4231613, -122.087159), 11.0, ), ); }, child: const Text('newLatLngZoom'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.scrollBy(150.0, -225.0), ); }, child: const Text('scrollBy'), ), ], ), Column( children: [ TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.zoomBy( -0.5, const Offset(30.0, 20.0), ), ); }, child: const Text('zoomBy with focus'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.zoomBy(-0.5), ); }, child: const Text('zoomBy'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.zoomIn(), ); }, child: const Text('zoomIn'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.zoomOut(), ); }, child: const Text('zoomOut'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.zoomTo(16.0), ); }, child: const Text('zoomTo'), ), ], ), ], ) ], ); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/padding.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; class PaddingPage extends GoogleMapExampleAppPage { const PaddingPage({Key? key}) : super(const Icon(Icons.map), 'Add padding to the map', key: key); @override Widget build(BuildContext context) { return const MarkerIconsBody(); } } class MarkerIconsBody extends StatefulWidget { const MarkerIconsBody({Key? key}) : super(key: key); @override State createState() => MarkerIconsBodyState(); } const LatLng _kMapCenter = LatLng(52.4478, -3.5402); class MarkerIconsBodyState extends State { ExampleGoogleMapController? controller; EdgeInsets _padding = EdgeInsets.zero; @override Widget build(BuildContext context) { final ExampleGoogleMap googleMap = ExampleGoogleMap( onMapCreated: _onMapCreated, initialCameraPosition: const CameraPosition( target: _kMapCenter, zoom: 7.0, ), padding: _padding, ); final List columnChildren = [ Padding( padding: const EdgeInsets.all(10.0), child: Center( child: SizedBox( width: 300.0, height: 200.0, child: googleMap, ), ), ), const Padding( padding: EdgeInsets.only(top: 20), child: Center( child: Text( 'Enter Padding Below', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), ), ]; columnChildren.addAll([_paddingInput(), _buttons()]); return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: columnChildren, ); } void _onMapCreated(ExampleGoogleMapController controllerParam) { setState(() { controller = controllerParam; }); } final TextEditingController _topController = TextEditingController(); final TextEditingController _bottomController = TextEditingController(); final TextEditingController _leftController = TextEditingController(); final TextEditingController _rightController = TextEditingController(); Widget _paddingInput() { return Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ Flexible( flex: 2, child: TextField( controller: _topController, keyboardType: TextInputType.number, textAlign: TextAlign.center, decoration: const InputDecoration( hintText: 'Top', ), ), ), const Spacer(), Flexible( flex: 2, child: TextField( controller: _bottomController, keyboardType: TextInputType.number, textAlign: TextAlign.center, decoration: const InputDecoration( hintText: 'Bottom', ), ), ), const Spacer(), Flexible( flex: 2, child: TextField( controller: _leftController, keyboardType: TextInputType.number, textAlign: TextAlign.center, decoration: const InputDecoration( hintText: 'Left', ), ), ), const Spacer(), Flexible( flex: 2, child: TextField( controller: _rightController, keyboardType: TextInputType.number, textAlign: TextAlign.center, decoration: const InputDecoration( hintText: 'Right', ), ), ), ], ), ); } Widget _buttons() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ TextButton( child: const Text('Set Padding'), onPressed: () { setState(() { _padding = EdgeInsets.fromLTRB( double.tryParse(_leftController.value.text) ?? 0, double.tryParse(_topController.value.text) ?? 0, double.tryParse(_rightController.value.text) ?? 0, double.tryParse(_bottomController.value.text) ?? 0); }); }, ), TextButton( child: const Text('Reset Padding'), onPressed: () { setState(() { _topController.clear(); _bottomController.clear(); _leftController.clear(); _rightController.clear(); _padding = EdgeInsets.zero; }); }, ) ], ), ); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/page.dart ================================================ // Copyright 2013 The Flutter 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'; abstract class GoogleMapExampleAppPage extends StatelessWidget { const GoogleMapExampleAppPage(this.leading, this.title, {Key? key}) : super(key: key); final Widget leading; final String title; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_circle.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; class PlaceCirclePage extends GoogleMapExampleAppPage { const PlaceCirclePage({Key? key}) : super(const Icon(Icons.linear_scale), 'Place circle', key: key); @override Widget build(BuildContext context) { return const PlaceCircleBody(); } } class PlaceCircleBody extends StatefulWidget { const PlaceCircleBody({Key? key}) : super(key: key); @override State createState() => PlaceCircleBodyState(); } class PlaceCircleBodyState extends State { PlaceCircleBodyState(); ExampleGoogleMapController? controller; Map circles = {}; int _circleIdCounter = 1; CircleId? selectedCircle; // Values when toggling circle color int fillColorsIndex = 0; int strokeColorsIndex = 0; List colors = [ Colors.purple, Colors.red, Colors.green, Colors.pink, ]; // Values when toggling circle stroke width int widthsIndex = 0; List widths = [10, 20, 5]; // ignore: use_setters_to_change_properties void _onMapCreated(ExampleGoogleMapController controller) { this.controller = controller; } @override void dispose() { super.dispose(); } void _onCircleTapped(CircleId circleId) { setState(() { selectedCircle = circleId; }); } void _remove(CircleId circleId) { setState(() { if (circles.containsKey(circleId)) { circles.remove(circleId); } if (circleId == selectedCircle) { selectedCircle = null; } }); } void _add() { final int circleCount = circles.length; if (circleCount == 12) { return; } final String circleIdVal = 'circle_id_$_circleIdCounter'; _circleIdCounter++; final CircleId circleId = CircleId(circleIdVal); final Circle circle = Circle( circleId: circleId, consumeTapEvents: true, strokeColor: Colors.orange, fillColor: Colors.green, strokeWidth: 5, center: _createCenter(), radius: 50000, onTap: () { _onCircleTapped(circleId); }, ); setState(() { circles[circleId] = circle; }); } void _toggleVisible(CircleId circleId) { final Circle circle = circles[circleId]!; setState(() { circles[circleId] = circle.copyWith( visibleParam: !circle.visible, ); }); } void _changeFillColor(CircleId circleId) { final Circle circle = circles[circleId]!; setState(() { circles[circleId] = circle.copyWith( fillColorParam: colors[++fillColorsIndex % colors.length], ); }); } void _changeStrokeColor(CircleId circleId) { final Circle circle = circles[circleId]!; setState(() { circles[circleId] = circle.copyWith( strokeColorParam: colors[++strokeColorsIndex % colors.length], ); }); } void _changeStrokeWidth(CircleId circleId) { final Circle circle = circles[circleId]!; setState(() { circles[circleId] = circle.copyWith( strokeWidthParam: widths[++widthsIndex % widths.length], ); }); } @override Widget build(BuildContext context) { final CircleId? selectedId = selectedCircle; return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 350.0, height: 300.0, child: ExampleGoogleMap( initialCameraPosition: const CameraPosition( target: LatLng(52.4478, -3.5402), zoom: 7.0, ), circles: Set.of(circles.values), onMapCreated: _onMapCreated, ), ), ), Expanded( child: SingleChildScrollView( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row( children: [ Column( children: [ TextButton( onPressed: _add, child: const Text('add'), ), TextButton( onPressed: (selectedId == null) ? null : () => _remove(selectedId), child: const Text('remove'), ), TextButton( onPressed: (selectedId == null) ? null : () => _toggleVisible(selectedId), child: const Text('toggle visible'), ), ], ), Column( children: [ TextButton( onPressed: (selectedId == null) ? null : () => _changeStrokeWidth(selectedId), child: const Text('change stroke width'), ), TextButton( onPressed: (selectedId == null) ? null : () => _changeStrokeColor(selectedId), child: const Text('change stroke color'), ), TextButton( onPressed: (selectedId == null) ? null : () => _changeFillColor(selectedId), child: const Text('change fill color'), ), ], ) ], ) ], ), ), ), ], ); } LatLng _createCenter() { final double offset = _circleIdCounter.ceilToDouble(); return _createLatLng(51.4816 + offset * 0.2, -3.1791); } LatLng _createLatLng(double lat, double lng) { return LatLng(lat, lng); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_marker.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'dart:math'; import 'dart:typed_data'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; class PlaceMarkerPage extends GoogleMapExampleAppPage { const PlaceMarkerPage({Key? key}) : super(const Icon(Icons.place), 'Place marker', key: key); @override Widget build(BuildContext context) { return const PlaceMarkerBody(); } } class PlaceMarkerBody extends StatefulWidget { const PlaceMarkerBody({Key? key}) : super(key: key); @override State createState() => PlaceMarkerBodyState(); } typedef MarkerUpdateAction = Marker Function(Marker marker); class PlaceMarkerBodyState extends State { PlaceMarkerBodyState(); static const LatLng center = LatLng(-33.86711, 151.1947171); ExampleGoogleMapController? controller; Map markers = {}; MarkerId? selectedMarker; int _markerIdCounter = 1; LatLng? markerPosition; // ignore: use_setters_to_change_properties void _onMapCreated(ExampleGoogleMapController controller) { this.controller = controller; } @override void dispose() { super.dispose(); } void _onMarkerTapped(MarkerId markerId) { final Marker? tappedMarker = markers[markerId]; if (tappedMarker != null) { setState(() { final MarkerId? previousMarkerId = selectedMarker; if (previousMarkerId != null && markers.containsKey(previousMarkerId)) { final Marker resetOld = markers[previousMarkerId]! .copyWith(iconParam: BitmapDescriptor.defaultMarker); markers[previousMarkerId] = resetOld; } selectedMarker = markerId; final Marker newMarker = tappedMarker.copyWith( iconParam: BitmapDescriptor.defaultMarkerWithHue( BitmapDescriptor.hueGreen, ), ); markers[markerId] = newMarker; markerPosition = null; }); } } Future _onMarkerDrag(MarkerId markerId, LatLng newPosition) async { setState(() { markerPosition = newPosition; }); } Future _onMarkerDragEnd(MarkerId markerId, LatLng newPosition) async { final Marker? tappedMarker = markers[markerId]; if (tappedMarker != null) { setState(() { markerPosition = null; }); await showDialog( context: context, builder: (BuildContext context) { return AlertDialog( actions: [ TextButton( child: const Text('OK'), onPressed: () => Navigator.of(context).pop(), ) ], content: Padding( padding: const EdgeInsets.symmetric(vertical: 66), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('Old position: ${tappedMarker.position}'), Text('New position: $newPosition'), ], ))); }); } } void _add() { final int markerCount = markers.length; if (markerCount == 12) { return; } final String markerIdVal = 'marker_id_$_markerIdCounter'; _markerIdCounter++; final MarkerId markerId = MarkerId(markerIdVal); final Marker marker = Marker( markerId: markerId, position: LatLng( center.latitude + sin(_markerIdCounter * pi / 6.0) / 20.0, center.longitude + cos(_markerIdCounter * pi / 6.0) / 20.0, ), infoWindow: InfoWindow(title: markerIdVal, snippet: '*'), onTap: () => _onMarkerTapped(markerId), onDragEnd: (LatLng position) => _onMarkerDragEnd(markerId, position), onDrag: (LatLng position) => _onMarkerDrag(markerId, position), ); setState(() { markers[markerId] = marker; }); } void _remove(MarkerId markerId) { setState(() { if (markers.containsKey(markerId)) { markers.remove(markerId); } }); } void _changePosition(MarkerId markerId) { final Marker marker = markers[markerId]!; final LatLng current = marker.position; final Offset offset = Offset( center.latitude - current.latitude, center.longitude - current.longitude, ); setState(() { markers[markerId] = marker.copyWith( positionParam: LatLng( center.latitude + offset.dy, center.longitude + offset.dx, ), ); }); } void _changeAnchor(MarkerId markerId) { final Marker marker = markers[markerId]!; final Offset currentAnchor = marker.anchor; final Offset newAnchor = Offset(1.0 - currentAnchor.dy, currentAnchor.dx); setState(() { markers[markerId] = marker.copyWith( anchorParam: newAnchor, ); }); } Future _changeInfoAnchor(MarkerId markerId) async { final Marker marker = markers[markerId]!; final Offset currentAnchor = marker.infoWindow.anchor; final Offset newAnchor = Offset(1.0 - currentAnchor.dy, currentAnchor.dx); setState(() { markers[markerId] = marker.copyWith( infoWindowParam: marker.infoWindow.copyWith( anchorParam: newAnchor, ), ); }); } Future _toggleDraggable(MarkerId markerId) async { final Marker marker = markers[markerId]!; setState(() { markers[markerId] = marker.copyWith( draggableParam: !marker.draggable, ); }); } Future _toggleFlat(MarkerId markerId) async { final Marker marker = markers[markerId]!; setState(() { markers[markerId] = marker.copyWith( flatParam: !marker.flat, ); }); } Future _changeInfo(MarkerId markerId) async { final Marker marker = markers[markerId]!; final String newSnippet = '${marker.infoWindow.snippet!}*'; setState(() { markers[markerId] = marker.copyWith( infoWindowParam: marker.infoWindow.copyWith( snippetParam: newSnippet, ), ); }); } Future _changeAlpha(MarkerId markerId) async { final Marker marker = markers[markerId]!; final double current = marker.alpha; setState(() { markers[markerId] = marker.copyWith( alphaParam: current < 0.1 ? 1.0 : current * 0.75, ); }); } Future _changeRotation(MarkerId markerId) async { final Marker marker = markers[markerId]!; final double current = marker.rotation; setState(() { markers[markerId] = marker.copyWith( rotationParam: current == 330.0 ? 0.0 : current + 30.0, ); }); } Future _toggleVisible(MarkerId markerId) async { final Marker marker = markers[markerId]!; setState(() { markers[markerId] = marker.copyWith( visibleParam: !marker.visible, ); }); } Future _changeZIndex(MarkerId markerId) async { final Marker marker = markers[markerId]!; final double current = marker.zIndex; setState(() { markers[markerId] = marker.copyWith( zIndexParam: current == 12.0 ? 0.0 : current + 1.0, ); }); } void _setMarkerIcon(MarkerId markerId, BitmapDescriptor assetIcon) { final Marker marker = markers[markerId]!; setState(() { markers[markerId] = marker.copyWith( iconParam: assetIcon, ); }); } Future _getAssetIcon(BuildContext context) async { final Completer bitmapIcon = Completer(); final ImageConfiguration config = createLocalImageConfiguration(context); const AssetImage('assets/red_square.png') .resolve(config) .addListener(ImageStreamListener((ImageInfo image, bool sync) async { final ByteData? bytes = await image.image.toByteData(format: ImageByteFormat.png); if (bytes == null) { bitmapIcon.completeError(Exception('Unable to encode icon')); return; } final BitmapDescriptor bitmap = BitmapDescriptor.fromBytes(bytes.buffer.asUint8List()); bitmapIcon.complete(bitmap); })); return bitmapIcon.future; } @override Widget build(BuildContext context) { final MarkerId? selectedId = selectedMarker; return Stack(children: [ Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded( child: ExampleGoogleMap( onMapCreated: _onMapCreated, initialCameraPosition: const CameraPosition( target: LatLng(-33.852, 151.211), zoom: 11.0, ), markers: Set.of(markers.values), ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ TextButton( onPressed: _add, child: const Text('Add'), ), TextButton( onPressed: selectedId == null ? null : () => _remove(selectedId), child: const Text('Remove'), ), ], ), Wrap( alignment: WrapAlignment.spaceEvenly, children: [ TextButton( onPressed: selectedId == null ? null : () => _changeInfo(selectedId), child: const Text('change info'), ), TextButton( onPressed: selectedId == null ? null : () => _changeInfoAnchor(selectedId), child: const Text('change info anchor'), ), TextButton( onPressed: selectedId == null ? null : () => _changeAlpha(selectedId), child: const Text('change alpha'), ), TextButton( onPressed: selectedId == null ? null : () => _changeAnchor(selectedId), child: const Text('change anchor'), ), TextButton( onPressed: selectedId == null ? null : () => _toggleDraggable(selectedId), child: const Text('toggle draggable'), ), TextButton( onPressed: selectedId == null ? null : () => _toggleFlat(selectedId), child: const Text('toggle flat'), ), TextButton( onPressed: selectedId == null ? null : () => _changePosition(selectedId), child: const Text('change position'), ), TextButton( onPressed: selectedId == null ? null : () => _changeRotation(selectedId), child: const Text('change rotation'), ), TextButton( onPressed: selectedId == null ? null : () => _toggleVisible(selectedId), child: const Text('toggle visible'), ), TextButton( onPressed: selectedId == null ? null : () => _changeZIndex(selectedId), child: const Text('change zIndex'), ), TextButton( onPressed: selectedId == null ? null : () { _getAssetIcon(context).then( (BitmapDescriptor icon) { _setMarkerIcon(selectedId, icon); }, ); }, child: const Text('set marker icon'), ), ], ), ], ), Visibility( visible: markerPosition != null, child: Container( color: Colors.white70, height: 30, padding: const EdgeInsets.only(left: 12, right: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ if (markerPosition == null) Container() else Expanded(child: Text('lat: ${markerPosition!.latitude}')), if (markerPosition == null) Container() else Expanded(child: Text('lng: ${markerPosition!.longitude}')), ], ), ), ), ]); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_polygon.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; class PlacePolygonPage extends GoogleMapExampleAppPage { const PlacePolygonPage({Key? key}) : super(const Icon(Icons.linear_scale), 'Place polygon', key: key); @override Widget build(BuildContext context) { return const PlacePolygonBody(); } } class PlacePolygonBody extends StatefulWidget { const PlacePolygonBody({Key? key}) : super(key: key); @override State createState() => PlacePolygonBodyState(); } class PlacePolygonBodyState extends State { PlacePolygonBodyState(); ExampleGoogleMapController? controller; Map polygons = {}; Map polygonOffsets = {}; int _polygonIdCounter = 0; PolygonId? selectedPolygon; // Values when toggling polygon color int strokeColorsIndex = 0; int fillColorsIndex = 0; List colors = [ Colors.purple, Colors.red, Colors.green, Colors.pink, ]; // Values when toggling polygon width int widthsIndex = 0; List widths = [10, 20, 5]; // ignore: use_setters_to_change_properties void _onMapCreated(ExampleGoogleMapController controller) { this.controller = controller; } @override void dispose() { super.dispose(); } void _onPolygonTapped(PolygonId polygonId) { setState(() { selectedPolygon = polygonId; }); } void _remove(PolygonId polygonId) { setState(() { if (polygons.containsKey(polygonId)) { polygons.remove(polygonId); } selectedPolygon = null; }); } void _add() { final int polygonCount = polygons.length; if (polygonCount == 12) { return; } final String polygonIdVal = 'polygon_id_$_polygonIdCounter'; final PolygonId polygonId = PolygonId(polygonIdVal); final Polygon polygon = Polygon( polygonId: polygonId, consumeTapEvents: true, strokeColor: Colors.orange, strokeWidth: 5, fillColor: Colors.green, points: _createPoints(), onTap: () { _onPolygonTapped(polygonId); }, ); setState(() { polygons[polygonId] = polygon; polygonOffsets[polygonId] = _polygonIdCounter.ceilToDouble(); // increment _polygonIdCounter to have unique polygon id each time _polygonIdCounter++; }); } void _toggleGeodesic(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith( geodesicParam: !polygon.geodesic, ); }); } void _toggleVisible(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith( visibleParam: !polygon.visible, ); }); } void _changeStrokeColor(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith( strokeColorParam: colors[++strokeColorsIndex % colors.length], ); }); } void _changeFillColor(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith( fillColorParam: colors[++fillColorsIndex % colors.length], ); }); } void _changeWidth(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith( strokeWidthParam: widths[++widthsIndex % widths.length], ); }); } void _addHoles(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith(holesParam: _createHoles(polygonId)); }); } void _removeHoles(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith( holesParam: >[], ); }); } @override Widget build(BuildContext context) { final PolygonId? selectedId = selectedPolygon; return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 350.0, height: 300.0, child: ExampleGoogleMap( initialCameraPosition: const CameraPosition( target: LatLng(52.4478, -3.5402), zoom: 7.0, ), polygons: Set.of(polygons.values), onMapCreated: _onMapCreated, ), ), ), Expanded( child: SingleChildScrollView( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row( children: [ Column( children: [ TextButton( onPressed: _add, child: const Text('add'), ), TextButton( onPressed: (selectedId == null) ? null : () => _remove(selectedId), child: const Text('remove'), ), TextButton( onPressed: (selectedId == null) ? null : () => _toggleVisible(selectedId), child: const Text('toggle visible'), ), TextButton( onPressed: (selectedId == null) ? null : () => _toggleGeodesic(selectedId), child: const Text('toggle geodesic'), ), ], ), Column( children: [ TextButton( onPressed: (selectedId == null) ? null : ((polygons[selectedId]!.holes.isNotEmpty) ? null : () => _addHoles(selectedId)), child: const Text('add holes'), ), TextButton( onPressed: (selectedId == null) ? null : ((polygons[selectedId]!.holes.isEmpty) ? null : () => _removeHoles(selectedId)), child: const Text('remove holes'), ), TextButton( onPressed: (selectedId == null) ? null : () => _changeWidth(selectedId), child: const Text('change stroke width'), ), TextButton( onPressed: (selectedId == null) ? null : () => _changeStrokeColor(selectedId), child: const Text('change stroke color'), ), TextButton( onPressed: (selectedId == null) ? null : () => _changeFillColor(selectedId), child: const Text('change fill color'), ), ], ) ], ) ], ), ), ), ], ); } List _createPoints() { final List points = []; final double offset = _polygonIdCounter.ceilToDouble(); points.add(_createLatLng(51.2395 + offset, -3.4314)); points.add(_createLatLng(53.5234 + offset, -3.5314)); points.add(_createLatLng(52.4351 + offset, -4.5235)); points.add(_createLatLng(52.1231 + offset, -5.0829)); return points; } List> _createHoles(PolygonId polygonId) { final List> holes = >[]; final double offset = polygonOffsets[polygonId]!; final List hole1 = []; hole1.add(_createLatLng(51.8395 + offset, -3.8814)); hole1.add(_createLatLng(52.0234 + offset, -3.9914)); hole1.add(_createLatLng(52.1351 + offset, -4.4435)); hole1.add(_createLatLng(52.0231 + offset, -4.5829)); holes.add(hole1); final List hole2 = []; hole2.add(_createLatLng(52.2395 + offset, -3.6814)); hole2.add(_createLatLng(52.4234 + offset, -3.7914)); hole2.add(_createLatLng(52.5351 + offset, -4.2435)); hole2.add(_createLatLng(52.4231 + offset, -4.3829)); holes.add(hole2); return holes; } LatLng _createLatLng(double lat, double lng) { return LatLng(lat, lng); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_polyline.dart ================================================ // Copyright 2013 The Flutter 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/foundation.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; class PlacePolylinePage extends GoogleMapExampleAppPage { const PlacePolylinePage({Key? key}) : super(const Icon(Icons.linear_scale), 'Place polyline', key: key); @override Widget build(BuildContext context) { return const PlacePolylineBody(); } } class PlacePolylineBody extends StatefulWidget { const PlacePolylineBody({Key? key}) : super(key: key); @override State createState() => PlacePolylineBodyState(); } class PlacePolylineBodyState extends State { PlacePolylineBodyState(); ExampleGoogleMapController? controller; Map polylines = {}; int _polylineIdCounter = 0; PolylineId? selectedPolyline; // Values when toggling polyline color int colorsIndex = 0; List colors = [ Colors.purple, Colors.red, Colors.green, Colors.pink, ]; // Values when toggling polyline width int widthsIndex = 0; List widths = [10, 20, 5]; int jointTypesIndex = 0; List jointTypes = [ JointType.mitered, JointType.bevel, JointType.round ]; // Values when toggling polyline end cap type int endCapsIndex = 0; List endCaps = [Cap.buttCap, Cap.squareCap, Cap.roundCap]; // Values when toggling polyline start cap type int startCapsIndex = 0; List startCaps = [Cap.buttCap, Cap.squareCap, Cap.roundCap]; // Values when toggling polyline pattern int patternsIndex = 0; List> patterns = >[ [], [ PatternItem.dash(30.0), PatternItem.gap(20.0), PatternItem.dot, PatternItem.gap(20.0) ], [PatternItem.dash(30.0), PatternItem.gap(20.0)], [PatternItem.dot, PatternItem.gap(10.0)], ]; // ignore: use_setters_to_change_properties void _onMapCreated(ExampleGoogleMapController controller) { this.controller = controller; } @override void dispose() { super.dispose(); } void _onPolylineTapped(PolylineId polylineId) { setState(() { selectedPolyline = polylineId; }); } void _remove(PolylineId polylineId) { setState(() { if (polylines.containsKey(polylineId)) { polylines.remove(polylineId); } selectedPolyline = null; }); } void _add() { final int polylineCount = polylines.length; if (polylineCount == 12) { return; } final String polylineIdVal = 'polyline_id_$_polylineIdCounter'; _polylineIdCounter++; final PolylineId polylineId = PolylineId(polylineIdVal); final Polyline polyline = Polyline( polylineId: polylineId, consumeTapEvents: true, color: Colors.orange, width: 5, points: _createPoints(), onTap: () { _onPolylineTapped(polylineId); }, ); setState(() { polylines[polylineId] = polyline; }); } void _toggleGeodesic(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( geodesicParam: !polyline.geodesic, ); }); } void _toggleVisible(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( visibleParam: !polyline.visible, ); }); } void _changeColor(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( colorParam: colors[++colorsIndex % colors.length], ); }); } void _changeWidth(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( widthParam: widths[++widthsIndex % widths.length], ); }); } void _changeJointType(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( jointTypeParam: jointTypes[++jointTypesIndex % jointTypes.length], ); }); } void _changeEndCap(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( endCapParam: endCaps[++endCapsIndex % endCaps.length], ); }); } void _changeStartCap(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( startCapParam: startCaps[++startCapsIndex % startCaps.length], ); }); } void _changePattern(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( patternsParam: patterns[++patternsIndex % patterns.length], ); }); } @override Widget build(BuildContext context) { final bool isIOS = !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS; final PolylineId? selectedId = selectedPolyline; return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 350.0, height: 300.0, child: ExampleGoogleMap( initialCameraPosition: const CameraPosition( target: LatLng(53.1721, -3.5402), zoom: 7.0, ), polylines: Set.of(polylines.values), onMapCreated: _onMapCreated, ), ), ), Expanded( child: SingleChildScrollView( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row( children: [ Column( children: [ TextButton( onPressed: _add, child: const Text('add'), ), TextButton( onPressed: (selectedId == null) ? null : () => _remove(selectedId), child: const Text('remove'), ), TextButton( onPressed: (selectedId == null) ? null : () => _toggleVisible(selectedId), child: const Text('toggle visible'), ), TextButton( onPressed: (selectedId == null) ? null : () => _toggleGeodesic(selectedId), child: const Text('toggle geodesic'), ), ], ), Column( children: [ TextButton( onPressed: (selectedId == null) ? null : () => _changeWidth(selectedId), child: const Text('change width'), ), TextButton( onPressed: (selectedId == null) ? null : () => _changeColor(selectedId), child: const Text('change color'), ), TextButton( onPressed: isIOS || (selectedId == null) ? null : () => _changeStartCap(selectedId), child: const Text('change start cap [Android only]'), ), TextButton( onPressed: isIOS || (selectedId == null) ? null : () => _changeEndCap(selectedId), child: const Text('change end cap [Android only]'), ), TextButton( onPressed: isIOS || (selectedId == null) ? null : () => _changeJointType(selectedId), child: const Text('change joint type [Android only]'), ), TextButton( onPressed: isIOS || (selectedId == null) ? null : () => _changePattern(selectedId), child: const Text('change pattern [Android only]'), ), ], ) ], ) ], ), ), ), ], ); } List _createPoints() { final List points = []; final double offset = _polylineIdCounter.ceilToDouble(); points.add(_createLatLng(51.4816 + offset, -3.1791)); points.add(_createLatLng(53.0430 + offset, -2.9925)); points.add(_createLatLng(53.1396 + offset, -4.2739)); points.add(_createLatLng(52.4153 + offset, -4.0829)); return points; } LatLng _createLatLng(double lat, double lng) { return LatLng(lat, lng); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/readme_excerpts.dart ================================================ // Copyright 2013 The Flutter 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'; // #docregion DisplayMode import 'package:google_maps_flutter_android/google_maps_flutter_android.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; void main() { // Require Hybrid Composition mode on Android. final GoogleMapsFlutterPlatform mapsImplementation = GoogleMapsFlutterPlatform.instance; if (mapsImplementation is GoogleMapsFlutterAndroid) { mapsImplementation.useAndroidViewSurface = true; } // #enddocregion DisplayMode runApp(const MyApp()); // #docregion DisplayMode } // #enddocregion DisplayMode class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { // #docregion MapRenderer AndroidMapRenderer mapRenderer = AndroidMapRenderer.platformDefault; // #enddocregion MapRenderer @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('README snippet app'), ), body: const Text('See example in main.dart'), ), ); } Future initializeLatestMapRenderer() async { // #docregion MapRenderer final GoogleMapsFlutterPlatform mapsImplementation = GoogleMapsFlutterPlatform.instance; if (mapsImplementation is GoogleMapsFlutterAndroid) { WidgetsFlutterBinding.ensureInitialized(); mapRenderer = await mapsImplementation .initializeWithRenderer(AndroidMapRenderer.latest); } // #enddocregion MapRenderer } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/scrolling_map.dart ================================================ // Copyright 2013 The Flutter 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/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; const LatLng _center = LatLng(32.080664, 34.9563837); class ScrollingMapPage extends GoogleMapExampleAppPage { const ScrollingMapPage({Key? key}) : super(const Icon(Icons.map), 'Scrolling map', key: key); @override Widget build(BuildContext context) { return const ScrollingMapBody(); } } class ScrollingMapBody extends StatelessWidget { const ScrollingMapBody({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return ListView( children: [ Card( child: Padding( padding: const EdgeInsets.symmetric(vertical: 30.0), child: Column( children: [ const Padding( padding: EdgeInsets.only(bottom: 12.0), child: Text('This map consumes all touch events.'), ), Center( child: SizedBox( width: 300.0, height: 300.0, child: ExampleGoogleMap( initialCameraPosition: const CameraPosition( target: _center, zoom: 11.0, ), gestureRecognizers: // >{ Factory( () => EagerGestureRecognizer(), ), }, ), ), ), ], ), ), ), Card( child: Padding( padding: const EdgeInsets.symmetric(vertical: 30.0), child: Column( children: [ const Text("This map doesn't consume the vertical drags."), const Padding( padding: EdgeInsets.only(bottom: 12.0), child: Text('It still gets other gestures (e.g scale or tap).'), ), Center( child: SizedBox( width: 300.0, height: 300.0, child: ExampleGoogleMap( initialCameraPosition: const CameraPosition( target: _center, zoom: 11.0, ), markers: { Marker( markerId: const MarkerId('test_marker_id'), position: LatLng( _center.latitude, _center.longitude, ), infoWindow: const InfoWindow( title: 'An interesting location', snippet: '*', ), ), }, gestureRecognizers: < Factory>{ Factory( () => ScaleGestureRecognizer(), ), }, ), ), ), ], ), ), ), ], ); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/snapshot.dart ================================================ // Copyright 2013 The Flutter 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 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; const CameraPosition _kInitialPosition = CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0); class SnapshotPage extends GoogleMapExampleAppPage { const SnapshotPage({Key? key}) : super(const Icon(Icons.camera_alt), 'Take a snapshot of the map', key: key); @override Widget build(BuildContext context) { return _SnapshotBody(); } } class _SnapshotBody extends StatefulWidget { @override _SnapshotBodyState createState() => _SnapshotBodyState(); } class _SnapshotBodyState extends State<_SnapshotBody> { ExampleGoogleMapController? _mapController; Uint8List? _imageBytes; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ SizedBox( height: 180, child: ExampleGoogleMap( onMapCreated: onMapCreated, initialCameraPosition: _kInitialPosition, ), ), TextButton( child: const Text('Take a snapshot'), onPressed: () async { final Uint8List? imageBytes = await _mapController?.takeSnapshot(); setState(() { _imageBytes = imageBytes; }); }, ), Container( decoration: BoxDecoration(color: Colors.blueGrey[50]), height: 180, child: _imageBytes != null ? Image.memory(_imageBytes!) : null, ), ], ), ); } // ignore: use_setters_to_change_properties void onMapCreated(ExampleGoogleMapController controller) { _mapController = controller; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/lib/tile_overlay.dart ================================================ // Copyright 2013 The Flutter 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 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; class TileOverlayPage extends GoogleMapExampleAppPage { const TileOverlayPage({Key? key}) : super(const Icon(Icons.map), 'Tile overlay', key: key); @override Widget build(BuildContext context) { return const TileOverlayBody(); } } class TileOverlayBody extends StatefulWidget { const TileOverlayBody({Key? key}) : super(key: key); @override State createState() => TileOverlayBodyState(); } class TileOverlayBodyState extends State { TileOverlayBodyState(); ExampleGoogleMapController? controller; TileOverlay? _tileOverlay; // ignore: use_setters_to_change_properties void _onMapCreated(ExampleGoogleMapController controller) { this.controller = controller; } @override void dispose() { super.dispose(); } void _removeTileOverlay() { setState(() { _tileOverlay = null; }); } void _addTileOverlay() { final TileOverlay tileOverlay = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_1'), tileProvider: _DebugTileProvider(), ); setState(() { _tileOverlay = tileOverlay; }); } void _clearTileCache() { if (_tileOverlay != null && controller != null) { controller!.clearTileCache(_tileOverlay!.tileOverlayId); } } @override Widget build(BuildContext context) { final Set overlays = { if (_tileOverlay != null) _tileOverlay!, }; return Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 350.0, height: 300.0, child: ExampleGoogleMap( initialCameraPosition: const CameraPosition( target: LatLng(59.935460, 30.325177), zoom: 7.0, ), tileOverlays: overlays, onMapCreated: _onMapCreated, ), ), ), TextButton( onPressed: _addTileOverlay, child: const Text('Add tile overlay'), ), TextButton( onPressed: _removeTileOverlay, child: const Text('Remove tile overlay'), ), TextButton( onPressed: _clearTileCache, child: const Text('Clear tile cache'), ), ], ); } } class _DebugTileProvider implements TileProvider { _DebugTileProvider() { boxPaint.isAntiAlias = true; boxPaint.color = Colors.blue; boxPaint.strokeWidth = 2.0; boxPaint.style = PaintingStyle.stroke; } static const int width = 100; static const int height = 100; static final Paint boxPaint = Paint(); static const TextStyle textStyle = TextStyle( color: Colors.red, fontSize: 20, ); @override Future getTile(int x, int y, int? zoom) async { final ui.PictureRecorder recorder = ui.PictureRecorder(); final Canvas canvas = Canvas(recorder); final TextSpan textSpan = TextSpan( text: '$x,$y', style: textStyle, ); final TextPainter textPainter = TextPainter( text: textSpan, textDirection: TextDirection.ltr, ); textPainter.layout( maxWidth: width.toDouble(), ); textPainter.paint(canvas, Offset.zero); canvas.drawRect( Rect.fromLTRB(0, 0, width.toDouble(), width.toDouble()), boxPaint); final ui.Picture picture = recorder.endRecording(); final Uint8List byteData = await picture .toImage(width, height) .then((ui.Image image) => image.toByteData(format: ui.ImageByteFormat.png)) .then((ByteData? byteData) => byteData!.buffer.asUint8List()); return Tile(width, height, byteData); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml ================================================ name: google_maps_flutter_example description: Demonstrates how to use the google_maps_flutter plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: cupertino_icons: ^1.0.5 flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.1 google_maps_flutter_android: # When depending on this package from a real application you should use: # google_maps_flutter_android: ^x.y.z # See https://dart.dev/tools/pub/dependencies#version-constraints # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ google_maps_flutter_platform_interface: ^2.2.1 dev_dependencies: build_runner: ^2.1.10 espresso: ^0.2.0 flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true assets: - assets/ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/lib/google_maps_flutter_android.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'src/google_maps_flutter_android.dart'; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_map_inspector_android.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; /// An Android of implementation of [GoogleMapsInspectorPlatform]. @visibleForTesting class GoogleMapsInspectorAndroid extends GoogleMapsInspectorPlatform { /// Creates a method-channel-based inspector instance that gets the channel /// for a given map ID from [channelProvider]. GoogleMapsInspectorAndroid(MethodChannel? Function(int mapId) channelProvider) : _channelProvider = channelProvider; final MethodChannel? Function(int mapId) _channelProvider; @override Future areBuildingsEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isBuildingsEnabled'))!; } @override Future areRotateGesturesEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isRotateGesturesEnabled'))!; } @override Future areScrollGesturesEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isScrollGesturesEnabled'))!; } @override Future areTiltGesturesEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isTiltGesturesEnabled'))!; } @override Future areZoomControlsEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isZoomControlsEnabled'))!; } @override Future areZoomGesturesEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isZoomGesturesEnabled'))!; } @override Future getMinMaxZoomLevels({required int mapId}) async { final List zoomLevels = (await _channelProvider(mapId)! .invokeMethod>('map#getMinMaxZoomLevels'))! .cast(); return MinMaxZoomPreference(zoomLevels[0], zoomLevels[1]); } @override Future getTileOverlayInfo(TileOverlayId tileOverlayId, {required int mapId}) async { final Map? tileInfo = await _channelProvider(mapId)! .invokeMapMethod( 'map#getTileOverlayInfo', { 'tileOverlayId': tileOverlayId.value, }); if (tileInfo == null) { return null; } return TileOverlay( tileOverlayId: tileOverlayId, fadeIn: tileInfo['fadeIn']! as bool, transparency: tileInfo['transparency']! as double, visible: tileInfo['visible']! as bool, // Android and iOS return different types. zIndex: (tileInfo['zIndex']! as num).toInt(), ); } @override Future isCompassEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isCompassEnabled'))!; } @override Future isLiteModeEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isLiteModeEnabled'))!; } @override Future isMapToolbarEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isMapToolbarEnabled'))!; } @override Future isMyLocationButtonEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isMyLocationButtonEnabled'))!; } @override Future isTrafficEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isTrafficEnabled'))!; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart ================================================ // Copyright 2013 The Flutter 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'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:stream_transform/stream_transform.dart'; import 'google_map_inspector_android.dart'; // TODO(stuartmorgan): Remove the dependency on platform interface toJson // methods. Channel serialization details should all be package-internal. /// Error thrown when an unknown map ID is provided to a method channel API. class UnknownMapIDError extends Error { /// Creates an assertion error with the provided [mapId] and optional /// [message]. UnknownMapIDError(this.mapId, [this.message]); /// The unknown ID. final int mapId; /// Message describing the assertion error. final Object? message; @override String toString() { if (message != null) { return 'Unknown map ID $mapId: ${Error.safeToString(message)}'; } return 'Unknown map ID $mapId'; } } /// The possible android map renderer types that can be /// requested from the native Google Maps SDK. enum AndroidMapRenderer { /// Latest renderer type. latest, /// Legacy renderer type. legacy, /// Requests the default map renderer type. platformDefault, } /// An implementation of [GoogleMapsFlutterPlatform] for Android. class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { /// Registers the Android implementation of GoogleMapsFlutterPlatform. static void registerWith() { GoogleMapsFlutterPlatform.instance = GoogleMapsFlutterAndroid(); } /// The method channel used to initialize the native Google Maps SDK. final MethodChannel _initializerChannel = const MethodChannel( 'plugins.flutter.dev/google_maps_android_initializer'); // Keep a collection of id -> channel // Every method call passes the int mapId final Map _channels = {}; /// Accesses the MethodChannel associated to the passed mapId. MethodChannel _channel(int mapId) { final MethodChannel? channel = _channels[mapId]; if (channel == null) { throw UnknownMapIDError(mapId); } return channel; } // Keep a collection of mapId to a map of TileOverlays. final Map> _tileOverlays = >{}; /// Returns the channel for [mapId], creating it if it doesn't already exist. @visibleForTesting MethodChannel ensureChannelInitialized(int mapId) { MethodChannel? channel = _channels[mapId]; if (channel == null) { channel = MethodChannel('plugins.flutter.dev/google_maps_android_$mapId'); channel.setMethodCallHandler( (MethodCall call) => _handleMethodCall(call, mapId)); _channels[mapId] = channel; } return channel; } @override Future init(int mapId) { final MethodChannel channel = ensureChannelInitialized(mapId); return channel.invokeMethod('map#waitForMap'); } @override void dispose({required int mapId}) { // Noop! } // The controller we need to broadcast the different events coming // from handleMethodCall. // // It is a `broadcast` because multiple controllers will connect to // different stream views of this Controller. final StreamController> _mapEventStreamController = StreamController>.broadcast(); // Returns a filtered view of the events in the _controller, by mapId. Stream> _events(int mapId) => _mapEventStreamController.stream .where((MapEvent event) => event.mapId == mapId); @override Stream onCameraMoveStarted({required int mapId}) { return _events(mapId).whereType(); } @override Stream onCameraMove({required int mapId}) { return _events(mapId).whereType(); } @override Stream onCameraIdle({required int mapId}) { return _events(mapId).whereType(); } @override Stream onMarkerTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onInfoWindowTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onMarkerDragStart({required int mapId}) { return _events(mapId).whereType(); } @override Stream onMarkerDrag({required int mapId}) { return _events(mapId).whereType(); } @override Stream onMarkerDragEnd({required int mapId}) { return _events(mapId).whereType(); } @override Stream onPolylineTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onPolygonTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onCircleTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onLongPress({required int mapId}) { return _events(mapId).whereType(); } Future _handleMethodCall(MethodCall call, int mapId) async { switch (call.method) { case 'camera#onMoveStarted': _mapEventStreamController.add(CameraMoveStartedEvent(mapId)); break; case 'camera#onMove': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(CameraMoveEvent( mapId, 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(arguments['markerId']! as String), )); break; case 'marker#onDragStart': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerDragStartEvent( mapId, LatLng.fromJson(arguments['position'])!, MarkerId(arguments['markerId']! as String), )); break; case 'marker#onDrag': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerDragEvent( mapId, LatLng.fromJson(arguments['position'])!, MarkerId(arguments['markerId']! as String), )); break; case 'marker#onDragEnd': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerDragEndEvent( mapId, LatLng.fromJson(arguments['position'])!, MarkerId(arguments['markerId']! as String), )); break; case 'infoWindow#onTap': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(InfoWindowTapEvent( mapId, MarkerId(arguments['markerId']! as String), )); break; case 'polyline#onTap': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(PolylineTapEvent( mapId, PolylineId(arguments['polylineId']! as String), )); break; case 'polygon#onTap': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(PolygonTapEvent( mapId, PolygonId(arguments['polygonId']! as String), )); break; case 'circle#onTap': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(CircleTapEvent( mapId, CircleId(arguments['circleId']! as String), )); break; case 'map#onTap': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MapTapEvent( mapId, LatLng.fromJson(arguments['position'])!, )); break; case 'map#onLongPress': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MapLongPressEvent( mapId, LatLng.fromJson(arguments['position'])!, )); break; case 'tileOverlay#getTile': final Map arguments = _getArgumentDictionary(call); final Map? tileOverlaysForThisMap = _tileOverlays[mapId]; final String tileOverlayId = arguments['tileOverlayId']! as String; final TileOverlay? tileOverlay = tileOverlaysForThisMap?[TileOverlayId(tileOverlayId)]; final TileProvider? tileProvider = tileOverlay?.tileProvider; if (tileProvider == null) { return TileProvider.noTile.toJson(); } final Tile tile = await tileProvider.getTile( arguments['x']! as int, arguments['y']! as int, arguments['zoom'] as int?, ); return tile.toJson(); 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(); } @override Future updateMapOptions( Map optionsUpdate, { required int mapId, }) { assert(optionsUpdate != null); return _channel(mapId).invokeMethod( 'map#update', { 'options': optionsUpdate, }, ); } @override Future updateMarkers( MarkerUpdates markerUpdates, { required int mapId, }) { assert(markerUpdates != null); return _channel(mapId).invokeMethod( 'markers#update', markerUpdates.toJson(), ); } @override Future updatePolygons( PolygonUpdates polygonUpdates, { required int mapId, }) { assert(polygonUpdates != null); return _channel(mapId).invokeMethod( 'polygons#update', polygonUpdates.toJson(), ); } @override Future updatePolylines( PolylineUpdates polylineUpdates, { required int mapId, }) { assert(polylineUpdates != null); return _channel(mapId).invokeMethod( 'polylines#update', polylineUpdates.toJson(), ); } @override Future updateCircles( CircleUpdates circleUpdates, { required int mapId, }) { assert(circleUpdates != null); return _channel(mapId).invokeMethod( 'circles#update', circleUpdates.toJson(), ); } @override Future updateTileOverlays({ required Set newTileOverlays, required int mapId, }) { final Map? currentTileOverlays = _tileOverlays[mapId]; final Set previousSet = currentTileOverlays != null ? currentTileOverlays.values.toSet() : {}; final _TileOverlayUpdates updates = _TileOverlayUpdates.from(previousSet, newTileOverlays); _tileOverlays[mapId] = keyTileOverlayId(newTileOverlays); return _channel(mapId).invokeMethod( 'tileOverlays#update', updates.toJson(), ); } @override Future clearTileCache( TileOverlayId tileOverlayId, { required int mapId, }) { return _channel(mapId) .invokeMethod('tileOverlays#clearTileCache', { 'tileOverlayId': tileOverlayId.value, }); } @override Future animateCamera( CameraUpdate cameraUpdate, { required int mapId, }) { return _channel(mapId) .invokeMethod('camera#animate', { 'cameraUpdate': cameraUpdate.toJson(), }); } @override Future moveCamera( CameraUpdate cameraUpdate, { required int mapId, }) { return _channel(mapId).invokeMethod('camera#move', { 'cameraUpdate': cameraUpdate.toJson(), }); } @override Future setMapStyle( String? mapStyle, { required int mapId, }) async { final List successAndError = (await _channel(mapId) .invokeMethod>('map#setStyle', mapStyle))!; final bool success = successAndError[0] as bool; if (!success) { throw MapStyleException(successAndError[1] as String); } } @override Future getVisibleRegion({ required int mapId, }) async { final Map latLngBounds = (await _channel(mapId) .invokeMapMethod('map#getVisibleRegion'))!; final LatLng southwest = LatLng.fromJson(latLngBounds['southwest'])!; final LatLng northeast = LatLng.fromJson(latLngBounds['northeast'])!; return LatLngBounds(northeast: northeast, southwest: southwest); } @override Future getScreenCoordinate( LatLng latLng, { required int mapId, }) async { final Map point = (await _channel(mapId) .invokeMapMethod( 'map#getScreenCoordinate', latLng.toJson()))!; return ScreenCoordinate(x: point['x']!, y: point['y']!); } @override Future getLatLng( ScreenCoordinate screenCoordinate, { required int mapId, }) async { final List latLng = (await _channel(mapId) .invokeMethod>( 'map#getLatLng', screenCoordinate.toJson()))!; return LatLng(latLng[0] as double, latLng[1] as double); } @override Future showMarkerInfoWindow( MarkerId markerId, { required int mapId, }) { assert(markerId != null); return _channel(mapId).invokeMethod( 'markers#showInfoWindow', {'markerId': markerId.value}); } @override Future hideMarkerInfoWindow( MarkerId markerId, { required int mapId, }) { assert(markerId != null); return _channel(mapId).invokeMethod( 'markers#hideInfoWindow', {'markerId': markerId.value}); } @override Future isMarkerInfoWindowShown( MarkerId markerId, { required int mapId, }) async { assert(markerId != null); return (await _channel(mapId).invokeMethod( 'markers#isInfoWindowShown', {'markerId': markerId.value}))!; } @override Future getZoomLevel({ required int mapId, }) async { return (await _channel(mapId).invokeMethod('map#getZoomLevel'))!; } @override Future takeSnapshot({ required int mapId, }) { return _channel(mapId).invokeMethod('map#takeSnapshot'); } /// Set [GoogleMapsFlutterPlatform] to use [AndroidViewSurface] to build the /// Google Maps widget. /// /// See https://pub.dev/packages/google_maps_flutter_android#display-mode /// for more information. /// /// Currently defaults to true, but the default is subject to change. bool useAndroidViewSurface = true; /// Requests Google Map Renderer with [AndroidMapRenderer] type. /// /// See https://pub.dev/packages/google_maps_flutter_android#map-renderer /// for more information. /// /// The renderer must be requested before creating GoogleMap instances as the /// renderer can be initialized only once per application context. /// Throws a [PlatformException] if method is called multiple times. /// /// The returned [Future] completes after renderer has been initialized. /// Initialized [AndroidMapRenderer] type is returned. Future initializeWithRenderer( AndroidMapRenderer? rendererType) async { String preferredRenderer; switch (rendererType) { case AndroidMapRenderer.latest: preferredRenderer = 'latest'; break; case AndroidMapRenderer.legacy: preferredRenderer = 'legacy'; break; case AndroidMapRenderer.platformDefault: case null: preferredRenderer = 'default'; } final String? initializedRenderer = await _initializerChannel .invokeMethod('initializer#preferRenderer', {'value': preferredRenderer}); if (initializedRenderer == null) { throw AndroidMapRendererException('Failed to initialize map renderer.'); } // Returns mapped [AndroidMapRenderer] enum type. switch (initializedRenderer) { case 'latest': return AndroidMapRenderer.latest; case 'legacy': return AndroidMapRenderer.legacy; default: throw AndroidMapRendererException( 'Failed to initialize latest or legacy renderer, got $initializedRenderer.'); } } Widget _buildView( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { required MapWidgetConfiguration widgetConfiguration, MapObjects mapObjects = const MapObjects(), Map mapOptions = const {}, }) { final Map creationParams = { 'initialCameraPosition': widgetConfiguration.initialCameraPosition.toMap(), 'options': mapOptions, 'markersToAdd': serializeMarkerSet(mapObjects.markers), 'polygonsToAdd': serializePolygonSet(mapObjects.polygons), 'polylinesToAdd': serializePolylineSet(mapObjects.polylines), 'circlesToAdd': serializeCircleSet(mapObjects.circles), 'tileOverlaysToAdd': serializeTileOverlaySet(mapObjects.tileOverlays), }; const String viewType = 'plugins.flutter.dev/google_maps_android'; if (useAndroidViewSurface) { return PlatformViewLink( viewType: viewType, surfaceFactory: ( BuildContext context, PlatformViewController controller, ) { return AndroidViewSurface( controller: controller as AndroidViewController, gestureRecognizers: widgetConfiguration.gestureRecognizers, hitTestBehavior: PlatformViewHitTestBehavior.opaque, ); }, onCreatePlatformView: (PlatformViewCreationParams params) { final AndroidViewController controller = PlatformViewsService.initExpensiveAndroidView( id: params.id, viewType: viewType, layoutDirection: widgetConfiguration.textDirection, creationParams: creationParams, creationParamsCodec: const StandardMessageCodec(), onFocus: () => params.onFocusChanged(true), ); controller.addOnPlatformViewCreatedListener( params.onPlatformViewCreated, ); controller.addOnPlatformViewCreatedListener( onPlatformViewCreated, ); controller.create(); return controller; }, ); } else { return AndroidView( viewType: viewType, onPlatformViewCreated: onPlatformViewCreated, gestureRecognizers: widgetConfiguration.gestureRecognizers, creationParams: creationParams, creationParamsCodec: const StandardMessageCodec(), ); } } @override Widget buildViewWithConfiguration( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { required MapWidgetConfiguration widgetConfiguration, MapConfiguration mapConfiguration = const MapConfiguration(), MapObjects mapObjects = const MapObjects(), }) { return _buildView( creationId, onPlatformViewCreated, widgetConfiguration: widgetConfiguration, mapObjects: mapObjects, mapOptions: _jsonForMapConfiguration(mapConfiguration), ); } @override Widget buildViewWithTextDirection( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { required CameraPosition initialCameraPosition, required TextDirection textDirection, Set markers = const {}, Set polygons = const {}, Set polylines = const {}, Set circles = const {}, Set tileOverlays = const {}, Set>? gestureRecognizers, Map mapOptions = const {}, }) { return _buildView( creationId, onPlatformViewCreated, widgetConfiguration: MapWidgetConfiguration( initialCameraPosition: initialCameraPosition, textDirection: textDirection), mapObjects: MapObjects( markers: markers, polygons: polygons, polylines: polylines, circles: circles, tileOverlays: tileOverlays), mapOptions: mapOptions, ); } @override Widget buildView( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { required CameraPosition initialCameraPosition, Set markers = const {}, Set polygons = const {}, Set polylines = const {}, Set circles = const {}, Set tileOverlays = const {}, Set>? gestureRecognizers, Map mapOptions = const {}, }) { return buildViewWithTextDirection( creationId, onPlatformViewCreated, initialCameraPosition: initialCameraPosition, textDirection: TextDirection.ltr, markers: markers, polygons: polygons, polylines: polylines, circles: circles, tileOverlays: tileOverlays, gestureRecognizers: gestureRecognizers, mapOptions: mapOptions, ); } @override @visibleForTesting void enableDebugInspection() { GoogleMapsInspectorPlatform.instance = GoogleMapsInspectorAndroid((int mapId) => _channel(mapId)); } } Map _jsonForMapConfiguration(MapConfiguration config) { final EdgeInsets? padding = config.padding; return { if (config.compassEnabled != null) 'compassEnabled': config.compassEnabled!, if (config.mapToolbarEnabled != null) 'mapToolbarEnabled': config.mapToolbarEnabled!, if (config.cameraTargetBounds != null) 'cameraTargetBounds': config.cameraTargetBounds!.toJson(), if (config.mapType != null) 'mapType': config.mapType!.index, if (config.minMaxZoomPreference != null) 'minMaxZoomPreference': config.minMaxZoomPreference!.toJson(), if (config.rotateGesturesEnabled != null) 'rotateGesturesEnabled': config.rotateGesturesEnabled!, if (config.scrollGesturesEnabled != null) 'scrollGesturesEnabled': config.scrollGesturesEnabled!, if (config.tiltGesturesEnabled != null) 'tiltGesturesEnabled': config.tiltGesturesEnabled!, if (config.zoomControlsEnabled != null) 'zoomControlsEnabled': config.zoomControlsEnabled!, if (config.zoomGesturesEnabled != null) 'zoomGesturesEnabled': config.zoomGesturesEnabled!, if (config.liteModeEnabled != null) 'liteModeEnabled': config.liteModeEnabled!, if (config.trackCameraPosition != null) 'trackCameraPosition': config.trackCameraPosition!, if (config.myLocationEnabled != null) 'myLocationEnabled': config.myLocationEnabled!, if (config.myLocationButtonEnabled != null) 'myLocationButtonEnabled': config.myLocationButtonEnabled!, if (padding != null) 'padding': [ padding.top, padding.left, padding.bottom, padding.right, ], if (config.indoorViewEnabled != null) 'indoorEnabled': config.indoorViewEnabled!, if (config.trafficEnabled != null) 'trafficEnabled': config.trafficEnabled!, if (config.buildingsEnabled != null) 'buildingsEnabled': config.buildingsEnabled!, }; } /// Update specification for a set of [TileOverlay]s. // TODO(stuartmorgan): Fix the missing export of this class in the platform // interface, and remove this copy. class _TileOverlayUpdates extends MapsObjectUpdates { /// Computes [TileOverlayUpdates] given previous and current [TileOverlay]s. _TileOverlayUpdates.from(Set previous, Set current) : super.from(previous, current, objectName: 'tileOverlay'); /// Set of TileOverlays to be added in this update. Set get tileOverlaysToAdd => objectsToAdd; /// Set of TileOverlayIds to be removed in this update. Set get tileOverlayIdsToRemove => objectIdsToRemove.cast(); /// Set of TileOverlays to be changed in this update. Set get tileOverlaysToChange => objectsToChange; } /// Thrown to indicate that a platform interaction failed to initialize renderer. class AndroidMapRendererException implements Exception { /// Creates a [AndroidMapRendererException] with an optional human-readable /// error message. AndroidMapRendererException([this.message]); /// A human-readable error message, possibly null. final String? message; @override String toString() => 'AndroidMapRendererException($message)'; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml ================================================ 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.5 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: google_maps_flutter platforms: android: package: io.flutter.plugins.googlemaps pluginClass: GoogleMapsPlugin dartPluginClass: GoogleMapsFlutterAndroid dependencies: flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.1 google_maps_flutter_platform_interface: ^2.2.1 stream_transform: ^2.0.0 dev_dependencies: async: ^2.5.0 flutter_test: sdk: flutter plugin_platform_interface: ^2.0.0 ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart ================================================ // Copyright 2013 The Flutter 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:async/async.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_android/google_maps_flutter_android.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); late List log; setUp(() async { log = []; }); /// Initializes a map with the given ID and canned responses, logging all /// calls to [log]. void configureMockMap( GoogleMapsFlutterAndroid maps, { required int mapId, required Future? Function(MethodCall call) handler, }) { final MethodChannel channel = maps.ensureChannelInitialized(mapId); _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( channel, (MethodCall methodCall) { log.add(methodCall.method); return handler(methodCall); }, ); } Future sendPlatformMessage( int mapId, String method, Map data) async { final ByteData byteData = const StandardMethodCodec().encodeMethodCall(MethodCall(method, data)); await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .handlePlatformMessage('plugins.flutter.dev/google_maps_android_$mapId', byteData, (ByteData? data) {}); } test('registers instance', () async { GoogleMapsFlutterAndroid.registerWith(); expect(GoogleMapsFlutterPlatform.instance, isA()); }); // Calls each method that uses invokeMethod with a return type other than // void to ensure that the casting/nullability handling succeeds. // // TODO(stuartmorgan): Remove this once there is real test coverage of // each method, since that would cover this issue. test('non-void invokeMethods handle types correctly', () async { const int mapId = 0; final GoogleMapsFlutterAndroid maps = GoogleMapsFlutterAndroid(); configureMockMap(maps, mapId: mapId, handler: (MethodCall methodCall) async { switch (methodCall.method) { case 'map#getLatLng': return [1.0, 2.0]; case 'markers#isInfoWindowShown': return true; case 'map#getZoomLevel': return 2.5; case 'map#takeSnapshot': return null; } }); await maps.getLatLng(const ScreenCoordinate(x: 0, y: 0), mapId: mapId); await maps.isMarkerInfoWindowShown(const MarkerId(''), mapId: mapId); await maps.getZoomLevel(mapId: mapId); await maps.takeSnapshot(mapId: mapId); // Check that all the invokeMethod calls happened. expect(log, [ 'map#getLatLng', 'markers#isInfoWindowShown', 'map#getZoomLevel', 'map#takeSnapshot', ]); }); test('markers send drag event to correct streams', () async { const int mapId = 1; final Map jsonMarkerDragStartEvent = { 'mapId': mapId, 'markerId': 'drag-start-marker', 'position': [1.0, 1.0] }; final Map jsonMarkerDragEvent = { 'mapId': mapId, 'markerId': 'drag-marker', 'position': [1.0, 1.0] }; final Map jsonMarkerDragEndEvent = { 'mapId': mapId, 'markerId': 'drag-end-marker', 'position': [1.0, 1.0] }; final GoogleMapsFlutterAndroid maps = GoogleMapsFlutterAndroid(); maps.ensureChannelInitialized(mapId); final StreamQueue markerDragStartStream = StreamQueue(maps.onMarkerDragStart(mapId: mapId)); final StreamQueue markerDragStream = StreamQueue(maps.onMarkerDrag(mapId: mapId)); final StreamQueue markerDragEndStream = StreamQueue(maps.onMarkerDragEnd(mapId: mapId)); await sendPlatformMessage( mapId, 'marker#onDragStart', jsonMarkerDragStartEvent); await sendPlatformMessage(mapId, 'marker#onDrag', jsonMarkerDragEvent); await sendPlatformMessage( mapId, 'marker#onDragEnd', jsonMarkerDragEndEvent); expect((await markerDragStartStream.next).value.value, equals('drag-start-marker')); expect((await markerDragStream.next).value.value, equals('drag-marker')); expect((await markerDragEndStream.next).value.value, equals('drag-end-marker')); }); test( 'Does not use PlatformViewLink when using TLHC', () async { final GoogleMapsFlutterAndroid maps = GoogleMapsFlutterAndroid(); maps.useAndroidViewSurface = false; final Widget widget = maps.buildViewWithConfiguration(1, (int _) {}, widgetConfiguration: const MapWidgetConfiguration( initialCameraPosition: CameraPosition(target: LatLng(0, 0), zoom: 1), textDirection: TextDirection.ltr)); expect(widget, isA()); }, ); testWidgets('Use PlatformViewLink when using surface view', (WidgetTester tester) async { final GoogleMapsFlutterAndroid maps = GoogleMapsFlutterAndroid(); maps.useAndroidViewSurface = true; final Widget widget = maps.buildViewWithConfiguration(1, (int _) {}, widgetConfiguration: const MapWidgetConfiguration( initialCameraPosition: CameraPosition(target: LatLng(0, 0), zoom: 1), textDirection: TextDirection.ltr)); expect(widget, isA()); }); testWidgets('Defaults to surface view', (WidgetTester tester) async { final GoogleMapsFlutterAndroid maps = GoogleMapsFlutterAndroid(); final Widget widget = maps.buildViewWithConfiguration(1, (int _) {}, widgetConfiguration: const MapWidgetConfiguration( initialCameraPosition: CameraPosition(target: LatLng(0, 0), zoom: 1), textDirection: TextDirection.ltr)); 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; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/AUTHORS ================================================ # 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> Taha Tesser ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md ================================================ ## 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. ## 2.1.12 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. * Fixes violations of new analysis option use_named_constants. * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 2.1.11 * Precaches Google Maps services initialization and syncing. ## 2.1.10 * Splits iOS implementation out of `google_maps_flutter` as a federated implementation. ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/LICENSE ================================================ 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. ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/README.md ================================================ # google\_maps\_flutter\_ios The iOS implementation of [`google_maps_flutter`][1]. ## Usage This package is [endorsed][2], which means you can simply use `google_maps_flutter` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/google_maps_flutter [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/.metadata ================================================ # 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: 3ea4d06340a97a1e9d7cae97567c64e0569dcaa2 channel: beta ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/README.md ================================================ # 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. ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/assets/night_mode.json ================================================ [ { "elementType": "geometry", "stylers": [ { "color": "#242f3e" } ] }, { "elementType": "labels.text.fill", "stylers": [ { "color": "#746855" } ] }, { "elementType": "labels.text.stroke", "stylers": [ { "color": "#242f3e" } ] }, { "featureType": "administrative.locality", "elementType": "labels.text.fill", "stylers": [ { "color": "#d59563" } ] }, { "featureType": "poi", "elementType": "labels.text.fill", "stylers": [ { "color": "#d59563" } ] }, { "featureType": "poi.park", "elementType": "geometry", "stylers": [ { "color": "#263c3f" } ] }, { "featureType": "poi.park", "elementType": "labels.text.fill", "stylers": [ { "color": "#6b9a76" } ] }, { "featureType": "road", "elementType": "geometry", "stylers": [ { "color": "#38414e" } ] }, { "featureType": "road", "elementType": "geometry.stroke", "stylers": [ { "color": "#212a37" } ] }, { "featureType": "road", "elementType": "labels.text.fill", "stylers": [ { "color": "#9ca5b3" } ] }, { "featureType": "road.highway", "elementType": "geometry", "stylers": [ { "color": "#746855" } ] }, { "featureType": "road.highway", "elementType": "geometry.stroke", "stylers": [ { "color": "#1f2835" } ] }, { "featureType": "road.highway", "elementType": "labels.text.fill", "stylers": [ { "color": "#f3d19c" } ] }, { "featureType": "transit", "elementType": "geometry", "stylers": [ { "color": "#2f3948" } ] }, { "featureType": "transit.station", "elementType": "labels.text.fill", "stylers": [ { "color": "#d59563" } ] }, { "featureType": "water", "elementType": "geometry", "stylers": [ { "color": "#17263c" } ] }, { "featureType": "water", "elementType": "labels.text.fill", "stylers": [ { "color": "#515c6d" } ] }, { "featureType": "water", "elementType": "labels.text.stroke", "stylers": [ { "color": "#17263c" } ] } ] ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/integration_test/google_maps_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_example/example_google_map.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:integration_test/integration_test.dart'; const LatLng _kInitialMapCenter = LatLng(0, 0); const double _kInitialZoomLevel = 5; const CameraPosition _kInitialCameraPosition = CameraPosition(target: _kInitialMapCenter, zoom: _kInitialZoomLevel); void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); GoogleMapsFlutterPlatform.instance.enableDebugInspection(); testWidgets('testCompassToggle', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, compassEnabled: false, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool compassEnabled = await inspector.isCompassEnabled(mapId: mapId); expect(compassEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); compassEnabled = await inspector.isCompassEnabled(mapId: mapId); expect(compassEnabled, true); }); testWidgets('testMapToolbar returns false', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; final bool mapToolbarEnabled = await inspector.isMapToolbarEnabled(mapId: mapId); // This is only supported on Android, so should always return false. expect(mapToolbarEnabled, false); }); testWidgets('updateMinMaxZoomLevels', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); const MinMaxZoomPreference initialZoomLevel = MinMaxZoomPreference(4, 8); const MinMaxZoomPreference finalZoomLevel = MinMaxZoomPreference(6, 10); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, minMaxZoomPreference: initialZoomLevel, onMapCreated: (ExampleGoogleMapController c) async { controllerCompleter.complete(c); }, ), )); final ExampleGoogleMapController controller = await controllerCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; MinMaxZoomPreference zoomLevel = await inspector.getMinMaxZoomLevels(mapId: controller.mapId); expect(zoomLevel, equals(initialZoomLevel)); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, minMaxZoomPreference: finalZoomLevel, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); zoomLevel = await inspector.getMinMaxZoomLevels(mapId: controller.mapId); expect(zoomLevel, equals(finalZoomLevel)); }); testWidgets('testZoomGesturesEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, zoomGesturesEnabled: false, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool zoomGesturesEnabled = await inspector.areZoomGesturesEnabled(mapId: mapId); expect(zoomGesturesEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); zoomGesturesEnabled = await inspector.areZoomGesturesEnabled(mapId: mapId); expect(zoomGesturesEnabled, true); }); testWidgets('testZoomControlsEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; final bool zoomControlsEnabled = await inspector.areZoomControlsEnabled(mapId: mapId); /// Zoom Controls functionality is not available on iOS at the moment. expect(zoomControlsEnabled, false); }); testWidgets('testRotateGesturesEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, rotateGesturesEnabled: false, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool rotateGesturesEnabled = await inspector.areRotateGesturesEnabled(mapId: mapId); expect(rotateGesturesEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); rotateGesturesEnabled = await inspector.areRotateGesturesEnabled(mapId: mapId); expect(rotateGesturesEnabled, true); }); testWidgets('testTiltGesturesEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, tiltGesturesEnabled: false, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool tiltGesturesEnabled = await inspector.areTiltGesturesEnabled(mapId: mapId); expect(tiltGesturesEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); tiltGesturesEnabled = await inspector.areTiltGesturesEnabled(mapId: mapId); expect(tiltGesturesEnabled, true); }); testWidgets('testScrollGesturesEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, scrollGesturesEnabled: false, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool scrollGesturesEnabled = await inspector.areScrollGesturesEnabled(mapId: mapId); expect(scrollGesturesEnabled, false); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); scrollGesturesEnabled = await inspector.areScrollGesturesEnabled(mapId: mapId); expect(scrollGesturesEnabled, true); }); testWidgets('testInitialCenterLocationAtCenter', (WidgetTester tester) async { await tester.binding.setSurfaceSize(const Size(800, 600)); final Completer mapControllerCompleter = Completer(); final Key key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { mapControllerCompleter.complete(controller); }, ), ), ); final ExampleGoogleMapController mapController = await mapControllerCompleter.future; await tester.pumpAndSettle(); // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen // in `mapRendered`. // https://github.com/flutter/flutter/issues/54758 await Future.delayed(const Duration(seconds: 1)); final ScreenCoordinate coordinate = await mapController.getScreenCoordinate(_kInitialCameraPosition.target); final Rect rect = tester.getRect(find.byKey(key)); expect(coordinate.x, (rect.center.dx - rect.topLeft.dx).round()); expect(coordinate.y, (rect.center.dy - rect.topLeft.dy).round()); await tester.binding.setSurfaceSize(null); }); testWidgets('testGetVisibleRegion', (WidgetTester tester) async { final Key key = GlobalKey(); final LatLngBounds zeroLatLngBounds = LatLngBounds( southwest: const LatLng(0, 0), northeast: const LatLng(0, 0)); final Completer mapControllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { mapControllerCompleter.complete(controller); }, ), )); await tester.pumpAndSettle(); final ExampleGoogleMapController mapController = await mapControllerCompleter.future; final LatLngBounds firstVisibleRegion = await mapController.getVisibleRegion(); expect(firstVisibleRegion, isNotNull); expect(firstVisibleRegion.southwest, isNotNull); expect(firstVisibleRegion.northeast, isNotNull); expect(firstVisibleRegion, isNot(zeroLatLngBounds)); expect(firstVisibleRegion.contains(_kInitialMapCenter), isTrue); // Making a new `LatLngBounds` about (10, 10) distance south west to the `firstVisibleRegion`. // The size of the `LatLngBounds` is 10 by 10. final LatLng southWest = LatLng(firstVisibleRegion.southwest.latitude - 20, firstVisibleRegion.southwest.longitude - 20); final LatLng northEast = LatLng(firstVisibleRegion.southwest.latitude - 10, firstVisibleRegion.southwest.longitude - 10); final LatLng newCenter = LatLng( (northEast.latitude + southWest.latitude) / 2, (northEast.longitude + southWest.longitude) / 2, ); expect(firstVisibleRegion.contains(northEast), isFalse); expect(firstVisibleRegion.contains(southWest), isFalse); final LatLngBounds latLngBounds = LatLngBounds(southwest: southWest, northeast: northEast); // TODO(iskakaushik): non-zero padding is needed for some device configurations // https://github.com/flutter/flutter/issues/30575 const double padding = 0; await mapController .moveCamera(CameraUpdate.newLatLngBounds(latLngBounds, padding)); await tester.pumpAndSettle(const Duration(seconds: 3)); final LatLngBounds secondVisibleRegion = await mapController.getVisibleRegion(); expect(secondVisibleRegion, isNotNull); expect(secondVisibleRegion.southwest, isNotNull); expect(secondVisibleRegion.northeast, isNotNull); expect(secondVisibleRegion, isNot(zeroLatLngBounds)); expect(firstVisibleRegion, isNot(secondVisibleRegion)); expect(secondVisibleRegion.contains(newCenter), isTrue); }); testWidgets('testTraffic', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, trafficEnabled: true, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool isTrafficEnabled = await inspector.isTrafficEnabled(mapId: mapId); expect(isTrafficEnabled, true); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); isTrafficEnabled = await inspector.isTrafficEnabled(mapId: mapId); expect(isTrafficEnabled, false); }); testWidgets('testBuildings', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; final bool isBuildingsEnabled = await inspector.areBuildingsEnabled(mapId: mapId); expect(isBuildingsEnabled, true); }); testWidgets('testMyLocationButtonToggle', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; bool myLocationButtonEnabled = await inspector.isMyLocationButtonEnabled(mapId: mapId); expect(myLocationButtonEnabled, true); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, myLocationButtonEnabled: false, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), )); myLocationButtonEnabled = await inspector.isMyLocationButtonEnabled(mapId: mapId); expect(myLocationButtonEnabled, false); }); testWidgets('testMyLocationButton initial value false', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, myLocationButtonEnabled: false, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; final bool myLocationButtonEnabled = await inspector.isMyLocationButtonEnabled(mapId: mapId); expect(myLocationButtonEnabled, false); }); testWidgets('testMyLocationButton initial value true', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer mapIdCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), )); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; final bool myLocationButtonEnabled = await inspector.isMyLocationButtonEnabled(mapId: mapId); expect(myLocationButtonEnabled, true); }); testWidgets('testSetMapStyle valid Json String', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { controllerCompleter.complete(controller); }, ), )); final ExampleGoogleMapController controller = await controllerCompleter.future; const String mapStyle = '[{"elementType":"geometry","stylers":[{"color":"#242f3e"}]}]'; await controller.setMapStyle(mapStyle); }); testWidgets('testSetMapStyle invalid Json String', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { controllerCompleter.complete(controller); }, ), )); final ExampleGoogleMapController controller = await controllerCompleter.future; try { await controller.setMapStyle('invalid_value'); fail('expected MapStyleException'); } on MapStyleException catch (e) { expect(e.cause, isNotNull); } }); testWidgets('testSetMapStyle null string', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { controllerCompleter.complete(controller); }, ), )); final ExampleGoogleMapController controller = await controllerCompleter.future; await controller.setMapStyle(null); }); testWidgets('testGetLatLng', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { controllerCompleter.complete(controller); }, ), )); final ExampleGoogleMapController controller = await controllerCompleter.future; await tester.pumpAndSettle(); // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen // in `mapRendered`. // https://github.com/flutter/flutter/issues/54758 await Future.delayed(const Duration(seconds: 1)); final LatLngBounds visibleRegion = await controller.getVisibleRegion(); final LatLng topLeft = await controller.getLatLng(const ScreenCoordinate(x: 0, y: 0)); final LatLng northWest = LatLng( visibleRegion.northeast.latitude, visibleRegion.southwest.longitude, ); expect(topLeft, northWest); }); testWidgets('testGetZoomLevel', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { controllerCompleter.complete(controller); }, ), )); final ExampleGoogleMapController controller = await controllerCompleter.future; await tester.pumpAndSettle(); // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen // in `mapRendered`. // https://github.com/flutter/flutter/issues/54758 await Future.delayed(const Duration(seconds: 1)); double zoom = await controller.getZoomLevel(); expect(zoom, _kInitialZoomLevel); await controller.moveCamera(CameraUpdate.zoomTo(7)); await tester.pumpAndSettle(); zoom = await controller.getZoomLevel(); expect(zoom, equals(7)); }); testWidgets('testScreenCoordinate', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { controllerCompleter.complete(controller); }, ), )); final ExampleGoogleMapController controller = await controllerCompleter.future; await tester.pumpAndSettle(); // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen // in `mapRendered`. // https://github.com/flutter/flutter/issues/54758 await Future.delayed(const Duration(seconds: 1)); final LatLngBounds visibleRegion = await controller.getVisibleRegion(); final LatLng northWest = LatLng( visibleRegion.northeast.latitude, visibleRegion.southwest.longitude, ); final ScreenCoordinate topLeft = await controller.getScreenCoordinate(northWest); expect(topLeft, const ScreenCoordinate(x: 0, y: 0)); }); testWidgets('testResizeWidget', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final ExampleGoogleMap map = ExampleGoogleMap( initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) async { controllerCompleter.complete(controller); }, ); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: MaterialApp( home: Scaffold( body: SizedBox(height: 100, width: 100, child: map))))); final ExampleGoogleMapController controller = await controllerCompleter.future; await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: MaterialApp( home: Scaffold( body: SizedBox(height: 400, width: 400, child: map))))); await tester.pumpAndSettle(); // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen // in `mapRendered`. // https://github.com/flutter/flutter/issues/54758 await Future.delayed(const Duration(seconds: 1)); // Simple call to make sure that the app hasn't crashed. final LatLngBounds bounds1 = await controller.getVisibleRegion(); final LatLngBounds bounds2 = await controller.getVisibleRegion(); expect(bounds1, bounds2); }); testWidgets('testToggleInfoWindow', (WidgetTester tester) async { const Marker marker = Marker( markerId: MarkerId('marker'), infoWindow: InfoWindow(title: 'InfoWindow')); final Set markers = {marker}; final Completer controllerCompleter = Completer(); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)), markers: markers, onMapCreated: (ExampleGoogleMapController googleMapController) { controllerCompleter.complete(googleMapController); }, ), )); final ExampleGoogleMapController controller = await controllerCompleter.future; bool iwVisibleStatus = await controller.isMarkerInfoWindowShown(marker.markerId); expect(iwVisibleStatus, false); await controller.showMarkerInfoWindow(marker.markerId); iwVisibleStatus = await controller.isMarkerInfoWindowShown(marker.markerId); expect(iwVisibleStatus, true); await controller.hideMarkerInfoWindow(marker.markerId); iwVisibleStatus = await controller.isMarkerInfoWindowShown(marker.markerId); expect(iwVisibleStatus, false); }); testWidgets('fromAssetImage', (WidgetTester tester) async { const double pixelRatio = 2; const ImageConfiguration imageConfiguration = ImageConfiguration(devicePixelRatio: pixelRatio); final BitmapDescriptor mip = await BitmapDescriptor.fromAssetImage( imageConfiguration, 'red_square.png'); final BitmapDescriptor scaled = await BitmapDescriptor.fromAssetImage( imageConfiguration, 'red_square.png', mipmaps: false); expect((mip.toJson() as List)[2], 1); expect((scaled.toJson() as List)[2], 2); }); testWidgets('testTakeSnapshot', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { controllerCompleter.complete(controller); }, ), ), ); await tester.pumpAndSettle(const Duration(seconds: 3)); final ExampleGoogleMapController controller = await controllerCompleter.future; final Uint8List? bytes = await controller.takeSnapshot(); expect(bytes?.isNotEmpty, true); }); testWidgets( 'set tileOverlay correctly', (WidgetTester tester) async { final Completer mapIdCompleter = Completer(); final TileOverlay tileOverlay1 = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_1'), tileProvider: _DebugTileProvider(), zIndex: 2, transparency: 0.2, ); final TileOverlay tileOverlay2 = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_2'), tileProvider: _DebugTileProvider(), zIndex: 1, visible: false, transparency: 0.3, fadeIn: false, ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( initialCameraPosition: _kInitialCameraPosition, tileOverlays: {tileOverlay1, tileOverlay2}, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), ), ); await tester.pumpAndSettle(const Duration(seconds: 3)); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; final TileOverlay tileOverlayInfo1 = (await inspector .getTileOverlayInfo(tileOverlay1.mapsId, mapId: mapId))!; final TileOverlay tileOverlayInfo2 = (await inspector .getTileOverlayInfo(tileOverlay2.mapsId, mapId: mapId))!; expect(tileOverlayInfo1.visible, isTrue); expect(tileOverlayInfo1.fadeIn, isTrue); expect( tileOverlayInfo1.transparency, moreOrLessEquals(0.2, epsilon: 0.001)); expect(tileOverlayInfo1.zIndex, 2); expect(tileOverlayInfo2.visible, isFalse); expect(tileOverlayInfo2.fadeIn, isFalse); expect( tileOverlayInfo2.transparency, moreOrLessEquals(0.3, epsilon: 0.001)); expect(tileOverlayInfo2.zIndex, 1); }, ); testWidgets( 'update tileOverlays correctly', (WidgetTester tester) async { final Completer mapIdCompleter = Completer(); final Key key = GlobalKey(); final TileOverlay tileOverlay1 = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_1'), tileProvider: _DebugTileProvider(), zIndex: 2, transparency: 0.2, ); final TileOverlay tileOverlay2 = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_2'), tileProvider: _DebugTileProvider(), zIndex: 3, transparency: 0.5, ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, tileOverlays: {tileOverlay1, tileOverlay2}, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), ), ); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; final TileOverlay tileOverlay1New = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_1'), tileProvider: _DebugTileProvider(), zIndex: 1, visible: false, transparency: 0.3, fadeIn: false, ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, tileOverlays: {tileOverlay1New}, onMapCreated: (ExampleGoogleMapController controller) { fail('update: OnMapCreated should get called only once.'); }, ), ), ); await tester.pumpAndSettle(const Duration(seconds: 3)); final TileOverlay tileOverlayInfo1 = (await inspector .getTileOverlayInfo(tileOverlay1.mapsId, mapId: mapId))!; final TileOverlay? tileOverlayInfo2 = await inspector.getTileOverlayInfo(tileOverlay2.mapsId, mapId: mapId); expect(tileOverlayInfo1.visible, isFalse); expect(tileOverlayInfo1.fadeIn, isFalse); expect( tileOverlayInfo1.transparency, moreOrLessEquals(0.3, epsilon: 0.001)); expect(tileOverlayInfo1.zIndex, 1); expect(tileOverlayInfo2, isNull); }, ); testWidgets( 'remove tileOverlays correctly', (WidgetTester tester) async { final Completer mapIdCompleter = Completer(); final Key key = GlobalKey(); final TileOverlay tileOverlay1 = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_1'), tileProvider: _DebugTileProvider(), zIndex: 2, transparency: 0.2, ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, tileOverlays: {tileOverlay1}, onMapCreated: (ExampleGoogleMapController controller) { mapIdCompleter.complete(controller.mapId); }, ), ), ); final int mapId = await mapIdCompleter.future; final GoogleMapsInspectorPlatform inspector = GoogleMapsInspectorPlatform.instance!; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: ExampleGoogleMap( key: key, initialCameraPosition: _kInitialCameraPosition, onMapCreated: (ExampleGoogleMapController controller) { fail('OnMapCreated should get called only once.'); }, ), ), ); await tester.pumpAndSettle(const Duration(seconds: 3)); final TileOverlay? tileOverlayInfo1 = await inspector.getTileOverlayInfo(tileOverlay1.mapsId, mapId: mapId); expect(tileOverlayInfo1, isNull); }, ); } class _DebugTileProvider implements TileProvider { _DebugTileProvider() { boxPaint.isAntiAlias = true; boxPaint.color = Colors.blue; boxPaint.strokeWidth = 2.0; boxPaint.style = PaintingStyle.stroke; } static const int width = 100; static const int height = 100; static final Paint boxPaint = Paint(); static const TextStyle textStyle = TextStyle( color: Colors.red, fontSize: 20, ); @override Future getTile(int x, int y, int? zoom) async { final ui.PictureRecorder recorder = ui.PictureRecorder(); final Canvas canvas = Canvas(recorder); final TextSpan textSpan = TextSpan( text: '$x,$y', style: textStyle, ); final TextPainter textPainter = TextPainter( text: textSpan, textDirection: TextDirection.ltr, ); textPainter.layout( maxWidth: width.toDouble(), ); textPainter.paint(canvas, Offset.zero); canvas.drawRect( Rect.fromLTRB(0, 0, width.toDouble(), width.toDouble()), boxPaint); final ui.Picture picture = recorder.endRecording(); final Uint8List byteData = await picture .toImage(width, height) .then((ui.Image image) => image.toByteData(format: ui.ImageByteFormat.png)) .then((ByteData? byteData) => byteData!.buffer.asUint8List()); return Tile(width, height, byteData); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 UIRequiredDeviceCapabilities arm64 MinimumOSVersion 9.0 ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Flutter/Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Flutter/Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths pod 'OCMock', '~> 3.9.1' end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |build_configuration| build_configuration.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64 i386' end end end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "AppDelegate.h" #import "GeneratedPluginRegistrant.h" @import GoogleMaps; @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Provide the GoogleMaps API key. NSString *mapsApiKey = [[NSProcessInfo processInfo] environment][@"MAPS_API_KEY"]; if ([mapsApiKey length] == 0) { mapsApiKey = @"YOUR KEY HERE"; } [GMSServices provideAPIKey:mapsApiKey]; // Register Flutter plugins. [GeneratedPluginRegistrant registerWithRegistry:self]; return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "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" } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName google_maps_flutter_example CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS NSLocationWhenInUseUsageDescription This app needs your location to test the location feature of the Google Maps plugin. UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 4510D964F3B1259FEDD3ABA6 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */; }; 4A097997B7B27CE82FFC3AB8 /* libPods-RunnerUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DC8ED0578E8D540BBDA17645 /* libPods-RunnerUITests.a */; }; 6851F3562835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6851F3552835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m */; }; 68E4726A2836FF0C00BDDDAC /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 68E472692836FF0C00BDDDAC /* MapKit.framework */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 982F2A6C27BADE17003C81F4 /* PartiallyMockedMapView.m in Sources */ = {isa = PBXBuildFile; fileRef = 982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */; }; F7151F13265D7ED70028CB91 /* GoogleMapsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */; }; F7151F21265D7EE50028CB91 /* GoogleMapsUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F20265D7EE50028CB91 /* GoogleMapsUITests.m */; }; FC8F35FC8CD533B128950487 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F267F68029D1A4E2E4C572A7 /* libPods-RunnerTests.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ F7151F15265D7ED70028CB91 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; F7151F23265D7EE50028CB91 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 6851F3552835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTGoogleMapJSONConversionsConversionTests.m; sourceTree = ""; }; 68E472692836FF0C00BDDDAC /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk/System/iOSSupport/System/Library/Frameworks/MapKit.framework; sourceTree = DEVELOPER_DIR; }; 6AC1E6095B09DE4B02ECF64E /* Pods-RunnerUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerUITests/Pods-RunnerUITests.release.xcconfig"; sourceTree = ""; }; 733AFAB37683A9DA7512F09C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 982F2A6A27BADE17003C81F4 /* PartiallyMockedMapView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PartiallyMockedMapView.h; sourceTree = ""; }; 982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PartiallyMockedMapView.m; sourceTree = ""; }; B7AFC65E3DD5AC60D834D83D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; DC8ED0578E8D540BBDA17645 /* libPods-RunnerUITests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerUITests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; DDDAC1342ABDF2F125577581 /* Pods-RunnerUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerUITests/Pods-RunnerUITests.debug.xcconfig"; sourceTree = ""; }; E52C6A6210A56F027C582EF9 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; EA0E91726245EDC22B97E8B9 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; F267F68029D1A4E2E4C572A7 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; F7151F10265D7ED70028CB91 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GoogleMapsTests.m; sourceTree = ""; }; F7151F14265D7ED70028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F7151F1E265D7EE50028CB91 /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F7151F20265D7EE50028CB91 /* GoogleMapsUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GoogleMapsUITests.m; sourceTree = ""; }; F7151F22265D7EE50028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 4510D964F3B1259FEDD3ABA6 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F0D265D7ED70028CB91 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 68E4726A2836FF0C00BDDDAC /* MapKit.framework in Frameworks */, FC8F35FC8CD533B128950487 /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F1B265D7EE50028CB91 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 4A097997B7B27CE82FFC3AB8 /* libPods-RunnerUITests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 1E7CF0857EFC88FC263CF3B2 /* Frameworks */ = { isa = PBXGroup; children = ( 68E472692836FF0C00BDDDAC /* MapKit.framework */, 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */, F267F68029D1A4E2E4C572A7 /* libPods-RunnerTests.a */, DC8ED0578E8D540BBDA17645 /* libPods-RunnerUITests.a */, ); name = Frameworks; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, F7151F11265D7ED70028CB91 /* RunnerTests */, F7151F1F265D7EE50028CB91 /* RunnerUITests */, 97C146EF1CF9000F007C117D /* Products */, A189CFE5474BF8A07908B2E0 /* Pods */, 1E7CF0857EFC88FC263CF3B2 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, F7151F10265D7ED70028CB91 /* RunnerTests.xctest */, F7151F1E265D7EE50028CB91 /* RunnerUITests.xctest */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; A189CFE5474BF8A07908B2E0 /* Pods */ = { isa = PBXGroup; children = ( B7AFC65E3DD5AC60D834D83D /* Pods-Runner.debug.xcconfig */, EA0E91726245EDC22B97E8B9 /* Pods-Runner.release.xcconfig */, E52C6A6210A56F027C582EF9 /* Pods-RunnerTests.debug.xcconfig */, 733AFAB37683A9DA7512F09C /* Pods-RunnerTests.release.xcconfig */, DDDAC1342ABDF2F125577581 /* Pods-RunnerUITests.debug.xcconfig */, 6AC1E6095B09DE4B02ECF64E /* Pods-RunnerUITests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; F7151F11265D7ED70028CB91 /* RunnerTests */ = { isa = PBXGroup; children = ( 6851F3552835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m */, F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */, 982F2A6A27BADE17003C81F4 /* PartiallyMockedMapView.h */, 982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */, F7151F14265D7ED70028CB91 /* Info.plist */, ); path = RunnerTests; sourceTree = ""; }; F7151F1F265D7EE50028CB91 /* RunnerUITests */ = { isa = PBXGroup; children = ( F7151F20265D7EE50028CB91 /* GoogleMapsUITests.m */, F7151F22265D7EE50028CB91 /* Info.plist */, ); path = RunnerUITests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 74BF216DF17B0C7F983459BD /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, BB6BD9A1101E970BEF85B6D2 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; F7151F0F265D7ED70028CB91 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = F7151F19265D7ED70028CB91 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( D067548A17DC238B80D2BD12 /* [CP] Check Pods Manifest.lock */, F7151F0C265D7ED70028CB91 /* Sources */, F7151F0D265D7ED70028CB91 /* Frameworks */, F7151F0E265D7ED70028CB91 /* Resources */, ); buildRules = ( ); dependencies = ( F7151F16265D7ED70028CB91 /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = F7151F10265D7ED70028CB91 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; F7151F1D265D7EE50028CB91 /* RunnerUITests */ = { isa = PBXNativeTarget; buildConfigurationList = F7151F25265D7EE50028CB91 /* Build configuration list for PBXNativeTarget "RunnerUITests" */; buildPhases = ( BD39F60794E9A0264D5D3752 /* [CP] Check Pods Manifest.lock */, F7151F1A265D7EE50028CB91 /* Sources */, F7151F1B265D7EE50028CB91 /* Frameworks */, F7151F1C265D7EE50028CB91 /* Resources */, ); buildRules = ( ); dependencies = ( F7151F24265D7EE50028CB91 /* PBXTargetDependency */, ); name = RunnerUITests; productName = RunnerUITests; productReference = F7151F1E265D7EE50028CB91 /* RunnerUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1320; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; }; F7151F0F265D7ED70028CB91 = { CreatedOnToolsVersion = 12.5; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; F7151F1D265D7EE50028CB91 = { CreatedOnToolsVersion = 12.5; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, F7151F0F265D7ED70028CB91 /* RunnerTests */, F7151F1D265D7EE50028CB91 /* RunnerUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F0E265D7ED70028CB91 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; F7151F1C265D7EE50028CB91 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 74BF216DF17B0C7F983459BD /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; 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"; }; BB6BD9A1101E970BEF85B6D2 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", "${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMaps.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; showEnvVarsInLog = 0; }; BD39F60794E9A0264D5D3752 /* [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-RunnerUITests-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; }; D067548A17DC238B80D2BD12 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F0C265D7ED70028CB91 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F7151F13265D7ED70028CB91 /* GoogleMapsTests.m in Sources */, 6851F3562835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m in Sources */, 982F2A6C27BADE17003C81F4 /* PartiallyMockedMapView.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F1A265D7EE50028CB91 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F7151F21265D7EE50028CB91 /* GoogleMapsUITests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ F7151F16265D7ED70028CB91 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = F7151F15265D7ED70028CB91 /* PBXContainerItemProxy */; }; F7151F24265D7EE50028CB91 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = F7151F23265D7EE50028CB91 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 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++"; 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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 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 = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ENABLE_BITCODE = NO; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.googleMobileMapsExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ENABLE_BITCODE = NO; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64"; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.googleMobileMapsExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; F7151F17265D7ED70028CB91 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = E52C6A6210A56F027C582EF9 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64"; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Debug; }; F7151F18265D7ED70028CB91 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 733AFAB37683A9DA7512F09C /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64"; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; }; F7151F26265D7EE50028CB91 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = DDDAC1342ABDF2F125577581 /* Pods-RunnerUITests.debug.xcconfig */; buildSettings = { CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = Runner; }; name = Debug; }; F7151F27265D7EE50028CB91 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 6AC1E6095B09DE4B02ECF64E /* Pods-RunnerUITests.release.xcconfig */; buildSettings = { CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = Runner; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F7151F19265D7ED70028CB91 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( F7151F17265D7ED70028CB91 /* Debug */, F7151F18265D7ED70028CB91 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F7151F25265D7EE50028CB91 /* Build configuration list for PBXNativeTarget "RunnerUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( F7151F26265D7EE50028CB91 /* Debug */, F7151F27265D7EE50028CB91 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/FLTGoogleMapJSONConversionsConversionTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import google_maps_flutter_ios; @import google_maps_flutter_ios.Test; @import XCTest; @import MapKit; @import GoogleMaps; #import #import "PartiallyMockedMapView.h" @interface FLTGoogleMapJSONConversionsTests : XCTestCase @end @implementation FLTGoogleMapJSONConversionsTests - (void)testLocationFromLatLong { NSArray *latlong = @[ @1, @2 ]; CLLocationCoordinate2D location = [FLTGoogleMapJSONConversions locationFromLatLong:latlong]; XCTAssertEqual(location.latitude, 1); XCTAssertEqual(location.longitude, 2); } - (void)testPointFromArray { NSArray *array = @[ @1, @2 ]; CGPoint point = [FLTGoogleMapJSONConversions pointFromArray:array]; XCTAssertEqual(point.x, 1); XCTAssertEqual(point.y, 2); } - (void)testArrayFromLocation { CLLocationCoordinate2D location = CLLocationCoordinate2DMake(1, 2); NSArray *array = [FLTGoogleMapJSONConversions arrayFromLocation:location]; XCTAssertEqual([array[0] integerValue], 1); XCTAssertEqual([array[1] integerValue], 2); } - (void)testColorFromRGBA { NSNumber *rgba = @(0x01020304); UIColor *color = [FLTGoogleMapJSONConversions colorFromRGBA:rgba]; CGFloat red, green, blue, alpha; BOOL success = [color getRed:&red green:&green blue:&blue alpha:&alpha]; XCTAssertTrue(success); const CGFloat accuracy = 0.0001; XCTAssertEqualWithAccuracy(red, 2 / 255.0, accuracy); XCTAssertEqualWithAccuracy(green, 3 / 255.0, accuracy); XCTAssertEqualWithAccuracy(blue, 4 / 255.0, accuracy); XCTAssertEqualWithAccuracy(alpha, 1 / 255.0, accuracy); } - (void)testPointsFromLatLongs { NSArray *latlongs = @[ @[ @1, @2 ], @[ @(3), @(4) ] ]; NSArray *locations = [FLTGoogleMapJSONConversions pointsFromLatLongs:latlongs]; XCTAssertEqual(locations.count, 2); XCTAssertEqual(locations[0].coordinate.latitude, 1); XCTAssertEqual(locations[0].coordinate.longitude, 2); XCTAssertEqual(locations[1].coordinate.latitude, 3); XCTAssertEqual(locations[1].coordinate.longitude, 4); } - (void)testHolesFromPointsArray { NSArray *pointsArray = @[ @[ @[ @1, @2 ], @[ @(3), @(4) ] ], @[ @[ @(5), @(6) ], @[ @(7), @(8) ] ] ]; NSArray *> *holes = [FLTGoogleMapJSONConversions holesFromPointsArray:pointsArray]; XCTAssertEqual(holes.count, 2); XCTAssertEqual(holes[0][0].coordinate.latitude, 1); XCTAssertEqual(holes[0][0].coordinate.longitude, 2); XCTAssertEqual(holes[0][1].coordinate.latitude, 3); XCTAssertEqual(holes[0][1].coordinate.longitude, 4); XCTAssertEqual(holes[1][0].coordinate.latitude, 5); XCTAssertEqual(holes[1][0].coordinate.longitude, 6); XCTAssertEqual(holes[1][1].coordinate.latitude, 7); XCTAssertEqual(holes[1][1].coordinate.longitude, 8); } - (void)testDictionaryFromPosition { id mockPosition = OCMClassMock([GMSCameraPosition class]); NSValue *locationValue = [NSValue valueWithMKCoordinate:CLLocationCoordinate2DMake(1, 2)]; [(GMSCameraPosition *)[[mockPosition stub] andReturnValue:locationValue] target]; [[[mockPosition stub] andReturnValue:@(2.0)] zoom]; [[[mockPosition stub] andReturnValue:@(3.0)] bearing]; [[[mockPosition stub] andReturnValue:@(75.0)] viewingAngle]; NSDictionary *dictionary = [FLTGoogleMapJSONConversions dictionaryFromPosition:mockPosition]; NSArray *targetArray = @[ @1, @2 ]; XCTAssertEqualObjects(dictionary[@"target"], targetArray); XCTAssertEqualObjects(dictionary[@"zoom"], @2.0); XCTAssertEqualObjects(dictionary[@"bearing"], @3.0); XCTAssertEqualObjects(dictionary[@"tilt"], @75.0); } - (void)testDictionaryFromPoint { CGPoint point = CGPointMake(10, 20); NSDictionary *dictionary = [FLTGoogleMapJSONConversions dictionaryFromPoint:point]; const CGFloat accuracy = 0.0001; XCTAssertEqualWithAccuracy([dictionary[@"x"] floatValue], point.x, accuracy); XCTAssertEqualWithAccuracy([dictionary[@"y"] floatValue], point.y, accuracy); } - (void)testDictionaryFromCoordinateBounds { XCTAssertNil([FLTGoogleMapJSONConversions dictionaryFromCoordinateBounds:nil]); GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:CLLocationCoordinate2DMake(10, 20) coordinate:CLLocationCoordinate2DMake(30, 40)]; NSDictionary *dictionary = [FLTGoogleMapJSONConversions dictionaryFromCoordinateBounds:bounds]; NSArray *southwest = @[ @10, @20 ]; NSArray *northeast = @[ @30, @40 ]; XCTAssertEqualObjects(dictionary[@"southwest"], southwest); XCTAssertEqualObjects(dictionary[@"northeast"], northeast); } - (void)testCameraPostionFromDictionary { XCTAssertNil([FLTGoogleMapJSONConversions cameraPostionFromDictionary:nil]); NSDictionary *channelValue = @{@"target" : @[ @1, @2 ], @"zoom" : @3, @"bearing" : @4, @"tilt" : @5}; GMSCameraPosition *cameraPosition = [FLTGoogleMapJSONConversions cameraPostionFromDictionary:channelValue]; const CGFloat accuracy = 0.001; XCTAssertEqualWithAccuracy(cameraPosition.target.latitude, 1, accuracy); XCTAssertEqualWithAccuracy(cameraPosition.target.longitude, 2, accuracy); XCTAssertEqualWithAccuracy(cameraPosition.zoom, 3, accuracy); XCTAssertEqualWithAccuracy(cameraPosition.bearing, 4, accuracy); XCTAssertEqualWithAccuracy(cameraPosition.viewingAngle, 5, accuracy); } - (void)testPointFromDictionary { XCTAssertNil([FLTGoogleMapJSONConversions cameraPostionFromDictionary:nil]); NSDictionary *dictionary = @{ @"x" : @1, @"y" : @2, }; CGPoint point = [FLTGoogleMapJSONConversions pointFromDictionary:dictionary]; const CGFloat accuracy = 0.001; XCTAssertEqualWithAccuracy(point.x, 1, accuracy); XCTAssertEqualWithAccuracy(point.y, 2, accuracy); } - (void)testCoordinateBoundsFromLatLongs { NSArray *latlong1 = @[ @1, @2 ]; NSArray *latlong2 = @[ @(3), @(4) ]; GMSCoordinateBounds *bounds = [FLTGoogleMapJSONConversions coordinateBoundsFromLatLongs:@[ latlong1, latlong2 ]]; const CGFloat accuracy = 0.001; XCTAssertEqualWithAccuracy(bounds.southWest.latitude, 1, accuracy); XCTAssertEqualWithAccuracy(bounds.southWest.longitude, 2, accuracy); XCTAssertEqualWithAccuracy(bounds.northEast.latitude, 3, accuracy); XCTAssertEqualWithAccuracy(bounds.northEast.longitude, 4, accuracy); } - (void)testMapViewTypeFromTypeValue { XCTAssertEqual(kGMSTypeNormal, [FLTGoogleMapJSONConversions mapViewTypeFromTypeValue:@1]); XCTAssertEqual(kGMSTypeSatellite, [FLTGoogleMapJSONConversions mapViewTypeFromTypeValue:@2]); XCTAssertEqual(kGMSTypeTerrain, [FLTGoogleMapJSONConversions mapViewTypeFromTypeValue:@3]); XCTAssertEqual(kGMSTypeHybrid, [FLTGoogleMapJSONConversions mapViewTypeFromTypeValue:@4]); XCTAssertEqual(kGMSTypeNone, [FLTGoogleMapJSONConversions mapViewTypeFromTypeValue:@5]); } - (void)testCameraUpdateFromChannelValueNewCameraPosition { NSArray *channelValue = @[ @"newCameraPosition", @{@"target" : @[ @1, @2 ], @"zoom" : @3, @"bearing" : @4, @"tilt" : @5} ]; id classMockCameraUpdate = OCMClassMock([GMSCameraUpdate class]); [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValue]; [[classMockCameraUpdate expect] setCamera:[FLTGoogleMapJSONConversions cameraPostionFromDictionary:channelValue[1]]]; [classMockCameraUpdate stopMocking]; } // TODO(cyanglaz): Fix the test for CameraUpdateFromChannelValue with the "NewLatlng" key. // 2 approaches have been tried and neither worked for the tests. // // 1. Use OCMock to vefiry that [GMSCameraUpdate setTarget:] is triggered with the correct value. // This class method conflicts with certain category method in OCMock, causing OCMock not able to // disambigious them. // // 2. Directly verify the GMSCameraUpdate object returned by the method. // The GMSCameraUpdate object returned from the method doesn't have any accessors to the "target" // property. It can be used to update the "camera" property in GMSMapView. However, [GMSMapView // moveCamera:] doesn't update the camera immediately. Thus the GMSCameraUpdate object cannot be // verified. // // The code in below test uses the 2nd approach. - (void)skip_testCameraUpdateFromChannelValueNewLatLong { NSArray *channelValue = @[ @"newLatLng", @[ @1, @2 ] ]; GMSCameraUpdate *update = [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValue]; GMSMapView *mapView = [[GMSMapView alloc] initWithFrame:CGRectZero camera:[GMSCameraPosition cameraWithTarget:CLLocationCoordinate2DMake(5, 6) zoom:1]]; [mapView moveCamera:update]; const CGFloat accuracy = 0.001; XCTAssertEqualWithAccuracy(mapView.camera.target.latitude, 1, accuracy); // mapView.camera.target.latitude is still 5. XCTAssertEqualWithAccuracy(mapView.camera.target.longitude, 2, accuracy); // mapView.camera.target.longitude is still 6. } - (void)testCameraUpdateFromChannelValueNewLatLngBounds { NSArray *latlong1 = @[ @1, @2 ]; NSArray *latlong2 = @[ @(3), @(4) ]; GMSCoordinateBounds *bounds = [FLTGoogleMapJSONConversions coordinateBoundsFromLatLongs:@[ latlong1, latlong2 ]]; NSArray *channelValue = @[ @"newLatLngBounds", @[ latlong1, latlong2 ], @20 ]; id classMockCameraUpdate = OCMClassMock([GMSCameraUpdate class]); [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValue]; [[classMockCameraUpdate expect] fitBounds:bounds withPadding:20]; [classMockCameraUpdate stopMocking]; } - (void)testCameraUpdateFromChannelValueNewLatLngZoom { NSArray *channelValue = @[ @"newLatLngZoom", @[ @1, @2 ], @3 ]; id classMockCameraUpdate = OCMClassMock([GMSCameraUpdate class]); [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValue]; [[classMockCameraUpdate expect] setTarget:CLLocationCoordinate2DMake(1, 2) zoom:3]; [classMockCameraUpdate stopMocking]; } - (void)testCameraUpdateFromChannelValueScrollBy { NSArray *channelValue = @[ @"scrollBy", @1, @2 ]; id classMockCameraUpdate = OCMClassMock([GMSCameraUpdate class]); [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValue]; [[classMockCameraUpdate expect] scrollByX:1 Y:2]; [classMockCameraUpdate stopMocking]; } - (void)testCameraUpdateFromChannelValueZoomBy { NSArray *channelValueNoPoint = @[ @"zoomBy", @1 ]; id classMockCameraUpdate = OCMClassMock([GMSCameraUpdate class]); [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValueNoPoint]; [[classMockCameraUpdate expect] zoomBy:1]; NSArray *channelValueWithPoint = @[ @"zoomBy", @1, @[ @2, @3 ] ]; [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValueWithPoint]; [[classMockCameraUpdate expect] zoomBy:1 atPoint:CGPointMake(2, 3)]; [classMockCameraUpdate stopMocking]; } - (void)testCameraUpdateFromChannelValueZoomIn { NSArray *channelValueNoPoint = @[ @"zoomIn" ]; id classMockCameraUpdate = OCMClassMock([GMSCameraUpdate class]); [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValueNoPoint]; [[classMockCameraUpdate expect] zoomIn]; [classMockCameraUpdate stopMocking]; } - (void)testCameraUpdateFromChannelValueZoomOut { NSArray *channelValueNoPoint = @[ @"zoomOut" ]; id classMockCameraUpdate = OCMClassMock([GMSCameraUpdate class]); [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValueNoPoint]; [[classMockCameraUpdate expect] zoomOut]; [classMockCameraUpdate stopMocking]; } - (void)testCameraUpdateFromChannelValueZoomTo { NSArray *channelValueNoPoint = @[ @"zoomTo", @1 ]; id classMockCameraUpdate = OCMClassMock([GMSCameraUpdate class]); [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValueNoPoint]; [[classMockCameraUpdate expect] zoomTo:1]; [classMockCameraUpdate stopMocking]; } @end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/GoogleMapsTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import google_maps_flutter_ios; @import google_maps_flutter_ios.Test; @import XCTest; @import GoogleMaps; #import #import "PartiallyMockedMapView.h" @interface FLTGoogleMapFactory (Test) @property(strong, nonatomic, readonly) id sharedMapServices; @end @interface GoogleMapsTests : XCTestCase @end @implementation GoogleMapsTests - (void)testPlugin { FLTGoogleMapsPlugin *plugin = [[FLTGoogleMapsPlugin alloc] init]; XCTAssertNotNil(plugin); } - (void)testFrameObserver { id registrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); CGRect frame = CGRectMake(0, 0, 100, 100); PartiallyMockedMapView *mapView = [[PartiallyMockedMapView alloc] initWithFrame:frame camera:[[GMSCameraPosition alloc] initWithLatitude:0 longitude:0 zoom:0]]; FLTGoogleMapController *controller = [[FLTGoogleMapController alloc] initWithMapView:mapView viewIdentifier:0 arguments:nil registrar:registrar]; for (NSInteger i = 0; i < 10; ++i) { [controller view]; } XCTAssertEqual(mapView.frameObserverCount, 1); mapView.frame = frame; XCTAssertEqual(mapView.frameObserverCount, 0); } - (void)testMapsServiceSync { id registrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); FLTGoogleMapFactory *factory1 = [[FLTGoogleMapFactory alloc] initWithRegistrar:registrar]; XCTAssertNotNil(factory1.sharedMapServices); FLTGoogleMapFactory *factory2 = [[FLTGoogleMapFactory alloc] initWithRegistrar:registrar]; // Test pointer equality, should be same retained singleton +[GMSServices sharedServices] object. // Retaining the opaque object should be enough to avoid multiple internal initializations, // but don't test the internals of the GoogleMaps API. Assume that it does what is documented. // https://developers.google.com/maps/documentation/ios-sdk/reference/interface_g_m_s_services#a436e03c32b1c0be74e072310a7158831 XCTAssertEqual(factory1.sharedMapServices, factory2.sharedMapServices); } @end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/Info.plist ================================================ 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 ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/PartiallyMockedMapView.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import GoogleMaps; /** * Defines a map view used for testing key-value observing. */ @interface PartiallyMockedMapView : GMSMapView /** * The number of times that the `frame` KVO has been added. */ @property(nonatomic, assign, readonly) NSInteger frameObserverCount; @end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/PartiallyMockedMapView.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "PartiallyMockedMapView.h" @interface PartiallyMockedMapView () @property(nonatomic, assign) NSInteger frameObserverCount; @end @implementation PartiallyMockedMapView - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context { [super addObserver:observer forKeyPath:keyPath options:options context:context]; if ([keyPath isEqualToString:@"frame"]) { ++self.frameObserverCount; } } - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath { [super removeObserver:observer forKeyPath:keyPath]; if ([keyPath isEqualToString:@"frame"]) { --self.frameObserverCount; } } @end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerUITests/GoogleMapsUITests.m ================================================ // Copyright 2013 The Flutter 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 CoreLocation; @import XCTest; @import os.log; @interface GoogleMapsUITests : XCTestCase @property(nonatomic, strong) XCUIApplication *app; @end @implementation GoogleMapsUITests - (void)setUp { self.continueAfterFailure = NO; self.app = [[XCUIApplication alloc] init]; [self.app launch]; [self addUIInterruptionMonitorWithDescription:@"Permission popups" handler:^BOOL(XCUIElement *_Nonnull interruptingElement) { if (@available(iOS 14, *)) { XCUIElement *locationPermission = interruptingElement.buttons[@"Allow While Using App"]; if (![locationPermission waitForExistenceWithTimeout:30.0]) { XCTFail(@"Failed due to not able to find " @"locationPermission button"); } [locationPermission tap]; } else { XCUIElement *allow = interruptingElement.buttons[@"Allow"]; if (![allow waitForExistenceWithTimeout:30.0]) { XCTFail(@"Failed due to not able to find Allow button"); } [allow tap]; } return YES; }]; } - (void)testUserInterface { XCUIApplication *app = self.app; XCUIElement *userInteface = app.staticTexts[@"User interface"]; if (![userInteface waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find User interface"); } [userInteface tap]; XCUIElement *platformView = app.otherElements[@"platform_view[0]"]; if (![platformView waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find platform view"); } // 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. // iOS 16 has a bug where if the app itself is directly tapped: [app tap], the first button // (disable compass) in the app is also tapped, so instead we tap a arbitrary location in the app // instead. XCUICoordinate *coordinate = [app coordinateWithNormalizedOffset:CGVectorMake(0, 0)]; [coordinate tap]; XCUIElement *compass = app.buttons[@"disable compass"]; if (![compass waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find disable compass button"); } [self forceTap:compass]; } - (void)testMapCoordinatesPage { XCUIApplication *app = self.app; XCUIElement *mapCoordinates = app.staticTexts[@"Map coordinates"]; if (![mapCoordinates waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find 'Map coordinates''"); } [mapCoordinates tap]; XCUIElement *platformView = app.otherElements[@"platform_view[0]"]; if (![platformView waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find platform view"); } XCUIElement *titleBar = app.otherElements[@"Map coordinates"]; if (![titleBar waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find title bar"); } NSPredicate *visibleRegionPredicate = [NSPredicate predicateWithFormat:@"label BEGINSWITH 'VisibleRegion'"]; XCUIElement *visibleRegionText = [app.staticTexts elementMatchingPredicate:visibleRegionPredicate]; if (![visibleRegionText waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find Visible Region label'"); } // Validate visible region does not change when scrolled under safe areas. // https://github.com/flutter/flutter/issues/107913 // Example -33.79495661816674, 151.313996873796 CLLocationCoordinate2D originalNortheast; // Example -33.90900557679571, 151.10800322145224 CLLocationCoordinate2D originalSouthwest; [self validateVisibleRegion:visibleRegionText.label northeast:&originalNortheast southwest:&originalSouthwest]; XCTAssertGreaterThan(originalNortheast.latitude, originalSouthwest.latitude); XCTAssertGreaterThan(originalNortheast.longitude, originalSouthwest.longitude); XCTAssertLessThan(originalNortheast.latitude, 0); XCTAssertLessThan(originalSouthwest.latitude, 0); XCTAssertGreaterThan(originalNortheast.longitude, 0); XCTAssertGreaterThan(originalSouthwest.longitude, 0); // Drag the map upward to under the title bar. [platformView pressForDuration:0 thenDragToElement:titleBar]; CLLocationCoordinate2D draggedNortheast; CLLocationCoordinate2D draggedSouthwest; [self validateVisibleRegion:visibleRegionText.label northeast:&draggedNortheast southwest:&draggedSouthwest]; XCTAssertEqual(originalNortheast.latitude, draggedNortheast.latitude); XCTAssertEqual(originalNortheast.longitude, draggedNortheast.longitude); XCTAssertEqual(originalSouthwest.latitude, draggedSouthwest.latitude); XCTAssertEqual(originalSouthwest.latitude, draggedSouthwest.latitude); } - (void)validateVisibleRegion:(NSString *)label northeast:(CLLocationCoordinate2D *)northeast southwest:(CLLocationCoordinate2D *)southwest { // String will be "VisibleRegion:\nnortheast: LatLng(-33.79495661816674, // 151.313996873796),\nsouthwest: LatLng(-33.90900557679571, 151.10800322145224)" NSScanner *scan = [NSScanner scannerWithString:label]; // northeast [scan scanString:@"VisibleRegion:\nnortheast: LatLng(" intoString:NULL]; double northeastLatitude; [scan scanDouble:&northeastLatitude]; [scan scanString:@", " intoString:NULL]; XCTAssertNotEqual(northeastLatitude, 0); double northeastLongitude; [scan scanDouble:&northeastLongitude]; XCTAssertNotEqual(northeastLongitude, 0); [scan scanString:@"),\nsouthwest: LatLng(" intoString:NULL]; double southwestLatitude; [scan scanDouble:&southwestLatitude]; XCTAssertNotEqual(southwestLatitude, 0); [scan scanString:@", " intoString:NULL]; double southwestLongitude; [scan scanDouble:&southwestLongitude]; XCTAssertNotEqual(southwestLongitude, 0); *northeast = CLLocationCoordinate2DMake(northeastLatitude, northeastLongitude); *southwest = CLLocationCoordinate2DMake(southwestLatitude, southwestLongitude); } - (void)testMapClickPage { XCUIApplication *app = self.app; XCUIElement *mapClick = app.staticTexts[@"Map click"]; if (![mapClick waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find 'Map click''"); } [mapClick tap]; XCUIElement *platformView = app.otherElements[@"platform_view[0]"]; if (![platformView waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find platform view"); } [platformView tap]; XCUIElement *tapped = app.staticTexts[@"Tapped"]; if (![tapped waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find 'tapped''"); } [platformView pressForDuration:5.0]; XCUIElement *longPressed = app.staticTexts[@"Long pressed"]; if (![longPressed waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find 'longPressed''"); } } - (void)forceTap:(XCUIElement *)button { // iOS 16 introduced a bug where hittable is NO for buttons. We force hit the location of the // button if that is the case. It is likely similar to // https://github.com/flutter/flutter/issues/113377. if (button.isHittable) { [button tap]; return; } XCUICoordinate *coordinate = [button coordinateWithNormalizedOffset:CGVectorMake(0, 0)]; [coordinate tap]; } @end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerUITests/Info.plist ================================================ 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 ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/lib/animate_camera.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; class AnimateCameraPage extends GoogleMapExampleAppPage { const AnimateCameraPage({Key? key}) : super(const Icon(Icons.map), 'Camera control, animated', key: key); @override Widget build(BuildContext context) { return const AnimateCamera(); } } class AnimateCamera extends StatefulWidget { const AnimateCamera({Key? key}) : super(key: key); @override State createState() => AnimateCameraState(); } class AnimateCameraState extends State { ExampleGoogleMapController? mapController; // ignore: use_setters_to_change_properties void _onMapCreated(ExampleGoogleMapController controller) { mapController = controller; } @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 300.0, height: 200.0, child: ExampleGoogleMap( onMapCreated: _onMapCreated, initialCameraPosition: const CameraPosition(target: LatLng(0.0, 0.0)), ), ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Column( children: [ TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.newCameraPosition( const CameraPosition( bearing: 270.0, target: LatLng(51.5160895, -0.1294527), tilt: 30.0, zoom: 17.0, ), ), ); }, child: const Text('newCameraPosition'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.newLatLng( const LatLng(56.1725505, 10.1850512), ), ); }, child: const Text('newLatLng'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.newLatLngBounds( LatLngBounds( southwest: const LatLng(-38.483935, 113.248673), northeast: const LatLng(-8.982446, 153.823821), ), 10.0, ), ); }, child: const Text('newLatLngBounds'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.newLatLngZoom( const LatLng(37.4231613, -122.087159), 11.0, ), ); }, child: const Text('newLatLngZoom'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.scrollBy(150.0, -225.0), ); }, child: const Text('scrollBy'), ), ], ), Column( children: [ TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.zoomBy( -0.5, const Offset(30.0, 20.0), ), ); }, child: const Text('zoomBy with focus'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.zoomBy(-0.5), ); }, child: const Text('zoomBy'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.zoomIn(), ); }, child: const Text('zoomIn'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.zoomOut(), ); }, child: const Text('zoomOut'), ), TextButton( onPressed: () { mapController?.animateCamera( CameraUpdate.zoomTo(16.0), ); }, child: const Text('zoomTo'), ), ], ), ], ) ], ); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/lib/example_google_map.dart ================================================ // Copyright 2013 The Flutter 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'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; // This is a pared down version of the Dart code from the app-facing package, // to allow running the same examples for package-local testing. // TODO(stuartmorgan): Consider extracting this to a shared package. See also // https://github.com/flutter/flutter/issues/46716. /// Controller for a single ExampleGoogleMap instance running on the host platform. class ExampleGoogleMapController { ExampleGoogleMapController._( this._googleMapState, { required this.mapId, }) { _connectStreams(mapId); } /// The mapId for this controller final int mapId; /// Initialize control of a [ExampleGoogleMap] with [id]. /// /// Mainly for internal use when instantiating a [ExampleGoogleMapController] passed /// in [ExampleGoogleMap.onMapCreated] callback. static Future _init( int id, CameraPosition initialCameraPosition, _ExampleGoogleMapState googleMapState, ) async { await GoogleMapsFlutterPlatform.instance.init(id); return ExampleGoogleMapController._( googleMapState, mapId: id, ); } final _ExampleGoogleMapState _googleMapState; void _connectStreams(int mapId) { if (_googleMapState.widget.onCameraMoveStarted != null) { GoogleMapsFlutterPlatform.instance .onCameraMoveStarted(mapId: mapId) .listen((_) => _googleMapState.widget.onCameraMoveStarted!()); } if (_googleMapState.widget.onCameraMove != null) { GoogleMapsFlutterPlatform.instance.onCameraMove(mapId: mapId).listen( (CameraMoveEvent e) => _googleMapState.widget.onCameraMove!(e.value)); } if (_googleMapState.widget.onCameraIdle != null) { GoogleMapsFlutterPlatform.instance .onCameraIdle(mapId: mapId) .listen((_) => _googleMapState.widget.onCameraIdle!()); } GoogleMapsFlutterPlatform.instance .onMarkerTap(mapId: mapId) .listen((MarkerTapEvent e) => _googleMapState.onMarkerTap(e.value)); GoogleMapsFlutterPlatform.instance.onMarkerDragStart(mapId: mapId).listen( (MarkerDragStartEvent e) => _googleMapState.onMarkerDragStart(e.value, e.position)); GoogleMapsFlutterPlatform.instance.onMarkerDrag(mapId: mapId).listen( (MarkerDragEvent e) => _googleMapState.onMarkerDrag(e.value, e.position)); GoogleMapsFlutterPlatform.instance.onMarkerDragEnd(mapId: mapId).listen( (MarkerDragEndEvent e) => _googleMapState.onMarkerDragEnd(e.value, e.position)); GoogleMapsFlutterPlatform.instance.onInfoWindowTap(mapId: mapId).listen( (InfoWindowTapEvent e) => _googleMapState.onInfoWindowTap(e.value)); GoogleMapsFlutterPlatform.instance .onPolylineTap(mapId: mapId) .listen((PolylineTapEvent e) => _googleMapState.onPolylineTap(e.value)); GoogleMapsFlutterPlatform.instance .onPolygonTap(mapId: mapId) .listen((PolygonTapEvent e) => _googleMapState.onPolygonTap(e.value)); GoogleMapsFlutterPlatform.instance .onCircleTap(mapId: mapId) .listen((CircleTapEvent e) => _googleMapState.onCircleTap(e.value)); GoogleMapsFlutterPlatform.instance .onTap(mapId: mapId) .listen((MapTapEvent e) => _googleMapState.onTap(e.position)); GoogleMapsFlutterPlatform.instance.onLongPress(mapId: mapId).listen( (MapLongPressEvent e) => _googleMapState.onLongPress(e.position)); } /// Updates configuration options of the map user interface. Future _updateMapConfiguration(MapConfiguration update) { return GoogleMapsFlutterPlatform.instance .updateMapConfiguration(update, mapId: mapId); } /// Updates marker configuration. Future _updateMarkers(MarkerUpdates markerUpdates) { return GoogleMapsFlutterPlatform.instance .updateMarkers(markerUpdates, mapId: mapId); } /// Updates polygon configuration. Future _updatePolygons(PolygonUpdates polygonUpdates) { return GoogleMapsFlutterPlatform.instance .updatePolygons(polygonUpdates, mapId: mapId); } /// Updates polyline configuration. Future _updatePolylines(PolylineUpdates polylineUpdates) { return GoogleMapsFlutterPlatform.instance .updatePolylines(polylineUpdates, mapId: mapId); } /// Updates circle configuration. Future _updateCircles(CircleUpdates circleUpdates) { return GoogleMapsFlutterPlatform.instance .updateCircles(circleUpdates, mapId: mapId); } /// Updates tile overlays configuration. Future _updateTileOverlays(Set newTileOverlays) { return GoogleMapsFlutterPlatform.instance .updateTileOverlays(newTileOverlays: newTileOverlays, mapId: mapId); } /// Clears the tile cache so that all tiles will be requested again from the /// [TileProvider]. Future clearTileCache(TileOverlayId tileOverlayId) async { return GoogleMapsFlutterPlatform.instance .clearTileCache(tileOverlayId, mapId: mapId); } /// Starts an animated change of the map camera position. Future animateCamera(CameraUpdate cameraUpdate) { return GoogleMapsFlutterPlatform.instance .animateCamera(cameraUpdate, mapId: mapId); } /// Changes the map camera position. Future moveCamera(CameraUpdate cameraUpdate) { return GoogleMapsFlutterPlatform.instance .moveCamera(cameraUpdate, mapId: mapId); } /// Sets the styling of the base map. Future setMapStyle(String? mapStyle) { return GoogleMapsFlutterPlatform.instance .setMapStyle(mapStyle, mapId: mapId); } /// Return [LatLngBounds] defining the region that is visible in a map. Future getVisibleRegion() { return GoogleMapsFlutterPlatform.instance.getVisibleRegion(mapId: mapId); } /// Return [ScreenCoordinate] of the [LatLng] in the current map view. Future getScreenCoordinate(LatLng latLng) { return GoogleMapsFlutterPlatform.instance .getScreenCoordinate(latLng, mapId: mapId); } /// Returns [LatLng] corresponding to the [ScreenCoordinate] in the current map view. Future getLatLng(ScreenCoordinate screenCoordinate) { return GoogleMapsFlutterPlatform.instance .getLatLng(screenCoordinate, mapId: mapId); } /// Programmatically show the Info Window for a [Marker]. Future showMarkerInfoWindow(MarkerId markerId) { return GoogleMapsFlutterPlatform.instance .showMarkerInfoWindow(markerId, mapId: mapId); } /// Programmatically hide the Info Window for a [Marker]. Future hideMarkerInfoWindow(MarkerId markerId) { return GoogleMapsFlutterPlatform.instance .hideMarkerInfoWindow(markerId, mapId: mapId); } /// Returns `true` when the [InfoWindow] is showing, `false` otherwise. Future isMarkerInfoWindowShown(MarkerId markerId) { return GoogleMapsFlutterPlatform.instance .isMarkerInfoWindowShown(markerId, mapId: mapId); } /// Returns the current zoom level of the map Future getZoomLevel() { return GoogleMapsFlutterPlatform.instance.getZoomLevel(mapId: mapId); } /// Returns the image bytes of the map Future takeSnapshot() { return GoogleMapsFlutterPlatform.instance.takeSnapshot(mapId: mapId); } /// Disposes of the platform resources void dispose() { GoogleMapsFlutterPlatform.instance.dispose(mapId: mapId); } } // The next map ID to create. int _nextMapCreationId = 0; /// A widget which displays a map with data obtained from the Google Maps service. class ExampleGoogleMap extends StatefulWidget { /// Creates a widget displaying data from Google Maps services. /// /// [AssertionError] will be thrown if [initialCameraPosition] is null; const ExampleGoogleMap({ Key? key, required this.initialCameraPosition, this.onMapCreated, this.gestureRecognizers = const >{}, this.compassEnabled = true, this.mapToolbarEnabled = true, this.cameraTargetBounds = CameraTargetBounds.unbounded, this.mapType = MapType.normal, this.minMaxZoomPreference = MinMaxZoomPreference.unbounded, this.rotateGesturesEnabled = true, this.scrollGesturesEnabled = true, this.zoomControlsEnabled = true, this.zoomGesturesEnabled = true, this.liteModeEnabled = false, this.tiltGesturesEnabled = true, this.myLocationEnabled = false, this.myLocationButtonEnabled = true, this.layoutDirection, /// If no padding is specified default padding will be 0. this.padding = EdgeInsets.zero, this.indoorViewEnabled = false, this.trafficEnabled = false, this.buildingsEnabled = true, this.markers = const {}, this.polygons = const {}, this.polylines = const {}, this.circles = const {}, this.onCameraMoveStarted, this.tileOverlays = const {}, this.onCameraMove, this.onCameraIdle, this.onTap, this.onLongPress, }) : super(key: key); /// Callback method for when the map is ready to be used. /// /// Used to receive a [ExampleGoogleMapController] for this [ExampleGoogleMap]. final void Function(ExampleGoogleMapController controller)? onMapCreated; /// The initial position of the map's camera. final CameraPosition initialCameraPosition; /// True if the map should show a compass when rotated. final bool compassEnabled; /// True if the map should show a toolbar when you interact with the map. Android only. final bool mapToolbarEnabled; /// Geographical bounding box for the camera target. final CameraTargetBounds cameraTargetBounds; /// Type of map tiles to be rendered. final MapType mapType; /// The layout direction to use for the embedded view. final TextDirection? layoutDirection; /// Preferred bounds for the camera zoom level. /// /// Actual bounds depend on map data and device. final MinMaxZoomPreference minMaxZoomPreference; /// True if the map view should respond to rotate gestures. final bool rotateGesturesEnabled; /// True if the map view should respond to scroll gestures. final bool scrollGesturesEnabled; /// True if the map view should show zoom controls. This includes two buttons /// to zoom in and zoom out. The default value is to show zoom controls. final bool zoomControlsEnabled; /// True if the map view should respond to zoom gestures. final bool zoomGesturesEnabled; /// True if the map view should be in lite mode. Android only. final bool liteModeEnabled; /// True if the map view should respond to tilt gestures. final bool tiltGesturesEnabled; /// Padding to be set on map. final EdgeInsets padding; /// Markers to be placed on the map. final Set markers; /// Polygons to be placed on the map. final Set polygons; /// Polylines to be placed on the map. final Set polylines; /// Circles to be placed on the map. final Set circles; /// Tile overlays to be placed on the map. final Set tileOverlays; /// Called when the camera starts moving. final VoidCallback? onCameraMoveStarted; /// Called repeatedly as the camera continues to move after an /// onCameraMoveStarted call. final CameraPositionCallback? onCameraMove; /// Called when camera movement has ended, there are no pending /// animations and the user has stopped interacting with the map. final VoidCallback? onCameraIdle; /// Called every time a [ExampleGoogleMap] is tapped. final ArgumentCallback? onTap; /// Called every time a [ExampleGoogleMap] is long pressed. final ArgumentCallback? onLongPress; /// True if a "My Location" layer should be shown on the map. final bool myLocationEnabled; /// Enables or disables the my-location button. final bool myLocationButtonEnabled; /// Enables or disables the indoor view from the map final bool indoorViewEnabled; /// Enables or disables the traffic layer of the map final bool trafficEnabled; /// Enables or disables showing 3D buildings where available final bool buildingsEnabled; /// Which gestures should be consumed by the map. final Set> gestureRecognizers; /// Creates a [State] for this [ExampleGoogleMap]. @override State createState() => _ExampleGoogleMapState(); } class _ExampleGoogleMapState extends State { final int _mapId = _nextMapCreationId++; final Completer _controller = Completer(); Map _markers = {}; Map _polygons = {}; Map _polylines = {}; Map _circles = {}; late MapConfiguration _mapConfiguration; @override Widget build(BuildContext context) { return GoogleMapsFlutterPlatform.instance.buildViewWithConfiguration( _mapId, onPlatformViewCreated, widgetConfiguration: MapWidgetConfiguration( textDirection: widget.layoutDirection ?? Directionality.maybeOf(context) ?? TextDirection.ltr, initialCameraPosition: widget.initialCameraPosition, gestureRecognizers: widget.gestureRecognizers, ), mapObjects: MapObjects( markers: widget.markers, polygons: widget.polygons, polylines: widget.polylines, circles: widget.circles, ), mapConfiguration: _mapConfiguration, ); } @override void initState() { super.initState(); _mapConfiguration = _configurationFromMapWidget(widget); _markers = keyByMarkerId(widget.markers); _polygons = keyByPolygonId(widget.polygons); _polylines = keyByPolylineId(widget.polylines); _circles = keyByCircleId(widget.circles); } @override void dispose() { _controller.future .then((ExampleGoogleMapController controller) => controller.dispose()); super.dispose(); } @override void didUpdateWidget(ExampleGoogleMap oldWidget) { super.didUpdateWidget(oldWidget); _updateOptions(); _updateMarkers(); _updatePolygons(); _updatePolylines(); _updateCircles(); _updateTileOverlays(); } Future _updateOptions() async { final MapConfiguration newConfig = _configurationFromMapWidget(widget); final MapConfiguration updates = newConfig.diffFrom(_mapConfiguration); if (updates.isEmpty) { return; } final ExampleGoogleMapController controller = await _controller.future; controller._updateMapConfiguration(updates); _mapConfiguration = newConfig; } Future _updateMarkers() async { final ExampleGoogleMapController controller = await _controller.future; controller._updateMarkers( MarkerUpdates.from(_markers.values.toSet(), widget.markers)); _markers = keyByMarkerId(widget.markers); } Future _updatePolygons() async { final ExampleGoogleMapController controller = await _controller.future; controller._updatePolygons( PolygonUpdates.from(_polygons.values.toSet(), widget.polygons)); _polygons = keyByPolygonId(widget.polygons); } Future _updatePolylines() async { final ExampleGoogleMapController controller = await _controller.future; controller._updatePolylines( PolylineUpdates.from(_polylines.values.toSet(), widget.polylines)); _polylines = keyByPolylineId(widget.polylines); } Future _updateCircles() async { final ExampleGoogleMapController controller = await _controller.future; controller._updateCircles( CircleUpdates.from(_circles.values.toSet(), widget.circles)); _circles = keyByCircleId(widget.circles); } Future _updateTileOverlays() async { final ExampleGoogleMapController controller = await _controller.future; controller._updateTileOverlays(widget.tileOverlays); } Future onPlatformViewCreated(int id) async { final ExampleGoogleMapController controller = await ExampleGoogleMapController._init( id, widget.initialCameraPosition, this, ); _controller.complete(controller); _updateTileOverlays(); widget.onMapCreated?.call(controller); } void onMarkerTap(MarkerId markerId) { _markers[markerId]!.onTap?.call(); } void onMarkerDragStart(MarkerId markerId, LatLng position) { _markers[markerId]!.onDragStart?.call(position); } void onMarkerDrag(MarkerId markerId, LatLng position) { _markers[markerId]!.onDrag?.call(position); } void onMarkerDragEnd(MarkerId markerId, LatLng position) { _markers[markerId]!.onDragEnd?.call(position); } void onPolygonTap(PolygonId polygonId) { _polygons[polygonId]!.onTap?.call(); } void onPolylineTap(PolylineId polylineId) { _polylines[polylineId]!.onTap?.call(); } void onCircleTap(CircleId circleId) { _circles[circleId]!.onTap?.call(); } void onInfoWindowTap(MarkerId markerId) { _markers[markerId]!.infoWindow.onTap?.call(); } void onTap(LatLng position) { widget.onTap?.call(position); } void onLongPress(LatLng position) { widget.onLongPress?.call(position); } } /// Builds a [MapConfiguration] from the given [map]. MapConfiguration _configurationFromMapWidget(ExampleGoogleMap map) { return MapConfiguration( compassEnabled: map.compassEnabled, mapToolbarEnabled: map.mapToolbarEnabled, cameraTargetBounds: map.cameraTargetBounds, mapType: map.mapType, minMaxZoomPreference: map.minMaxZoomPreference, rotateGesturesEnabled: map.rotateGesturesEnabled, scrollGesturesEnabled: map.scrollGesturesEnabled, tiltGesturesEnabled: map.tiltGesturesEnabled, trackCameraPosition: map.onCameraMove != null, zoomControlsEnabled: map.zoomControlsEnabled, zoomGesturesEnabled: map.zoomGesturesEnabled, liteModeEnabled: map.liteModeEnabled, myLocationEnabled: map.myLocationEnabled, myLocationButtonEnabled: map.myLocationButtonEnabled, padding: map.padding, indoorViewEnabled: map.indoorViewEnabled, trafficEnabled: map.trafficEnabled, buildingsEnabled: map.buildingsEnabled, ); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/lib/lite_mode.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; const CameraPosition _kInitialPosition = CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0); class LiteModePage extends GoogleMapExampleAppPage { const LiteModePage({Key? key}) : super(const Icon(Icons.map), 'Lite mode', key: key); @override Widget build(BuildContext context) { return const _LiteModeBody(); } } class _LiteModeBody extends StatelessWidget { const _LiteModeBody(); @override Widget build(BuildContext context) { return const Card( child: Padding( padding: EdgeInsets.symmetric(vertical: 30.0), child: Center( child: SizedBox( width: 300.0, height: 300.0, child: ExampleGoogleMap( initialCameraPosition: _kInitialPosition, liteModeEnabled: true, ), ), ), ), ); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'animate_camera.dart'; import 'lite_mode.dart'; import 'map_click.dart'; import 'map_coordinates.dart'; import 'map_ui.dart'; import 'marker_icons.dart'; import 'move_camera.dart'; import 'padding.dart'; import 'page.dart'; import 'place_circle.dart'; import 'place_marker.dart'; import 'place_polygon.dart'; import 'place_polyline.dart'; import 'scrolling_map.dart'; import 'snapshot.dart'; import 'tile_overlay.dart'; final List _allPages = [ const MapUiPage(), const MapCoordinatesPage(), const MapClickPage(), const AnimateCameraPage(), const MoveCameraPage(), const PlaceMarkerPage(), const MarkerIconsPage(), const ScrollingMapPage(), const PlacePolylinePage(), const PlacePolygonPage(), const PlaceCirclePage(), const PaddingPage(), const SnapshotPage(), const LiteModePage(), const TileOverlayPage(), ]; /// MapsDemo is the Main Application. class MapsDemo extends StatelessWidget { /// Default Constructor const MapsDemo({Key? key}) : super(key: key); void _pushPage(BuildContext context, GoogleMapExampleAppPage page) { Navigator.of(context).push(MaterialPageRoute( builder: (_) => Scaffold( appBar: AppBar(title: Text(page.title)), body: page, ))); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('GoogleMaps examples')), body: ListView.builder( itemCount: _allPages.length, itemBuilder: (_, int index) => ListTile( leading: _allPages[index].leading, title: Text(_allPages[index].title), onTap: () => _pushPage(context, _allPages[index]), ), ), ); } } void main() { runApp(const MaterialApp(home: MapsDemo())); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_click.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; const CameraPosition _kInitialPosition = CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0); class MapClickPage extends GoogleMapExampleAppPage { const MapClickPage({Key? key}) : super(const Icon(Icons.mouse), 'Map click', key: key); @override Widget build(BuildContext context) { return const _MapClickBody(); } } class _MapClickBody extends StatefulWidget { const _MapClickBody(); @override State createState() => _MapClickBodyState(); } class _MapClickBodyState extends State<_MapClickBody> { _MapClickBodyState(); ExampleGoogleMapController? mapController; LatLng? _lastTap; LatLng? _lastLongPress; @override Widget build(BuildContext context) { final ExampleGoogleMap googleMap = ExampleGoogleMap( onMapCreated: onMapCreated, initialCameraPosition: _kInitialPosition, onTap: (LatLng pos) { setState(() { _lastTap = pos; }); }, onLongPress: (LatLng pos) { setState(() { _lastLongPress = pos; }); }, ); final List columnChildren = [ Padding( padding: const EdgeInsets.all(10.0), child: Center( child: SizedBox( width: 300.0, height: 200.0, child: googleMap, ), ), ), ]; if (mapController != null) { final String lastTap = 'Tap:\n${_lastTap ?? ""}\n'; final String lastLongPress = 'Long press:\n${_lastLongPress ?? ""}'; columnChildren.add(Center( child: Text( lastTap, textAlign: TextAlign.center, ))); columnChildren.add(Center( child: Text( _lastTap != null ? 'Tapped' : '', textAlign: TextAlign.center, ))); columnChildren.add(Center( child: Text( lastLongPress, textAlign: TextAlign.center, ))); columnChildren.add(Center( child: Text( _lastLongPress != null ? 'Long pressed' : '', textAlign: TextAlign.center, ))); } return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: columnChildren, ); } Future onMapCreated(ExampleGoogleMapController controller) async { setState(() { mapController = controller; }); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_coordinates.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; const CameraPosition _kInitialPosition = CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0); class MapCoordinatesPage extends GoogleMapExampleAppPage { const MapCoordinatesPage({Key? key}) : super(const Icon(Icons.map), 'Map coordinates', key: key); @override Widget build(BuildContext context) { return const _MapCoordinatesBody(); } } class _MapCoordinatesBody extends StatefulWidget { const _MapCoordinatesBody(); @override State createState() => _MapCoordinatesBodyState(); } class _MapCoordinatesBodyState extends State<_MapCoordinatesBody> { _MapCoordinatesBodyState(); ExampleGoogleMapController? mapController; LatLngBounds _visibleRegion = LatLngBounds( southwest: const LatLng(0, 0), northeast: const LatLng(0, 0), ); @override Widget build(BuildContext context) { final ExampleGoogleMap googleMap = ExampleGoogleMap( onMapCreated: onMapCreated, initialCameraPosition: _kInitialPosition, onCameraIdle: _updateVisibleRegion, // https://github.com/flutter/flutter/issues/54758 ); return NotificationListener( onNotification: (ScrollNotification scrollState) { _updateVisibleRegion(); return true; }, child: Stack( children: [ ListView( children: [ Padding( padding: const EdgeInsets.all(10.0), child: Center( child: SizedBox( width: 300.0, height: 200.0, child: googleMap, ), ), ), // Add a block at the bottom of this list to allow validation that the visible region of the map // does not change when scrolled under the safe view on iOS. // https://github.com/flutter/flutter/issues/107913 const SizedBox( width: 300, height: 1000, ), ], ), if (mapController != null) Center( child: Text('VisibleRegion:' '\nnortheast: ${_visibleRegion.northeast},' '\nsouthwest: ${_visibleRegion.southwest}'), ), ], ), ); } Future onMapCreated(ExampleGoogleMapController controller) async { final LatLngBounds visibleRegion = await controller.getVisibleRegion(); setState(() { mapController = controller; _visibleRegion = visibleRegion; }); } Future _updateVisibleRegion() async { final LatLngBounds visibleRegion = await mapController!.getVisibleRegion(); setState(() { _visibleRegion = visibleRegion; }); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_ui.dart ================================================ // Copyright 2013 The Flutter 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:flutter/services.dart' show rootBundle; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; final LatLngBounds sydneyBounds = LatLngBounds( southwest: const LatLng(-34.022631, 150.620685), northeast: const LatLng(-33.571835, 151.325952), ); class MapUiPage extends GoogleMapExampleAppPage { const MapUiPage({Key? key}) : super(const Icon(Icons.map), 'User interface', key: key); @override Widget build(BuildContext context) { return const MapUiBody(); } } class MapUiBody extends StatefulWidget { const MapUiBody({Key? key}) : super(key: key); @override State createState() => MapUiBodyState(); } class MapUiBodyState extends State { MapUiBodyState(); static const CameraPosition _kInitialPosition = CameraPosition( target: LatLng(-33.852, 151.211), zoom: 11.0, ); CameraPosition _position = _kInitialPosition; bool _isMapCreated = false; final bool _isMoving = false; bool _compassEnabled = true; bool _mapToolbarEnabled = true; CameraTargetBounds _cameraTargetBounds = CameraTargetBounds.unbounded; MinMaxZoomPreference _minMaxZoomPreference = MinMaxZoomPreference.unbounded; MapType _mapType = MapType.normal; bool _rotateGesturesEnabled = true; bool _scrollGesturesEnabled = true; bool _tiltGesturesEnabled = true; bool _zoomControlsEnabled = false; bool _zoomGesturesEnabled = true; bool _indoorViewEnabled = true; bool _myLocationEnabled = true; bool _myTrafficEnabled = false; bool _myLocationButtonEnabled = true; late ExampleGoogleMapController _controller; bool _nightMode = false; @override void initState() { super.initState(); } @override void dispose() { super.dispose(); } Widget _compassToggler() { return TextButton( child: Text('${_compassEnabled ? 'disable' : 'enable'} compass'), onPressed: () { setState(() { _compassEnabled = !_compassEnabled; }); }, ); } Widget _mapToolbarToggler() { return TextButton( child: Text('${_mapToolbarEnabled ? 'disable' : 'enable'} map toolbar'), onPressed: () { setState(() { _mapToolbarEnabled = !_mapToolbarEnabled; }); }, ); } Widget _latLngBoundsToggler() { return TextButton( child: Text( _cameraTargetBounds.bounds == null ? 'bound camera target' : 'release camera target', ), onPressed: () { setState(() { _cameraTargetBounds = _cameraTargetBounds.bounds == null ? CameraTargetBounds(sydneyBounds) : CameraTargetBounds.unbounded; }); }, ); } Widget _zoomBoundsToggler() { return TextButton( child: Text(_minMaxZoomPreference.minZoom == null ? 'bound zoom' : 'release zoom'), onPressed: () { setState(() { _minMaxZoomPreference = _minMaxZoomPreference.minZoom == null ? const MinMaxZoomPreference(12.0, 16.0) : MinMaxZoomPreference.unbounded; }); }, ); } Widget _mapTypeCycler() { final MapType nextType = MapType.values[(_mapType.index + 1) % MapType.values.length]; return TextButton( child: Text('change map type to $nextType'), onPressed: () { setState(() { _mapType = nextType; }); }, ); } Widget _rotateToggler() { return TextButton( child: Text('${_rotateGesturesEnabled ? 'disable' : 'enable'} rotate'), onPressed: () { setState(() { _rotateGesturesEnabled = !_rotateGesturesEnabled; }); }, ); } Widget _scrollToggler() { return TextButton( child: Text('${_scrollGesturesEnabled ? 'disable' : 'enable'} scroll'), onPressed: () { setState(() { _scrollGesturesEnabled = !_scrollGesturesEnabled; }); }, ); } Widget _tiltToggler() { return TextButton( child: Text('${_tiltGesturesEnabled ? 'disable' : 'enable'} tilt'), onPressed: () { setState(() { _tiltGesturesEnabled = !_tiltGesturesEnabled; }); }, ); } Widget _zoomToggler() { return TextButton( child: Text('${_zoomGesturesEnabled ? 'disable' : 'enable'} zoom'), onPressed: () { setState(() { _zoomGesturesEnabled = !_zoomGesturesEnabled; }); }, ); } Widget _zoomControlsToggler() { return TextButton( child: Text('${_zoomControlsEnabled ? 'disable' : 'enable'} zoom controls'), onPressed: () { setState(() { _zoomControlsEnabled = !_zoomControlsEnabled; }); }, ); } Widget _indoorViewToggler() { return TextButton( child: Text('${_indoorViewEnabled ? 'disable' : 'enable'} indoor'), onPressed: () { setState(() { _indoorViewEnabled = !_indoorViewEnabled; }); }, ); } Widget _myLocationToggler() { return TextButton( child: Text( '${_myLocationEnabled ? 'disable' : 'enable'} my location marker'), onPressed: () { setState(() { _myLocationEnabled = !_myLocationEnabled; }); }, ); } Widget _myLocationButtonToggler() { return TextButton( child: Text( '${_myLocationButtonEnabled ? 'disable' : 'enable'} my location button'), onPressed: () { setState(() { _myLocationButtonEnabled = !_myLocationButtonEnabled; }); }, ); } Widget _myTrafficToggler() { return TextButton( child: Text('${_myTrafficEnabled ? 'disable' : 'enable'} my traffic'), onPressed: () { setState(() { _myTrafficEnabled = !_myTrafficEnabled; }); }, ); } Future _getFileData(String path) async { return rootBundle.loadString(path); } void _setMapStyle(String mapStyle) { setState(() { _nightMode = true; _controller.setMapStyle(mapStyle); }); } // Should only be called if _isMapCreated is true. Widget _nightModeToggler() { assert(_isMapCreated); return TextButton( child: Text('${_nightMode ? 'disable' : 'enable'} night mode'), onPressed: () { if (_nightMode) { setState(() { _nightMode = false; _controller.setMapStyle(null); }); } else { _getFileData('assets/night_mode.json').then(_setMapStyle); } }, ); } @override Widget build(BuildContext context) { final ExampleGoogleMap googleMap = ExampleGoogleMap( onMapCreated: onMapCreated, initialCameraPosition: _kInitialPosition, compassEnabled: _compassEnabled, mapToolbarEnabled: _mapToolbarEnabled, cameraTargetBounds: _cameraTargetBounds, minMaxZoomPreference: _minMaxZoomPreference, mapType: _mapType, rotateGesturesEnabled: _rotateGesturesEnabled, scrollGesturesEnabled: _scrollGesturesEnabled, tiltGesturesEnabled: _tiltGesturesEnabled, zoomGesturesEnabled: _zoomGesturesEnabled, zoomControlsEnabled: _zoomControlsEnabled, indoorViewEnabled: _indoorViewEnabled, myLocationEnabled: _myLocationEnabled, myLocationButtonEnabled: _myLocationButtonEnabled, trafficEnabled: _myTrafficEnabled, onCameraMove: _updateCameraPosition, ); final List columnChildren = [ Padding( padding: const EdgeInsets.all(10.0), child: Center( child: SizedBox( width: 300.0, height: 200.0, child: googleMap, ), ), ), ]; if (_isMapCreated) { columnChildren.add( Expanded( child: ListView( children: [ Text('camera bearing: ${_position.bearing}'), Text( 'camera target: ${_position.target.latitude.toStringAsFixed(4)},' '${_position.target.longitude.toStringAsFixed(4)}'), Text('camera zoom: ${_position.zoom}'), Text('camera tilt: ${_position.tilt}'), Text(_isMoving ? '(Camera moving)' : '(Camera idle)'), _compassToggler(), _mapToolbarToggler(), _latLngBoundsToggler(), _mapTypeCycler(), _zoomBoundsToggler(), _rotateToggler(), _scrollToggler(), _tiltToggler(), _zoomToggler(), _zoomControlsToggler(), _indoorViewToggler(), _myLocationToggler(), _myLocationButtonToggler(), _myTrafficToggler(), _nightModeToggler(), ], ), ), ); } return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: columnChildren, ); } void _updateCameraPosition(CameraPosition position) { setState(() { _position = position; }); } void onMapCreated(ExampleGoogleMapController controller) { setState(() { _controller = controller; _isMapCreated = true; }); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/lib/marker_icons.dart ================================================ // Copyright 2013 The Flutter 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 // ignore_for_file: unawaited_futures import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; class MarkerIconsPage extends GoogleMapExampleAppPage { const MarkerIconsPage({Key? key}) : super(const Icon(Icons.image), 'Marker icons', key: key); @override Widget build(BuildContext context) { return const MarkerIconsBody(); } } class MarkerIconsBody extends StatefulWidget { const MarkerIconsBody({Key? key}) : super(key: key); @override State createState() => MarkerIconsBodyState(); } const LatLng _kMapCenter = LatLng(52.4478, -3.5402); class MarkerIconsBodyState extends State { ExampleGoogleMapController? controller; BitmapDescriptor? _markerIcon; @override Widget build(BuildContext context) { _createMarkerImageFromAsset(context); return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 350.0, height: 300.0, child: ExampleGoogleMap( initialCameraPosition: const CameraPosition( target: _kMapCenter, zoom: 7.0, ), markers: {_createMarker()}, onMapCreated: _onMapCreated, ), ), ) ], ); } Marker _createMarker() { if (_markerIcon != null) { return Marker( markerId: const MarkerId('marker_1'), position: _kMapCenter, icon: _markerIcon!, ); } else { return const Marker( markerId: MarkerId('marker_1'), position: _kMapCenter, ); } } Future _createMarkerImageFromAsset(BuildContext context) async { if (_markerIcon == null) { final ImageConfiguration imageConfiguration = createLocalImageConfiguration(context, size: const Size.square(48)); BitmapDescriptor.fromAssetImage( imageConfiguration, 'assets/red_square.png') .then(_updateBitmap); } } void _updateBitmap(BitmapDescriptor bitmap) { setState(() { _markerIcon = bitmap; }); } void _onMapCreated(ExampleGoogleMapController controllerParam) { setState(() { controller = controllerParam; }); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/lib/move_camera.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; class MoveCameraPage extends GoogleMapExampleAppPage { const MoveCameraPage({Key? key}) : super(const Icon(Icons.map), 'Camera control', key: key); @override Widget build(BuildContext context) { return const MoveCamera(); } } class MoveCamera extends StatefulWidget { const MoveCamera({Key? key}) : super(key: key); @override State createState() => MoveCameraState(); } class MoveCameraState extends State { ExampleGoogleMapController? mapController; // ignore: use_setters_to_change_properties void _onMapCreated(ExampleGoogleMapController controller) { mapController = controller; } @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 300.0, height: 200.0, child: ExampleGoogleMap( onMapCreated: _onMapCreated, initialCameraPosition: const CameraPosition(target: LatLng(0.0, 0.0)), ), ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Column( children: [ TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.newCameraPosition( const CameraPosition( bearing: 270.0, target: LatLng(51.5160895, -0.1294527), tilt: 30.0, zoom: 17.0, ), ), ); }, child: const Text('newCameraPosition'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.newLatLng( const LatLng(56.1725505, 10.1850512), ), ); }, child: const Text('newLatLng'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.newLatLngBounds( LatLngBounds( southwest: const LatLng(-38.483935, 113.248673), northeast: const LatLng(-8.982446, 153.823821), ), 10.0, ), ); }, child: const Text('newLatLngBounds'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.newLatLngZoom( const LatLng(37.4231613, -122.087159), 11.0, ), ); }, child: const Text('newLatLngZoom'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.scrollBy(150.0, -225.0), ); }, child: const Text('scrollBy'), ), ], ), Column( children: [ TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.zoomBy( -0.5, const Offset(30.0, 20.0), ), ); }, child: const Text('zoomBy with focus'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.zoomBy(-0.5), ); }, child: const Text('zoomBy'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.zoomIn(), ); }, child: const Text('zoomIn'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.zoomOut(), ); }, child: const Text('zoomOut'), ), TextButton( onPressed: () { mapController?.moveCamera( CameraUpdate.zoomTo(16.0), ); }, child: const Text('zoomTo'), ), ], ), ], ) ], ); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/lib/padding.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; class PaddingPage extends GoogleMapExampleAppPage { const PaddingPage({Key? key}) : super(const Icon(Icons.map), 'Add padding to the map', key: key); @override Widget build(BuildContext context) { return const MarkerIconsBody(); } } class MarkerIconsBody extends StatefulWidget { const MarkerIconsBody({Key? key}) : super(key: key); @override State createState() => MarkerIconsBodyState(); } const LatLng _kMapCenter = LatLng(52.4478, -3.5402); class MarkerIconsBodyState extends State { ExampleGoogleMapController? controller; EdgeInsets _padding = EdgeInsets.zero; @override Widget build(BuildContext context) { final ExampleGoogleMap googleMap = ExampleGoogleMap( onMapCreated: _onMapCreated, initialCameraPosition: const CameraPosition( target: _kMapCenter, zoom: 7.0, ), padding: _padding, ); final List columnChildren = [ Padding( padding: const EdgeInsets.all(10.0), child: Center( child: SizedBox( width: 300.0, height: 200.0, child: googleMap, ), ), ), const Padding( padding: EdgeInsets.only(top: 20), child: Center( child: Text( 'Enter Padding Below', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), ), ), ]; columnChildren.addAll([_paddingInput(), _buttons()]); return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: columnChildren, ); } void _onMapCreated(ExampleGoogleMapController controllerParam) { setState(() { controller = controllerParam; }); } final TextEditingController _topController = TextEditingController(); final TextEditingController _bottomController = TextEditingController(); final TextEditingController _leftController = TextEditingController(); final TextEditingController _rightController = TextEditingController(); Widget _paddingInput() { return Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ Flexible( flex: 2, child: TextField( controller: _topController, keyboardType: TextInputType.number, textAlign: TextAlign.center, decoration: const InputDecoration( hintText: 'Top', ), ), ), const Spacer(), Flexible( flex: 2, child: TextField( controller: _bottomController, keyboardType: TextInputType.number, textAlign: TextAlign.center, decoration: const InputDecoration( hintText: 'Bottom', ), ), ), const Spacer(), Flexible( flex: 2, child: TextField( controller: _leftController, keyboardType: TextInputType.number, textAlign: TextAlign.center, decoration: const InputDecoration( hintText: 'Left', ), ), ), const Spacer(), Flexible( flex: 2, child: TextField( controller: _rightController, keyboardType: TextInputType.number, textAlign: TextAlign.center, decoration: const InputDecoration( hintText: 'Right', ), ), ), ], ), ); } Widget _buttons() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ TextButton( child: const Text('Set Padding'), onPressed: () { setState(() { _padding = EdgeInsets.fromLTRB( double.tryParse(_leftController.value.text) ?? 0, double.tryParse(_topController.value.text) ?? 0, double.tryParse(_rightController.value.text) ?? 0, double.tryParse(_bottomController.value.text) ?? 0); }); }, ), TextButton( child: const Text('Reset Padding'), onPressed: () { setState(() { _topController.clear(); _bottomController.clear(); _leftController.clear(); _rightController.clear(); _padding = EdgeInsets.zero; }); }, ) ], ), ); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/lib/page.dart ================================================ // Copyright 2013 The Flutter 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'; abstract class GoogleMapExampleAppPage extends StatelessWidget { const GoogleMapExampleAppPage(this.leading, this.title, {Key? key}) : super(key: key); final Widget leading; final String title; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_circle.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; class PlaceCirclePage extends GoogleMapExampleAppPage { const PlaceCirclePage({Key? key}) : super(const Icon(Icons.linear_scale), 'Place circle', key: key); @override Widget build(BuildContext context) { return const PlaceCircleBody(); } } class PlaceCircleBody extends StatefulWidget { const PlaceCircleBody({Key? key}) : super(key: key); @override State createState() => PlaceCircleBodyState(); } class PlaceCircleBodyState extends State { PlaceCircleBodyState(); ExampleGoogleMapController? controller; Map circles = {}; int _circleIdCounter = 1; CircleId? selectedCircle; // Values when toggling circle color int fillColorsIndex = 0; int strokeColorsIndex = 0; List colors = [ Colors.purple, Colors.red, Colors.green, Colors.pink, ]; // Values when toggling circle stroke width int widthsIndex = 0; List widths = [10, 20, 5]; // ignore: use_setters_to_change_properties void _onMapCreated(ExampleGoogleMapController controller) { this.controller = controller; } @override void dispose() { super.dispose(); } void _onCircleTapped(CircleId circleId) { setState(() { selectedCircle = circleId; }); } void _remove(CircleId circleId) { setState(() { if (circles.containsKey(circleId)) { circles.remove(circleId); } if (circleId == selectedCircle) { selectedCircle = null; } }); } void _add() { final int circleCount = circles.length; if (circleCount == 12) { return; } final String circleIdVal = 'circle_id_$_circleIdCounter'; _circleIdCounter++; final CircleId circleId = CircleId(circleIdVal); final Circle circle = Circle( circleId: circleId, consumeTapEvents: true, strokeColor: Colors.orange, fillColor: Colors.green, strokeWidth: 5, center: _createCenter(), radius: 50000, onTap: () { _onCircleTapped(circleId); }, ); setState(() { circles[circleId] = circle; }); } void _toggleVisible(CircleId circleId) { final Circle circle = circles[circleId]!; setState(() { circles[circleId] = circle.copyWith( visibleParam: !circle.visible, ); }); } void _changeFillColor(CircleId circleId) { final Circle circle = circles[circleId]!; setState(() { circles[circleId] = circle.copyWith( fillColorParam: colors[++fillColorsIndex % colors.length], ); }); } void _changeStrokeColor(CircleId circleId) { final Circle circle = circles[circleId]!; setState(() { circles[circleId] = circle.copyWith( strokeColorParam: colors[++strokeColorsIndex % colors.length], ); }); } void _changeStrokeWidth(CircleId circleId) { final Circle circle = circles[circleId]!; setState(() { circles[circleId] = circle.copyWith( strokeWidthParam: widths[++widthsIndex % widths.length], ); }); } @override Widget build(BuildContext context) { final CircleId? selectedId = selectedCircle; return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 350.0, height: 300.0, child: ExampleGoogleMap( initialCameraPosition: const CameraPosition( target: LatLng(52.4478, -3.5402), zoom: 7.0, ), circles: Set.of(circles.values), onMapCreated: _onMapCreated, ), ), ), Expanded( child: SingleChildScrollView( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row( children: [ Column( children: [ TextButton( onPressed: _add, child: const Text('add'), ), TextButton( onPressed: (selectedId == null) ? null : () => _remove(selectedId), child: const Text('remove'), ), TextButton( onPressed: (selectedId == null) ? null : () => _toggleVisible(selectedId), child: const Text('toggle visible'), ), ], ), Column( children: [ TextButton( onPressed: (selectedId == null) ? null : () => _changeStrokeWidth(selectedId), child: const Text('change stroke width'), ), TextButton( onPressed: (selectedId == null) ? null : () => _changeStrokeColor(selectedId), child: const Text('change stroke color'), ), TextButton( onPressed: (selectedId == null) ? null : () => _changeFillColor(selectedId), child: const Text('change fill color'), ), ], ) ], ) ], ), ), ), ], ); } LatLng _createCenter() { final double offset = _circleIdCounter.ceilToDouble(); return _createLatLng(51.4816 + offset * 0.2, -3.1791); } LatLng _createLatLng(double lat, double lng) { return LatLng(lat, lng); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_marker.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'dart:math'; import 'dart:typed_data'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; class PlaceMarkerPage extends GoogleMapExampleAppPage { const PlaceMarkerPage({Key? key}) : super(const Icon(Icons.place), 'Place marker', key: key); @override Widget build(BuildContext context) { return const PlaceMarkerBody(); } } class PlaceMarkerBody extends StatefulWidget { const PlaceMarkerBody({Key? key}) : super(key: key); @override State createState() => PlaceMarkerBodyState(); } typedef MarkerUpdateAction = Marker Function(Marker marker); class PlaceMarkerBodyState extends State { PlaceMarkerBodyState(); static const LatLng center = LatLng(-33.86711, 151.1947171); ExampleGoogleMapController? controller; Map markers = {}; MarkerId? selectedMarker; int _markerIdCounter = 1; LatLng? markerPosition; // ignore: use_setters_to_change_properties void _onMapCreated(ExampleGoogleMapController controller) { this.controller = controller; } @override void dispose() { super.dispose(); } void _onMarkerTapped(MarkerId markerId) { final Marker? tappedMarker = markers[markerId]; if (tappedMarker != null) { setState(() { final MarkerId? previousMarkerId = selectedMarker; if (previousMarkerId != null && markers.containsKey(previousMarkerId)) { final Marker resetOld = markers[previousMarkerId]! .copyWith(iconParam: BitmapDescriptor.defaultMarker); markers[previousMarkerId] = resetOld; } selectedMarker = markerId; final Marker newMarker = tappedMarker.copyWith( iconParam: BitmapDescriptor.defaultMarkerWithHue( BitmapDescriptor.hueGreen, ), ); markers[markerId] = newMarker; markerPosition = null; }); } } Future _onMarkerDrag(MarkerId markerId, LatLng newPosition) async { setState(() { markerPosition = newPosition; }); } Future _onMarkerDragEnd(MarkerId markerId, LatLng newPosition) async { final Marker? tappedMarker = markers[markerId]; if (tappedMarker != null) { setState(() { markerPosition = null; }); await showDialog( context: context, builder: (BuildContext context) { return AlertDialog( actions: [ TextButton( child: const Text('OK'), onPressed: () => Navigator.of(context).pop(), ) ], content: Padding( padding: const EdgeInsets.symmetric(vertical: 66), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('Old position: ${tappedMarker.position}'), Text('New position: $newPosition'), ], ))); }); } } void _add() { final int markerCount = markers.length; if (markerCount == 12) { return; } final String markerIdVal = 'marker_id_$_markerIdCounter'; _markerIdCounter++; final MarkerId markerId = MarkerId(markerIdVal); final Marker marker = Marker( markerId: markerId, position: LatLng( center.latitude + sin(_markerIdCounter * pi / 6.0) / 20.0, center.longitude + cos(_markerIdCounter * pi / 6.0) / 20.0, ), infoWindow: InfoWindow(title: markerIdVal, snippet: '*'), onTap: () => _onMarkerTapped(markerId), onDragEnd: (LatLng position) => _onMarkerDragEnd(markerId, position), onDrag: (LatLng position) => _onMarkerDrag(markerId, position), ); setState(() { markers[markerId] = marker; }); } void _remove(MarkerId markerId) { setState(() { if (markers.containsKey(markerId)) { markers.remove(markerId); } }); } void _changePosition(MarkerId markerId) { final Marker marker = markers[markerId]!; final LatLng current = marker.position; final Offset offset = Offset( center.latitude - current.latitude, center.longitude - current.longitude, ); setState(() { markers[markerId] = marker.copyWith( positionParam: LatLng( center.latitude + offset.dy, center.longitude + offset.dx, ), ); }); } void _changeAnchor(MarkerId markerId) { final Marker marker = markers[markerId]!; final Offset currentAnchor = marker.anchor; final Offset newAnchor = Offset(1.0 - currentAnchor.dy, currentAnchor.dx); setState(() { markers[markerId] = marker.copyWith( anchorParam: newAnchor, ); }); } Future _changeInfoAnchor(MarkerId markerId) async { final Marker marker = markers[markerId]!; final Offset currentAnchor = marker.infoWindow.anchor; final Offset newAnchor = Offset(1.0 - currentAnchor.dy, currentAnchor.dx); setState(() { markers[markerId] = marker.copyWith( infoWindowParam: marker.infoWindow.copyWith( anchorParam: newAnchor, ), ); }); } Future _toggleDraggable(MarkerId markerId) async { final Marker marker = markers[markerId]!; setState(() { markers[markerId] = marker.copyWith( draggableParam: !marker.draggable, ); }); } Future _toggleFlat(MarkerId markerId) async { final Marker marker = markers[markerId]!; setState(() { markers[markerId] = marker.copyWith( flatParam: !marker.flat, ); }); } Future _changeInfo(MarkerId markerId) async { final Marker marker = markers[markerId]!; final String newSnippet = '${marker.infoWindow.snippet!}*'; setState(() { markers[markerId] = marker.copyWith( infoWindowParam: marker.infoWindow.copyWith( snippetParam: newSnippet, ), ); }); } Future _changeAlpha(MarkerId markerId) async { final Marker marker = markers[markerId]!; final double current = marker.alpha; setState(() { markers[markerId] = marker.copyWith( alphaParam: current < 0.1 ? 1.0 : current * 0.75, ); }); } Future _changeRotation(MarkerId markerId) async { final Marker marker = markers[markerId]!; final double current = marker.rotation; setState(() { markers[markerId] = marker.copyWith( rotationParam: current == 330.0 ? 0.0 : current + 30.0, ); }); } Future _toggleVisible(MarkerId markerId) async { final Marker marker = markers[markerId]!; setState(() { markers[markerId] = marker.copyWith( visibleParam: !marker.visible, ); }); } Future _changeZIndex(MarkerId markerId) async { final Marker marker = markers[markerId]!; final double current = marker.zIndex; setState(() { markers[markerId] = marker.copyWith( zIndexParam: current == 12.0 ? 0.0 : current + 1.0, ); }); } void _setMarkerIcon(MarkerId markerId, BitmapDescriptor assetIcon) { final Marker marker = markers[markerId]!; setState(() { markers[markerId] = marker.copyWith( iconParam: assetIcon, ); }); } Future _getAssetIcon(BuildContext context) async { final Completer bitmapIcon = Completer(); final ImageConfiguration config = createLocalImageConfiguration(context); const AssetImage('assets/red_square.png') .resolve(config) .addListener(ImageStreamListener((ImageInfo image, bool sync) async { final ByteData? bytes = await image.image.toByteData(format: ImageByteFormat.png); if (bytes == null) { bitmapIcon.completeError(Exception('Unable to encode icon')); return; } final BitmapDescriptor bitmap = BitmapDescriptor.fromBytes(bytes.buffer.asUint8List()); bitmapIcon.complete(bitmap); })); return bitmapIcon.future; } @override Widget build(BuildContext context) { final MarkerId? selectedId = selectedMarker; return Stack(children: [ Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded( child: ExampleGoogleMap( onMapCreated: _onMapCreated, initialCameraPosition: const CameraPosition( target: LatLng(-33.852, 151.211), zoom: 11.0, ), markers: Set.of(markers.values), ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ TextButton( onPressed: _add, child: const Text('Add'), ), TextButton( onPressed: selectedId == null ? null : () => _remove(selectedId), child: const Text('Remove'), ), ], ), Wrap( alignment: WrapAlignment.spaceEvenly, children: [ TextButton( onPressed: selectedId == null ? null : () => _changeInfo(selectedId), child: const Text('change info'), ), TextButton( onPressed: selectedId == null ? null : () => _changeInfoAnchor(selectedId), child: const Text('change info anchor'), ), TextButton( onPressed: selectedId == null ? null : () => _changeAlpha(selectedId), child: const Text('change alpha'), ), TextButton( onPressed: selectedId == null ? null : () => _changeAnchor(selectedId), child: const Text('change anchor'), ), TextButton( onPressed: selectedId == null ? null : () => _toggleDraggable(selectedId), child: const Text('toggle draggable'), ), TextButton( onPressed: selectedId == null ? null : () => _toggleFlat(selectedId), child: const Text('toggle flat'), ), TextButton( onPressed: selectedId == null ? null : () => _changePosition(selectedId), child: const Text('change position'), ), TextButton( onPressed: selectedId == null ? null : () => _changeRotation(selectedId), child: const Text('change rotation'), ), TextButton( onPressed: selectedId == null ? null : () => _toggleVisible(selectedId), child: const Text('toggle visible'), ), TextButton( onPressed: selectedId == null ? null : () => _changeZIndex(selectedId), child: const Text('change zIndex'), ), TextButton( onPressed: selectedId == null ? null : () { _getAssetIcon(context).then( (BitmapDescriptor icon) { _setMarkerIcon(selectedId, icon); }, ); }, child: const Text('set marker icon'), ), ], ), ], ), Visibility( visible: markerPosition != null, child: Container( color: Colors.white70, height: 30, padding: const EdgeInsets.only(left: 12, right: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ if (markerPosition == null) Container() else Expanded(child: Text('lat: ${markerPosition!.latitude}')), if (markerPosition == null) Container() else Expanded(child: Text('lng: ${markerPosition!.longitude}')), ], ), ), ), ]); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_polygon.dart ================================================ // Copyright 2013 The Flutter 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:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; class PlacePolygonPage extends GoogleMapExampleAppPage { const PlacePolygonPage({Key? key}) : super(const Icon(Icons.linear_scale), 'Place polygon', key: key); @override Widget build(BuildContext context) { return const PlacePolygonBody(); } } class PlacePolygonBody extends StatefulWidget { const PlacePolygonBody({Key? key}) : super(key: key); @override State createState() => PlacePolygonBodyState(); } class PlacePolygonBodyState extends State { PlacePolygonBodyState(); ExampleGoogleMapController? controller; Map polygons = {}; Map polygonOffsets = {}; int _polygonIdCounter = 0; PolygonId? selectedPolygon; // Values when toggling polygon color int strokeColorsIndex = 0; int fillColorsIndex = 0; List colors = [ Colors.purple, Colors.red, Colors.green, Colors.pink, ]; // Values when toggling polygon width int widthsIndex = 0; List widths = [10, 20, 5]; // ignore: use_setters_to_change_properties void _onMapCreated(ExampleGoogleMapController controller) { this.controller = controller; } @override void dispose() { super.dispose(); } void _onPolygonTapped(PolygonId polygonId) { setState(() { selectedPolygon = polygonId; }); } void _remove(PolygonId polygonId) { setState(() { if (polygons.containsKey(polygonId)) { polygons.remove(polygonId); } selectedPolygon = null; }); } void _add() { final int polygonCount = polygons.length; if (polygonCount == 12) { return; } final String polygonIdVal = 'polygon_id_$_polygonIdCounter'; final PolygonId polygonId = PolygonId(polygonIdVal); final Polygon polygon = Polygon( polygonId: polygonId, consumeTapEvents: true, strokeColor: Colors.orange, strokeWidth: 5, fillColor: Colors.green, points: _createPoints(), onTap: () { _onPolygonTapped(polygonId); }, ); setState(() { polygons[polygonId] = polygon; polygonOffsets[polygonId] = _polygonIdCounter.ceilToDouble(); // increment _polygonIdCounter to have unique polygon id each time _polygonIdCounter++; }); } void _toggleGeodesic(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith( geodesicParam: !polygon.geodesic, ); }); } void _toggleVisible(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith( visibleParam: !polygon.visible, ); }); } void _changeStrokeColor(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith( strokeColorParam: colors[++strokeColorsIndex % colors.length], ); }); } void _changeFillColor(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith( fillColorParam: colors[++fillColorsIndex % colors.length], ); }); } void _changeWidth(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith( strokeWidthParam: widths[++widthsIndex % widths.length], ); }); } void _addHoles(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith(holesParam: _createHoles(polygonId)); }); } void _removeHoles(PolygonId polygonId) { final Polygon polygon = polygons[polygonId]!; setState(() { polygons[polygonId] = polygon.copyWith( holesParam: >[], ); }); } @override Widget build(BuildContext context) { final PolygonId? selectedId = selectedPolygon; return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 350.0, height: 300.0, child: ExampleGoogleMap( initialCameraPosition: const CameraPosition( target: LatLng(52.4478, -3.5402), zoom: 7.0, ), polygons: Set.of(polygons.values), onMapCreated: _onMapCreated, ), ), ), Expanded( child: SingleChildScrollView( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row( children: [ Column( children: [ TextButton( onPressed: _add, child: const Text('add'), ), TextButton( onPressed: (selectedId == null) ? null : () => _remove(selectedId), child: const Text('remove'), ), TextButton( onPressed: (selectedId == null) ? null : () => _toggleVisible(selectedId), child: const Text('toggle visible'), ), TextButton( onPressed: (selectedId == null) ? null : () => _toggleGeodesic(selectedId), child: const Text('toggle geodesic'), ), ], ), Column( children: [ TextButton( onPressed: (selectedId == null) ? null : ((polygons[selectedId]!.holes.isNotEmpty) ? null : () => _addHoles(selectedId)), child: const Text('add holes'), ), TextButton( onPressed: (selectedId == null) ? null : ((polygons[selectedId]!.holes.isEmpty) ? null : () => _removeHoles(selectedId)), child: const Text('remove holes'), ), TextButton( onPressed: (selectedId == null) ? null : () => _changeWidth(selectedId), child: const Text('change stroke width'), ), TextButton( onPressed: (selectedId == null) ? null : () => _changeStrokeColor(selectedId), child: const Text('change stroke color'), ), TextButton( onPressed: (selectedId == null) ? null : () => _changeFillColor(selectedId), child: const Text('change fill color'), ), ], ) ], ) ], ), ), ), ], ); } List _createPoints() { final List points = []; final double offset = _polygonIdCounter.ceilToDouble(); points.add(_createLatLng(51.2395 + offset, -3.4314)); points.add(_createLatLng(53.5234 + offset, -3.5314)); points.add(_createLatLng(52.4351 + offset, -4.5235)); points.add(_createLatLng(52.1231 + offset, -5.0829)); return points; } List> _createHoles(PolygonId polygonId) { final List> holes = >[]; final double offset = polygonOffsets[polygonId]!; final List hole1 = []; hole1.add(_createLatLng(51.8395 + offset, -3.8814)); hole1.add(_createLatLng(52.0234 + offset, -3.9914)); hole1.add(_createLatLng(52.1351 + offset, -4.4435)); hole1.add(_createLatLng(52.0231 + offset, -4.5829)); holes.add(hole1); final List hole2 = []; hole2.add(_createLatLng(52.2395 + offset, -3.6814)); hole2.add(_createLatLng(52.4234 + offset, -3.7914)); hole2.add(_createLatLng(52.5351 + offset, -4.2435)); hole2.add(_createLatLng(52.4231 + offset, -4.3829)); holes.add(hole2); return holes; } LatLng _createLatLng(double lat, double lng) { return LatLng(lat, lng); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_polyline.dart ================================================ // Copyright 2013 The Flutter 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/foundation.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; class PlacePolylinePage extends GoogleMapExampleAppPage { const PlacePolylinePage({Key? key}) : super(const Icon(Icons.linear_scale), 'Place polyline', key: key); @override Widget build(BuildContext context) { return const PlacePolylineBody(); } } class PlacePolylineBody extends StatefulWidget { const PlacePolylineBody({Key? key}) : super(key: key); @override State createState() => PlacePolylineBodyState(); } class PlacePolylineBodyState extends State { PlacePolylineBodyState(); ExampleGoogleMapController? controller; Map polylines = {}; int _polylineIdCounter = 0; PolylineId? selectedPolyline; // Values when toggling polyline color int colorsIndex = 0; List colors = [ Colors.purple, Colors.red, Colors.green, Colors.pink, ]; // Values when toggling polyline width int widthsIndex = 0; List widths = [10, 20, 5]; int jointTypesIndex = 0; List jointTypes = [ JointType.mitered, JointType.bevel, JointType.round ]; // Values when toggling polyline end cap type int endCapsIndex = 0; List endCaps = [Cap.buttCap, Cap.squareCap, Cap.roundCap]; // Values when toggling polyline start cap type int startCapsIndex = 0; List startCaps = [Cap.buttCap, Cap.squareCap, Cap.roundCap]; // Values when toggling polyline pattern int patternsIndex = 0; List> patterns = >[ [], [ PatternItem.dash(30.0), PatternItem.gap(20.0), PatternItem.dot, PatternItem.gap(20.0) ], [PatternItem.dash(30.0), PatternItem.gap(20.0)], [PatternItem.dot, PatternItem.gap(10.0)], ]; // ignore: use_setters_to_change_properties void _onMapCreated(ExampleGoogleMapController controller) { this.controller = controller; } @override void dispose() { super.dispose(); } void _onPolylineTapped(PolylineId polylineId) { setState(() { selectedPolyline = polylineId; }); } void _remove(PolylineId polylineId) { setState(() { if (polylines.containsKey(polylineId)) { polylines.remove(polylineId); } selectedPolyline = null; }); } void _add() { final int polylineCount = polylines.length; if (polylineCount == 12) { return; } final String polylineIdVal = 'polyline_id_$_polylineIdCounter'; _polylineIdCounter++; final PolylineId polylineId = PolylineId(polylineIdVal); final Polyline polyline = Polyline( polylineId: polylineId, consumeTapEvents: true, color: Colors.orange, width: 5, points: _createPoints(), onTap: () { _onPolylineTapped(polylineId); }, ); setState(() { polylines[polylineId] = polyline; }); } void _toggleGeodesic(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( geodesicParam: !polyline.geodesic, ); }); } void _toggleVisible(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( visibleParam: !polyline.visible, ); }); } void _changeColor(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( colorParam: colors[++colorsIndex % colors.length], ); }); } void _changeWidth(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( widthParam: widths[++widthsIndex % widths.length], ); }); } void _changeJointType(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( jointTypeParam: jointTypes[++jointTypesIndex % jointTypes.length], ); }); } void _changeEndCap(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( endCapParam: endCaps[++endCapsIndex % endCaps.length], ); }); } void _changeStartCap(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( startCapParam: startCaps[++startCapsIndex % startCaps.length], ); }); } void _changePattern(PolylineId polylineId) { final Polyline polyline = polylines[polylineId]!; setState(() { polylines[polylineId] = polyline.copyWith( patternsParam: patterns[++patternsIndex % patterns.length], ); }); } @override Widget build(BuildContext context) { final bool isIOS = !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS; final PolylineId? selectedId = selectedPolyline; return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 350.0, height: 300.0, child: ExampleGoogleMap( initialCameraPosition: const CameraPosition( target: LatLng(53.1721, -3.5402), zoom: 7.0, ), polylines: Set.of(polylines.values), onMapCreated: _onMapCreated, ), ), ), Expanded( child: SingleChildScrollView( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row( children: [ Column( children: [ TextButton( onPressed: _add, child: const Text('add'), ), TextButton( onPressed: (selectedId == null) ? null : () => _remove(selectedId), child: const Text('remove'), ), TextButton( onPressed: (selectedId == null) ? null : () => _toggleVisible(selectedId), child: const Text('toggle visible'), ), TextButton( onPressed: (selectedId == null) ? null : () => _toggleGeodesic(selectedId), child: const Text('toggle geodesic'), ), ], ), Column( children: [ TextButton( onPressed: (selectedId == null) ? null : () => _changeWidth(selectedId), child: const Text('change width'), ), TextButton( onPressed: (selectedId == null) ? null : () => _changeColor(selectedId), child: const Text('change color'), ), TextButton( onPressed: isIOS || (selectedId == null) ? null : () => _changeStartCap(selectedId), child: const Text('change start cap [Android only]'), ), TextButton( onPressed: isIOS || (selectedId == null) ? null : () => _changeEndCap(selectedId), child: const Text('change end cap [Android only]'), ), TextButton( onPressed: isIOS || (selectedId == null) ? null : () => _changeJointType(selectedId), child: const Text('change joint type [Android only]'), ), TextButton( onPressed: isIOS || (selectedId == null) ? null : () => _changePattern(selectedId), child: const Text('change pattern [Android only]'), ), ], ) ], ) ], ), ), ), ], ); } List _createPoints() { final List points = []; final double offset = _polylineIdCounter.ceilToDouble(); points.add(_createLatLng(51.4816 + offset, -3.1791)); points.add(_createLatLng(53.0430 + offset, -2.9925)); points.add(_createLatLng(53.1396 + offset, -4.2739)); points.add(_createLatLng(52.4153 + offset, -4.0829)); return points; } LatLng _createLatLng(double lat, double lng) { return LatLng(lat, lng); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/lib/scrolling_map.dart ================================================ // Copyright 2013 The Flutter 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/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; const LatLng _center = LatLng(32.080664, 34.9563837); class ScrollingMapPage extends GoogleMapExampleAppPage { const ScrollingMapPage({Key? key}) : super(const Icon(Icons.map), 'Scrolling map', key: key); @override Widget build(BuildContext context) { return const ScrollingMapBody(); } } class ScrollingMapBody extends StatelessWidget { const ScrollingMapBody({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return ListView( children: [ Card( child: Padding( padding: const EdgeInsets.symmetric(vertical: 30.0), child: Column( children: [ const Padding( padding: EdgeInsets.only(bottom: 12.0), child: Text('This map consumes all touch events.'), ), Center( child: SizedBox( width: 300.0, height: 300.0, child: ExampleGoogleMap( initialCameraPosition: const CameraPosition( target: _center, zoom: 11.0, ), gestureRecognizers: // >{ Factory( () => EagerGestureRecognizer(), ), }, ), ), ), ], ), ), ), Card( child: Padding( padding: const EdgeInsets.symmetric(vertical: 30.0), child: Column( children: [ const Text("This map doesn't consume the vertical drags."), const Padding( padding: EdgeInsets.only(bottom: 12.0), child: Text('It still gets other gestures (e.g scale or tap).'), ), Center( child: SizedBox( width: 300.0, height: 300.0, child: ExampleGoogleMap( initialCameraPosition: const CameraPosition( target: _center, zoom: 11.0, ), markers: { Marker( markerId: const MarkerId('test_marker_id'), position: LatLng( _center.latitude, _center.longitude, ), infoWindow: const InfoWindow( title: 'An interesting location', snippet: '*', ), ), }, gestureRecognizers: < Factory>{ Factory( () => ScaleGestureRecognizer(), ), }, ), ), ), ], ), ), ), ], ); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/lib/snapshot.dart ================================================ // Copyright 2013 The Flutter 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 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; const CameraPosition _kInitialPosition = CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0); class SnapshotPage extends GoogleMapExampleAppPage { const SnapshotPage({Key? key}) : super(const Icon(Icons.camera_alt), 'Take a snapshot of the map', key: key); @override Widget build(BuildContext context) { return _SnapshotBody(); } } class _SnapshotBody extends StatefulWidget { @override _SnapshotBodyState createState() => _SnapshotBodyState(); } class _SnapshotBodyState extends State<_SnapshotBody> { ExampleGoogleMapController? _mapController; Uint8List? _imageBytes; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ SizedBox( height: 180, child: ExampleGoogleMap( onMapCreated: onMapCreated, initialCameraPosition: _kInitialPosition, ), ), TextButton( child: const Text('Take a snapshot'), onPressed: () async { final Uint8List? imageBytes = await _mapController?.takeSnapshot(); setState(() { _imageBytes = imageBytes; }); }, ), Container( decoration: BoxDecoration(color: Colors.blueGrey[50]), height: 180, child: _imageBytes != null ? Image.memory(_imageBytes!) : null, ), ], ), ); } // ignore: use_setters_to_change_properties void onMapCreated(ExampleGoogleMapController controller) { _mapController = controller; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/lib/tile_overlay.dart ================================================ // Copyright 2013 The Flutter 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 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'example_google_map.dart'; import 'page.dart'; class TileOverlayPage extends GoogleMapExampleAppPage { const TileOverlayPage({Key? key}) : super(const Icon(Icons.map), 'Tile overlay', key: key); @override Widget build(BuildContext context) { return const TileOverlayBody(); } } class TileOverlayBody extends StatefulWidget { const TileOverlayBody({Key? key}) : super(key: key); @override State createState() => TileOverlayBodyState(); } class TileOverlayBodyState extends State { TileOverlayBodyState(); ExampleGoogleMapController? controller; TileOverlay? _tileOverlay; // ignore: use_setters_to_change_properties void _onMapCreated(ExampleGoogleMapController controller) { this.controller = controller; } @override void dispose() { super.dispose(); } void _removeTileOverlay() { setState(() { _tileOverlay = null; }); } void _addTileOverlay() { final TileOverlay tileOverlay = TileOverlay( tileOverlayId: const TileOverlayId('tile_overlay_1'), tileProvider: _DebugTileProvider(), ); setState(() { _tileOverlay = tileOverlay; }); } void _clearTileCache() { if (_tileOverlay != null && controller != null) { controller!.clearTileCache(_tileOverlay!.tileOverlayId); } } @override Widget build(BuildContext context) { final Set overlays = { if (_tileOverlay != null) _tileOverlay!, }; return Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Center( child: SizedBox( width: 350.0, height: 300.0, child: ExampleGoogleMap( initialCameraPosition: const CameraPosition( target: LatLng(59.935460, 30.325177), zoom: 7.0, ), tileOverlays: overlays, onMapCreated: _onMapCreated, ), ), ), TextButton( onPressed: _addTileOverlay, child: const Text('Add tile overlay'), ), TextButton( onPressed: _removeTileOverlay, child: const Text('Remove tile overlay'), ), TextButton( onPressed: _clearTileCache, child: const Text('Clear tile cache'), ), ], ); } } class _DebugTileProvider implements TileProvider { _DebugTileProvider() { boxPaint.isAntiAlias = true; boxPaint.color = Colors.blue; boxPaint.strokeWidth = 2.0; boxPaint.style = PaintingStyle.stroke; } static const int width = 100; static const int height = 100; static final Paint boxPaint = Paint(); static const TextStyle textStyle = TextStyle( color: Colors.red, fontSize: 20, ); @override Future getTile(int x, int y, int? zoom) async { final ui.PictureRecorder recorder = ui.PictureRecorder(); final Canvas canvas = Canvas(recorder); final TextSpan textSpan = TextSpan( text: '$x,$y', style: textStyle, ); final TextPainter textPainter = TextPainter( text: textSpan, textDirection: TextDirection.ltr, ); textPainter.layout( maxWidth: width.toDouble(), ); textPainter.paint(canvas, Offset.zero); canvas.drawRect( Rect.fromLTRB(0, 0, width.toDouble(), width.toDouble()), boxPaint); final ui.Picture picture = recorder.endRecording(); final Uint8List byteData = await picture .toImage(width, height) .then((ui.Image image) => image.toByteData(format: ui.ImageByteFormat.png)) .then((ByteData? byteData) => byteData!.buffer.asUint8List()); return Tile(width, height, byteData); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/pubspec.yaml ================================================ name: google_maps_flutter_example description: Demonstrates how to use the google_maps_flutter plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: cupertino_icons: ^1.0.5 flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.1 google_maps_flutter_ios: # When depending on this package from a real application you should use: # google_maps_flutter_ios: ^x.y.z # See https://dart.dev/tools/pub/dependencies#version-constraints # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ google_maps_flutter_platform_interface: ^2.2.1 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true assets: - assets/ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Assets/.gitkeep ================================================ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapJSONConversions.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import #import NS_ASSUME_NONNULL_BEGIN @interface FLTGoogleMapJSONConversions : NSObject + (CLLocationCoordinate2D)locationFromLatLong:(NSArray *)latlong; + (CGPoint)pointFromArray:(NSArray *)array; + (NSArray *)arrayFromLocation:(CLLocationCoordinate2D)location; + (UIColor *)colorFromRGBA:(NSNumber *)data; + (NSArray *)pointsFromLatLongs:(NSArray *)data; + (NSArray *> *)holesFromPointsArray:(NSArray *)data; + (nullable NSDictionary *)dictionaryFromPosition: (nullable GMSCameraPosition *)position; + (NSDictionary *)dictionaryFromPoint:(CGPoint)point; + (nullable NSDictionary *)dictionaryFromCoordinateBounds:(nullable GMSCoordinateBounds *)bounds; + (nullable GMSCameraPosition *)cameraPostionFromDictionary:(nullable NSDictionary *)channelValue; + (CGPoint)pointFromDictionary:(NSDictionary *)dictionary; + (GMSCoordinateBounds *)coordinateBoundsFromLatLongs:(NSArray *)latlongs; + (GMSMapViewType)mapViewTypeFromTypeValue:(NSNumber *)value; + (nullable GMSCameraUpdate *)cameraUpdateFromChannelValue:(NSArray *)channelValue; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapJSONConversions.m ================================================ // Copyright 2013 The Flutter 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 "FLTGoogleMapJSONConversions.h" @implementation FLTGoogleMapJSONConversions + (CLLocationCoordinate2D)locationFromLatLong:(NSArray *)latlong { return CLLocationCoordinate2DMake([latlong[0] doubleValue], [latlong[1] doubleValue]); } + (CGPoint)pointFromArray:(NSArray *)array { return CGPointMake([array[0] doubleValue], [array[1] doubleValue]); } + (NSArray *)arrayFromLocation:(CLLocationCoordinate2D)location { return @[ @(location.latitude), @(location.longitude) ]; } + (UIColor *)colorFromRGBA:(NSNumber *)numberColor { unsigned long value = [numberColor unsignedLongValue]; return [UIColor colorWithRed:((float)((value & 0xFF0000) >> 16)) / 255.0 green:((float)((value & 0xFF00) >> 8)) / 255.0 blue:((float)(value & 0xFF)) / 255.0 alpha:((float)((value & 0xFF000000) >> 24)) / 255.0]; } + (NSArray *)pointsFromLatLongs:(NSArray *)data { NSMutableArray *points = [[NSMutableArray alloc] init]; for (unsigned i = 0; i < [data count]; i++) { NSNumber *latitude = data[i][0]; NSNumber *longitude = data[i][1]; CLLocation *point = [[CLLocation alloc] initWithLatitude:[latitude doubleValue] longitude:[longitude doubleValue]]; [points addObject:point]; } return points; } + (NSArray *> *)holesFromPointsArray:(NSArray *)data { NSMutableArray *> *holes = [[[NSMutableArray alloc] init] init]; for (unsigned i = 0; i < [data count]; i++) { NSArray *points = [FLTGoogleMapJSONConversions pointsFromLatLongs:data[i]]; [holes addObject:points]; } return holes; } + (nullable NSDictionary *)dictionaryFromPosition:(GMSCameraPosition *)position { if (!position) { return nil; } return @{ @"target" : [FLTGoogleMapJSONConversions arrayFromLocation:[position target]], @"zoom" : @([position zoom]), @"bearing" : @([position bearing]), @"tilt" : @([position viewingAngle]), }; } + (NSDictionary *)dictionaryFromPoint:(CGPoint)point { return @{ @"x" : @(lroundf(point.x)), @"y" : @(lroundf(point.y)), }; } + (nullable NSDictionary *)dictionaryFromCoordinateBounds:(GMSCoordinateBounds *)bounds { if (!bounds) { return nil; } return @{ @"southwest" : [FLTGoogleMapJSONConversions arrayFromLocation:[bounds southWest]], @"northeast" : [FLTGoogleMapJSONConversions arrayFromLocation:[bounds northEast]], }; } + (nullable GMSCameraPosition *)cameraPostionFromDictionary:(nullable NSDictionary *)data { if (!data) { return nil; } return [GMSCameraPosition cameraWithTarget:[FLTGoogleMapJSONConversions locationFromLatLong:data[@"target"]] zoom:[data[@"zoom"] floatValue] bearing:[data[@"bearing"] doubleValue] viewingAngle:[data[@"tilt"] doubleValue]]; } + (CGPoint)pointFromDictionary:(NSDictionary *)dictionary { double x = [dictionary[@"x"] doubleValue]; double y = [dictionary[@"y"] doubleValue]; return CGPointMake(x, y); } + (GMSCoordinateBounds *)coordinateBoundsFromLatLongs:(NSArray *)latlongs { return [[GMSCoordinateBounds alloc] initWithCoordinate:[FLTGoogleMapJSONConversions locationFromLatLong:latlongs[0]] coordinate:[FLTGoogleMapJSONConversions locationFromLatLong:latlongs[1]]]; } + (GMSMapViewType)mapViewTypeFromTypeValue:(NSNumber *)typeValue { int value = [typeValue intValue]; return (GMSMapViewType)(value == 0 ? 5 : value); } + (nullable GMSCameraUpdate *)cameraUpdateFromChannelValue:(NSArray *)channelValue { NSString *update = channelValue[0]; if ([update isEqualToString:@"newCameraPosition"]) { return [GMSCameraUpdate setCamera:[FLTGoogleMapJSONConversions cameraPostionFromDictionary:channelValue[1]]]; } else if ([update isEqualToString:@"newLatLng"]) { return [GMSCameraUpdate setTarget:[FLTGoogleMapJSONConversions locationFromLatLong:channelValue[1]]]; } else if ([update isEqualToString:@"newLatLngBounds"]) { return [GMSCameraUpdate fitBounds:[FLTGoogleMapJSONConversions coordinateBoundsFromLatLongs:channelValue[1]] withPadding:[channelValue[2] doubleValue]]; } else if ([update isEqualToString:@"newLatLngZoom"]) { return [GMSCameraUpdate setTarget:[FLTGoogleMapJSONConversions locationFromLatLong:channelValue[1]] zoom:[channelValue[2] floatValue]]; } else if ([update isEqualToString:@"scrollBy"]) { return [GMSCameraUpdate scrollByX:[channelValue[1] doubleValue] Y:[channelValue[2] doubleValue]]; } else if ([update isEqualToString:@"zoomBy"]) { if (channelValue.count == 2) { return [GMSCameraUpdate zoomBy:[channelValue[1] floatValue]]; } else { return [GMSCameraUpdate zoomBy:[channelValue[1] floatValue] atPoint:[FLTGoogleMapJSONConversions pointFromArray:channelValue[2]]]; } } else if ([update isEqualToString:@"zoomIn"]) { return [GMSCameraUpdate zoomIn]; } else if ([update isEqualToString:@"zoomOut"]) { return [GMSCameraUpdate zoomOut]; } else if ([update isEqualToString:@"zoomTo"]) { return [GMSCameraUpdate zoomTo:[channelValue[1] floatValue]]; } return nil; } @end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapTileOverlayController.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import #import NS_ASSUME_NONNULL_BEGIN @interface FLTGoogleMapTileOverlayController : NSObject - (instancetype)initWithTileLayer:(GMSTileLayer *)tileLayer mapView:(GMSMapView *)mapView options:(NSDictionary *)optionsData; - (void)removeTileOverlay; - (void)clearTileCache; - (NSDictionary *)getTileOverlayInfo; @end @interface FLTTileProviderController : GMSTileLayer @property(copy, nonatomic, readonly) NSString *tileOverlayIdentifier; - (instancetype)init:(FlutterMethodChannel *)methodChannel withTileOverlayIdentifier:(NSString *)identifier; @end @interface FLTTileOverlaysController : NSObject - (instancetype)init:(FlutterMethodChannel *)methodChannel mapView:(GMSMapView *)mapView registrar:(NSObject *)registrar; - (void)addTileOverlays:(NSArray *)tileOverlaysToAdd; - (void)changeTileOverlays:(NSArray *)tileOverlaysToChange; - (void)removeTileOverlayWithIdentifiers:(NSArray *)identifiers; - (void)clearTileCacheWithIdentifier:(NSString *)identifier; - (nullable NSDictionary *)tileOverlayInfoWithIdentifier:(NSString *)identifier; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapTileOverlayController.m ================================================ // Copyright 2013 The Flutter 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 "FLTGoogleMapTileOverlayController.h" #import "FLTGoogleMapJSONConversions.h" @interface FLTGoogleMapTileOverlayController () @property(strong, nonatomic) GMSTileLayer *layer; @property(weak, nonatomic) GMSMapView *mapView; @end @implementation FLTGoogleMapTileOverlayController - (instancetype)initWithTileLayer:(GMSTileLayer *)tileLayer mapView:(GMSMapView *)mapView options:(NSDictionary *)optionsData { self = [super init]; if (self) { _layer = tileLayer; _mapView = mapView; [self interpretTileOverlayOptions:optionsData]; } return self; } - (void)removeTileOverlay { self.layer.map = nil; } - (void)clearTileCache { [self.layer clearTileCache]; } - (NSDictionary *)getTileOverlayInfo { NSMutableDictionary *info = [[NSMutableDictionary alloc] init]; BOOL visible = self.layer.map != nil; info[@"visible"] = @(visible); info[@"fadeIn"] = @(self.layer.fadeIn); float transparency = 1.0 - self.layer.opacity; info[@"transparency"] = @(transparency); info[@"zIndex"] = @(self.layer.zIndex); return info; } - (void)setFadeIn:(BOOL)fadeIn { self.layer.fadeIn = fadeIn; } - (void)setTransparency:(float)transparency { float opacity = 1.0 - transparency; self.layer.opacity = opacity; } - (void)setVisible:(BOOL)visible { self.layer.map = visible ? self.mapView : nil; } - (void)setZIndex:(int)zIndex { self.layer.zIndex = zIndex; } - (void)setTileSize:(NSInteger)tileSize { self.layer.tileSize = tileSize; } - (void)interpretTileOverlayOptions:(NSDictionary *)data { if (!data) { return; } NSNumber *visible = data[@"visible"]; if (visible != nil && visible != (id)[NSNull null]) { [self setVisible:visible.boolValue]; } NSNumber *transparency = data[@"transparency"]; if (transparency != nil && transparency != (id)[NSNull null]) { [self setTransparency:transparency.floatValue]; } NSNumber *zIndex = data[@"zIndex"]; if (zIndex != nil && zIndex != (id)[NSNull null]) { [self setZIndex:zIndex.intValue]; } NSNumber *fadeIn = data[@"fadeIn"]; if (fadeIn != nil && fadeIn != (id)[NSNull null]) { [self setFadeIn:fadeIn.boolValue]; } NSNumber *tileSize = data[@"tileSize"]; if (tileSize != nil && tileSize != (id)[NSNull null]) { [self setTileSize:tileSize.integerValue]; } } @end @interface FLTTileProviderController () @property(strong, nonatomic) FlutterMethodChannel *methodChannel; @end @implementation FLTTileProviderController - (instancetype)init:(FlutterMethodChannel *)methodChannel withTileOverlayIdentifier:(NSString *)identifier { self = [super init]; if (self) { _methodChannel = methodChannel; _tileOverlayIdentifier = identifier; } return self; } #pragma mark - GMSTileLayer method - (void)requestTileForX:(NSUInteger)x y:(NSUInteger)y zoom:(NSUInteger)zoom receiver:(id)receiver { [self.methodChannel invokeMethod:@"tileOverlay#getTile" arguments:@{ @"tileOverlayId" : self.tileOverlayIdentifier, @"x" : @(x), @"y" : @(y), @"zoom" : @(zoom) } result:^(id _Nullable result) { UIImage *tileImage; if ([result isKindOfClass:[NSDictionary class]]) { FlutterStandardTypedData *typedData = (FlutterStandardTypedData *)result[@"data"]; if (typedData == nil) { tileImage = kGMSTileLayerNoTile; } else { tileImage = [UIImage imageWithData:typedData.data]; } } else { if ([result isKindOfClass:[FlutterError class]]) { FlutterError *error = (FlutterError *)result; NSLog(@"Can't get tile: errorCode = %@, errorMessage = %@, details = %@", [error code], [error message], [error details]); } if ([result isKindOfClass:[FlutterMethodNotImplemented class]]) { NSLog(@"Can't get tile: notImplemented"); } tileImage = kGMSTileLayerNoTile; } [receiver receiveTileWithX:x y:y zoom:zoom image:tileImage]; }]; } @end @interface FLTTileOverlaysController () @property(strong, nonatomic) NSMutableDictionary *tileOverlayIdentifierToController; @property(strong, nonatomic) FlutterMethodChannel *methodChannel; @property(weak, nonatomic) GMSMapView *mapView; @end @implementation FLTTileOverlaysController - (instancetype)init:(FlutterMethodChannel *)methodChannel mapView:(GMSMapView *)mapView registrar:(NSObject *)registrar { self = [super init]; if (self) { _methodChannel = methodChannel; _mapView = mapView; _tileOverlayIdentifierToController = [[NSMutableDictionary alloc] init]; } return self; } - (void)addTileOverlays:(NSArray *)tileOverlaysToAdd { for (NSDictionary *tileOverlay in tileOverlaysToAdd) { NSString *identifier = [FLTTileOverlaysController identifierForTileOverlay:tileOverlay]; FLTTileProviderController *tileProvider = [[FLTTileProviderController alloc] init:self.methodChannel withTileOverlayIdentifier:identifier]; FLTGoogleMapTileOverlayController *controller = [[FLTGoogleMapTileOverlayController alloc] initWithTileLayer:tileProvider mapView:self.mapView options:tileOverlay]; self.tileOverlayIdentifierToController[identifier] = controller; } } - (void)changeTileOverlays:(NSArray *)tileOverlaysToChange { for (NSDictionary *tileOverlay in tileOverlaysToChange) { NSString *identifier = [FLTTileOverlaysController identifierForTileOverlay:tileOverlay]; FLTGoogleMapTileOverlayController *controller = self.tileOverlayIdentifierToController[identifier]; if (!controller) { continue; } [controller interpretTileOverlayOptions:tileOverlay]; } } - (void)removeTileOverlayWithIdentifiers:(NSArray *)identifiers { for (NSString *identifier in identifiers) { FLTGoogleMapTileOverlayController *controller = self.tileOverlayIdentifierToController[identifier]; if (!controller) { continue; } [controller removeTileOverlay]; [self.tileOverlayIdentifierToController removeObjectForKey:identifier]; } } - (void)clearTileCacheWithIdentifier:(NSString *)identifier { FLTGoogleMapTileOverlayController *controller = self.tileOverlayIdentifierToController[identifier]; if (!controller) { return; } [controller clearTileCache]; } - (nullable NSDictionary *)tileOverlayInfoWithIdentifier:(NSString *)identifier { if (self.tileOverlayIdentifierToController[identifier] == nil) { return nil; } return [self.tileOverlayIdentifierToController[identifier] getTileOverlayInfo]; } + (NSString *)identifierForTileOverlay:(NSDictionary *)tileOverlay { return tileOverlay[@"tileOverlayId"]; } @end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapsPlugin.h ================================================ // Copyright 2013 The Flutter 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 "GoogleMapCircleController.h" #import "GoogleMapController.h" #import "GoogleMapMarkerController.h" #import "GoogleMapPolygonController.h" #import "GoogleMapPolylineController.h" NS_ASSUME_NONNULL_BEGIN @interface FLTGoogleMapsPlugin : NSObject @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapsPlugin.m ================================================ // Copyright 2013 The Flutter 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 "FLTGoogleMapsPlugin.h" #pragma mark - GoogleMaps plugin implementation @implementation FLTGoogleMapsPlugin + (void)registerWithRegistrar:(NSObject *)registrar { FLTGoogleMapFactory *googleMapFactory = [[FLTGoogleMapFactory alloc] initWithRegistrar:registrar]; [registrar registerViewFactory:googleMapFactory withId:@"plugins.flutter.dev/google_maps_ios" gestureRecognizersBlockingPolicy: FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded]; } @end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapCircleController.h ================================================ // Copyright 2013 The Flutter 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 // Defines circle controllable by Flutter. @interface FLTGoogleMapCircleController : NSObject - (instancetype)initCircleWithPosition:(CLLocationCoordinate2D)position radius:(CLLocationDistance)radius circleId:(NSString *)circleIdentifier mapView:(GMSMapView *)mapView options:(NSDictionary *)options; - (void)removeCircle; @end @interface FLTCirclesController : NSObject - (instancetype)init:(FlutterMethodChannel *)methodChannel mapView:(GMSMapView *)mapView registrar:(NSObject *)registrar; - (void)addCircles:(NSArray *)circlesToAdd; - (void)changeCircles:(NSArray *)circlesToChange; - (void)removeCircleWithIdentifiers:(NSArray *)identifiers; - (void)didTapCircleWithIdentifier:(NSString *)identifier; - (bool)hasCircleWithIdentifier:(NSString *)identifier; @end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapCircleController.m ================================================ // Copyright 2013 The Flutter 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 "GoogleMapCircleController.h" #import "FLTGoogleMapJSONConversions.h" @interface FLTGoogleMapCircleController () @property(nonatomic, strong) GMSCircle *circle; @property(nonatomic, weak) GMSMapView *mapView; @end @implementation FLTGoogleMapCircleController - (instancetype)initCircleWithPosition:(CLLocationCoordinate2D)position radius:(CLLocationDistance)radius circleId:(NSString *)circleIdentifier mapView:(GMSMapView *)mapView options:(NSDictionary *)options { self = [super init]; if (self) { _circle = [GMSCircle circleWithPosition:position radius:radius]; _mapView = mapView; _circle.userData = @[ circleIdentifier ]; [self interpretCircleOptions:options]; } return self; } - (void)removeCircle { self.circle.map = nil; } - (void)setConsumeTapEvents:(BOOL)consumes { self.circle.tappable = consumes; } - (void)setVisible:(BOOL)visible { self.circle.map = visible ? self.mapView : nil; } - (void)setZIndex:(int)zIndex { self.circle.zIndex = zIndex; } - (void)setCenter:(CLLocationCoordinate2D)center { self.circle.position = center; } - (void)setRadius:(CLLocationDistance)radius { self.circle.radius = radius; } - (void)setStrokeColor:(UIColor *)color { self.circle.strokeColor = color; } - (void)setStrokeWidth:(CGFloat)width { self.circle.strokeWidth = width; } - (void)setFillColor:(UIColor *)color { self.circle.fillColor = color; } - (void)interpretCircleOptions:(NSDictionary *)data { NSNumber *consumeTapEvents = data[@"consumeTapEvents"]; if (consumeTapEvents && consumeTapEvents != (id)[NSNull null]) { [self setConsumeTapEvents:consumeTapEvents.boolValue]; } NSNumber *visible = data[@"visible"]; if (visible && visible != (id)[NSNull null]) { [self setVisible:[visible boolValue]]; } NSNumber *zIndex = data[@"zIndex"]; if (zIndex && zIndex != (id)[NSNull null]) { [self setZIndex:[zIndex intValue]]; } NSArray *center = data[@"center"]; if (center && center != (id)[NSNull null]) { [self setCenter:[FLTGoogleMapJSONConversions locationFromLatLong:center]]; } NSNumber *radius = data[@"radius"]; if (radius && radius != (id)[NSNull null]) { [self setRadius:[radius floatValue]]; } NSNumber *strokeColor = data[@"strokeColor"]; if (strokeColor && strokeColor != (id)[NSNull null]) { [self setStrokeColor:[FLTGoogleMapJSONConversions colorFromRGBA:strokeColor]]; } NSNumber *strokeWidth = data[@"strokeWidth"]; if (strokeWidth && strokeWidth != (id)[NSNull null]) { [self setStrokeWidth:[strokeWidth intValue]]; } NSNumber *fillColor = data[@"fillColor"]; if (fillColor && fillColor != (id)[NSNull null]) { [self setFillColor:[FLTGoogleMapJSONConversions colorFromRGBA:fillColor]]; } } @end @interface FLTCirclesController () @property(strong, nonatomic) FlutterMethodChannel *methodChannel; @property(weak, nonatomic) GMSMapView *mapView; @property(strong, nonatomic) NSMutableDictionary *circleIdToController; @end @implementation FLTCirclesController - (instancetype)init:(FlutterMethodChannel *)methodChannel mapView:(GMSMapView *)mapView registrar:(NSObject *)registrar { self = [super init]; if (self) { _methodChannel = methodChannel; _mapView = mapView; _circleIdToController = [NSMutableDictionary dictionaryWithCapacity:1]; } return self; } - (void)addCircles:(NSArray *)circlesToAdd { for (NSDictionary *circle in circlesToAdd) { CLLocationCoordinate2D position = [FLTCirclesController getPosition:circle]; CLLocationDistance radius = [FLTCirclesController getRadius:circle]; NSString *circleId = [FLTCirclesController getCircleId:circle]; FLTGoogleMapCircleController *controller = [[FLTGoogleMapCircleController alloc] initCircleWithPosition:position radius:radius circleId:circleId mapView:self.mapView options:circle]; self.circleIdToController[circleId] = controller; } } - (void)changeCircles:(NSArray *)circlesToChange { for (NSDictionary *circle in circlesToChange) { NSString *circleId = [FLTCirclesController getCircleId:circle]; FLTGoogleMapCircleController *controller = self.circleIdToController[circleId]; if (!controller) { continue; } [controller interpretCircleOptions:circle]; } } - (void)removeCircleWithIdentifiers:(NSArray *)identifiers { for (NSString *identifier in identifiers) { FLTGoogleMapCircleController *controller = self.circleIdToController[identifier]; if (!controller) { continue; } [controller removeCircle]; [self.circleIdToController removeObjectForKey:identifier]; } } - (bool)hasCircleWithIdentifier:(NSString *)identifier { if (!identifier) { return false; } return self.circleIdToController[identifier] != nil; } - (void)didTapCircleWithIdentifier:(NSString *)identifier { if (!identifier) { return; } FLTGoogleMapCircleController *controller = self.circleIdToController[identifier]; if (!controller) { return; } [self.methodChannel invokeMethod:@"circle#onTap" arguments:@{@"circleId" : identifier}]; } + (CLLocationCoordinate2D)getPosition:(NSDictionary *)circle { NSArray *center = circle[@"center"]; return [FLTGoogleMapJSONConversions locationFromLatLong:center]; } + (CLLocationDistance)getRadius:(NSDictionary *)circle { NSNumber *radius = circle[@"radius"]; return [radius floatValue]; } + (NSString *)getCircleId:(NSDictionary *)circle { return circle[@"circleId"]; } @end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController.h ================================================ // Copyright 2013 The Flutter 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 "GoogleMapCircleController.h" #import "GoogleMapMarkerController.h" #import "GoogleMapPolygonController.h" #import "GoogleMapPolylineController.h" NS_ASSUME_NONNULL_BEGIN // Defines map overlay controllable from Flutter. @interface FLTGoogleMapController : NSObject - (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(nullable id)args registrar:(NSObject *)registrar; - (void)showAtOrigin:(CGPoint)origin; - (void)hide; - (void)animateWithCameraUpdate:(GMSCameraUpdate *)cameraUpdate; - (void)moveWithCameraUpdate:(GMSCameraUpdate *)cameraUpdate; - (nullable GMSCameraPosition *)cameraPosition; @end // Allows the engine to create new Google Map instances. @interface FLTGoogleMapFactory : NSObject - (instancetype)initWithRegistrar:(NSObject *)registrar; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController.m ================================================ // Copyright 2013 The Flutter 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 "GoogleMapController.h" #import "FLTGoogleMapJSONConversions.h" #import "FLTGoogleMapTileOverlayController.h" #pragma mark - Conversion of JSON-like values sent via platform channels. Forward declarations. @interface FLTGoogleMapFactory () @property(weak, nonatomic) NSObject *registrar; @property(strong, nonatomic, readonly) id sharedMapServices; @end @implementation FLTGoogleMapFactory @synthesize sharedMapServices = _sharedMapServices; - (instancetype)initWithRegistrar:(NSObject *)registrar { self = [super init]; if (self) { _registrar = registrar; } return self; } - (NSObject *)createArgsCodec { return [FlutterStandardMessageCodec sharedInstance]; } - (NSObject *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args { // Precache shared map services, if needed. // Retain the shared map services singleton, don't use the result for anything. (void)[self sharedMapServices]; return [[FLTGoogleMapController alloc] initWithFrame:frame viewIdentifier:viewId arguments:args registrar:self.registrar]; } - (id)sharedMapServices { if (_sharedMapServices == nil) { // Calling this prepares GMSServices on a background thread controlled // by the GoogleMaps framework. // Retain the singleton to cache the initialization work across all map views. _sharedMapServices = [GMSServices sharedServices]; } return _sharedMapServices; } @end @interface FLTGoogleMapController () @property(nonatomic, strong) GMSMapView *mapView; @property(nonatomic, strong) FlutterMethodChannel *channel; @property(nonatomic, assign) BOOL trackCameraPosition; @property(nonatomic, weak) NSObject *registrar; @property(nonatomic, strong) FLTMarkersController *markersController; @property(nonatomic, strong) FLTPolygonsController *polygonsController; @property(nonatomic, strong) FLTPolylinesController *polylinesController; @property(nonatomic, strong) FLTCirclesController *circlesController; @property(nonatomic, strong) FLTTileOverlaysController *tileOverlaysController; @end @implementation FLTGoogleMapController - (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args registrar:(NSObject *)registrar { GMSCameraPosition *camera = [FLTGoogleMapJSONConversions cameraPostionFromDictionary:args[@"initialCameraPosition"]]; GMSMapView *mapView = [GMSMapView mapWithFrame:frame camera:camera]; return [self initWithMapView:mapView viewIdentifier:viewId arguments:args registrar:registrar]; } - (instancetype)initWithMapView:(GMSMapView *_Nonnull)mapView viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args registrar:(NSObject *_Nonnull)registrar { if (self = [super init]) { _mapView = mapView; _mapView.accessibilityElementsHidden = NO; // TODO(cyanglaz): avoid sending message to self in the middle of the init method. // https://github.com/flutter/flutter/issues/104121 [self interpretMapOptions:args[@"options"]]; NSString *channelName = [NSString stringWithFormat:@"plugins.flutter.dev/google_maps_ios_%lld", viewId]; _channel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:registrar.messenger]; __weak __typeof__(self) weakSelf = self; [_channel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) { if (weakSelf) { [weakSelf onMethodCall:call result:result]; } }]; _mapView.delegate = weakSelf; _mapView.paddingAdjustmentBehavior = kGMSMapViewPaddingAdjustmentBehaviorNever; _registrar = registrar; _markersController = [[FLTMarkersController alloc] initWithMethodChannel:_channel mapView:_mapView registrar:registrar]; _polygonsController = [[FLTPolygonsController alloc] init:_channel mapView:_mapView registrar:registrar]; _polylinesController = [[FLTPolylinesController alloc] init:_channel mapView:_mapView registrar:registrar]; _circlesController = [[FLTCirclesController alloc] init:_channel mapView:_mapView registrar:registrar]; _tileOverlaysController = [[FLTTileOverlaysController alloc] init:_channel mapView:_mapView registrar:registrar]; id markersToAdd = args[@"markersToAdd"]; if ([markersToAdd isKindOfClass:[NSArray class]]) { [_markersController addMarkers:markersToAdd]; } id polygonsToAdd = args[@"polygonsToAdd"]; if ([polygonsToAdd isKindOfClass:[NSArray class]]) { [_polygonsController addPolygons:polygonsToAdd]; } id polylinesToAdd = args[@"polylinesToAdd"]; if ([polylinesToAdd isKindOfClass:[NSArray class]]) { [_polylinesController addPolylines:polylinesToAdd]; } id circlesToAdd = args[@"circlesToAdd"]; if ([circlesToAdd isKindOfClass:[NSArray class]]) { [_circlesController addCircles:circlesToAdd]; } id tileOverlaysToAdd = args[@"tileOverlaysToAdd"]; if ([tileOverlaysToAdd isKindOfClass:[NSArray class]]) { [_tileOverlaysController addTileOverlays:tileOverlaysToAdd]; } [_mapView addObserver:self forKeyPath:@"frame" options:0 context:nil]; } return self; } - (UIView *)view { return self.mapView; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (object == self.mapView && [keyPath isEqualToString:@"frame"]) { CGRect bounds = self.mapView.bounds; if (CGRectEqualToRect(bounds, CGRectZero)) { // The workaround is to fix an issue that the camera location is not current when // the size of the map is zero at initialization. // So We only care about the size of the `self.mapView`, ignore the frame changes when the // size is zero. return; } // We only observe the frame for initial setup. [self.mapView removeObserver:self forKeyPath:@"frame"]; [self.mapView moveCamera:[GMSCameraUpdate setCamera:self.mapView.camera]]; } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } - (void)onMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if ([call.method isEqualToString:@"map#show"]) { [self showAtOrigin:CGPointMake([call.arguments[@"x"] doubleValue], [call.arguments[@"y"] doubleValue])]; result(nil); } else if ([call.method isEqualToString:@"map#hide"]) { [self hide]; result(nil); } else if ([call.method isEqualToString:@"camera#animate"]) { [self animateWithCameraUpdate:[FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:call.arguments[@"cameraUpdate"]]]; result(nil); } else if ([call.method isEqualToString:@"camera#move"]) { [self moveWithCameraUpdate:[FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:call.arguments[@"cameraUpdate"]]]; result(nil); } else if ([call.method isEqualToString:@"map#update"]) { [self interpretMapOptions:call.arguments[@"options"]]; result([FLTGoogleMapJSONConversions dictionaryFromPosition:[self cameraPosition]]); } else if ([call.method isEqualToString:@"map#getVisibleRegion"]) { if (self.mapView != nil) { GMSVisibleRegion visibleRegion = self.mapView.projection.visibleRegion; GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithRegion:visibleRegion]; result([FLTGoogleMapJSONConversions dictionaryFromCoordinateBounds:bounds]); } else { result([FlutterError errorWithCode:@"GoogleMap uninitialized" message:@"getVisibleRegion called prior to map initialization" details:nil]); } } else if ([call.method isEqualToString:@"map#getScreenCoordinate"]) { if (self.mapView != nil) { CLLocationCoordinate2D location = [FLTGoogleMapJSONConversions locationFromLatLong:call.arguments]; CGPoint point = [self.mapView.projection pointForCoordinate:location]; result([FLTGoogleMapJSONConversions dictionaryFromPoint:point]); } else { result([FlutterError errorWithCode:@"GoogleMap uninitialized" message:@"getScreenCoordinate called prior to map initialization" details:nil]); } } else if ([call.method isEqualToString:@"map#getLatLng"]) { if (self.mapView != nil && call.arguments) { CGPoint point = [FLTGoogleMapJSONConversions pointFromDictionary:call.arguments]; CLLocationCoordinate2D latlng = [self.mapView.projection coordinateForPoint:point]; result([FLTGoogleMapJSONConversions arrayFromLocation:latlng]); } else { result([FlutterError errorWithCode:@"GoogleMap uninitialized" message:@"getLatLng called prior to map initialization" details:nil]); } } else if ([call.method isEqualToString:@"map#waitForMap"]) { result(nil); } else if ([call.method isEqualToString:@"map#takeSnapshot"]) { if (@available(iOS 10.0, *)) { if (self.mapView != nil) { UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat defaultFormat]; format.scale = [[UIScreen mainScreen] scale]; UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:self.mapView.frame.size format:format]; UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext *context) { [self.mapView.layer renderInContext:context.CGContext]; }]; result([FlutterStandardTypedData typedDataWithBytes:UIImagePNGRepresentation(image)]); } else { result([FlutterError errorWithCode:@"GoogleMap uninitialized" message:@"takeSnapshot called prior to map initialization" details:nil]); } } else { NSLog(@"Taking snapshots is not supported for Flutter Google Maps prior to iOS 10."); result(nil); } } else if ([call.method isEqualToString:@"markers#update"]) { id markersToAdd = call.arguments[@"markersToAdd"]; if ([markersToAdd isKindOfClass:[NSArray class]]) { [self.markersController addMarkers:markersToAdd]; } id markersToChange = call.arguments[@"markersToChange"]; if ([markersToChange isKindOfClass:[NSArray class]]) { [self.markersController changeMarkers:markersToChange]; } id markerIdsToRemove = call.arguments[@"markerIdsToRemove"]; if ([markerIdsToRemove isKindOfClass:[NSArray class]]) { [self.markersController removeMarkersWithIdentifiers:markerIdsToRemove]; } result(nil); } else if ([call.method isEqualToString:@"markers#showInfoWindow"]) { id markerId = call.arguments[@"markerId"]; if ([markerId isKindOfClass:[NSString class]]) { [self.markersController showMarkerInfoWindowWithIdentifier:markerId result:result]; } else { result([FlutterError errorWithCode:@"Invalid markerId" message:@"showInfoWindow called with invalid markerId" details:nil]); } } else if ([call.method isEqualToString:@"markers#hideInfoWindow"]) { id markerId = call.arguments[@"markerId"]; if ([markerId isKindOfClass:[NSString class]]) { [self.markersController hideMarkerInfoWindowWithIdentifier:markerId result:result]; } else { result([FlutterError errorWithCode:@"Invalid markerId" message:@"hideInfoWindow called with invalid markerId" details:nil]); } } else if ([call.method isEqualToString:@"markers#isInfoWindowShown"]) { id markerId = call.arguments[@"markerId"]; if ([markerId isKindOfClass:[NSString class]]) { [self.markersController isInfoWindowShownForMarkerWithIdentifier:markerId result:result]; } else { result([FlutterError errorWithCode:@"Invalid markerId" message:@"isInfoWindowShown called with invalid markerId" details:nil]); } } else if ([call.method isEqualToString:@"polygons#update"]) { id polygonsToAdd = call.arguments[@"polygonsToAdd"]; if ([polygonsToAdd isKindOfClass:[NSArray class]]) { [self.polygonsController addPolygons:polygonsToAdd]; } id polygonsToChange = call.arguments[@"polygonsToChange"]; if ([polygonsToChange isKindOfClass:[NSArray class]]) { [self.polygonsController changePolygons:polygonsToChange]; } id polygonIdsToRemove = call.arguments[@"polygonIdsToRemove"]; if ([polygonIdsToRemove isKindOfClass:[NSArray class]]) { [self.polygonsController removePolygonWithIdentifiers:polygonIdsToRemove]; } result(nil); } else if ([call.method isEqualToString:@"polylines#update"]) { id polylinesToAdd = call.arguments[@"polylinesToAdd"]; if ([polylinesToAdd isKindOfClass:[NSArray class]]) { [self.polylinesController addPolylines:polylinesToAdd]; } id polylinesToChange = call.arguments[@"polylinesToChange"]; if ([polylinesToChange isKindOfClass:[NSArray class]]) { [self.polylinesController changePolylines:polylinesToChange]; } id polylineIdsToRemove = call.arguments[@"polylineIdsToRemove"]; if ([polylineIdsToRemove isKindOfClass:[NSArray class]]) { [self.polylinesController removePolylineWithIdentifiers:polylineIdsToRemove]; } result(nil); } else if ([call.method isEqualToString:@"circles#update"]) { id circlesToAdd = call.arguments[@"circlesToAdd"]; if ([circlesToAdd isKindOfClass:[NSArray class]]) { [self.circlesController addCircles:circlesToAdd]; } id circlesToChange = call.arguments[@"circlesToChange"]; if ([circlesToChange isKindOfClass:[NSArray class]]) { [self.circlesController changeCircles:circlesToChange]; } id circleIdsToRemove = call.arguments[@"circleIdsToRemove"]; if ([circleIdsToRemove isKindOfClass:[NSArray class]]) { [self.circlesController removeCircleWithIdentifiers:circleIdsToRemove]; } result(nil); } else if ([call.method isEqualToString:@"tileOverlays#update"]) { id tileOverlaysToAdd = call.arguments[@"tileOverlaysToAdd"]; if ([tileOverlaysToAdd isKindOfClass:[NSArray class]]) { [self.tileOverlaysController addTileOverlays:tileOverlaysToAdd]; } id tileOverlaysToChange = call.arguments[@"tileOverlaysToChange"]; if ([tileOverlaysToChange isKindOfClass:[NSArray class]]) { [self.tileOverlaysController changeTileOverlays:tileOverlaysToChange]; } id tileOverlayIdsToRemove = call.arguments[@"tileOverlayIdsToRemove"]; if ([tileOverlayIdsToRemove isKindOfClass:[NSArray class]]) { [self.tileOverlaysController removeTileOverlayWithIdentifiers:tileOverlayIdsToRemove]; } result(nil); } else if ([call.method isEqualToString:@"tileOverlays#clearTileCache"]) { id rawTileOverlayId = call.arguments[@"tileOverlayId"]; [self.tileOverlaysController clearTileCacheWithIdentifier:rawTileOverlayId]; result(nil); } else if ([call.method isEqualToString:@"map#isCompassEnabled"]) { NSNumber *isCompassEnabled = @(self.mapView.settings.compassButton); result(isCompassEnabled); } else if ([call.method isEqualToString:@"map#isMapToolbarEnabled"]) { NSNumber *isMapToolbarEnabled = @NO; result(isMapToolbarEnabled); } else if ([call.method isEqualToString:@"map#getMinMaxZoomLevels"]) { NSArray *zoomLevels = @[ @(self.mapView.minZoom), @(self.mapView.maxZoom) ]; result(zoomLevels); } else if ([call.method isEqualToString:@"map#getZoomLevel"]) { result(@(self.mapView.camera.zoom)); } else if ([call.method isEqualToString:@"map#isZoomGesturesEnabled"]) { NSNumber *isZoomGesturesEnabled = @(self.mapView.settings.zoomGestures); result(isZoomGesturesEnabled); } else if ([call.method isEqualToString:@"map#isZoomControlsEnabled"]) { NSNumber *isZoomControlsEnabled = @NO; result(isZoomControlsEnabled); } else if ([call.method isEqualToString:@"map#isTiltGesturesEnabled"]) { NSNumber *isTiltGesturesEnabled = @(self.mapView.settings.tiltGestures); result(isTiltGesturesEnabled); } else if ([call.method isEqualToString:@"map#isRotateGesturesEnabled"]) { NSNumber *isRotateGesturesEnabled = @(self.mapView.settings.rotateGestures); result(isRotateGesturesEnabled); } else if ([call.method isEqualToString:@"map#isScrollGesturesEnabled"]) { NSNumber *isScrollGesturesEnabled = @(self.mapView.settings.scrollGestures); result(isScrollGesturesEnabled); } else if ([call.method isEqualToString:@"map#isMyLocationButtonEnabled"]) { NSNumber *isMyLocationButtonEnabled = @(self.mapView.settings.myLocationButton); result(isMyLocationButtonEnabled); } else if ([call.method isEqualToString:@"map#isTrafficEnabled"]) { NSNumber *isTrafficEnabled = @(self.mapView.trafficEnabled); result(isTrafficEnabled); } else if ([call.method isEqualToString:@"map#isBuildingsEnabled"]) { NSNumber *isBuildingsEnabled = @(self.mapView.buildingsEnabled); result(isBuildingsEnabled); } else if ([call.method isEqualToString:@"map#setStyle"]) { NSString *mapStyle = [call arguments]; NSString *error = [self setMapStyle:mapStyle]; if (error == nil) { result(@[ @(YES) ]); } else { result(@[ @(NO), error ]); } } else if ([call.method isEqualToString:@"map#getTileOverlayInfo"]) { NSString *rawTileOverlayId = call.arguments[@"tileOverlayId"]; result([self.tileOverlaysController tileOverlayInfoWithIdentifier:rawTileOverlayId]); } else { result(FlutterMethodNotImplemented); } } - (void)showAtOrigin:(CGPoint)origin { CGRect frame = {origin, self.mapView.frame.size}; self.mapView.frame = frame; self.mapView.hidden = NO; } - (void)hide { self.mapView.hidden = YES; } - (void)animateWithCameraUpdate:(GMSCameraUpdate *)cameraUpdate { [self.mapView animateWithCameraUpdate:cameraUpdate]; } - (void)moveWithCameraUpdate:(GMSCameraUpdate *)cameraUpdate { [self.mapView moveCamera:cameraUpdate]; } - (GMSCameraPosition *)cameraPosition { if (self.trackCameraPosition) { return self.mapView.camera; } else { return nil; } } - (void)setCamera:(GMSCameraPosition *)camera { self.mapView.camera = camera; } - (void)setCameraTargetBounds:(GMSCoordinateBounds *)bounds { self.mapView.cameraTargetBounds = bounds; } - (void)setCompassEnabled:(BOOL)enabled { self.mapView.settings.compassButton = enabled; } - (void)setIndoorEnabled:(BOOL)enabled { self.mapView.indoorEnabled = enabled; } - (void)setTrafficEnabled:(BOOL)enabled { self.mapView.trafficEnabled = enabled; } - (void)setBuildingsEnabled:(BOOL)enabled { self.mapView.buildingsEnabled = enabled; } - (void)setMapType:(GMSMapViewType)mapType { self.mapView.mapType = mapType; } - (void)setMinZoom:(float)minZoom maxZoom:(float)maxZoom { [self.mapView setMinZoom:minZoom maxZoom:maxZoom]; } - (void)setPaddingTop:(float)top left:(float)left bottom:(float)bottom right:(float)right { self.mapView.padding = UIEdgeInsetsMake(top, left, bottom, right); } - (void)setRotateGesturesEnabled:(BOOL)enabled { self.mapView.settings.rotateGestures = enabled; } - (void)setScrollGesturesEnabled:(BOOL)enabled { self.mapView.settings.scrollGestures = enabled; } - (void)setTiltGesturesEnabled:(BOOL)enabled { self.mapView.settings.tiltGestures = enabled; } - (void)setTrackCameraPosition:(BOOL)enabled { _trackCameraPosition = enabled; } - (void)setZoomGesturesEnabled:(BOOL)enabled { self.mapView.settings.zoomGestures = enabled; } - (void)setMyLocationEnabled:(BOOL)enabled { self.mapView.myLocationEnabled = enabled; } - (void)setMyLocationButtonEnabled:(BOOL)enabled { self.mapView.settings.myLocationButton = enabled; } - (NSString *)setMapStyle:(NSString *)mapStyle { if (mapStyle == (id)[NSNull null] || mapStyle.length == 0) { self.mapView.mapStyle = nil; return nil; } NSError *error; GMSMapStyle *style = [GMSMapStyle styleWithJSONString:mapStyle error:&error]; if (!style) { return [error localizedDescription]; } else { self.mapView.mapStyle = style; return nil; } } #pragma mark - GMSMapViewDelegate methods - (void)mapView:(GMSMapView *)mapView willMove:(BOOL)gesture { [self.channel invokeMethod:@"camera#onMoveStarted" arguments:@{@"isGesture" : @(gesture)}]; } - (void)mapView:(GMSMapView *)mapView didChangeCameraPosition:(GMSCameraPosition *)position { if (self.trackCameraPosition) { [self.channel invokeMethod:@"camera#onMove" arguments:@{ @"position" : [FLTGoogleMapJSONConversions dictionaryFromPosition:position] }]; } } - (void)mapView:(GMSMapView *)mapView idleAtCameraPosition:(GMSCameraPosition *)position { [self.channel invokeMethod:@"camera#onIdle" arguments:@{}]; } - (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker { NSString *markerId = marker.userData[0]; return [self.markersController didTapMarkerWithIdentifier:markerId]; } - (void)mapView:(GMSMapView *)mapView didEndDraggingMarker:(GMSMarker *)marker { NSString *markerId = marker.userData[0]; [self.markersController didEndDraggingMarkerWithIdentifier:markerId location:marker.position]; } - (void)mapView:(GMSMapView *)mapView didStartDraggingMarker:(GMSMarker *)marker { NSString *markerId = marker.userData[0]; [self.markersController didStartDraggingMarkerWithIdentifier:markerId location:marker.position]; } - (void)mapView:(GMSMapView *)mapView didDragMarker:(GMSMarker *)marker { NSString *markerId = marker.userData[0]; [self.markersController didDragMarkerWithIdentifier:markerId location:marker.position]; } - (void)mapView:(GMSMapView *)mapView didTapInfoWindowOfMarker:(GMSMarker *)marker { NSString *markerId = marker.userData[0]; [self.markersController didTapInfoWindowOfMarkerWithIdentifier:markerId]; } - (void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSOverlay *)overlay { NSString *overlayId = overlay.userData[0]; if ([self.polylinesController hasPolylineWithIdentifier:overlayId]) { [self.polylinesController didTapPolylineWithIdentifier:overlayId]; } else if ([self.polygonsController hasPolygonWithIdentifier:overlayId]) { [self.polygonsController didTapPolygonWithIdentifier:overlayId]; } else if ([self.circlesController hasCircleWithIdentifier:overlayId]) { [self.circlesController didTapCircleWithIdentifier:overlayId]; } } - (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate { [self.channel invokeMethod:@"map#onTap" arguments:@{@"position" : [FLTGoogleMapJSONConversions arrayFromLocation:coordinate]}]; } - (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate { [self.channel invokeMethod:@"map#onLongPress" arguments:@{@"position" : [FLTGoogleMapJSONConversions arrayFromLocation:coordinate]}]; } - (void)interpretMapOptions:(NSDictionary *)data { NSArray *cameraTargetBounds = data[@"cameraTargetBounds"]; if (cameraTargetBounds && cameraTargetBounds != (id)[NSNull null]) { [self setCameraTargetBounds:cameraTargetBounds.count > 0 && cameraTargetBounds[0] != [NSNull null] ? [FLTGoogleMapJSONConversions coordinateBoundsFromLatLongs:cameraTargetBounds.firstObject] : nil]; } NSNumber *compassEnabled = data[@"compassEnabled"]; if (compassEnabled && compassEnabled != (id)[NSNull null]) { [self setCompassEnabled:[compassEnabled boolValue]]; } id indoorEnabled = data[@"indoorEnabled"]; if (indoorEnabled && indoorEnabled != [NSNull null]) { [self setIndoorEnabled:[indoorEnabled boolValue]]; } id trafficEnabled = data[@"trafficEnabled"]; if (trafficEnabled && trafficEnabled != [NSNull null]) { [self setTrafficEnabled:[trafficEnabled boolValue]]; } id buildingsEnabled = data[@"buildingsEnabled"]; if (buildingsEnabled && buildingsEnabled != [NSNull null]) { [self setBuildingsEnabled:[buildingsEnabled boolValue]]; } id mapType = data[@"mapType"]; if (mapType && mapType != [NSNull null]) { [self setMapType:[FLTGoogleMapJSONConversions mapViewTypeFromTypeValue:mapType]]; } NSArray *zoomData = data[@"minMaxZoomPreference"]; if (zoomData && zoomData != (id)[NSNull null]) { float minZoom = (zoomData[0] == [NSNull null]) ? kGMSMinZoomLevel : [zoomData[0] floatValue]; float maxZoom = (zoomData[1] == [NSNull null]) ? kGMSMaxZoomLevel : [zoomData[1] floatValue]; [self setMinZoom:minZoom maxZoom:maxZoom]; } NSArray *paddingData = data[@"padding"]; if (paddingData) { float top = (paddingData[0] == [NSNull null]) ? 0 : [paddingData[0] floatValue]; float left = (paddingData[1] == [NSNull null]) ? 0 : [paddingData[1] floatValue]; float bottom = (paddingData[2] == [NSNull null]) ? 0 : [paddingData[2] floatValue]; float right = (paddingData[3] == [NSNull null]) ? 0 : [paddingData[3] floatValue]; [self setPaddingTop:top left:left bottom:bottom right:right]; } NSNumber *rotateGesturesEnabled = data[@"rotateGesturesEnabled"]; if (rotateGesturesEnabled && rotateGesturesEnabled != (id)[NSNull null]) { [self setRotateGesturesEnabled:[rotateGesturesEnabled boolValue]]; } NSNumber *scrollGesturesEnabled = data[@"scrollGesturesEnabled"]; if (scrollGesturesEnabled && scrollGesturesEnabled != (id)[NSNull null]) { [self setScrollGesturesEnabled:[scrollGesturesEnabled boolValue]]; } NSNumber *tiltGesturesEnabled = data[@"tiltGesturesEnabled"]; if (tiltGesturesEnabled && tiltGesturesEnabled != (id)[NSNull null]) { [self setTiltGesturesEnabled:[tiltGesturesEnabled boolValue]]; } NSNumber *trackCameraPosition = data[@"trackCameraPosition"]; if (trackCameraPosition && trackCameraPosition != (id)[NSNull null]) { [self setTrackCameraPosition:[trackCameraPosition boolValue]]; } NSNumber *zoomGesturesEnabled = data[@"zoomGesturesEnabled"]; if (zoomGesturesEnabled && zoomGesturesEnabled != (id)[NSNull null]) { [self setZoomGesturesEnabled:[zoomGesturesEnabled boolValue]]; } NSNumber *myLocationEnabled = data[@"myLocationEnabled"]; if (myLocationEnabled && myLocationEnabled != (id)[NSNull null]) { [self setMyLocationEnabled:[myLocationEnabled boolValue]]; } NSNumber *myLocationButtonEnabled = data[@"myLocationButtonEnabled"]; if (myLocationButtonEnabled && myLocationButtonEnabled != (id)[NSNull null]) { [self setMyLocationButtonEnabled:[myLocationButtonEnabled boolValue]]; } } @end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController_Test.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import #import NS_ASSUME_NONNULL_BEGIN @interface FLTGoogleMapController (Test) /** * Initializes a map controller with a concrete map view. * * @param mapView A map view that will be displayed by the controller * @param viewId A unique identifier for the controller. * @param args Parameters for initialising the map view. * @param registrar The plugin registrar passed from Flutter. */ - (instancetype)initWithMapView:(GMSMapView *)mapView viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args registrar:(NSObject *)registrar; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapMarkerController.h ================================================ // Copyright 2013 The Flutter 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 "GoogleMapController.h" NS_ASSUME_NONNULL_BEGIN // Defines marker controllable by Flutter. @interface FLTGoogleMapMarkerController : NSObject @property(assign, nonatomic, readonly) BOOL consumeTapEvents; - (instancetype)initMarkerWithPosition:(CLLocationCoordinate2D)position identifier:(NSString *)identifier mapView:(GMSMapView *)mapView; - (void)showInfoWindow; - (void)hideInfoWindow; - (BOOL)isInfoWindowShown; - (void)removeMarker; @end @interface FLTMarkersController : NSObject - (instancetype)initWithMethodChannel:(FlutterMethodChannel *)methodChannel mapView:(GMSMapView *)mapView registrar:(NSObject *)registrar; - (void)addMarkers:(NSArray *)markersToAdd; - (void)changeMarkers:(NSArray *)markersToChange; - (void)removeMarkersWithIdentifiers:(NSArray *)identifiers; - (BOOL)didTapMarkerWithIdentifier:(NSString *)identifier; - (void)didStartDraggingMarkerWithIdentifier:(NSString *)identifier location:(CLLocationCoordinate2D)coordinate; - (void)didEndDraggingMarkerWithIdentifier:(NSString *)identifier location:(CLLocationCoordinate2D)coordinate; - (void)didDragMarkerWithIdentifier:(NSString *)identifier location:(CLLocationCoordinate2D)coordinate; - (void)didTapInfoWindowOfMarkerWithIdentifier:(NSString *)identifier; - (void)showMarkerInfoWindowWithIdentifier:(NSString *)identifier result:(FlutterResult)result; - (void)hideMarkerInfoWindowWithIdentifier:(NSString *)identifier result:(FlutterResult)result; - (void)isInfoWindowShownForMarkerWithIdentifier:(NSString *)identifier result:(FlutterResult)result; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapMarkerController.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "GoogleMapMarkerController.h" #import "FLTGoogleMapJSONConversions.h" @interface FLTGoogleMapMarkerController () @property(strong, nonatomic) GMSMarker *marker; @property(weak, nonatomic) GMSMapView *mapView; @property(assign, nonatomic, readwrite) BOOL consumeTapEvents; @end @implementation FLTGoogleMapMarkerController - (instancetype)initMarkerWithPosition:(CLLocationCoordinate2D)position identifier:(NSString *)identifier mapView:(GMSMapView *)mapView { self = [super init]; if (self) { _marker = [GMSMarker markerWithPosition:position]; _mapView = mapView; _marker.userData = @[ identifier ]; } return self; } - (void)showInfoWindow { self.mapView.selectedMarker = self.marker; } - (void)hideInfoWindow { if (self.mapView.selectedMarker == self.marker) { self.mapView.selectedMarker = nil; } } - (BOOL)isInfoWindowShown { return self.mapView.selectedMarker == self.marker; } - (void)removeMarker { self.marker.map = nil; } - (void)setAlpha:(float)alpha { self.marker.opacity = alpha; } - (void)setAnchor:(CGPoint)anchor { self.marker.groundAnchor = anchor; } - (void)setDraggable:(BOOL)draggable { self.marker.draggable = draggable; } - (void)setFlat:(BOOL)flat { self.marker.flat = flat; } - (void)setIcon:(UIImage *)icon { self.marker.icon = icon; } - (void)setInfoWindowAnchor:(CGPoint)anchor { self.marker.infoWindowAnchor = anchor; } - (void)setInfoWindowTitle:(NSString *)title snippet:(NSString *)snippet { self.marker.title = title; self.marker.snippet = snippet; } - (void)setPosition:(CLLocationCoordinate2D)position { self.marker.position = position; } - (void)setRotation:(CLLocationDegrees)rotation { self.marker.rotation = rotation; } - (void)setVisible:(BOOL)visible { self.marker.map = visible ? self.mapView : nil; } - (void)setZIndex:(int)zIndex { self.marker.zIndex = zIndex; } - (void)interpretMarkerOptions:(NSDictionary *)data registrar:(NSObject *)registrar { NSNumber *alpha = data[@"alpha"]; if (alpha && alpha != (id)[NSNull null]) { [self setAlpha:[alpha floatValue]]; } NSArray *anchor = data[@"anchor"]; if (anchor && anchor != (id)[NSNull null]) { [self setAnchor:[FLTGoogleMapJSONConversions pointFromArray:anchor]]; } NSNumber *draggable = data[@"draggable"]; if (draggable && draggable != (id)[NSNull null]) { [self setDraggable:[draggable boolValue]]; } NSArray *icon = data[@"icon"]; if (icon && icon != (id)[NSNull null]) { UIImage *image = [self extractIconFromData:icon registrar:registrar]; [self setIcon:image]; } NSNumber *flat = data[@"flat"]; if (flat && flat != (id)[NSNull null]) { [self setFlat:[flat boolValue]]; } NSNumber *consumeTapEvents = data[@"consumeTapEvents"]; if (consumeTapEvents && consumeTapEvents != (id)[NSNull null]) { [self setConsumeTapEvents:[consumeTapEvents boolValue]]; } [self interpretInfoWindow:data]; NSArray *position = data[@"position"]; if (position && position != (id)[NSNull null]) { [self setPosition:[FLTGoogleMapJSONConversions locationFromLatLong:position]]; } NSNumber *rotation = data[@"rotation"]; if (rotation && rotation != (id)[NSNull null]) { [self setRotation:[rotation doubleValue]]; } NSNumber *visible = data[@"visible"]; if (visible && visible != (id)[NSNull null]) { [self setVisible:[visible boolValue]]; } NSNumber *zIndex = data[@"zIndex"]; if (zIndex && zIndex != (id)[NSNull null]) { [self setZIndex:[zIndex intValue]]; } } - (void)interpretInfoWindow:(NSDictionary *)data { NSDictionary *infoWindow = data[@"infoWindow"]; if (infoWindow && infoWindow != (id)[NSNull null]) { NSString *title = infoWindow[@"title"]; NSString *snippet = infoWindow[@"snippet"]; if (title && title != (id)[NSNull null]) { [self setInfoWindowTitle:title snippet:snippet]; } NSArray *infoWindowAnchor = infoWindow[@"infoWindowAnchor"]; if (infoWindowAnchor && infoWindowAnchor != (id)[NSNull null]) { [self setInfoWindowAnchor:[FLTGoogleMapJSONConversions pointFromArray:infoWindowAnchor]]; } } } - (UIImage *)extractIconFromData:(NSArray *)iconData registrar:(NSObject *)registrar { UIImage *image; if ([iconData.firstObject isEqualToString:@"defaultMarker"]) { CGFloat hue = (iconData.count == 1) ? 0.0f : [iconData[1] doubleValue]; image = [GMSMarker markerImageWithColor:[UIColor colorWithHue:hue / 360.0 saturation:1.0 brightness:0.7 alpha:1.0]]; } else if ([iconData.firstObject isEqualToString:@"fromAsset"]) { if (iconData.count == 2) { image = [UIImage imageNamed:[registrar lookupKeyForAsset:iconData[1]]]; } else { image = [UIImage imageNamed:[registrar lookupKeyForAsset:iconData[1] fromPackage:iconData[2]]]; } } else if ([iconData.firstObject isEqualToString:@"fromAssetImage"]) { if (iconData.count == 3) { image = [UIImage imageNamed:[registrar lookupKeyForAsset:iconData[1]]]; id scaleParam = iconData[2]; image = [self scaleImage:image by:scaleParam]; } else { NSString *error = [NSString stringWithFormat:@"'fromAssetImage' should have exactly 3 arguments. Got: %lu", (unsigned long)iconData.count]; NSException *exception = [NSException exceptionWithName:@"InvalidBitmapDescriptor" reason:error userInfo:nil]; @throw exception; } } else if ([iconData[0] isEqualToString:@"fromBytes"]) { if (iconData.count == 2) { @try { FlutterStandardTypedData *byteData = iconData[1]; CGFloat screenScale = [[UIScreen mainScreen] scale]; image = [UIImage imageWithData:[byteData data] scale:screenScale]; } @catch (NSException *exception) { @throw [NSException exceptionWithName:@"InvalidByteDescriptor" reason:@"Unable to interpret bytes as a valid image." userInfo:nil]; } } else { NSString *error = [NSString stringWithFormat:@"fromBytes should have exactly one argument, the bytes. Got: %lu", (unsigned long)iconData.count]; NSException *exception = [NSException exceptionWithName:@"InvalidByteDescriptor" reason:error userInfo:nil]; @throw exception; } } return image; } - (UIImage *)scaleImage:(UIImage *)image by:(id)scaleParam { double scale = 1.0; if ([scaleParam isKindOfClass:[NSNumber class]]) { scale = [scaleParam doubleValue]; } if (fabs(scale - 1) > 1e-3) { return [UIImage imageWithCGImage:[image CGImage] scale:(image.scale * scale) orientation:(image.imageOrientation)]; } return image; } @end @interface FLTMarkersController () @property(strong, nonatomic) NSMutableDictionary *markerIdentifierToController; @property(strong, nonatomic) FlutterMethodChannel *methodChannel; @property(weak, nonatomic) NSObject *registrar; @property(weak, nonatomic) GMSMapView *mapView; @end @implementation FLTMarkersController - (instancetype)initWithMethodChannel:(FlutterMethodChannel *)methodChannel mapView:(GMSMapView *)mapView registrar:(NSObject *)registrar { self = [super init]; if (self) { _methodChannel = methodChannel; _mapView = mapView; _markerIdentifierToController = [[NSMutableDictionary alloc] init]; _registrar = registrar; } return self; } - (void)addMarkers:(NSArray *)markersToAdd { for (NSDictionary *marker in markersToAdd) { CLLocationCoordinate2D position = [FLTMarkersController getPosition:marker]; NSString *identifier = marker[@"markerId"]; FLTGoogleMapMarkerController *controller = [[FLTGoogleMapMarkerController alloc] initMarkerWithPosition:position identifier:identifier mapView:self.mapView]; [controller interpretMarkerOptions:marker registrar:self.registrar]; self.markerIdentifierToController[identifier] = controller; } } - (void)changeMarkers:(NSArray *)markersToChange { for (NSDictionary *marker in markersToChange) { NSString *identifier = marker[@"markerId"]; FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier]; if (!controller) { continue; } [controller interpretMarkerOptions:marker registrar:self.registrar]; } } - (void)removeMarkersWithIdentifiers:(NSArray *)identifiers { for (NSString *identifier in identifiers) { FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier]; if (!controller) { continue; } [controller removeMarker]; [self.markerIdentifierToController removeObjectForKey:identifier]; } } - (BOOL)didTapMarkerWithIdentifier:(NSString *)identifier { if (!identifier) { return NO; } FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier]; if (!controller) { return NO; } [self.methodChannel invokeMethod:@"marker#onTap" arguments:@{@"markerId" : identifier}]; return controller.consumeTapEvents; } - (void)didStartDraggingMarkerWithIdentifier:(NSString *)identifier location:(CLLocationCoordinate2D)location { if (!identifier) { return; } FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier]; if (!controller) { return; } [self.methodChannel invokeMethod:@"marker#onDragStart" arguments:@{ @"markerId" : identifier, @"position" : [FLTGoogleMapJSONConversions arrayFromLocation:location] }]; } - (void)didDragMarkerWithIdentifier:(NSString *)identifier location:(CLLocationCoordinate2D)location { if (!identifier) { return; } FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier]; if (!controller) { return; } [self.methodChannel invokeMethod:@"marker#onDrag" arguments:@{ @"markerId" : identifier, @"position" : [FLTGoogleMapJSONConversions arrayFromLocation:location] }]; } - (void)didEndDraggingMarkerWithIdentifier:(NSString *)identifier location:(CLLocationCoordinate2D)location { FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier]; if (!controller) { return; } [self.methodChannel invokeMethod:@"marker#onDragEnd" arguments:@{ @"markerId" : identifier, @"position" : [FLTGoogleMapJSONConversions arrayFromLocation:location] }]; } - (void)didTapInfoWindowOfMarkerWithIdentifier:(NSString *)identifier { if (identifier && self.markerIdentifierToController[identifier]) { [self.methodChannel invokeMethod:@"infoWindow#onTap" arguments:@{@"markerId" : identifier}]; } } - (void)showMarkerInfoWindowWithIdentifier:(NSString *)identifier result:(FlutterResult)result { FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier]; if (controller) { [controller showInfoWindow]; result(nil); } else { result([FlutterError errorWithCode:@"Invalid markerId" message:@"showInfoWindow called with invalid markerId" details:nil]); } } - (void)hideMarkerInfoWindowWithIdentifier:(NSString *)identifier result:(FlutterResult)result { FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier]; if (controller) { [controller hideInfoWindow]; result(nil); } else { result([FlutterError errorWithCode:@"Invalid markerId" message:@"hideInfoWindow called with invalid markerId" details:nil]); } } - (void)isInfoWindowShownForMarkerWithIdentifier:(NSString *)identifier result:(FlutterResult)result { FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier]; if (controller) { result(@([controller isInfoWindowShown])); } else { result([FlutterError errorWithCode:@"Invalid markerId" message:@"isInfoWindowShown called with invalid markerId" details:nil]); } } + (CLLocationCoordinate2D)getPosition:(NSDictionary *)marker { NSArray *position = marker[@"position"]; return [FLTGoogleMapJSONConversions locationFromLatLong:position]; } @end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolygonController.h ================================================ // Copyright 2013 The Flutter 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 // Defines polygon controllable by Flutter. @interface FLTGoogleMapPolygonController : NSObject - (instancetype)initPolygonWithPath:(GMSMutablePath *)path identifier:(NSString *)identifier mapView:(GMSMapView *)mapView; - (void)removePolygon; @end @interface FLTPolygonsController : NSObject - (instancetype)init:(FlutterMethodChannel *)methodChannel mapView:(GMSMapView *)mapView registrar:(NSObject *)registrar; - (void)addPolygons:(NSArray *)polygonsToAdd; - (void)changePolygons:(NSArray *)polygonsToChange; - (void)removePolygonWithIdentifiers:(NSArray *)identifiers; - (void)didTapPolygonWithIdentifier:(NSString *)identifier; - (bool)hasPolygonWithIdentifier:(NSString *)identifier; @end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolygonController.m ================================================ // Copyright 2013 The Flutter 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 "GoogleMapPolygonController.h" #import "FLTGoogleMapJSONConversions.h" @interface FLTGoogleMapPolygonController () @property(strong, nonatomic) GMSPolygon *polygon; @property(weak, nonatomic) GMSMapView *mapView; @end @implementation FLTGoogleMapPolygonController - (instancetype)initPolygonWithPath:(GMSMutablePath *)path identifier:(NSString *)identifier mapView:(GMSMapView *)mapView { self = [super init]; if (self) { _polygon = [GMSPolygon polygonWithPath:path]; _mapView = mapView; _polygon.userData = @[ identifier ]; } return self; } - (void)removePolygon { self.polygon.map = nil; } - (void)setConsumeTapEvents:(BOOL)consumes { self.polygon.tappable = consumes; } - (void)setVisible:(BOOL)visible { self.polygon.map = visible ? self.mapView : nil; } - (void)setZIndex:(int)zIndex { self.polygon.zIndex = zIndex; } - (void)setPoints:(NSArray *)points { GMSMutablePath *path = [GMSMutablePath path]; for (CLLocation *location in points) { [path addCoordinate:location.coordinate]; } self.polygon.path = path; } - (void)setHoles:(NSArray *> *)rawHoles { NSMutableArray *holes = [[NSMutableArray alloc] init]; for (NSArray *points in rawHoles) { GMSMutablePath *path = [GMSMutablePath path]; for (CLLocation *location in points) { [path addCoordinate:location.coordinate]; } [holes addObject:path]; } self.polygon.holes = holes; } - (void)setFillColor:(UIColor *)color { self.polygon.fillColor = color; } - (void)setStrokeColor:(UIColor *)color { self.polygon.strokeColor = color; } - (void)setStrokeWidth:(CGFloat)width { self.polygon.strokeWidth = width; } - (void)interpretPolygonOptions:(NSDictionary *)data registrar:(NSObject *)registrar { NSNumber *consumeTapEvents = data[@"consumeTapEvents"]; if (consumeTapEvents && consumeTapEvents != (id)[NSNull null]) { [self setConsumeTapEvents:[consumeTapEvents boolValue]]; } NSNumber *visible = data[@"visible"]; if (visible && visible != (id)[NSNull null]) { [self setVisible:[visible boolValue]]; } NSNumber *zIndex = data[@"zIndex"]; if (zIndex && zIndex != (id)[NSNull null]) { [self setZIndex:[zIndex intValue]]; } NSArray *points = data[@"points"]; if (points && points != (id)[NSNull null]) { [self setPoints:[FLTGoogleMapJSONConversions pointsFromLatLongs:points]]; } NSArray *holes = data[@"holes"]; if (holes && holes != (id)[NSNull null]) { [self setHoles:[FLTGoogleMapJSONConversions holesFromPointsArray:holes]]; } NSNumber *fillColor = data[@"fillColor"]; if (fillColor && fillColor != (id)[NSNull null]) { [self setFillColor:[FLTGoogleMapJSONConversions colorFromRGBA:fillColor]]; } NSNumber *strokeColor = data[@"strokeColor"]; if (strokeColor && strokeColor != (id)[NSNull null]) { [self setStrokeColor:[FLTGoogleMapJSONConversions colorFromRGBA:strokeColor]]; } NSNumber *strokeWidth = data[@"strokeWidth"]; if (strokeWidth && strokeWidth != (id)[NSNull null]) { [self setStrokeWidth:[strokeWidth intValue]]; } } @end @interface FLTPolygonsController () @property(strong, nonatomic) NSMutableDictionary *polygonIdentifierToController; @property(strong, nonatomic) FlutterMethodChannel *methodChannel; @property(weak, nonatomic) NSObject *registrar; @property(weak, nonatomic) GMSMapView *mapView; @end @implementation FLTPolygonsController - (instancetype)init:(FlutterMethodChannel *)methodChannel mapView:(GMSMapView *)mapView registrar:(NSObject *)registrar { self = [super init]; if (self) { _methodChannel = methodChannel; _mapView = mapView; _polygonIdentifierToController = [NSMutableDictionary dictionaryWithCapacity:1]; _registrar = registrar; } return self; } - (void)addPolygons:(NSArray *)polygonsToAdd { for (NSDictionary *polygon in polygonsToAdd) { GMSMutablePath *path = [FLTPolygonsController getPath:polygon]; NSString *identifier = polygon[@"polygonId"]; FLTGoogleMapPolygonController *controller = [[FLTGoogleMapPolygonController alloc] initPolygonWithPath:path identifier:identifier mapView:self.mapView]; [controller interpretPolygonOptions:polygon registrar:self.registrar]; self.polygonIdentifierToController[identifier] = controller; } } - (void)changePolygons:(NSArray *)polygonsToChange { for (NSDictionary *polygon in polygonsToChange) { NSString *identifier = polygon[@"polygonId"]; FLTGoogleMapPolygonController *controller = self.polygonIdentifierToController[identifier]; if (!controller) { continue; } [controller interpretPolygonOptions:polygon registrar:self.registrar]; } } - (void)removePolygonWithIdentifiers:(NSArray *)identifiers { for (NSString *identifier in identifiers) { FLTGoogleMapPolygonController *controller = self.polygonIdentifierToController[identifier]; if (!controller) { continue; } [controller removePolygon]; [self.polygonIdentifierToController removeObjectForKey:identifier]; } } - (void)didTapPolygonWithIdentifier:(NSString *)identifier { if (!identifier) { return; } FLTGoogleMapPolygonController *controller = self.polygonIdentifierToController[identifier]; if (!controller) { return; } [self.methodChannel invokeMethod:@"polygon#onTap" arguments:@{@"polygonId" : identifier}]; } - (bool)hasPolygonWithIdentifier:(NSString *)identifier { if (!identifier) { return false; } return self.polygonIdentifierToController[identifier] != nil; } + (GMSMutablePath *)getPath:(NSDictionary *)polygon { NSArray *pointArray = polygon[@"points"]; NSArray *points = [FLTGoogleMapJSONConversions pointsFromLatLongs:pointArray]; GMSMutablePath *path = [GMSMutablePath path]; for (CLLocation *location in points) { [path addCoordinate:location.coordinate]; } return path; } @end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolylineController.h ================================================ // Copyright 2013 The Flutter 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 // Defines polyline controllable by Flutter. @interface FLTGoogleMapPolylineController : NSObject - (instancetype)initPolylineWithPath:(GMSMutablePath *)path identifier:(NSString *)identifier mapView:(GMSMapView *)mapView; - (void)removePolyline; @end @interface FLTPolylinesController : NSObject - (instancetype)init:(FlutterMethodChannel *)methodChannel mapView:(GMSMapView *)mapView registrar:(NSObject *)registrar; - (void)addPolylines:(NSArray *)polylinesToAdd; - (void)changePolylines:(NSArray *)polylinesToChange; - (void)removePolylineWithIdentifiers:(NSArray *)identifiers; - (void)didTapPolylineWithIdentifier:(NSString *)identifier; - (bool)hasPolylineWithIdentifier:(NSString *)identifier; @end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolylineController.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "GoogleMapPolylineController.h" #import "FLTGoogleMapJSONConversions.h" @interface FLTGoogleMapPolylineController () @property(strong, nonatomic) GMSPolyline *polyline; @property(weak, nonatomic) GMSMapView *mapView; @end @implementation FLTGoogleMapPolylineController - (instancetype)initPolylineWithPath:(GMSMutablePath *)path identifier:(NSString *)identifier mapView:(GMSMapView *)mapView { self = [super init]; if (self) { _polyline = [GMSPolyline polylineWithPath:path]; _mapView = mapView; _polyline.userData = @[ identifier ]; } return self; } - (void)removePolyline { self.polyline.map = nil; } - (void)setConsumeTapEvents:(BOOL)consumes { self.polyline.tappable = consumes; } - (void)setVisible:(BOOL)visible { self.polyline.map = visible ? self.mapView : nil; } - (void)setZIndex:(int)zIndex { self.polyline.zIndex = zIndex; } - (void)setPoints:(NSArray *)points { GMSMutablePath *path = [GMSMutablePath path]; for (CLLocation *location in points) { [path addCoordinate:location.coordinate]; } self.polyline.path = path; } - (void)setColor:(UIColor *)color { self.polyline.strokeColor = color; } - (void)setStrokeWidth:(CGFloat)width { self.polyline.strokeWidth = width; } - (void)setGeodesic:(BOOL)isGeodesic { self.polyline.geodesic = isGeodesic; } - (void)interpretPolylineOptions:(NSDictionary *)data registrar:(NSObject *)registrar { NSNumber *consumeTapEvents = data[@"consumeTapEvents"]; if (consumeTapEvents && consumeTapEvents != (id)[NSNull null]) { [self setConsumeTapEvents:[consumeTapEvents boolValue]]; } NSNumber *visible = data[@"visible"]; if (visible && visible != (id)[NSNull null]) { [self setVisible:[visible boolValue]]; } NSNumber *zIndex = data[@"zIndex"]; if (zIndex && zIndex != (id)[NSNull null]) { [self setZIndex:[zIndex intValue]]; } NSArray *points = data[@"points"]; if (points && points != (id)[NSNull null]) { [self setPoints:[FLTGoogleMapJSONConversions pointsFromLatLongs:points]]; } NSNumber *strokeColor = data[@"color"]; if (strokeColor && strokeColor != (id)[NSNull null]) { [self setColor:[FLTGoogleMapJSONConversions colorFromRGBA:strokeColor]]; } NSNumber *strokeWidth = data[@"width"]; if (strokeWidth && strokeWidth != (id)[NSNull null]) { [self setStrokeWidth:[strokeWidth intValue]]; } NSNumber *geodesic = data[@"geodesic"]; if (geodesic && geodesic != (id)[NSNull null]) { [self setGeodesic:geodesic.boolValue]; } } @end @interface FLTPolylinesController () @property(strong, nonatomic) NSMutableDictionary *polylineIdentifierToController; @property(strong, nonatomic) FlutterMethodChannel *methodChannel; @property(weak, nonatomic) NSObject *registrar; @property(weak, nonatomic) GMSMapView *mapView; @end ; @implementation FLTPolylinesController - (instancetype)init:(FlutterMethodChannel *)methodChannel mapView:(GMSMapView *)mapView registrar:(NSObject *)registrar { self = [super init]; if (self) { _methodChannel = methodChannel; _mapView = mapView; _polylineIdentifierToController = [NSMutableDictionary dictionaryWithCapacity:1]; _registrar = registrar; } return self; } - (void)addPolylines:(NSArray *)polylinesToAdd { for (NSDictionary *polyline in polylinesToAdd) { GMSMutablePath *path = [FLTPolylinesController getPath:polyline]; NSString *identifier = polyline[@"polylineId"]; FLTGoogleMapPolylineController *controller = [[FLTGoogleMapPolylineController alloc] initPolylineWithPath:path identifier:identifier mapView:self.mapView]; [controller interpretPolylineOptions:polyline registrar:self.registrar]; self.polylineIdentifierToController[identifier] = controller; } } - (void)changePolylines:(NSArray *)polylinesToChange { for (NSDictionary *polyline in polylinesToChange) { NSString *identifier = polyline[@"polylineId"]; FLTGoogleMapPolylineController *controller = self.polylineIdentifierToController[identifier]; if (!controller) { continue; } [controller interpretPolylineOptions:polyline registrar:self.registrar]; } } - (void)removePolylineWithIdentifiers:(NSArray *)identifiers { for (NSString *identifier in identifiers) { FLTGoogleMapPolylineController *controller = self.polylineIdentifierToController[identifier]; if (!controller) { continue; } [controller removePolyline]; [self.polylineIdentifierToController removeObjectForKey:identifier]; } } - (void)didTapPolylineWithIdentifier:(NSString *)identifier { if (!identifier) { return; } FLTGoogleMapPolylineController *controller = self.polylineIdentifierToController[identifier]; if (!controller) { return; } [self.methodChannel invokeMethod:@"polyline#onTap" arguments:@{@"polylineId" : identifier}]; } - (bool)hasPolylineWithIdentifier:(NSString *)identifier { if (!identifier) { return false; } return self.polylineIdentifierToController[identifier] != nil; } + (GMSMutablePath *)getPath:(NSDictionary *)polyline { NSArray *pointArray = polyline[@"points"]; NSArray *points = [FLTGoogleMapJSONConversions pointsFromLatLongs:pointArray]; GMSMutablePath *path = [GMSMutablePath path]; for (CLLocation *location in points) { [path addCoordinate:location.coordinate]; } return path; } @end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/google_maps_flutter_ios-umbrella.h ================================================ // Copyright 2013 The Flutter 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 #import FOUNDATION_EXPORT double google_maps_flutterVersionNumber; FOUNDATION_EXPORT const unsigned char google_maps_flutterVersionString[]; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/google_maps_flutter_ios.modulemap ================================================ framework module google_maps_flutter_ios { umbrella header "google_maps_flutter_ios-umbrella.h" export * module * { export * } explicit module Test { header "GoogleMapController_Test.h" } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| s.name = 'google_maps_flutter_ios' s.version = '0.0.1' s.summary = 'Google Maps for Flutter' s.description = <<-DESC A Flutter plugin that provides a Google Maps widget. 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/google_maps_flutter/google_maps_flutter/ios' } s.documentation_url = 'https://pub.dev/packages/google_maps_flutter_ios' s.source_files = 'Classes/**/*.{h,m}' s.public_header_files = 'Classes/**/*.h' s.module_map = 'Classes/google_maps_flutter_ios.modulemap' s.dependency 'Flutter' s.dependency 'GoogleMaps' s.static_framework = true s.platform = :ios, '9.0' # GoogleMaps does not support arm64 simulators. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' } end ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/lib/google_maps_flutter_ios.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'src/google_maps_flutter_ios.dart'; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_map_inspector_ios.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; /// An Android of implementation of [GoogleMapsInspectorPlatform]. @visibleForTesting class GoogleMapsInspectorIOS extends GoogleMapsInspectorPlatform { /// Creates a method-channel-based inspector instance that gets the channel /// for a given map ID from [channelProvider]. GoogleMapsInspectorIOS(MethodChannel? Function(int mapId) channelProvider) : _channelProvider = channelProvider; final MethodChannel? Function(int mapId) _channelProvider; @override Future areBuildingsEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isBuildingsEnabled'))!; } @override Future areRotateGesturesEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isRotateGesturesEnabled'))!; } @override Future areScrollGesturesEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isScrollGesturesEnabled'))!; } @override Future areTiltGesturesEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isTiltGesturesEnabled'))!; } @override Future areZoomControlsEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isZoomControlsEnabled'))!; } @override Future areZoomGesturesEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isZoomGesturesEnabled'))!; } @override Future getMinMaxZoomLevels({required int mapId}) async { final List zoomLevels = (await _channelProvider(mapId)! .invokeMethod>('map#getMinMaxZoomLevels'))! .cast(); return MinMaxZoomPreference(zoomLevels[0], zoomLevels[1]); } @override Future getTileOverlayInfo(TileOverlayId tileOverlayId, {required int mapId}) async { final Map? tileInfo = await _channelProvider(mapId)! .invokeMapMethod( 'map#getTileOverlayInfo', { 'tileOverlayId': tileOverlayId.value, }); if (tileInfo == null) { return null; } return TileOverlay( tileOverlayId: tileOverlayId, fadeIn: tileInfo['fadeIn']! as bool, transparency: tileInfo['transparency']! as double, visible: tileInfo['visible']! as bool, // Android and iOS return different types. zIndex: (tileInfo['zIndex']! as num).toInt(), ); } @override Future isCompassEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isCompassEnabled'))!; } @override Future isLiteModeEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isLiteModeEnabled'))!; } @override Future isMapToolbarEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isMapToolbarEnabled'))!; } @override Future isMyLocationButtonEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isMyLocationButtonEnabled'))!; } @override Future isTrafficEnabled({required int mapId}) async { return (await _channelProvider(mapId)! .invokeMethod('map#isTrafficEnabled'))!; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart ================================================ // Copyright 2013 The Flutter 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'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:stream_transform/stream_transform.dart'; import 'google_map_inspector_ios.dart'; // TODO(stuartmorgan): Remove the dependency on platform interface toJson // methods. Channel serialization details should all be package-internal. /// Error thrown when an unknown map ID is provided to a method channel API. class UnknownMapIDError extends Error { /// Creates an assertion error with the provided [mapId] and optional /// [message]. UnknownMapIDError(this.mapId, [this.message]); /// The unknown ID. final int mapId; /// Message describing the assertion error. final Object? message; @override String toString() { if (message != null) { return 'Unknown map ID $mapId: ${Error.safeToString(message)}'; } return 'Unknown map ID $mapId'; } } /// An implementation of [GoogleMapsFlutterPlatform] for iOS. class GoogleMapsFlutterIOS extends GoogleMapsFlutterPlatform { /// Registers the iOS implementation of GoogleMapsFlutterPlatform. static void registerWith() { GoogleMapsFlutterPlatform.instance = GoogleMapsFlutterIOS(); } // Keep a collection of id -> channel // Every method call passes the int mapId final Map _channels = {}; /// Accesses the MethodChannel associated to the passed mapId. MethodChannel _channel(int mapId) { final MethodChannel? channel = _channels[mapId]; if (channel == null) { throw UnknownMapIDError(mapId); } return channel; } // Keep a collection of mapId to a map of TileOverlays. final Map> _tileOverlays = >{}; /// Returns the channel for [mapId], creating it if it doesn't already exist. @visibleForTesting MethodChannel ensureChannelInitialized(int mapId) { MethodChannel? channel = _channels[mapId]; if (channel == null) { channel = MethodChannel('plugins.flutter.dev/google_maps_ios_$mapId'); channel.setMethodCallHandler( (MethodCall call) => _handleMethodCall(call, mapId)); _channels[mapId] = channel; } return channel; } @override Future init(int mapId) { final MethodChannel channel = ensureChannelInitialized(mapId); return channel.invokeMethod('map#waitForMap'); } @override void dispose({required int mapId}) { // Noop! } // The controller we need to broadcast the different events coming // from handleMethodCall. // // It is a `broadcast` because multiple controllers will connect to // different stream views of this Controller. final StreamController> _mapEventStreamController = StreamController>.broadcast(); // Returns a filtered view of the events in the _controller, by mapId. Stream> _events(int mapId) => _mapEventStreamController.stream .where((MapEvent event) => event.mapId == mapId); @override Stream onCameraMoveStarted({required int mapId}) { return _events(mapId).whereType(); } @override Stream onCameraMove({required int mapId}) { return _events(mapId).whereType(); } @override Stream onCameraIdle({required int mapId}) { return _events(mapId).whereType(); } @override Stream onMarkerTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onInfoWindowTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onMarkerDragStart({required int mapId}) { return _events(mapId).whereType(); } @override Stream onMarkerDrag({required int mapId}) { return _events(mapId).whereType(); } @override Stream onMarkerDragEnd({required int mapId}) { return _events(mapId).whereType(); } @override Stream onPolylineTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onPolygonTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onCircleTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onLongPress({required int mapId}) { return _events(mapId).whereType(); } Future _handleMethodCall(MethodCall call, int mapId) async { switch (call.method) { case 'camera#onMoveStarted': _mapEventStreamController.add(CameraMoveStartedEvent(mapId)); break; case 'camera#onMove': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(CameraMoveEvent( mapId, 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(arguments['markerId']! as String), )); break; case 'marker#onDragStart': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerDragStartEvent( mapId, LatLng.fromJson(arguments['position'])!, MarkerId(arguments['markerId']! as String), )); break; case 'marker#onDrag': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerDragEvent( mapId, LatLng.fromJson(arguments['position'])!, MarkerId(arguments['markerId']! as String), )); break; case 'marker#onDragEnd': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerDragEndEvent( mapId, LatLng.fromJson(arguments['position'])!, MarkerId(arguments['markerId']! as String), )); break; case 'infoWindow#onTap': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(InfoWindowTapEvent( mapId, MarkerId(arguments['markerId']! as String), )); break; case 'polyline#onTap': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(PolylineTapEvent( mapId, PolylineId(arguments['polylineId']! as String), )); break; case 'polygon#onTap': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(PolygonTapEvent( mapId, PolygonId(arguments['polygonId']! as String), )); break; case 'circle#onTap': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(CircleTapEvent( mapId, CircleId(arguments['circleId']! as String), )); break; case 'map#onTap': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MapTapEvent( mapId, LatLng.fromJson(arguments['position'])!, )); break; case 'map#onLongPress': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MapLongPressEvent( mapId, LatLng.fromJson(arguments['position'])!, )); break; case 'tileOverlay#getTile': final Map arguments = _getArgumentDictionary(call); final Map? tileOverlaysForThisMap = _tileOverlays[mapId]; final String tileOverlayId = arguments['tileOverlayId']! as String; final TileOverlay? tileOverlay = tileOverlaysForThisMap?[TileOverlayId(tileOverlayId)]; final TileProvider? tileProvider = tileOverlay?.tileProvider; if (tileProvider == null) { return TileProvider.noTile.toJson(); } final Tile tile = await tileProvider.getTile( arguments['x']! as int, arguments['y']! as int, arguments['zoom'] as int?, ); return tile.toJson(); 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(); } @override Future updateMapOptions( Map optionsUpdate, { required int mapId, }) { assert(optionsUpdate != null); return _channel(mapId).invokeMethod( 'map#update', { 'options': optionsUpdate, }, ); } @override Future updateMarkers( MarkerUpdates markerUpdates, { required int mapId, }) { assert(markerUpdates != null); return _channel(mapId).invokeMethod( 'markers#update', markerUpdates.toJson(), ); } @override Future updatePolygons( PolygonUpdates polygonUpdates, { required int mapId, }) { assert(polygonUpdates != null); return _channel(mapId).invokeMethod( 'polygons#update', polygonUpdates.toJson(), ); } @override Future updatePolylines( PolylineUpdates polylineUpdates, { required int mapId, }) { assert(polylineUpdates != null); return _channel(mapId).invokeMethod( 'polylines#update', polylineUpdates.toJson(), ); } @override Future updateCircles( CircleUpdates circleUpdates, { required int mapId, }) { assert(circleUpdates != null); return _channel(mapId).invokeMethod( 'circles#update', circleUpdates.toJson(), ); } @override Future updateTileOverlays({ required Set newTileOverlays, required int mapId, }) { final Map? currentTileOverlays = _tileOverlays[mapId]; final Set previousSet = currentTileOverlays != null ? currentTileOverlays.values.toSet() : {}; final _TileOverlayUpdates updates = _TileOverlayUpdates.from(previousSet, newTileOverlays); _tileOverlays[mapId] = keyTileOverlayId(newTileOverlays); return _channel(mapId).invokeMethod( 'tileOverlays#update', updates.toJson(), ); } @override Future clearTileCache( TileOverlayId tileOverlayId, { required int mapId, }) { return _channel(mapId) .invokeMethod('tileOverlays#clearTileCache', { 'tileOverlayId': tileOverlayId.value, }); } @override Future animateCamera( CameraUpdate cameraUpdate, { required int mapId, }) { return _channel(mapId) .invokeMethod('camera#animate', { 'cameraUpdate': cameraUpdate.toJson(), }); } @override Future moveCamera( CameraUpdate cameraUpdate, { required int mapId, }) { return _channel(mapId).invokeMethod('camera#move', { 'cameraUpdate': cameraUpdate.toJson(), }); } @override Future setMapStyle( String? mapStyle, { required int mapId, }) async { final List successAndError = (await _channel(mapId) .invokeMethod>('map#setStyle', mapStyle))!; final bool success = successAndError[0] as bool; if (!success) { throw MapStyleException(successAndError[1] as String); } } @override Future getVisibleRegion({ required int mapId, }) async { final Map latLngBounds = (await _channel(mapId) .invokeMapMethod('map#getVisibleRegion'))!; final LatLng southwest = LatLng.fromJson(latLngBounds['southwest'])!; final LatLng northeast = LatLng.fromJson(latLngBounds['northeast'])!; return LatLngBounds(northeast: northeast, southwest: southwest); } @override Future getScreenCoordinate( LatLng latLng, { required int mapId, }) async { final Map point = (await _channel(mapId) .invokeMapMethod( 'map#getScreenCoordinate', latLng.toJson()))!; return ScreenCoordinate(x: point['x']!, y: point['y']!); } @override Future getLatLng( ScreenCoordinate screenCoordinate, { required int mapId, }) async { final List latLng = (await _channel(mapId) .invokeMethod>( 'map#getLatLng', screenCoordinate.toJson()))!; return LatLng(latLng[0] as double, latLng[1] as double); } @override Future showMarkerInfoWindow( MarkerId markerId, { required int mapId, }) { assert(markerId != null); return _channel(mapId).invokeMethod( 'markers#showInfoWindow', {'markerId': markerId.value}); } @override Future hideMarkerInfoWindow( MarkerId markerId, { required int mapId, }) { assert(markerId != null); return _channel(mapId).invokeMethod( 'markers#hideInfoWindow', {'markerId': markerId.value}); } @override Future isMarkerInfoWindowShown( MarkerId markerId, { required int mapId, }) async { assert(markerId != null); return (await _channel(mapId).invokeMethod( 'markers#isInfoWindowShown', {'markerId': markerId.value}))!; } @override Future getZoomLevel({ required int mapId, }) async { return (await _channel(mapId).invokeMethod('map#getZoomLevel'))!; } @override Future takeSnapshot({ required int mapId, }) { return _channel(mapId).invokeMethod('map#takeSnapshot'); } Widget _buildView( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { required MapWidgetConfiguration widgetConfiguration, MapObjects mapObjects = const MapObjects(), Map mapOptions = const {}, }) { final Map creationParams = { 'initialCameraPosition': widgetConfiguration.initialCameraPosition.toMap(), 'options': mapOptions, 'markersToAdd': serializeMarkerSet(mapObjects.markers), 'polygonsToAdd': serializePolygonSet(mapObjects.polygons), 'polylinesToAdd': serializePolylineSet(mapObjects.polylines), 'circlesToAdd': serializeCircleSet(mapObjects.circles), 'tileOverlaysToAdd': serializeTileOverlaySet(mapObjects.tileOverlays), }; return UiKitView( viewType: 'plugins.flutter.dev/google_maps_ios', onPlatformViewCreated: onPlatformViewCreated, gestureRecognizers: widgetConfiguration.gestureRecognizers, creationParams: creationParams, creationParamsCodec: const StandardMessageCodec(), ); } @override Widget buildViewWithConfiguration( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { required MapWidgetConfiguration widgetConfiguration, MapConfiguration mapConfiguration = const MapConfiguration(), MapObjects mapObjects = const MapObjects(), }) { return _buildView( creationId, onPlatformViewCreated, widgetConfiguration: widgetConfiguration, mapObjects: mapObjects, mapOptions: _jsonForMapConfiguration(mapConfiguration), ); } @override Widget buildViewWithTextDirection( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { required CameraPosition initialCameraPosition, required TextDirection textDirection, Set markers = const {}, Set polygons = const {}, Set polylines = const {}, Set circles = const {}, Set tileOverlays = const {}, Set>? gestureRecognizers, Map mapOptions = const {}, }) { return _buildView( creationId, onPlatformViewCreated, widgetConfiguration: MapWidgetConfiguration( initialCameraPosition: initialCameraPosition, textDirection: textDirection), mapObjects: MapObjects( markers: markers, polygons: polygons, polylines: polylines, circles: circles, tileOverlays: tileOverlays), mapOptions: mapOptions, ); } @override Widget buildView( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { required CameraPosition initialCameraPosition, Set markers = const {}, Set polygons = const {}, Set polylines = const {}, Set circles = const {}, Set tileOverlays = const {}, Set>? gestureRecognizers, Map mapOptions = const {}, }) { return buildViewWithTextDirection( creationId, onPlatformViewCreated, initialCameraPosition: initialCameraPosition, textDirection: TextDirection.ltr, markers: markers, polygons: polygons, polylines: polylines, circles: circles, tileOverlays: tileOverlays, gestureRecognizers: gestureRecognizers, mapOptions: mapOptions, ); } @override @visibleForTesting void enableDebugInspection() { GoogleMapsInspectorPlatform.instance = GoogleMapsInspectorIOS((int mapId) => _channel(mapId)); } } Map _jsonForMapConfiguration(MapConfiguration config) { final EdgeInsets? padding = config.padding; return { if (config.compassEnabled != null) 'compassEnabled': config.compassEnabled!, if (config.mapToolbarEnabled != null) 'mapToolbarEnabled': config.mapToolbarEnabled!, if (config.cameraTargetBounds != null) 'cameraTargetBounds': config.cameraTargetBounds!.toJson(), if (config.mapType != null) 'mapType': config.mapType!.index, if (config.minMaxZoomPreference != null) 'minMaxZoomPreference': config.minMaxZoomPreference!.toJson(), if (config.rotateGesturesEnabled != null) 'rotateGesturesEnabled': config.rotateGesturesEnabled!, if (config.scrollGesturesEnabled != null) 'scrollGesturesEnabled': config.scrollGesturesEnabled!, if (config.tiltGesturesEnabled != null) 'tiltGesturesEnabled': config.tiltGesturesEnabled!, if (config.zoomControlsEnabled != null) 'zoomControlsEnabled': config.zoomControlsEnabled!, if (config.zoomGesturesEnabled != null) 'zoomGesturesEnabled': config.zoomGesturesEnabled!, if (config.liteModeEnabled != null) 'liteModeEnabled': config.liteModeEnabled!, if (config.trackCameraPosition != null) 'trackCameraPosition': config.trackCameraPosition!, if (config.myLocationEnabled != null) 'myLocationEnabled': config.myLocationEnabled!, if (config.myLocationButtonEnabled != null) 'myLocationButtonEnabled': config.myLocationButtonEnabled!, if (padding != null) 'padding': [ padding.top, padding.left, padding.bottom, padding.right, ], if (config.indoorViewEnabled != null) 'indoorEnabled': config.indoorViewEnabled!, if (config.trafficEnabled != null) 'trafficEnabled': config.trafficEnabled!, if (config.buildingsEnabled != null) 'buildingsEnabled': config.buildingsEnabled!, }; } /// Update specification for a set of [TileOverlay]s. // TODO(stuartmorgan): Fix the missing export of this class in the platform // interface, and remove this copy. class _TileOverlayUpdates extends MapsObjectUpdates { /// Computes [TileOverlayUpdates] given previous and current [TileOverlay]s. _TileOverlayUpdates.from(Set previous, Set current) : super.from(previous, current, objectName: 'tileOverlay'); /// Set of TileOverlays to be added in this update. Set get tileOverlaysToAdd => objectsToAdd; /// Set of TileOverlayIds to be removed in this update. Set get tileOverlayIdsToRemove => objectIdsToRemove.cast(); /// Set of TileOverlays to be changed in this update. Set get tileOverlaysToChange => objectsToChange; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml ================================================ 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.13 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: google_maps_flutter platforms: ios: pluginClass: FLTGoogleMapsPlugin dartPluginClass: GoogleMapsFlutterIOS dependencies: flutter: sdk: flutter google_maps_flutter_platform_interface: ^2.2.1 stream_transform: ^2.0.0 dev_dependencies: async: ^2.5.0 flutter_test: sdk: flutter plugin_platform_interface: ^2.0.0 ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart ================================================ // Copyright 2013 The Flutter 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:async/async.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_ios/google_maps_flutter_ios.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); late List log; setUp(() async { log = []; }); /// Initializes a map with the given ID and canned responses, logging all /// calls to [log]. void configureMockMap( GoogleMapsFlutterIOS maps, { required int mapId, required Future? Function(MethodCall call) handler, }) { final MethodChannel channel = maps.ensureChannelInitialized(mapId); _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( channel, (MethodCall methodCall) { log.add(methodCall.method); return handler(methodCall); }, ); } Future sendPlatformMessage( int mapId, String method, Map data) async { final ByteData byteData = const StandardMethodCodec().encodeMethodCall(MethodCall(method, data)); await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .handlePlatformMessage('plugins.flutter.dev/google_maps_ios_$mapId', byteData, (ByteData? data) {}); } test('registers instance', () async { GoogleMapsFlutterIOS.registerWith(); expect(GoogleMapsFlutterPlatform.instance, isA()); }); // Calls each method that uses invokeMethod with a return type other than // void to ensure that the casting/nullability handling succeeds. // // TODO(stuartmorgan): Remove this once there is real test coverage of // each method, since that would cover this issue. test('non-void invokeMethods handle types correctly', () async { const int mapId = 0; final GoogleMapsFlutterIOS maps = GoogleMapsFlutterIOS(); configureMockMap(maps, mapId: mapId, handler: (MethodCall methodCall) async { switch (methodCall.method) { case 'map#getLatLng': return [1.0, 2.0]; case 'markers#isInfoWindowShown': return true; case 'map#getZoomLevel': return 2.5; case 'map#takeSnapshot': return null; } }); await maps.getLatLng(const ScreenCoordinate(x: 0, y: 0), mapId: mapId); await maps.isMarkerInfoWindowShown(const MarkerId(''), mapId: mapId); await maps.getZoomLevel(mapId: mapId); await maps.takeSnapshot(mapId: mapId); // Check that all the invokeMethod calls happened. expect(log, [ 'map#getLatLng', 'markers#isInfoWindowShown', 'map#getZoomLevel', 'map#takeSnapshot', ]); }); test('markers send drag event to correct streams', () async { const int mapId = 1; final Map jsonMarkerDragStartEvent = { 'mapId': mapId, 'markerId': 'drag-start-marker', 'position': [1.0, 1.0] }; final Map jsonMarkerDragEvent = { 'mapId': mapId, 'markerId': 'drag-marker', 'position': [1.0, 1.0] }; final Map jsonMarkerDragEndEvent = { 'mapId': mapId, 'markerId': 'drag-end-marker', 'position': [1.0, 1.0] }; final GoogleMapsFlutterIOS maps = GoogleMapsFlutterIOS(); maps.ensureChannelInitialized(mapId); final StreamQueue markerDragStartStream = StreamQueue(maps.onMarkerDragStart(mapId: mapId)); final StreamQueue markerDragStream = StreamQueue(maps.onMarkerDrag(mapId: mapId)); final StreamQueue markerDragEndStream = StreamQueue(maps.onMarkerDragEnd(mapId: mapId)); await sendPlatformMessage( mapId, 'marker#onDragStart', jsonMarkerDragStartEvent); await sendPlatformMessage(mapId, 'marker#onDrag', jsonMarkerDragEvent); await sendPlatformMessage( mapId, 'marker#onDragEnd', jsonMarkerDragEndEvent); expect((await markerDragStartStream.next).value.value, equals('drag-start-marker')); expect((await markerDragStream.next).value.value, equals('drag-marker')); expect((await markerDragEndStream.next).value.value, 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; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/AUTHORS ================================================ # 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> ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md ================================================ ## 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. ## 2.2.3 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. ## 2.2.2 * Adds a `size` parameter to `BitmapDescriptor.fromBytes`, so **web** applications can specify the actual *physical size* of the bitmap. The parameter is not needed (and ignored) in other platforms. Issue [#73789](https://github.com/flutter/flutter/issues/73789). * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 2.2.1 * Adds a new interface for inspecting the platform map state in tests. ## 2.2.0 * Adds new versions of `buildView` and `updateOptions` that take a new option class instead of a dictionary, to remove the cross-package dependency on magic string keys. * Adopts several parameter objects in the new `buildView` variant to future-proof it against future changes. * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/104231). ## 2.1.7 * Updates code for stricter analysis options. * Removes unnecessary imports. ## 2.1.6 * Migrates from `ui.hash*` to `Object.hash*`. * Updates minimum Flutter version to 2.5.0. ## 2.1.5 Removes dependency on `meta`. ## 2.1.4 * Update to use the `verify` method introduced in plugin_platform_interface 2.1.0. ## 2.1.3 * `LatLng` constructor maintains longitude precision when given within acceptable range ## 2.1.2 * Add additional marker drag events ## 2.1.1 * Method `buildViewWithTextDirection` has been added to the platform interface. ## 2.1.0 * Add support for Hybrid Composition when building the Google Maps widget on Android. Set `MethodChannelGoogleMapsFlutter.useAndroidViewSurface` to `true` to build with Hybrid Composition. ## 2.0.4 * Preserve the `TileProvider` when copying `TileOverlay`, fixing a regression with tile overlays introduced in the null safety migration. ## 2.0.3 * Fix type issues in `isMarkerInfoWindowShown` and `getZoomLevel` introduced in the null safety migration. ## 2.0.2 * Mark constructors for CameraUpdate, CircleId, MapsObjectId, MarkerId, PolygonId, PolylineId and TileOverlayId as const ## 2.0.1 * Update platform_plugin_interface version requirement. ## 2.0.0 * Migrated to null-safety. * BREAKING CHANGE: Removed deprecated APIs. * BREAKING CHANGE: Many sets in APIs that used to treat null and empty set as equivalent now require passing an empty set. * BREAKING CHANGE: toJson now always returns an `Object`; the details of the object type and structure should be treated as an implementation detail. ## 1.2.0 * Add TileOverlay support. ## 1.1.0 * Add support for holes in Polygons. ## 1.0.6 * Update Flutter SDK constraint. ## 1.0.5 * Temporarily add a `fromJson` constructor to `BitmapDescriptor` so serialized descriptors can be synchronously re-hydrated. This will be removed when a fix for [this issue](https://github.com/flutter/flutter/issues/70330) lands. ## 1.0.4 * Add a `dispose` method to the interface, so implementations may cleanup resources acquired on `init`. ## 1.0.3 * Pass icon width/height if present on `fromAssetImage` BitmapDescriptors (web only) ## 1.0.2 * Update lower bound of dart dependency to 2.1.0. ## 1.0.1 * Initial open source release. ## 1.0.0 ... 1.0.0+5 * Development. ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/LICENSE ================================================ 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. ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/README.md ================================================ # google_maps_flutter_platform_interface A common platform interface for the [`google_maps_flutter`][1] plugin. This interface allows platform-specific implementations of the `google_maps_flutter` plugin, as well as the plugin itself, to ensure they are supporting the same interface. # Usage To implement a new platform-specific implementation of `google_maps_flutter`, extend [`GoogleMapsFlutterPlatform`][2] with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `GoogleMapsFlutterPlatform` by calling `GoogleMapsFlutterPlatform.instance = MyPlatformGoogleMapsFlutter()`. # Note on breaking changes Strongly prefer non-breaking changes (such as adding a method to the interface) over breaking changes for this package. See https://flutter.dev/go/platform-interface-breaking-changes for a discussion on why a less-clean interface is preferable to a breaking change. [1]: ../google_maps_flutter [2]: lib/google_maps_flutter_platform_interface.dart ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/google_maps_flutter_platform_interface.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'src/events/map_event.dart'; export 'src/method_channel/method_channel_google_maps_flutter.dart' show MethodChannelGoogleMapsFlutter; export 'src/platform_interface/google_maps_flutter_platform.dart'; export 'src/platform_interface/google_maps_inspector_platform.dart'; export 'src/types/types.dart'; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import '../../google_maps_flutter_platform_interface.dart'; /// Generic Event coming from the native side of Maps. /// /// All MapEvents contain the `mapId` that originated the event. This should /// never be `null`. /// /// The `` on this event represents the type of the `value` that is /// contained within the event. /// /// This class is used as a base class for all the events that might be /// triggered from a Map, but it is never used directly as an event type. /// /// Do NOT instantiate new events like `MapEvent(mapId, val)` directly, /// use a specific class instead: /// /// Do `class NewEvent extend MapEvent` when creating your own events. /// See below for examples: `CameraMoveStartedEvent`, `MarkerDragEndEvent`... /// These events are more semantic and pleasant to use than raw generics. They /// can be (and in fact, are) filtered by the `instanceof`-operator. /// /// (See [MethodChannelGoogleMapsFlutter.onCameraMoveStarted], for example) /// /// If your event needs a `position`, alongside the `value`, do /// `extends _PositionedMapEvent` instead. This adds a `LatLng position` /// attribute. /// /// If your event *only* needs a `position`, do `extend _PositionedMapEvent` /// do NOT `extend MapEvent`. The former lets consumers of these /// events to access the `.position` property, rather than the more generic `.value` /// yielded from the latter. class MapEvent { /// Build a Map Event, that relates a mapId with a given value. /// /// The `mapId` is the id of the map that triggered the event. /// `value` may be `null` in events that don't transport any meaningful data. MapEvent(this.mapId, this.value); /// The ID of the Map this event is associated to. final int mapId; /// The value wrapped by this event final T value; } /// A `MapEvent` associated to a `position`. class _PositionedMapEvent extends MapEvent { /// Build a Positioned MapEvent, that relates a mapId and a position with a value. /// /// The `mapId` is the id of the map that triggered the event. /// `value` may be `null` in events that don't transport any meaningful data. _PositionedMapEvent(int mapId, this.position, T value) : super(mapId, value); /// The position where this event happened. final LatLng position; } // The following events are the ones exposed to the end user. They are semantic extensions // of the two base classes above. // // These events are used to create the appropriate [Stream] objects, with information // coming from the native side. /// An event fired when the Camera of a [mapId] starts moving. class CameraMoveStartedEvent extends MapEvent { /// Build a CameraMoveStarted Event triggered from the map represented by `mapId`. CameraMoveStartedEvent(int mapId) : super(mapId, null); } /// An event fired while the Camera of a [mapId] moves. class CameraMoveEvent extends MapEvent { /// Build a CameraMove Event triggered from the map represented by `mapId`. /// /// The `value` of this event is a [CameraPosition] object with the current position of the Camera. CameraMoveEvent(int mapId, CameraPosition position) : super(mapId, position); } /// An event fired when the Camera of a [mapId] becomes idle. class CameraIdleEvent extends MapEvent { /// Build a CameraIdle Event triggered from the map represented by `mapId`. CameraIdleEvent(int mapId) : super(mapId, null); } /// An event fired when a [Marker] is tapped. class MarkerTapEvent extends MapEvent { /// Build a MarkerTap Event triggered from the map represented by `mapId`. /// /// The `value` of this event is a [MarkerId] object that represents the tapped Marker. MarkerTapEvent(int mapId, MarkerId markerId) : super(mapId, markerId); } /// An event fired when an [InfoWindow] is tapped. class InfoWindowTapEvent extends MapEvent { /// Build an InfoWindowTap Event triggered from the map represented by `mapId`. /// /// The `value` of this event is a [MarkerId] object that represents the tapped InfoWindow. InfoWindowTapEvent(int mapId, MarkerId markerId) : super(mapId, markerId); } /// An event fired when a [Marker] is starting to be dragged to a new [LatLng]. class MarkerDragStartEvent extends _PositionedMapEvent { /// Build a MarkerDragStart Event triggered from the map represented by `mapId`. /// /// The `position` on this event is the [LatLng] on which the Marker was picked up from. /// The `value` of this event is a [MarkerId] object that represents the Marker. MarkerDragStartEvent(int mapId, LatLng position, MarkerId markerId) : super(mapId, position, markerId); } /// An event fired when a [Marker] is being dragged to a new [LatLng]. class MarkerDragEvent extends _PositionedMapEvent { /// Build a MarkerDrag Event triggered from the map represented by `mapId`. /// /// The `position` on this event is the [LatLng] on which the Marker was dragged to. /// The `value` of this event is a [MarkerId] object that represents the Marker. MarkerDragEvent(int mapId, LatLng position, MarkerId markerId) : super(mapId, position, markerId); } /// An event fired when a [Marker] is dragged to a new [LatLng]. class MarkerDragEndEvent extends _PositionedMapEvent { /// Build a MarkerDragEnd Event triggered from the map represented by `mapId`. /// /// The `position` on this event is the [LatLng] on which the Marker was dropped. /// The `value` of this event is a [MarkerId] object that represents the moved Marker. MarkerDragEndEvent(int mapId, LatLng position, MarkerId markerId) : super(mapId, position, markerId); } /// An event fired when a [Polyline] is tapped. class PolylineTapEvent extends MapEvent { /// Build an PolylineTap Event triggered from the map represented by `mapId`. /// /// The `value` of this event is a [PolylineId] object that represents the tapped Polyline. PolylineTapEvent(int mapId, PolylineId polylineId) : super(mapId, polylineId); } /// An event fired when a [Polygon] is tapped. class PolygonTapEvent extends MapEvent { /// Build an PolygonTap Event triggered from the map represented by `mapId`. /// /// The `value` of this event is a [PolygonId] object that represents the tapped Polygon. PolygonTapEvent(int mapId, PolygonId polygonId) : super(mapId, polygonId); } /// An event fired when a [Circle] is tapped. class CircleTapEvent extends MapEvent { /// Build an CircleTap Event triggered from the map represented by `mapId`. /// /// The `value` of this event is a [CircleId] object that represents the tapped Circle. CircleTapEvent(int mapId, CircleId circleId) : super(mapId, circleId); } /// An event fired when a Map is tapped. class MapTapEvent extends _PositionedMapEvent { /// Build an MapTap Event triggered from the map represented by `mapId`. /// /// The `position` of this event is the LatLng where the Map was tapped. MapTapEvent(int mapId, LatLng position) : super(mapId, position, null); } /// An event fired when a Map is long pressed. class MapLongPressEvent extends _PositionedMapEvent { /// Build an MapTap Event triggered from the map represented by `mapId`. /// /// The `position` of this event is the LatLng where the Map was long pressed. MapLongPressEvent(int mapId, LatLng position) : super(mapId, position, null); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart ================================================ // Copyright 2013 The Flutter 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'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:stream_transform/stream_transform.dart'; import '../../google_maps_flutter_platform_interface.dart'; import '../types/tile_overlay_updates.dart'; import '../types/utils/map_configuration_serialization.dart'; /// Error thrown when an unknown map ID is provided to a method channel API. class UnknownMapIDError extends Error { /// Creates an assertion error with the provided [mapId] and optional /// [message]. UnknownMapIDError(this.mapId, [this.message]); /// The unknown ID. final int mapId; /// Message describing the assertion error. final Object? message; @override String toString() { if (message != null) { return 'Unknown map ID $mapId: ${Error.safeToString(message)}'; } return 'Unknown map ID $mapId'; } } /// An implementation of [GoogleMapsFlutterPlatform] that uses [MethodChannel] to communicate with the native code. /// /// The `google_maps_flutter` plugin code itself never talks to the native code directly. It delegates /// all those calls to an instance of a class that extends the GoogleMapsFlutterPlatform. /// /// The architecture above allows for platforms that communicate differently with the native side /// (like web) to have a common interface to extend. /// /// This is the instance that runs when the native side talks to your Flutter app through MethodChannels, /// like the Android and iOS platforms. class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { // Keep a collection of id -> channel // Every method call passes the int mapId final Map _channels = {}; /// Accesses the MethodChannel associated to the passed mapId. MethodChannel channel(int mapId) { final MethodChannel? channel = _channels[mapId]; if (channel == null) { throw UnknownMapIDError(mapId); } return channel; } // Keep a collection of mapId to a map of TileOverlays. final Map> _tileOverlays = >{}; /// Returns the channel for [mapId], creating it if it doesn't already exist. @visibleForTesting MethodChannel ensureChannelInitialized(int mapId) { MethodChannel? channel = _channels[mapId]; if (channel == null) { channel = MethodChannel('plugins.flutter.io/google_maps_$mapId'); channel.setMethodCallHandler( (MethodCall call) => _handleMethodCall(call, mapId)); _channels[mapId] = channel; } return channel; } @override Future init(int mapId) { final MethodChannel channel = ensureChannelInitialized(mapId); return channel.invokeMethod('map#waitForMap'); } @override void dispose({required int mapId}) { // Noop! } // The controller we need to broadcast the different events coming // from handleMethodCall. // // It is a `broadcast` because multiple controllers will connect to // different stream views of this Controller. final StreamController> _mapEventStreamController = StreamController>.broadcast(); // Returns a filtered view of the events in the _controller, by mapId. Stream> _events(int mapId) => _mapEventStreamController.stream .where((MapEvent event) => event.mapId == mapId); @override Stream onCameraMoveStarted({required int mapId}) { return _events(mapId).whereType(); } @override Stream onCameraMove({required int mapId}) { return _events(mapId).whereType(); } @override Stream onCameraIdle({required int mapId}) { return _events(mapId).whereType(); } @override Stream onMarkerTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onInfoWindowTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onMarkerDragStart({required int mapId}) { return _events(mapId).whereType(); } @override Stream onMarkerDrag({required int mapId}) { return _events(mapId).whereType(); } @override Stream onMarkerDragEnd({required int mapId}) { return _events(mapId).whereType(); } @override Stream onPolylineTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onPolygonTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onCircleTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onLongPress({required int mapId}) { return _events(mapId).whereType(); } Future _handleMethodCall(MethodCall call, int mapId) async { switch (call.method) { case 'camera#onMoveStarted': _mapEventStreamController.add(CameraMoveStartedEvent(mapId)); break; case 'camera#onMove': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(CameraMoveEvent( mapId, 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(arguments['markerId']! as String), )); break; case 'marker#onDragStart': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerDragStartEvent( mapId, LatLng.fromJson(arguments['position'])!, MarkerId(arguments['markerId']! as String), )); break; case 'marker#onDrag': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerDragEvent( mapId, LatLng.fromJson(arguments['position'])!, MarkerId(arguments['markerId']! as String), )); break; case 'marker#onDragEnd': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MarkerDragEndEvent( mapId, LatLng.fromJson(arguments['position'])!, MarkerId(arguments['markerId']! as String), )); break; case 'infoWindow#onTap': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(InfoWindowTapEvent( mapId, MarkerId(arguments['markerId']! as String), )); break; case 'polyline#onTap': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(PolylineTapEvent( mapId, PolylineId(arguments['polylineId']! as String), )); break; case 'polygon#onTap': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(PolygonTapEvent( mapId, PolygonId(arguments['polygonId']! as String), )); break; case 'circle#onTap': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(CircleTapEvent( mapId, CircleId(arguments['circleId']! as String), )); break; case 'map#onTap': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MapTapEvent( mapId, LatLng.fromJson(arguments['position'])!, )); break; case 'map#onLongPress': final Map arguments = _getArgumentDictionary(call); _mapEventStreamController.add(MapLongPressEvent( mapId, LatLng.fromJson(arguments['position'])!, )); break; case 'tileOverlay#getTile': final Map arguments = _getArgumentDictionary(call); final Map? tileOverlaysForThisMap = _tileOverlays[mapId]; final String tileOverlayId = arguments['tileOverlayId']! as String; final TileOverlay? tileOverlay = tileOverlaysForThisMap?[TileOverlayId(tileOverlayId)]; final TileProvider? tileProvider = tileOverlay?.tileProvider; if (tileProvider == null) { return TileProvider.noTile.toJson(); } final Tile tile = await tileProvider.getTile( arguments['x']! as int, arguments['y']! as int, arguments['zoom'] as int?, ); return tile.toJson(); 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(); } @override Future updateMapOptions( Map optionsUpdate, { required int mapId, }) { assert(optionsUpdate != null); return channel(mapId).invokeMethod( 'map#update', { 'options': optionsUpdate, }, ); } @override Future updateMarkers( MarkerUpdates markerUpdates, { required int mapId, }) { assert(markerUpdates != null); return channel(mapId).invokeMethod( 'markers#update', markerUpdates.toJson(), ); } @override Future updatePolygons( PolygonUpdates polygonUpdates, { required int mapId, }) { assert(polygonUpdates != null); return channel(mapId).invokeMethod( 'polygons#update', polygonUpdates.toJson(), ); } @override Future updatePolylines( PolylineUpdates polylineUpdates, { required int mapId, }) { assert(polylineUpdates != null); return channel(mapId).invokeMethod( 'polylines#update', polylineUpdates.toJson(), ); } @override Future updateCircles( CircleUpdates circleUpdates, { required int mapId, }) { assert(circleUpdates != null); return channel(mapId).invokeMethod( 'circles#update', circleUpdates.toJson(), ); } @override Future updateTileOverlays({ required Set newTileOverlays, required int mapId, }) { final Map? currentTileOverlays = _tileOverlays[mapId]; final Set previousSet = currentTileOverlays != null ? currentTileOverlays.values.toSet() : {}; final TileOverlayUpdates updates = TileOverlayUpdates.from(previousSet, newTileOverlays); _tileOverlays[mapId] = keyTileOverlayId(newTileOverlays); return channel(mapId).invokeMethod( 'tileOverlays#update', updates.toJson(), ); } @override Future clearTileCache( TileOverlayId tileOverlayId, { required int mapId, }) { return channel(mapId) .invokeMethod('tileOverlays#clearTileCache', { 'tileOverlayId': tileOverlayId.value, }); } @override Future animateCamera( CameraUpdate cameraUpdate, { required int mapId, }) { return channel(mapId).invokeMethod('camera#animate', { 'cameraUpdate': cameraUpdate.toJson(), }); } @override Future moveCamera( CameraUpdate cameraUpdate, { required int mapId, }) { return channel(mapId).invokeMethod('camera#move', { 'cameraUpdate': cameraUpdate.toJson(), }); } @override Future setMapStyle( String? mapStyle, { required int mapId, }) async { final List successAndError = (await channel(mapId) .invokeMethod>('map#setStyle', mapStyle))!; final bool success = successAndError[0] as bool; if (!success) { throw MapStyleException(successAndError[1] as String); } } @override Future getVisibleRegion({ required int mapId, }) async { final Map latLngBounds = (await channel(mapId) .invokeMapMethod('map#getVisibleRegion'))!; final LatLng southwest = LatLng.fromJson(latLngBounds['southwest'])!; final LatLng northeast = LatLng.fromJson(latLngBounds['northeast'])!; return LatLngBounds(northeast: northeast, southwest: southwest); } @override Future getScreenCoordinate( LatLng latLng, { required int mapId, }) async { final Map point = (await channel(mapId) .invokeMapMethod( 'map#getScreenCoordinate', latLng.toJson()))!; return ScreenCoordinate(x: point['x']!, y: point['y']!); } @override Future getLatLng( ScreenCoordinate screenCoordinate, { required int mapId, }) async { final List latLng = (await channel(mapId) .invokeMethod>( 'map#getLatLng', screenCoordinate.toJson()))!; return LatLng(latLng[0] as double, latLng[1] as double); } @override Future showMarkerInfoWindow( MarkerId markerId, { required int mapId, }) { assert(markerId != null); return channel(mapId).invokeMethod( 'markers#showInfoWindow', {'markerId': markerId.value}); } @override Future hideMarkerInfoWindow( MarkerId markerId, { required int mapId, }) { assert(markerId != null); return channel(mapId).invokeMethod( 'markers#hideInfoWindow', {'markerId': markerId.value}); } @override Future isMarkerInfoWindowShown( MarkerId markerId, { required int mapId, }) async { assert(markerId != null); return (await channel(mapId).invokeMethod('markers#isInfoWindowShown', {'markerId': markerId.value}))!; } @override Future getZoomLevel({ required int mapId, }) async { return (await channel(mapId).invokeMethod('map#getZoomLevel'))!; } @override Future takeSnapshot({ required int mapId, }) { return channel(mapId).invokeMethod('map#takeSnapshot'); } /// Set [GoogleMapsFlutterPlatform] to use [AndroidViewSurface] to build the Google Maps widget. /// /// This implementation uses hybrid composition to render the Google Maps /// Widget on Android. This comes at the cost of some performance on Android /// versions below 10. See /// https://flutter.dev/docs/development/platform-integration/platform-views#performance for more /// information. /// /// If set to true, the google map widget should be built with /// [buildViewWithTextDirection] instead of [buildView]. /// /// Defaults to false. bool useAndroidViewSurface = false; Widget _buildView( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { required MapWidgetConfiguration widgetConfiguration, MapObjects mapObjects = const MapObjects(), Map mapOptions = const {}, }) { final Map creationParams = { 'initialCameraPosition': widgetConfiguration.initialCameraPosition.toMap(), 'options': mapOptions, 'markersToAdd': serializeMarkerSet(mapObjects.markers), 'polygonsToAdd': serializePolygonSet(mapObjects.polygons), 'polylinesToAdd': serializePolylineSet(mapObjects.polylines), 'circlesToAdd': serializeCircleSet(mapObjects.circles), 'tileOverlaysToAdd': serializeTileOverlaySet(mapObjects.tileOverlays), }; if (defaultTargetPlatform == TargetPlatform.android) { if (useAndroidViewSurface) { return PlatformViewLink( viewType: 'plugins.flutter.io/google_maps', surfaceFactory: ( BuildContext context, PlatformViewController controller, ) { return AndroidViewSurface( controller: controller as AndroidViewController, gestureRecognizers: widgetConfiguration.gestureRecognizers, hitTestBehavior: PlatformViewHitTestBehavior.opaque, ); }, onCreatePlatformView: (PlatformViewCreationParams params) { final SurfaceAndroidViewController controller = PlatformViewsService.initSurfaceAndroidView( id: params.id, viewType: 'plugins.flutter.io/google_maps', layoutDirection: widgetConfiguration.textDirection, creationParams: creationParams, creationParamsCodec: const StandardMessageCodec(), onFocus: () => params.onFocusChanged(true), ); controller.addOnPlatformViewCreatedListener( params.onPlatformViewCreated, ); controller.addOnPlatformViewCreatedListener( onPlatformViewCreated, ); controller.create(); return controller; }, ); } else { return AndroidView( viewType: 'plugins.flutter.io/google_maps', onPlatformViewCreated: onPlatformViewCreated, gestureRecognizers: widgetConfiguration.gestureRecognizers, creationParams: creationParams, creationParamsCodec: const StandardMessageCodec(), ); } } else if (defaultTargetPlatform == TargetPlatform.iOS) { return UiKitView( viewType: 'plugins.flutter.io/google_maps', onPlatformViewCreated: onPlatformViewCreated, gestureRecognizers: widgetConfiguration.gestureRecognizers, creationParams: creationParams, creationParamsCodec: const StandardMessageCodec(), ); } return Text( '$defaultTargetPlatform is not yet supported by the maps plugin'); } @override Widget buildViewWithConfiguration( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { required MapWidgetConfiguration widgetConfiguration, MapConfiguration mapConfiguration = const MapConfiguration(), MapObjects mapObjects = const MapObjects(), }) { return _buildView( creationId, onPlatformViewCreated, widgetConfiguration: widgetConfiguration, mapObjects: mapObjects, mapOptions: jsonForMapConfiguration(mapConfiguration), ); } @override Widget buildViewWithTextDirection( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { required CameraPosition initialCameraPosition, required TextDirection textDirection, Set markers = const {}, Set polygons = const {}, Set polylines = const {}, Set circles = const {}, Set tileOverlays = const {}, Set>? gestureRecognizers, Map mapOptions = const {}, }) { return _buildView( creationId, onPlatformViewCreated, widgetConfiguration: MapWidgetConfiguration( initialCameraPosition: initialCameraPosition, textDirection: textDirection), mapObjects: MapObjects( markers: markers, polygons: polygons, polylines: polylines, circles: circles, tileOverlays: tileOverlays), mapOptions: mapOptions, ); } @override Widget buildView( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { required CameraPosition initialCameraPosition, Set markers = const {}, Set polygons = const {}, Set polylines = const {}, Set circles = const {}, Set tileOverlays = const {}, Set>? gestureRecognizers, Map mapOptions = const {}, }) { return buildViewWithTextDirection( creationId, onPlatformViewCreated, initialCameraPosition: initialCameraPosition, textDirection: TextDirection.ltr, markers: markers, polygons: polygons, polylines: polylines, circles: circles, tileOverlays: tileOverlays, gestureRecognizers: gestureRecognizers, mapOptions: mapOptions, ); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart ================================================ // Copyright 2013 The Flutter 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'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import '../../google_maps_flutter_platform_interface.dart'; import '../types/utils/map_configuration_serialization.dart'; /// The interface that platform-specific implementations of `google_maps_flutter` must extend. /// /// Avoid `implements` of this interface. Using `implements` makes adding any new /// methods here a breaking change for end users of your platform! /// /// Do `extends GoogleMapsFlutterPlatform` instead, so new methods added here are /// inherited in your code with the default implementation (that throws at runtime), /// rather than breaking your users at compile time. abstract class GoogleMapsFlutterPlatform extends PlatformInterface { /// Constructs a GoogleMapsFlutterPlatform. GoogleMapsFlutterPlatform() : super(token: _token); static final Object _token = Object(); static GoogleMapsFlutterPlatform _instance = MethodChannelGoogleMapsFlutter(); /// The default instance of [GoogleMapsFlutterPlatform] to use. /// /// Defaults to [MethodChannelGoogleMapsFlutter]. static GoogleMapsFlutterPlatform get instance => _instance; /// Platform-specific plugins should set this with their own platform-specific /// class that extends [GoogleMapsFlutterPlatform] when they register themselves. static set instance(GoogleMapsFlutterPlatform instance) { PlatformInterface.verify(instance, _token); _instance = instance; } /// /// Initializes the platform interface with [id]. /// /// This method is called when the plugin is first initialized. Future init(int mapId) { throw UnimplementedError('init() has not been implemented.'); } /// Updates configuration options of the map user interface - deprecated, use /// updateMapConfiguration instead. /// /// Change listeners are notified once the update has been made on the /// platform side. /// /// The returned [Future] completes after listeners have been notified. Future updateMapOptions( Map optionsUpdate, { required int mapId, }) { throw UnimplementedError('updateMapOptions() has not been implemented.'); } /// Updates configuration options of the map user interface. /// /// Change listeners are notified once the update has been made on the /// platform side. /// /// The returned [Future] completes after listeners have been notified. Future updateMapConfiguration( MapConfiguration configuration, { required int mapId, }) { return updateMapOptions(jsonForMapConfiguration(configuration), mapId: mapId); } /// Updates marker configuration. /// /// Change listeners are notified once the update has been made on the /// platform side. /// /// The returned [Future] completes after listeners have been notified. Future updateMarkers( MarkerUpdates markerUpdates, { required int mapId, }) { throw UnimplementedError('updateMarkers() has not been implemented.'); } /// Updates polygon configuration. /// /// Change listeners are notified once the update has been made on the /// platform side. /// /// The returned [Future] completes after listeners have been notified. Future updatePolygons( PolygonUpdates polygonUpdates, { required int mapId, }) { throw UnimplementedError('updatePolygons() has not been implemented.'); } /// Updates polyline configuration. /// /// Change listeners are notified once the update has been made on the /// platform side. /// /// The returned [Future] completes after listeners have been notified. Future updatePolylines( PolylineUpdates polylineUpdates, { required int mapId, }) { throw UnimplementedError('updatePolylines() has not been implemented.'); } /// Updates circle configuration. /// /// Change listeners are notified once the update has been made on the /// platform side. /// /// The returned [Future] completes after listeners have been notified. Future updateCircles( CircleUpdates circleUpdates, { required int mapId, }) { throw UnimplementedError('updateCircles() has not been implemented.'); } /// Updates tile overlay configuration. /// /// Change listeners are notified once the update has been made on the /// platform side. /// /// The returned [Future] completes after listeners have been notified. Future updateTileOverlays({ required Set newTileOverlays, required int mapId, }) { throw UnimplementedError('updateTileOverlays() has not been implemented.'); } /// Clears the tile cache so that all tiles will be requested again from the /// [TileProvider]. /// /// The current tiles from this tile overlay will also be /// cleared from the map after calling this method. The Google Maps SDK maintains a small /// in-memory cache of tiles. If you want to cache tiles for longer, you /// should implement an on-disk cache. Future clearTileCache( TileOverlayId tileOverlayId, { required int mapId, }) { throw UnimplementedError('clearTileCache() has not been implemented.'); } /// Starts an animated change of the map camera position. /// /// The returned [Future] completes after the change has been started on the /// platform side. Future animateCamera( CameraUpdate cameraUpdate, { required int mapId, }) { throw UnimplementedError('animateCamera() has not been implemented.'); } /// Changes the map camera position. /// /// The returned [Future] completes after the change has been made on the /// platform side. Future moveCamera( CameraUpdate cameraUpdate, { required int mapId, }) { throw UnimplementedError('moveCamera() has not been implemented.'); } /// Sets the styling of the base map. /// /// Set to `null` to clear any previous custom styling. /// /// If problems were detected with the [mapStyle], including un-parsable /// styling JSON, unrecognized feature type, unrecognized element type, or /// invalid styler keys: [MapStyleException] is thrown and the current /// style is left unchanged. /// /// The style string can be generated using [map style tool](https://mapstyle.withgoogle.com/). Future setMapStyle( String? mapStyle, { required int mapId, }) { throw UnimplementedError('setMapStyle() has not been implemented.'); } /// Return the region that is visible in a map. Future getVisibleRegion({ required int mapId, }) { throw UnimplementedError('getVisibleRegion() has not been implemented.'); } /// Return [ScreenCoordinate] of the [LatLng] in the current map view. /// /// A projection is used to translate between on screen location and geographic coordinates. /// Screen location is in screen pixels (not display pixels) with respect to the top left corner /// of the map, not necessarily of the whole screen. Future getScreenCoordinate( LatLng latLng, { required int mapId, }) { throw UnimplementedError('getScreenCoordinate() has not been implemented.'); } /// Returns [LatLng] corresponding to the [ScreenCoordinate] in the current map view. /// /// A projection is used to translate between on screen location and geographic coordinates. /// Screen location is in screen pixels (not display pixels) with respect to the top left corner /// of the map, not necessarily of the whole screen. Future getLatLng( ScreenCoordinate screenCoordinate, { required int mapId, }) { throw UnimplementedError('getLatLng() has not been implemented.'); } /// Programmatically show the Info Window for a [Marker]. /// /// The `markerId` must match one of the markers on the map. /// An invalid `markerId` triggers an "Invalid markerId" error. /// /// * See also: /// * [hideMarkerInfoWindow] to hide the Info Window. /// * [isMarkerInfoWindowShown] to check if the Info Window is showing. Future showMarkerInfoWindow( MarkerId markerId, { required int mapId, }) { throw UnimplementedError( 'showMarkerInfoWindow() has not been implemented.'); } /// Programmatically hide the Info Window for a [Marker]. /// /// The `markerId` must match one of the markers on the map. /// An invalid `markerId` triggers an "Invalid markerId" error. /// /// * See also: /// * [showMarkerInfoWindow] to show the Info Window. /// * [isMarkerInfoWindowShown] to check if the Info Window is showing. Future hideMarkerInfoWindow( MarkerId markerId, { required int mapId, }) { throw UnimplementedError( 'hideMarkerInfoWindow() has not been implemented.'); } /// Returns `true` when the [InfoWindow] is showing, `false` otherwise. /// /// The `markerId` must match one of the markers on the map. /// An invalid `markerId` triggers an "Invalid markerId" error. /// /// * See also: /// * [showMarkerInfoWindow] to show the Info Window. /// * [hideMarkerInfoWindow] to hide the Info Window. Future isMarkerInfoWindowShown( MarkerId markerId, { required int mapId, }) { throw UnimplementedError('updateMapOptions() has not been implemented.'); } /// Returns the current zoom level of the map. Future getZoomLevel({ required int mapId, }) { throw UnimplementedError('getZoomLevel() has not been implemented.'); } /// Returns the image bytes of the map. /// /// Returns null if a snapshot cannot be created. Future takeSnapshot({ required int mapId, }) { throw UnimplementedError('takeSnapshot() has not been implemented.'); } // The following are the 11 possible streams of data from the native side // into the plugin /// The Camera started moving. Stream onCameraMoveStarted({required int mapId}) { throw UnimplementedError('onCameraMoveStarted() has not been implemented.'); } /// The Camera finished moving to a new [CameraPosition]. Stream onCameraMove({required int mapId}) { throw UnimplementedError('onCameraMove() has not been implemented.'); } /// The Camera is now idle. Stream onCameraIdle({required int mapId}) { throw UnimplementedError('onCameraMove() has not been implemented.'); } /// A [Marker] has been tapped. Stream onMarkerTap({required int mapId}) { throw UnimplementedError('onMarkerTap() has not been implemented.'); } /// An [InfoWindow] has been tapped. Stream onInfoWindowTap({required int mapId}) { throw UnimplementedError('onInfoWindowTap() has not been implemented.'); } /// A [Marker] has been dragged to a different [LatLng] position. Stream onMarkerDragStart({required int mapId}) { throw UnimplementedError('onMarkerDragEnd() has not been implemented.'); } /// A [Marker] has been dragged to a different [LatLng] position. Stream onMarkerDrag({required int mapId}) { throw UnimplementedError('onMarkerDragEnd() has not been implemented.'); } /// A [Marker] has been dragged to a different [LatLng] position. Stream onMarkerDragEnd({required int mapId}) { throw UnimplementedError('onMarkerDragEnd() has not been implemented.'); } /// A [Polyline] has been tapped. Stream onPolylineTap({required int mapId}) { throw UnimplementedError('onPolylineTap() has not been implemented.'); } /// A [Polygon] has been tapped. Stream onPolygonTap({required int mapId}) { throw UnimplementedError('onPolygonTap() has not been implemented.'); } /// A [Circle] has been tapped. Stream onCircleTap({required int mapId}) { throw UnimplementedError('onCircleTap() has not been implemented.'); } /// A Map has been tapped at a certain [LatLng]. Stream onTap({required int mapId}) { throw UnimplementedError('onTap() has not been implemented.'); } /// A Map has been long-pressed at a certain [LatLng]. Stream onLongPress({required int mapId}) { throw UnimplementedError('onLongPress() has not been implemented.'); } /// Dispose of whatever resources the `mapId` is holding on to. void dispose({required int mapId}) { throw UnimplementedError('dispose() has not been implemented.'); } /// Returns a widget displaying the map view - deprecated, use /// [buildViewWithConfiguration] instead. Widget buildView( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { required CameraPosition initialCameraPosition, Set markers = const {}, Set polygons = const {}, Set polylines = const {}, Set circles = const {}, Set tileOverlays = const {}, Set>? gestureRecognizers = const >{}, // TODO(stuartmorgan): Replace with a structured type that's part of the // interface. See https://github.com/flutter/flutter/issues/70330. Map mapOptions = const {}, }) { throw UnimplementedError('buildView() has not been implemented.'); } /// Returns a widget displaying the map view - deprecated, use /// [buildViewWithConfiguration] instead. /// /// This method is similar to [buildView], but contains a parameter for /// platforms that require a text direction. /// /// Default behavior passes all parameters except `textDirection` to /// [buildView]. This is for backward compatibility with existing /// implementations. Platforms that use the text direction should override /// this as the primary implementation, and delegate to it from buildView. Widget buildViewWithTextDirection( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { required CameraPosition initialCameraPosition, required TextDirection textDirection, Set>? gestureRecognizers, Set markers = const {}, Set polygons = const {}, Set polylines = const {}, Set circles = const {}, Set tileOverlays = const {}, Map mapOptions = const {}, }) { return buildView( creationId, onPlatformViewCreated, initialCameraPosition: initialCameraPosition, markers: markers, polygons: polygons, polylines: polylines, circles: circles, tileOverlays: tileOverlays, gestureRecognizers: gestureRecognizers, mapOptions: mapOptions, ); } /// Returns a widget displaying the map view. Widget buildViewWithConfiguration( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { required MapWidgetConfiguration widgetConfiguration, MapConfiguration mapConfiguration = const MapConfiguration(), MapObjects mapObjects = const MapObjects(), }) { return buildViewWithTextDirection( creationId, onPlatformViewCreated, initialCameraPosition: widgetConfiguration.initialCameraPosition, textDirection: widgetConfiguration.textDirection, markers: mapObjects.markers, polygons: mapObjects.polygons, polylines: mapObjects.polylines, circles: mapObjects.circles, tileOverlays: mapObjects.tileOverlays, gestureRecognizers: widgetConfiguration.gestureRecognizers, mapOptions: jsonForMapConfiguration(mapConfiguration), ); } /// Populates [GoogleMapsFlutterInspectorPlatform.instance] to allow /// inspecting the platform map state. @visibleForTesting void enableDebugInspection() { throw UnimplementedError( 'enableDebugInspection() has not been implemented.'); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_inspector_platform.dart ================================================ // Copyright 2013 The Flutter 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:plugin_platform_interface/plugin_platform_interface.dart'; import '../../google_maps_flutter_platform_interface.dart'; /// The interface that platform-specific implementations of /// `google_maps_flutter` can extend to support state inpsection in tests. /// /// Avoid `implements` of this interface. Using `implements` makes adding any /// new methods here a breaking change for end users of your platform! /// /// Do `extends GoogleMapsInspectorPlatform` instead, so new methods /// added here are inherited in your code with the default implementation (that /// throws at runtime), rather than breaking your users at compile time. abstract class GoogleMapsInspectorPlatform extends PlatformInterface { /// Constructs a GoogleMapsFlutterPlatform. GoogleMapsInspectorPlatform() : super(token: _token); static final Object _token = Object(); static GoogleMapsInspectorPlatform? _instance; /// The instance of [GoogleMapsInspectorPlatform], if any. /// /// This is usually populated by calling /// [GoogleMapsFlutterPlatform.enableDebugInspection]. static GoogleMapsInspectorPlatform? get instance => _instance; /// Platform-specific plugins should set this with their own platform-specific /// class that extends [GoogleMapsInspectorPlatform] in their /// implementation of [GoogleMapsFlutterPlatform.enableDebugInspection]. static set instance(GoogleMapsInspectorPlatform? instance) { if (instance != null) { PlatformInterface.verify(instance, _token); } _instance = instance; } /// Returns the minimum and maxmimum zoom level settings. Future getMinMaxZoomLevels({required int mapId}) { throw UnimplementedError('getMinMaxZoomLevels() has not been implemented.'); } /// Returns true if the compass is enabled. Future isCompassEnabled({required int mapId}) { throw UnimplementedError('isCompassEnabled() has not been implemented.'); } /// Returns true if lite mode is enabled. Future isLiteModeEnabled({required int mapId}) { throw UnimplementedError('isLiteModeEnabled() has not been implemented.'); } /// Returns true if the map toolbar is enabled. Future isMapToolbarEnabled({required int mapId}) { throw UnimplementedError('isMapToolbarEnabled() has not been implemented.'); } /// Returns true if the "my location" button is enabled. Future isMyLocationButtonEnabled({required int mapId}) { throw UnimplementedError( 'isMyLocationButtonEnabled() has not been implemented.'); } /// Returns true if the traffic overlay is enabled. Future isTrafficEnabled({required int mapId}) { throw UnimplementedError('isTrafficEnabled() has not been implemented.'); } /// Returns true if the building layer is enabled. Future areBuildingsEnabled({required int mapId}) { throw UnimplementedError('areBuildingsEnabled() has not been implemented.'); } /// Returns true if rotate gestures are enabled. Future areRotateGesturesEnabled({required int mapId}) { throw UnimplementedError( 'areRotateGesturesEnabled() has not been implemented.'); } /// Returns true if scroll gestures are enabled. Future areScrollGesturesEnabled({required int mapId}) { throw UnimplementedError( 'areScrollGesturesEnabled() has not been implemented.'); } /// Returns true if tilt gestures are enabled. Future areTiltGesturesEnabled({required int mapId}) { throw UnimplementedError( 'areTiltGesturesEnabled() has not been implemented.'); } /// Returns true if zoom controls are enabled. Future areZoomControlsEnabled({required int mapId}) { throw UnimplementedError( 'areZoomControlsEnabled() has not been implemented.'); } /// Returns true if zoom gestures are enabled. Future areZoomGesturesEnabled({required int mapId}) { throw UnimplementedError( 'areZoomGesturesEnabled() has not been implemented.'); } /// Returns information about the tile overlay with the given ID. /// /// The returned object will be synthesized from platform data, so will not /// be the same Dart object as the original [TileOverlay] provided to the /// platform interface with that ID, and not all fields (e.g., /// [TileOverlay.tileProvider]) will be populated. Future getTileOverlayInfo(TileOverlayId tileOverlayId, {required int mapId}) { throw UnimplementedError('getTileOverlayInfo() has not been implemented.'); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/bitmap.dart ================================================ // Copyright 2013 The Flutter 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' show Future; import 'dart:typed_data' show Uint8List; import 'dart:ui' show Size; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart' show ImageConfiguration, AssetImage, AssetBundleImageKey; import 'package:flutter/services.dart' show AssetBundle; /// Defines a bitmap image. For a marker, this class can be used to set the /// image of the marker icon. For a ground overlay, it can be used to set the /// image to place on the surface of the earth. class BitmapDescriptor { const BitmapDescriptor._(this._json); /// The inverse of .toJson. // TODO(stuartmorgan): Remove this in the next breaking change. @Deprecated('No longer supported') BitmapDescriptor.fromJson(Object json) : _json = json { assert(_json is List); final List jsonList = json as List; assert(_validTypes.contains(jsonList[0])); switch (jsonList[0]) { case _defaultMarker: assert(jsonList.length <= 2); if (jsonList.length == 2) { assert(jsonList[1] is num); final num secondElement = jsonList[1] as num; assert(0 <= secondElement && secondElement < 360); } break; case _fromBytes: assert(jsonList.length == 2); assert(jsonList[1] != null && jsonList[1] is List); assert((jsonList[1] as List).isNotEmpty); break; case _fromAsset: assert(jsonList.length <= 3); assert(jsonList[1] != null && jsonList[1] is String); assert((jsonList[1] as String).isNotEmpty); if (jsonList.length == 3) { assert(jsonList[2] != null && jsonList[2] is String); assert((jsonList[2] as String).isNotEmpty); } break; case _fromAssetImage: assert(jsonList.length <= 4); assert(jsonList[1] != null && jsonList[1] is String); assert((jsonList[1] as String).isNotEmpty); assert(jsonList[2] != null && jsonList[2] is double); if (jsonList.length == 4) { assert(jsonList[3] != null && jsonList[3] is List); assert((jsonList[3] as List).length == 2); } break; default: break; } } static const String _defaultMarker = 'defaultMarker'; static const String _fromAsset = 'fromAsset'; static const String _fromAssetImage = 'fromAssetImage'; static const String _fromBytes = 'fromBytes'; static const Set _validTypes = { _defaultMarker, _fromAsset, _fromAssetImage, _fromBytes, }; /// Convenience hue value representing red. static const double hueRed = 0.0; /// Convenience hue value representing orange. static const double hueOrange = 30.0; /// Convenience hue value representing yellow. static const double hueYellow = 60.0; /// Convenience hue value representing green. static const double hueGreen = 120.0; /// Convenience hue value representing cyan. static const double hueCyan = 180.0; /// Convenience hue value representing azure. static const double hueAzure = 210.0; /// Convenience hue value representing blue. static const double hueBlue = 240.0; /// Convenience hue value representing violet. static const double hueViolet = 270.0; /// Convenience hue value representing magenta. static const double hueMagenta = 300.0; /// Convenience hue value representing rose. static const double hueRose = 330.0; /// Creates a BitmapDescriptor that refers to the default marker image. static const BitmapDescriptor defaultMarker = BitmapDescriptor._([_defaultMarker]); /// Creates a BitmapDescriptor that refers to a colorization of the default /// marker image. For convenience, there is a predefined set of hue values. /// See e.g. [hueYellow]. static BitmapDescriptor defaultMarkerWithHue(double hue) { assert(0.0 <= hue && hue < 360.0); return BitmapDescriptor._([_defaultMarker, hue]); } /// Creates a [BitmapDescriptor] from an asset image. /// /// Asset images in flutter are stored per: /// https://flutter.dev/docs/development/ui/assets-and-images#declaring-resolution-aware-image-assets /// This method takes into consideration various asset resolutions /// and scales the images to the right resolution depending on the dpi. /// Set `mipmaps` to false to load the exact dpi version of the image, `mipmap` is true by default. static Future fromAssetImage( ImageConfiguration configuration, String assetName, { AssetBundle? bundle, String? package, bool mipmaps = true, }) async { final double? devicePixelRatio = configuration.devicePixelRatio; if (!mipmaps && devicePixelRatio != null) { return BitmapDescriptor._([ _fromAssetImage, assetName, devicePixelRatio, ]); } final AssetImage assetImage = AssetImage(assetName, package: package, bundle: bundle); final AssetBundleImageKey assetBundleImageKey = await assetImage.obtainKey(configuration); final Size? size = configuration.size; return BitmapDescriptor._([ _fromAssetImage, assetBundleImageKey.name, assetBundleImageKey.scale, if (kIsWeb && size != null) [ size.width, size.height, ], ]); } /// Creates a BitmapDescriptor using an array of bytes that must be encoded /// as PNG. /// On the web, the [size] parameter represents the *physical size* of the /// bitmap, regardless of the actual resolution of the encoded PNG. /// This helps the browser to render High-DPI images at the correct size. /// `size` is not required (and ignored, if passed) in other platforms. static BitmapDescriptor fromBytes(Uint8List byteData, {Size? size}) { assert(byteData.isNotEmpty, 'Cannot create BitmapDescriptor with empty byteData'); return BitmapDescriptor._([ _fromBytes, byteData, if (kIsWeb && size != null) [ size.width, size.height, ] ]); } final Object _json; /// Convert the object to a Json format. Object toJson() => _json; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/callbacks.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'types.dart'; /// Callback that receives updates to the camera position. /// /// This callback is triggered when the platform Google Map /// registers a camera movement. /// /// This is used in [GoogleMap.onCameraMove]. typedef CameraPositionCallback = void Function(CameraPosition position); /// Callback function taking a single argument. typedef ArgumentCallback = void Function(T argument); /// Mutable collection of [ArgumentCallback] instances, itself an [ArgumentCallback]. /// /// Additions and removals happening during a single [call] invocation do not /// change who gets a callback until the next such invocation. /// /// Optimized for the singleton case. class ArgumentCallbacks { final List> _callbacks = >[]; /// Callback method. Invokes the corresponding method on each callback /// in this collection. /// /// The list of callbacks being invoked is computed at the start of the /// method and is unaffected by any changes subsequently made to this /// collection. void call(T argument) { final int length = _callbacks.length; if (length == 1) { _callbacks[0].call(argument); } else if (0 < length) { for (final ArgumentCallback callback in List>.from(_callbacks)) { callback(argument); } } } /// Adds a callback to this collection. void add(ArgumentCallback callback) { assert(callback != null); _callbacks.add(callback); } /// Removes a callback from this collection. /// /// Does nothing, if the callback was not present. void remove(ArgumentCallback callback) { _callbacks.remove(callback); } /// Whether this collection is empty. bool get isEmpty => _callbacks.isEmpty; /// Whether this collection is non-empty. bool get isNotEmpty => _callbacks.isNotEmpty; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/camera.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:ui' show Offset; import 'package:flutter/foundation.dart'; import 'types.dart'; /// The position of the map "camera", the view point from which the world is shown in the map view. /// /// Aggregates the camera's [target] geographical location, its [zoom] level, /// [tilt] angle, and [bearing]. @immutable class CameraPosition { /// Creates a immutable representation of the [GoogleMap] camera. /// /// [AssertionError] is thrown if [bearing], [target], [tilt], or [zoom] are /// null. const CameraPosition({ this.bearing = 0.0, required this.target, this.tilt = 0.0, this.zoom = 0.0, }) : assert(bearing != null), assert(target != null), assert(tilt != null), assert(zoom != null); /// The camera's bearing in degrees, measured clockwise from north. /// /// A bearing of 0.0, the default, means the camera points north. /// A bearing of 90.0 means the camera points east. final double bearing; /// The geographical location that the camera is pointing at. final LatLng target; /// The angle, in degrees, of the camera angle from the nadir. /// /// A tilt of 0.0, the default and minimum supported value, means the camera /// is directly facing the Earth. /// /// The maximum tilt value depends on the current zoom level. Values beyond /// the supported range are allowed, but on applying them to a map they will /// be silently clamped to the supported range. final double tilt; /// The zoom level of the camera. /// /// A zoom of 0.0, the default, means the screen width of the world is 256. /// Adding 1.0 to the zoom level doubles the screen width of the map. So at /// zoom level 3.0, the screen width of the world is 2³x256=2048. /// /// Larger zoom levels thus means the camera is placed closer to the surface /// of the Earth, revealing more detail in a narrower geographical region. /// /// The supported zoom level range depends on the map data and device. Values /// beyond the supported range are allowed, but on applying them to a map they /// will be silently clamped to the supported range. final double zoom; /// Serializes [CameraPosition]. /// /// Mainly for internal use when calling [CameraUpdate.newCameraPosition]. Object toMap() => { 'bearing': bearing, 'target': target.toJson(), 'tilt': tilt, 'zoom': zoom, }; /// Deserializes [CameraPosition] from a map. /// /// Mainly for internal use. static CameraPosition? fromMap(Object? json) { if (json == null || json is! Map) { return null; } final LatLng? target = LatLng.fromJson(json['target']); if (target == null) { return null; } return CameraPosition( bearing: json['bearing'] as double, target: target, tilt: json['tilt'] as double, zoom: json['zoom'] as double, ); } @override bool operator ==(Object other) { if (identical(this, other)) { return true; } if (runtimeType != other.runtimeType) { return false; } return other is CameraPosition && bearing == other.bearing && target == other.target && tilt == other.tilt && zoom == other.zoom; } @override int get hashCode => Object.hash(bearing, target, tilt, zoom); @override String toString() => 'CameraPosition(bearing: $bearing, target: $target, tilt: $tilt, zoom: $zoom)'; } /// Defines a camera move, supporting absolute moves as well as moves relative /// the current position. class CameraUpdate { const CameraUpdate._(this._json); /// Returns a camera update that moves the camera to the specified position. static CameraUpdate newCameraPosition(CameraPosition cameraPosition) { return CameraUpdate._( ['newCameraPosition', cameraPosition.toMap()], ); } /// Returns a camera update that moves the camera target to the specified /// geographical location. static CameraUpdate newLatLng(LatLng latLng) { return CameraUpdate._(['newLatLng', latLng.toJson()]); } /// Returns a camera update that transforms the camera so that the specified /// geographical bounding box is centered in the map view at the greatest /// possible zoom level. A non-zero [padding] insets the bounding box from the /// map view's edges. The camera's new tilt and bearing will both be 0.0. static CameraUpdate newLatLngBounds(LatLngBounds bounds, double padding) { return CameraUpdate._([ 'newLatLngBounds', bounds.toJson(), padding, ]); } /// Returns a camera update that moves the camera target to the specified /// geographical location and zoom level. static CameraUpdate newLatLngZoom(LatLng latLng, double zoom) { return CameraUpdate._( ['newLatLngZoom', latLng.toJson(), zoom], ); } /// Returns a camera update that moves the camera target the specified screen /// distance. /// /// For a camera with bearing 0.0 (pointing north), scrolling by 50,75 moves /// the camera's target to a geographical location that is 50 to the east and /// 75 to the south of the current location, measured in screen coordinates. static CameraUpdate scrollBy(double dx, double dy) { return CameraUpdate._( ['scrollBy', dx, dy], ); } /// Returns a camera update that modifies the camera zoom level by the /// specified amount. The optional [focus] is a screen point whose underlying /// geographical location should be invariant, if possible, by the movement. static CameraUpdate zoomBy(double amount, [Offset? focus]) { if (focus == null) { return CameraUpdate._(['zoomBy', amount]); } else { return CameraUpdate._([ 'zoomBy', amount, [focus.dx, focus.dy], ]); } } /// Returns a camera update that zooms the camera in, bringing the camera /// closer to the surface of the Earth. /// /// Equivalent to the result of calling `zoomBy(1.0)`. static CameraUpdate zoomIn() { return const CameraUpdate._(['zoomIn']); } /// Returns a camera update that zooms the camera out, bringing the camera /// further away from the surface of the Earth. /// /// Equivalent to the result of calling `zoomBy(-1.0)`. static CameraUpdate zoomOut() { return const CameraUpdate._(['zoomOut']); } /// Returns a camera update that sets the camera zoom level. static CameraUpdate zoomTo(double zoom) { return CameraUpdate._(['zoomTo', zoom]); } final Object _json; /// Converts this object to something serializable in JSON. Object toJson() => _json; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cap.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart' show immutable; import 'types.dart'; /// Cap that can be applied at the start or end vertex of a [Polyline]. @immutable class Cap { const Cap._(this._json); /// Cap that is squared off exactly at the start or end vertex of a [Polyline] /// with solid stroke pattern, equivalent to having no additional cap beyond /// the start or end vertex. /// /// This is the default cap type at start and end vertices of Polylines with /// solid stroke pattern. static const Cap buttCap = Cap._(['buttCap']); /// Cap that is a semicircle with radius equal to half the stroke width, /// centered at the start or end vertex of a [Polyline] with solid stroke /// pattern. static const Cap roundCap = Cap._(['roundCap']); /// Cap that is squared off after extending half the stroke width beyond the /// start or end vertex of a [Polyline] with solid stroke pattern. static const Cap squareCap = Cap._(['squareCap']); /// Constructs a new CustomCap with a bitmap overlay centered at the start or /// end vertex of a [Polyline], orientated according to the direction of the line's /// first or last edge and scaled with respect to the line's stroke width. /// /// CustomCap can be applied to [Polyline] with any stroke pattern. /// /// [bitmapDescriptor] must not be null. /// /// [refWidth] is the reference stroke width (in pixels) - the stroke width for which /// the cap bitmap at its native dimension is designed. Must be positive. Default value /// is 10 pixels. static Cap customCapFromBitmap( BitmapDescriptor bitmapDescriptor, { double refWidth = 10, }) { assert(bitmapDescriptor != null); assert(refWidth > 0.0); return Cap._(['customCap', bitmapDescriptor.toJson(), refWidth]); } final Object _json; /// Converts this object to something serializable in JSON. Object toJson() => _json; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/circle.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart' show VoidCallback; import 'package:flutter/foundation.dart' show immutable; import 'package:flutter/material.dart' show Color, Colors; import 'types.dart'; /// Uniquely identifies a [Circle] among [GoogleMap] circles. /// /// This does not have to be globally unique, only unique among the list. @immutable class CircleId extends MapsObjectId { /// Creates an immutable identifier for a [Circle]. const CircleId(String value) : super(value); } /// Draws a circle on the map. @immutable class Circle implements MapsObject { /// Creates an immutable representation of a [Circle] to draw on [GoogleMap]. const Circle({ required this.circleId, this.consumeTapEvents = false, this.fillColor = Colors.transparent, this.center = const LatLng(0.0, 0.0), this.radius = 0, this.strokeColor = Colors.black, this.strokeWidth = 10, this.visible = true, this.zIndex = 0, this.onTap, }); /// Uniquely identifies a [Circle]. final CircleId circleId; @override CircleId get mapsId => circleId; /// True if the [Circle] consumes tap events. /// /// If this is false, [onTap] callback will not be triggered. final bool consumeTapEvents; /// Fill color in ARGB format, the same format used by Color. The default value is transparent (0x00000000). final Color fillColor; /// Geographical location of the circle center. final LatLng center; /// Radius of the circle in meters; must be positive. The default value is 0. final double radius; /// Fill color in ARGB format, the same format used by Color. The default value is black (0xff000000). final Color strokeColor; /// The width of the circle's outline in screen points. /// /// The width is constant and independent of the camera's zoom level. /// The default value is 10. /// Setting strokeWidth to 0 results in no stroke. final int strokeWidth; /// True if the circle is visible. final bool visible; /// The z-index of the circle, used to determine relative drawing order of /// map overlays. /// /// Overlays are drawn in order of z-index, so that lower values means drawn /// earlier, and thus appearing to be closer to the surface of the Earth. final int zIndex; /// Callbacks to receive tap events for circle placed on this map. final VoidCallback? onTap; /// Creates a new [Circle] object whose values are the same as this instance, /// unless overwritten by the specified parameters. Circle copyWith({ bool? consumeTapEventsParam, Color? fillColorParam, LatLng? centerParam, double? radiusParam, Color? strokeColorParam, int? strokeWidthParam, bool? visibleParam, int? zIndexParam, VoidCallback? onTapParam, }) { return Circle( circleId: circleId, consumeTapEvents: consumeTapEventsParam ?? consumeTapEvents, fillColor: fillColorParam ?? fillColor, center: centerParam ?? center, radius: radiusParam ?? radius, strokeColor: strokeColorParam ?? strokeColor, strokeWidth: strokeWidthParam ?? strokeWidth, visible: visibleParam ?? visible, zIndex: zIndexParam ?? zIndex, onTap: onTapParam ?? onTap, ); } /// Creates a new [Circle] object whose values are the same as this instance. @override Circle clone() => copyWith(); /// Converts this object to something serializable in JSON. @override Object toJson() { final Map json = {}; void addIfPresent(String fieldName, Object? value) { if (value != null) { json[fieldName] = value; } } addIfPresent('circleId', circleId.value); addIfPresent('consumeTapEvents', consumeTapEvents); addIfPresent('fillColor', fillColor.value); addIfPresent('center', center.toJson()); addIfPresent('radius', radius); addIfPresent('strokeColor', strokeColor.value); addIfPresent('strokeWidth', strokeWidth); addIfPresent('visible', visible); addIfPresent('zIndex', zIndex); return json; } @override bool operator ==(Object other) { if (identical(this, other)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is Circle && circleId == other.circleId && consumeTapEvents == other.consumeTapEvents && fillColor == other.fillColor && center == other.center && radius == other.radius && strokeColor == other.strokeColor && strokeWidth == other.strokeWidth && visible == other.visible && zIndex == other.zIndex; } @override int get hashCode => circleId.hashCode; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/circle_updates.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'types.dart'; /// [Circle] update events to be applied to the [GoogleMap]. /// /// Used in [GoogleMapController] when the map is updated. // (Do not re-export) class CircleUpdates extends MapsObjectUpdates { /// Computes [CircleUpdates] given previous and current [Circle]s. CircleUpdates.from(Set previous, Set current) : super.from(previous, current, objectName: 'circle'); /// Set of Circles to be added in this update. Set get circlesToAdd => objectsToAdd; /// Set of CircleIds to be removed in this update. Set get circleIdsToRemove => objectIdsToRemove.cast(); /// Set of Circles to be changed in this update. Set get circlesToChange => objectsToChange; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/joint_type.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart' show immutable; /// Joint types for [Polyline]. @immutable class JointType { const JointType._(this.value); /// The value representing the [JointType] on the sdk. final int value; /// Mitered joint, with fixed pointed extrusion equal to half the stroke width on the outside of the joint. /// /// Constant Value: 0 static const JointType mitered = JointType._(0); /// Flat bevel on the outside of the joint. /// /// Constant Value: 1 static const JointType bevel = JointType._(1); /// Rounded on the outside of the joint by an arc of radius equal to half the stroke width, centered at the vertex. /// /// Constant Value: 2 static const JointType round = JointType._(2); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/location.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart' show immutable, objectRuntimeType, visibleForTesting; /// A pair of latitude and longitude coordinates, stored as degrees. @immutable class LatLng { /// Creates a geographical location specified in degrees [latitude] and /// [longitude]. /// /// The latitude is clamped to the inclusive interval from -90.0 to +90.0. /// /// The longitude is normalized to the half-open interval from -180.0 /// (inclusive) to +180.0 (exclusive). const LatLng(double latitude, double longitude) : assert(latitude != null), assert(longitude != null), latitude = latitude < -90.0 ? -90.0 : (90.0 < latitude ? 90.0 : latitude), // Avoids normalization if possible to prevent unnecessary loss of precision longitude = longitude >= -180 && longitude < 180 ? longitude : (longitude + 180.0) % 360.0 - 180.0; /// The latitude in degrees between -90.0 and 90.0, both inclusive. final double latitude; /// The longitude in degrees between -180.0 (inclusive) and 180.0 (exclusive). final double longitude; /// Converts this object to something serializable in JSON. Object toJson() { return [latitude, longitude]; } /// Initialize a LatLng from an \[lat, lng\] array. static LatLng? fromJson(Object? json) { if (json == null) { return null; } assert(json is List && json.length == 2); final List list = json as List; return LatLng(list[0]! as double, list[1]! as double); } @override String toString() => '${objectRuntimeType(this, 'LatLng')}($latitude, $longitude)'; @override bool operator ==(Object other) { return other is LatLng && other.latitude == latitude && other.longitude == longitude; } @override int get hashCode => Object.hash(latitude, longitude); } /// A latitude/longitude aligned rectangle. /// /// The rectangle conceptually includes all points (lat, lng) where /// * lat ∈ [`southwest.latitude`, `northeast.latitude`] /// * lng ∈ [`southwest.longitude`, `northeast.longitude`], /// if `southwest.longitude` ≤ `northeast.longitude`, /// * lng ∈ [-180, `northeast.longitude`] ∪ [`southwest.longitude`, 180], /// if `northeast.longitude` < `southwest.longitude` @immutable class LatLngBounds { /// Creates geographical bounding box with the specified corners. /// /// The latitude of the southwest corner cannot be larger than the /// latitude of the northeast corner. LatLngBounds({required this.southwest, required this.northeast}) : assert(southwest != null), assert(northeast != null), assert(southwest.latitude <= northeast.latitude); /// The southwest corner of the rectangle. final LatLng southwest; /// The northeast corner of the rectangle. final LatLng northeast; /// Converts this object to something serializable in JSON. Object toJson() { return [southwest.toJson(), northeast.toJson()]; } /// Returns whether this rectangle contains the given [LatLng]. bool contains(LatLng point) { return _containsLatitude(point.latitude) && _containsLongitude(point.longitude); } bool _containsLatitude(double lat) { return (southwest.latitude <= lat) && (lat <= northeast.latitude); } bool _containsLongitude(double lng) { if (southwest.longitude <= northeast.longitude) { return southwest.longitude <= lng && lng <= northeast.longitude; } else { return southwest.longitude <= lng || lng <= northeast.longitude; } } /// Converts a list to [LatLngBounds]. @visibleForTesting static LatLngBounds? fromList(Object? json) { if (json == null) { return null; } assert(json is List && json.length == 2); final List list = json as List; return LatLngBounds( southwest: LatLng.fromJson(list[0])!, northeast: LatLng.fromJson(list[1])!, ); } @override String toString() { return '${objectRuntimeType(this, 'LatLngBounds')}($southwest, $northeast)'; } @override bool operator ==(Object other) { return other is LatLngBounds && other.southwest == southwest && other.northeast == northeast; } @override int get hashCode => Object.hash(southwest, northeast); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_configuration.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/widgets.dart'; import 'ui.dart'; /// Configuration options for the GoogleMaps user interface. @immutable class MapConfiguration { /// Creates a new configuration instance with the given options. /// /// Any options that aren't passed will be null, which allows this to serve /// as either a full configuration selection, or an update to an existing /// configuration where only non-null values are updated. const MapConfiguration({ this.compassEnabled, this.mapToolbarEnabled, this.cameraTargetBounds, this.mapType, this.minMaxZoomPreference, this.rotateGesturesEnabled, this.scrollGesturesEnabled, this.tiltGesturesEnabled, this.trackCameraPosition, this.zoomControlsEnabled, this.zoomGesturesEnabled, this.liteModeEnabled, this.myLocationEnabled, this.myLocationButtonEnabled, this.padding, this.indoorViewEnabled, this.trafficEnabled, this.buildingsEnabled, }); /// True if the compass UI should be shown. final bool? compassEnabled; /// True if the map toolbar should be shown. final bool? mapToolbarEnabled; /// The bounds to display. final CameraTargetBounds? cameraTargetBounds; /// The type of the map. final MapType? mapType; /// The prefered zoom range. final MinMaxZoomPreference? minMaxZoomPreference; /// True if rotate gestures should be enabled. final bool? rotateGesturesEnabled; /// True if scroll gestures should be enabled. final bool? scrollGesturesEnabled; /// True if tilt gestures should be enabled. final bool? tiltGesturesEnabled; /// True if camera position changes should trigger notifications. final bool? trackCameraPosition; /// True if zoom controls should be displayed. final bool? zoomControlsEnabled; /// True if zoom gestures should be enabled. final bool? zoomGesturesEnabled; /// True if the map should use Lite Mode, showing a limited-interactivity /// bitmap, on supported platforms. final bool? liteModeEnabled; /// True if the current location should be tracked and displayed. final bool? myLocationEnabled; /// True if the control to jump to the current location should be displayed. final bool? myLocationButtonEnabled; /// The padding for the map display. final EdgeInsets? padding; /// True if indoor map views should be enabled. final bool? indoorViewEnabled; /// True if the traffic overlay should be enabled. final bool? trafficEnabled; /// True if 3D building display should be enabled. final bool? buildingsEnabled; /// Returns a new options object containing only the values of this instance /// that are different from [other]. MapConfiguration diffFrom(MapConfiguration other) { return MapConfiguration( compassEnabled: compassEnabled != other.compassEnabled ? compassEnabled : null, mapToolbarEnabled: mapToolbarEnabled != other.mapToolbarEnabled ? mapToolbarEnabled : null, cameraTargetBounds: cameraTargetBounds != other.cameraTargetBounds ? cameraTargetBounds : null, mapType: mapType != other.mapType ? mapType : null, minMaxZoomPreference: minMaxZoomPreference != other.minMaxZoomPreference ? minMaxZoomPreference : null, rotateGesturesEnabled: rotateGesturesEnabled != other.rotateGesturesEnabled ? rotateGesturesEnabled : null, scrollGesturesEnabled: scrollGesturesEnabled != other.scrollGesturesEnabled ? scrollGesturesEnabled : null, tiltGesturesEnabled: tiltGesturesEnabled != other.tiltGesturesEnabled ? tiltGesturesEnabled : null, trackCameraPosition: trackCameraPosition != other.trackCameraPosition ? trackCameraPosition : null, zoomControlsEnabled: zoomControlsEnabled != other.zoomControlsEnabled ? zoomControlsEnabled : null, zoomGesturesEnabled: zoomGesturesEnabled != other.zoomGesturesEnabled ? zoomGesturesEnabled : null, liteModeEnabled: liteModeEnabled != other.liteModeEnabled ? liteModeEnabled : null, myLocationEnabled: myLocationEnabled != other.myLocationEnabled ? myLocationEnabled : null, myLocationButtonEnabled: myLocationButtonEnabled != other.myLocationButtonEnabled ? myLocationButtonEnabled : null, padding: padding != other.padding ? padding : null, indoorViewEnabled: indoorViewEnabled != other.indoorViewEnabled ? indoorViewEnabled : null, trafficEnabled: trafficEnabled != other.trafficEnabled ? trafficEnabled : null, buildingsEnabled: buildingsEnabled != other.buildingsEnabled ? buildingsEnabled : null, ); } /// Returns a copy of this instance with any non-null settings form [diff] /// replacing the previous values. MapConfiguration applyDiff(MapConfiguration diff) { return MapConfiguration( compassEnabled: diff.compassEnabled ?? compassEnabled, mapToolbarEnabled: diff.mapToolbarEnabled ?? mapToolbarEnabled, cameraTargetBounds: diff.cameraTargetBounds ?? cameraTargetBounds, mapType: diff.mapType ?? mapType, minMaxZoomPreference: diff.minMaxZoomPreference ?? minMaxZoomPreference, rotateGesturesEnabled: diff.rotateGesturesEnabled ?? rotateGesturesEnabled, scrollGesturesEnabled: diff.scrollGesturesEnabled ?? scrollGesturesEnabled, tiltGesturesEnabled: diff.tiltGesturesEnabled ?? tiltGesturesEnabled, trackCameraPosition: diff.trackCameraPosition ?? trackCameraPosition, zoomControlsEnabled: diff.zoomControlsEnabled ?? zoomControlsEnabled, zoomGesturesEnabled: diff.zoomGesturesEnabled ?? zoomGesturesEnabled, liteModeEnabled: diff.liteModeEnabled ?? liteModeEnabled, myLocationEnabled: diff.myLocationEnabled ?? myLocationEnabled, myLocationButtonEnabled: diff.myLocationButtonEnabled ?? myLocationButtonEnabled, padding: diff.padding ?? padding, indoorViewEnabled: diff.indoorViewEnabled ?? indoorViewEnabled, trafficEnabled: diff.trafficEnabled ?? trafficEnabled, buildingsEnabled: diff.buildingsEnabled ?? buildingsEnabled, ); } /// True if no options are set. bool get isEmpty => compassEnabled == null && mapToolbarEnabled == null && cameraTargetBounds == null && mapType == null && minMaxZoomPreference == null && rotateGesturesEnabled == null && scrollGesturesEnabled == null && tiltGesturesEnabled == null && trackCameraPosition == null && zoomControlsEnabled == null && zoomGesturesEnabled == null && liteModeEnabled == null && myLocationEnabled == null && myLocationButtonEnabled == null && padding == null && indoorViewEnabled == null && trafficEnabled == null && buildingsEnabled == null; @override bool operator ==(Object other) { if (identical(this, other)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is MapConfiguration && compassEnabled == other.compassEnabled && mapToolbarEnabled == other.mapToolbarEnabled && cameraTargetBounds == other.cameraTargetBounds && mapType == other.mapType && minMaxZoomPreference == other.minMaxZoomPreference && rotateGesturesEnabled == other.rotateGesturesEnabled && scrollGesturesEnabled == other.scrollGesturesEnabled && tiltGesturesEnabled == other.tiltGesturesEnabled && trackCameraPosition == other.trackCameraPosition && zoomControlsEnabled == other.zoomControlsEnabled && zoomGesturesEnabled == other.zoomGesturesEnabled && liteModeEnabled == other.liteModeEnabled && myLocationEnabled == other.myLocationEnabled && myLocationButtonEnabled == other.myLocationButtonEnabled && padding == other.padding && indoorViewEnabled == other.indoorViewEnabled && trafficEnabled == other.trafficEnabled && buildingsEnabled == other.buildingsEnabled; } @override int get hashCode => Object.hash( compassEnabled, mapToolbarEnabled, cameraTargetBounds, mapType, minMaxZoomPreference, rotateGesturesEnabled, scrollGesturesEnabled, tiltGesturesEnabled, trackCameraPosition, zoomControlsEnabled, zoomGesturesEnabled, liteModeEnabled, myLocationEnabled, myLocationButtonEnabled, padding, indoorViewEnabled, trafficEnabled, buildingsEnabled, ); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_objects.dart ================================================ // Copyright 2013 The Flutter 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/foundation.dart'; import 'types.dart'; /// A container object for all the types of maps objects. /// /// This is intended for use as a parameter in platform interface methods, to /// allow adding new object types to existing methods. @immutable class MapObjects { /// Creates a new set of map objects with all the given object types. const MapObjects({ this.markers = const {}, this.polygons = const {}, this.polylines = const {}, this.circles = const {}, this.tileOverlays = const {}, }); final Set markers; final Set polygons; final Set polylines; final Set circles; final Set tileOverlays; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_widget_configuration.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'types.dart'; /// A container object for configuration options when building a widget. /// /// This is intended for use as a parameter in platform interface methods, to /// allow adding new configuration options to existing methods. @immutable class MapWidgetConfiguration { /// Creates a new configuration with all the given settings. const MapWidgetConfiguration({ required this.initialCameraPosition, required this.textDirection, this.gestureRecognizers = const >{}, }); /// The initial camera position to display. final CameraPosition initialCameraPosition; /// The text direction for the widget. final TextDirection textDirection; /// Gesture recognizers to add to the widget. final Set> gestureRecognizers; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/maps_object.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart' show immutable, objectRuntimeType; /// Uniquely identifies object an among [GoogleMap] collections of a specific /// type. /// /// This does not have to be globally unique, only unique among the collection. @immutable class MapsObjectId { /// Creates an immutable object representing a [T] among [GoogleMap] Ts. /// /// An [AssertionError] will be thrown if [value] is null. const MapsObjectId(this.value) : assert(value != null); /// The value of the id. final String value; @override bool operator ==(Object other) { if (identical(this, other)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is MapsObjectId && value == other.value; } @override int get hashCode => value.hashCode; @override String toString() { return '${objectRuntimeType(this, 'MapsObjectId')}($value)'; } } /// A common interface for maps types. abstract class MapsObject { /// A identifier for this object. MapsObjectId get mapsId; /// Returns a duplicate of this object. T clone(); /// Converts this object to something serializable in JSON. Object toJson(); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/maps_object_updates.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart' show immutable, objectRuntimeType, setEquals; import 'maps_object.dart'; import 'utils/maps_object.dart'; /// Update specification for a set of objects. @immutable class MapsObjectUpdates> { /// Computes updates given previous and current object sets. /// /// [objectName] is the prefix to use when serializing the updates into a JSON /// dictionary. E.g., 'circle' will give 'circlesToAdd', 'circlesToUpdate', /// 'circleIdsToRemove'. MapsObjectUpdates.from( Set previous, Set current, { required this.objectName, }) { final Map, T> previousObjects = keyByMapsObjectId(previous); final Map, T> currentObjects = keyByMapsObjectId(current); final Set> previousObjectIds = previousObjects.keys.toSet(); final Set> currentObjectIds = currentObjects.keys.toSet(); /// Maps an ID back to a [T] in [currentObjects]. /// /// It is a programming error to call this with an ID that is not guaranteed /// to be in [currentObjects]. T idToCurrentObject(MapsObjectId id) { return currentObjects[id]!; } _objectIdsToRemove = previousObjectIds.difference(currentObjectIds); _objectsToAdd = currentObjectIds .difference(previousObjectIds) .map(idToCurrentObject) .toSet(); // Returns `true` if [current] is not equals to previous one with the // same id. bool hasChanged(T current) { final T? previous = previousObjects[current.mapsId]; return current != previous; } _objectsToChange = currentObjectIds .intersection(previousObjectIds) .map(idToCurrentObject) .where(hasChanged) .toSet(); } /// The name of the objects being updated, for use in serialization. final String objectName; /// Set of objects to be added in this update. Set get objectsToAdd { return _objectsToAdd; } late final Set _objectsToAdd; /// Set of objects to be removed in this update. Set> get objectIdsToRemove { return _objectIdsToRemove; } late final Set> _objectIdsToRemove; /// Set of objects to be changed in this update. Set get objectsToChange { return _objectsToChange; } late final Set _objectsToChange; /// Converts this object to JSON. Object toJson() { final Map updateMap = {}; void addIfNonNull(String fieldName, Object? value) { if (value != null) { updateMap[fieldName] = value; } } addIfNonNull('${objectName}sToAdd', serializeMapsObjectSet(_objectsToAdd)); addIfNonNull( '${objectName}sToChange', serializeMapsObjectSet(_objectsToChange)); addIfNonNull( '${objectName}IdsToRemove', _objectIdsToRemove .map((MapsObjectId m) => m.value) .toList()); return updateMap; } @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) { return false; } return other is MapsObjectUpdates && setEquals(_objectsToAdd, other._objectsToAdd) && setEquals(_objectIdsToRemove, other._objectIdsToRemove) && setEquals(_objectsToChange, other._objectsToChange); } @override int get hashCode => Object.hash(Object.hashAll(_objectsToAdd), Object.hashAll(_objectIdsToRemove), Object.hashAll(_objectsToChange)); @override String toString() { return '${objectRuntimeType(this, 'MapsObjectUpdates')}(add: $objectsToAdd, ' 'remove: $objectIdsToRemove, ' 'change: $objectsToChange)'; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:ui' show Offset; import 'package:flutter/foundation.dart' show immutable, ValueChanged, VoidCallback; import 'types.dart'; Object _offsetToJson(Offset offset) { return [offset.dx, offset.dy]; } /// Text labels for a [Marker] info window. @immutable class InfoWindow { /// Creates an immutable representation of a label on for [Marker]. const InfoWindow({ this.title, this.snippet, this.anchor = const Offset(0.5, 0.0), this.onTap, }); /// Text labels specifying that no text is to be displayed. static const InfoWindow noText = InfoWindow(); /// Text displayed in an info window when the user taps the marker. /// /// A null value means no title. final String? title; /// Additional text displayed below the [title]. /// /// A null value means no additional text. final String? snippet; /// The icon image point that will be the anchor of the info window when /// displayed. /// /// The image point is specified in normalized coordinates: An anchor of /// (0.0, 0.0) means the top left corner of the image. An anchor /// of (1.0, 1.0) means the bottom right corner of the image. final Offset anchor; /// onTap callback for this [InfoWindow]. final VoidCallback? onTap; /// Creates a new [InfoWindow] object whose values are the same as this instance, /// unless overwritten by the specified parameters. InfoWindow copyWith({ String? titleParam, String? snippetParam, Offset? anchorParam, VoidCallback? onTapParam, }) { return InfoWindow( title: titleParam ?? title, snippet: snippetParam ?? snippet, anchor: anchorParam ?? anchor, onTap: onTapParam ?? onTap, ); } Object _toJson() { final Map json = {}; void addIfPresent(String fieldName, Object? value) { if (value != null) { json[fieldName] = value; } } addIfPresent('title', title); addIfPresent('snippet', snippet); addIfPresent('anchor', _offsetToJson(anchor)); return json; } @override bool operator ==(Object other) { if (identical(this, other)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is InfoWindow && title == other.title && snippet == other.snippet && anchor == other.anchor; } @override int get hashCode => Object.hash(title.hashCode, snippet, anchor); @override String toString() { return 'InfoWindow{title: $title, snippet: $snippet, anchor: $anchor}'; } } /// Uniquely identifies a [Marker] among [GoogleMap] markers. /// /// This does not have to be globally unique, only unique among the list. @immutable class MarkerId extends MapsObjectId { /// Creates an immutable identifier for a [Marker]. const MarkerId(String value) : super(value); } /// Marks a geographical location on the map. /// /// A marker icon is drawn oriented against the device's screen rather than /// the map's surface; that is, it will not necessarily change orientation /// due to map rotations, tilting, or zooming. @immutable class Marker implements MapsObject { /// Creates a set of marker configuration options. /// /// Default marker options. /// /// Specifies a marker that /// * is fully opaque; [alpha] is 1.0 /// * uses icon bottom center to indicate map position; [anchor] is (0.5, 1.0) /// * has default tap handling; [consumeTapEvents] is false /// * is stationary; [draggable] is false /// * is drawn against the screen, not the map; [flat] is false /// * has a default icon; [icon] is `BitmapDescriptor.defaultMarker` /// * anchors the info window at top center; [infoWindowAnchor] is (0.5, 0.0) /// * has no info window text; [infoWindowText] is `InfoWindowText.noText` /// * is positioned at 0, 0; [position] is `LatLng(0.0, 0.0)` /// * has an axis-aligned icon; [rotation] is 0.0 /// * is visible; [visible] is true /// * is placed at the base of the drawing order; [zIndex] is 0.0 /// * reports [onTap] events /// * reports [onDragEnd] events const Marker({ required this.markerId, this.alpha = 1.0, this.anchor = const Offset(0.5, 1.0), this.consumeTapEvents = false, this.draggable = false, this.flat = false, this.icon = BitmapDescriptor.defaultMarker, this.infoWindow = InfoWindow.noText, this.position = const LatLng(0.0, 0.0), this.rotation = 0.0, this.visible = true, this.zIndex = 0.0, this.onTap, this.onDrag, this.onDragStart, this.onDragEnd, }) : assert(alpha == null || (0.0 <= alpha && alpha <= 1.0)); /// Uniquely identifies a [Marker]. final MarkerId markerId; @override MarkerId get mapsId => markerId; /// The opacity of the marker, between 0.0 and 1.0 inclusive. /// /// 0.0 means fully transparent, 1.0 means fully opaque. final double alpha; /// The icon image point that will be placed at the [position] of the marker. /// /// The image point is specified in normalized coordinates: An anchor of /// (0.0, 0.0) means the top left corner of the image. An anchor /// of (1.0, 1.0) means the bottom right corner of the image. final Offset anchor; /// True if the marker icon consumes tap events. If not, the map will perform /// default tap handling by centering the map on the marker and displaying its /// info window. final bool consumeTapEvents; /// True if the marker is draggable by user touch events. final bool draggable; /// True if the marker is rendered flatly against the surface of the Earth, so /// that it will rotate and tilt along with map camera movements. final bool flat; /// A description of the bitmap used to draw the marker icon. final BitmapDescriptor icon; /// A Google Maps InfoWindow. /// /// The window is displayed when the marker is tapped. final InfoWindow infoWindow; /// Geographical location of the marker. final LatLng position; /// Rotation of the marker image in degrees clockwise from the [anchor] point. final double rotation; /// True if the marker is visible. final bool visible; /// The z-index of the marker, used to determine relative drawing order of /// map overlays. /// /// Overlays are drawn in order of z-index, so that lower values means drawn /// earlier, and thus appearing to be closer to the surface of the Earth. final double zIndex; /// Callbacks to receive tap events for markers placed on this map. final VoidCallback? onTap; /// Signature reporting the new [LatLng] at the start of a drag event. final ValueChanged? onDragStart; /// Signature reporting the new [LatLng] at the end of a drag event. final ValueChanged? onDragEnd; /// Signature reporting the new [LatLng] during the drag event. final ValueChanged? onDrag; /// Creates a new [Marker] object whose values are the same as this instance, /// unless overwritten by the specified parameters. Marker copyWith({ double? alphaParam, Offset? anchorParam, bool? consumeTapEventsParam, bool? draggableParam, bool? flatParam, BitmapDescriptor? iconParam, InfoWindow? infoWindowParam, LatLng? positionParam, double? rotationParam, bool? visibleParam, double? zIndexParam, VoidCallback? onTapParam, ValueChanged? onDragStartParam, ValueChanged? onDragParam, ValueChanged? onDragEndParam, }) { return Marker( markerId: markerId, alpha: alphaParam ?? alpha, anchor: anchorParam ?? anchor, consumeTapEvents: consumeTapEventsParam ?? consumeTapEvents, draggable: draggableParam ?? draggable, flat: flatParam ?? flat, icon: iconParam ?? icon, infoWindow: infoWindowParam ?? infoWindow, position: positionParam ?? position, rotation: rotationParam ?? rotation, visible: visibleParam ?? visible, zIndex: zIndexParam ?? zIndex, onTap: onTapParam ?? onTap, onDragStart: onDragStartParam ?? onDragStart, onDrag: onDragParam ?? onDrag, onDragEnd: onDragEndParam ?? onDragEnd, ); } /// Creates a new [Marker] object whose values are the same as this instance. @override Marker clone() => copyWith(); /// Converts this object to something serializable in JSON. @override Object toJson() { final Map json = {}; void addIfPresent(String fieldName, Object? value) { if (value != null) { json[fieldName] = value; } } addIfPresent('markerId', markerId.value); addIfPresent('alpha', alpha); addIfPresent('anchor', _offsetToJson(anchor)); addIfPresent('consumeTapEvents', consumeTapEvents); addIfPresent('draggable', draggable); addIfPresent('flat', flat); addIfPresent('icon', icon.toJson()); addIfPresent('infoWindow', infoWindow._toJson()); addIfPresent('position', position.toJson()); addIfPresent('rotation', rotation); addIfPresent('visible', visible); addIfPresent('zIndex', zIndex); return json; } @override bool operator ==(Object other) { if (identical(this, other)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is Marker && markerId == other.markerId && alpha == other.alpha && anchor == other.anchor && consumeTapEvents == other.consumeTapEvents && draggable == other.draggable && flat == other.flat && icon == other.icon && infoWindow == other.infoWindow && position == other.position && rotation == other.rotation && visible == other.visible && zIndex == other.zIndex; } @override int get hashCode => markerId.hashCode; @override String toString() { return 'Marker{markerId: $markerId, alpha: $alpha, anchor: $anchor, ' 'consumeTapEvents: $consumeTapEvents, draggable: $draggable, flat: $flat, ' 'icon: $icon, infoWindow: $infoWindow, position: $position, rotation: $rotation, ' 'visible: $visible, zIndex: $zIndex, onTap: $onTap, onDragStart: $onDragStart, ' 'onDrag: $onDrag, onDragEnd: $onDragEnd}'; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker_updates.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'types.dart'; /// [Marker] update events to be applied to the [GoogleMap]. /// /// Used in [GoogleMapController] when the map is updated. // (Do not re-export) class MarkerUpdates extends MapsObjectUpdates { /// Computes [MarkerUpdates] given previous and current [Marker]s. MarkerUpdates.from(Set previous, Set current) : super.from(previous, current, objectName: 'marker'); /// Set of Markers to be added in this update. Set get markersToAdd => objectsToAdd; /// Set of MarkerIds to be removed in this update. Set get markerIdsToRemove => objectIdsToRemove.cast(); /// Set of Markers to be changed in this update. Set get markersToChange => objectsToChange; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/pattern_item.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart' show immutable; /// Item used in the stroke pattern for a Polyline. @immutable class PatternItem { const PatternItem._(this._json); /// A dot used in the stroke pattern for a [Polyline]. static const PatternItem dot = PatternItem._(['dot']); /// A dash used in the stroke pattern for a [Polyline]. /// /// [length] has to be non-negative. static PatternItem dash(double length) { assert(length >= 0.0); return PatternItem._(['dash', length]); } /// A gap used in the stroke pattern for a [Polyline]. /// /// [length] has to be non-negative. static PatternItem gap(double length) { assert(length >= 0.0); return PatternItem._(['gap', length]); } final Object _json; /// Converts this object to something serializable in JSON. Object toJson() => _json; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polygon.dart ================================================ // Copyright 2013 The Flutter 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:collection/collection.dart'; import 'package:flutter/foundation.dart' show immutable, listEquals, VoidCallback; import 'package:flutter/material.dart' show Color, Colors; import 'types.dart'; /// Uniquely identifies a [Polygon] among [GoogleMap] polygons. /// /// This does not have to be globally unique, only unique among the list. @immutable class PolygonId extends MapsObjectId { /// Creates an immutable identifier for a [Polygon]. const PolygonId(String value) : super(value); } /// Draws a polygon through geographical locations on the map. @immutable class Polygon implements MapsObject { /// Creates an immutable representation of a polygon through geographical locations on the map. const Polygon({ required this.polygonId, this.consumeTapEvents = false, this.fillColor = Colors.black, this.geodesic = false, this.points = const [], this.holes = const >[], this.strokeColor = Colors.black, this.strokeWidth = 10, this.visible = true, this.zIndex = 0, this.onTap, }); /// Uniquely identifies a [Polygon]. final PolygonId polygonId; @override PolygonId get mapsId => polygonId; /// True if the [Polygon] consumes tap events. /// /// If this is false, [onTap] callback will not be triggered. final bool consumeTapEvents; /// Fill color in ARGB format, the same format used by Color. The default value is black (0xff000000). final Color fillColor; /// Indicates whether the segments of the polygon should be drawn as geodesics, as opposed to straight lines /// on the Mercator projection. /// /// A geodesic is the shortest path between two points on the Earth's surface. /// The geodesic curve is constructed assuming the Earth is a sphere final bool geodesic; /// The vertices of the polygon to be drawn. /// /// Line segments are drawn between consecutive points. A polygon is not closed by /// default; to form a closed polygon, the start and end points must be the same. final List points; /// To create an empty area within a polygon, you need to use holes. /// To create the hole, the coordinates defining the hole path must be inside the polygon. /// /// The vertices of the holes to be cut out of polygon. /// /// Line segments of each points of hole are drawn inside polygon between consecutive hole points. final List> holes; /// True if the marker is visible. final bool visible; /// Line color in ARGB format, the same format used by Color. The default value is black (0xff000000). final Color strokeColor; /// Width of the polygon, used to define the width of the line to be drawn. /// /// The width is constant and independent of the camera's zoom level. /// The default value is 10. final int strokeWidth; /// The z-index of the polygon, used to determine relative drawing order of /// map overlays. /// /// Overlays are drawn in order of z-index, so that lower values means drawn /// earlier, and thus appearing to be closer to the surface of the Earth. final int zIndex; /// Callbacks to receive tap events for polygon placed on this map. final VoidCallback? onTap; /// Creates a new [Polygon] object whose values are the same as this instance, /// unless overwritten by the specified parameters. Polygon copyWith({ bool? consumeTapEventsParam, Color? fillColorParam, bool? geodesicParam, List? pointsParam, List>? holesParam, Color? strokeColorParam, int? strokeWidthParam, bool? visibleParam, int? zIndexParam, VoidCallback? onTapParam, }) { return Polygon( polygonId: polygonId, consumeTapEvents: consumeTapEventsParam ?? consumeTapEvents, fillColor: fillColorParam ?? fillColor, geodesic: geodesicParam ?? geodesic, points: pointsParam ?? points, holes: holesParam ?? holes, strokeColor: strokeColorParam ?? strokeColor, strokeWidth: strokeWidthParam ?? strokeWidth, visible: visibleParam ?? visible, onTap: onTapParam ?? onTap, zIndex: zIndexParam ?? zIndex, ); } /// Creates a new [Polygon] object whose values are the same as this instance. @override Polygon clone() { return copyWith(pointsParam: List.of(points)); } /// Converts this object to something serializable in JSON. @override Object toJson() { final Map json = {}; void addIfPresent(String fieldName, Object? value) { if (value != null) { json[fieldName] = value; } } addIfPresent('polygonId', polygonId.value); addIfPresent('consumeTapEvents', consumeTapEvents); addIfPresent('fillColor', fillColor.value); addIfPresent('geodesic', geodesic); addIfPresent('strokeColor', strokeColor.value); addIfPresent('strokeWidth', strokeWidth); addIfPresent('visible', visible); addIfPresent('zIndex', zIndex); if (points != null) { json['points'] = _pointsToJson(); } if (holes != null) { json['holes'] = _holesToJson(); } return json; } @override bool operator ==(Object other) { if (identical(this, other)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is Polygon && polygonId == other.polygonId && consumeTapEvents == other.consumeTapEvents && fillColor == other.fillColor && geodesic == other.geodesic && listEquals(points, other.points) && const DeepCollectionEquality().equals(holes, other.holes) && visible == other.visible && strokeColor == other.strokeColor && strokeWidth == other.strokeWidth && zIndex == other.zIndex; } @override int get hashCode => polygonId.hashCode; Object _pointsToJson() { final List result = []; for (final LatLng point in points) { result.add(point.toJson()); } return result; } List> _holesToJson() { final List> result = >[]; for (final List hole in holes) { final List jsonHole = []; for (final LatLng point in hole) { jsonHole.add(point.toJson()); } result.add(jsonHole); } return result; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polygon_updates.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'types.dart'; /// [Polygon] update events to be applied to the [GoogleMap]. /// /// Used in [GoogleMapController] when the map is updated. // (Do not re-export) class PolygonUpdates extends MapsObjectUpdates { /// Computes [PolygonUpdates] given previous and current [Polygon]s. PolygonUpdates.from(Set previous, Set current) : super.from(previous, current, objectName: 'polygon'); /// Set of Polygons to be added in this update. Set get polygonsToAdd => objectsToAdd; /// Set of PolygonIds to be removed in this update. Set get polygonIdsToRemove => objectIdsToRemove.cast(); /// Set of Polygons to be changed in this update. Set get polygonsToChange => objectsToChange; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polyline.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart' show immutable, listEquals, VoidCallback; import 'package:flutter/material.dart' show Color, Colors; import 'types.dart'; /// Uniquely identifies a [Polyline] among [GoogleMap] polylines. /// /// This does not have to be globally unique, only unique among the list. @immutable class PolylineId extends MapsObjectId { /// Creates an immutable object representing a [PolylineId] among [GoogleMap] polylines. /// /// An [AssertionError] will be thrown if [value] is null. const PolylineId(String value) : super(value); } /// Draws a line through geographical locations on the map. @immutable class Polyline implements MapsObject { /// Creates an immutable object representing a line drawn through geographical locations on the map. const Polyline({ required this.polylineId, this.consumeTapEvents = false, this.color = Colors.black, this.endCap = Cap.buttCap, this.geodesic = false, this.jointType = JointType.mitered, this.points = const [], this.patterns = const [], this.startCap = Cap.buttCap, this.visible = true, this.width = 10, this.zIndex = 0, this.onTap, }); /// Uniquely identifies a [Polyline]. final PolylineId polylineId; @override PolylineId get mapsId => polylineId; /// True if the [Polyline] consumes tap events. /// /// If this is false, [onTap] callback will not be triggered. final bool consumeTapEvents; /// Line segment color in ARGB format, the same format used by Color. The default value is black (0xff000000). final Color color; /// Indicates whether the segments of the polyline should be drawn as geodesics, as opposed to straight lines /// on the Mercator projection. /// /// A geodesic is the shortest path between two points on the Earth's surface. /// The geodesic curve is constructed assuming the Earth is a sphere final bool geodesic; /// Joint type of the polyline line segments. /// /// The joint type defines the shape to be used when joining adjacent line segments at all vertices of the /// polyline except the start and end vertices. See [JointType] for supported joint types. The default value is /// mitered. /// /// Supported on Android only. final JointType jointType; /// The stroke pattern for the polyline. /// /// Solid or a sequence of PatternItem objects to be repeated along the line. /// Available PatternItem types: Gap (defined by gap length in pixels), Dash (defined by line width and dash /// length in pixels) and Dot (circular, centered on the line, diameter defined by line width in pixels). final List patterns; /// The vertices of the polyline to be drawn. /// /// Line segments are drawn between consecutive points. A polyline is not closed by /// default; to form a closed polyline, the start and end points must be the same. final List points; /// The cap at the start vertex of the polyline. /// /// The default start cap is ButtCap. /// /// Supported on Android only. final Cap startCap; /// The cap at the end vertex of the polyline. /// /// The default end cap is ButtCap. /// /// Supported on Android only. final Cap endCap; /// True if the marker is visible. final bool visible; /// Width of the polyline, used to define the width of the line segment to be drawn. /// /// The width is constant and independent of the camera's zoom level. /// The default value is 10. final int width; /// The z-index of the polyline, used to determine relative drawing order of /// map overlays. /// /// Overlays are drawn in order of z-index, so that lower values means drawn /// earlier, and thus appearing to be closer to the surface of the Earth. final int zIndex; /// Callbacks to receive tap events for polyline placed on this map. final VoidCallback? onTap; /// Creates a new [Polyline] object whose values are the same as this instance, /// unless overwritten by the specified parameters. Polyline copyWith({ Color? colorParam, bool? consumeTapEventsParam, Cap? endCapParam, bool? geodesicParam, JointType? jointTypeParam, List? patternsParam, List? pointsParam, Cap? startCapParam, bool? visibleParam, int? widthParam, int? zIndexParam, VoidCallback? onTapParam, }) { return Polyline( polylineId: polylineId, color: colorParam ?? color, consumeTapEvents: consumeTapEventsParam ?? consumeTapEvents, endCap: endCapParam ?? endCap, geodesic: geodesicParam ?? geodesic, jointType: jointTypeParam ?? jointType, patterns: patternsParam ?? patterns, points: pointsParam ?? points, startCap: startCapParam ?? startCap, visible: visibleParam ?? visible, width: widthParam ?? width, onTap: onTapParam ?? onTap, zIndex: zIndexParam ?? zIndex, ); } /// Creates a new [Polyline] object whose values are the same as this /// instance. @override Polyline clone() { return copyWith( patternsParam: List.of(patterns), pointsParam: List.of(points), ); } /// Converts this object to something serializable in JSON. @override Object toJson() { final Map json = {}; void addIfPresent(String fieldName, Object? value) { if (value != null) { json[fieldName] = value; } } addIfPresent('polylineId', polylineId.value); addIfPresent('consumeTapEvents', consumeTapEvents); addIfPresent('color', color.value); addIfPresent('endCap', endCap.toJson()); addIfPresent('geodesic', geodesic); addIfPresent('jointType', jointType.value); addIfPresent('startCap', startCap.toJson()); addIfPresent('visible', visible); addIfPresent('width', width); addIfPresent('zIndex', zIndex); if (points != null) { json['points'] = _pointsToJson(); } if (patterns != null) { json['pattern'] = _patternToJson(); } return json; } @override bool operator ==(Object other) { if (identical(this, other)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is Polyline && polylineId == other.polylineId && consumeTapEvents == other.consumeTapEvents && color == other.color && geodesic == other.geodesic && jointType == other.jointType && listEquals(patterns, other.patterns) && listEquals(points, other.points) && startCap == other.startCap && endCap == other.endCap && visible == other.visible && width == other.width && zIndex == other.zIndex; } @override int get hashCode => polylineId.hashCode; Object _pointsToJson() { final List result = []; for (final LatLng point in points) { result.add(point.toJson()); } return result; } Object _patternToJson() { final List result = []; for (final PatternItem patternItem in patterns) { if (patternItem != null) { result.add(patternItem.toJson()); } } return result; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polyline_updates.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'types.dart'; /// [Polyline] update events to be applied to the [GoogleMap]. /// /// Used in [GoogleMapController] when the map is updated. // (Do not re-export) class PolylineUpdates extends MapsObjectUpdates { /// Computes [PolylineUpdates] given previous and current [Polyline]s. PolylineUpdates.from(Set previous, Set current) : super.from(previous, current, objectName: 'polyline'); /// Set of Polylines to be added in this update. Set get polylinesToAdd => objectsToAdd; /// Set of PolylineIds to be removed in this update. Set get polylineIdsToRemove => objectIdsToRemove.cast(); /// Set of Polylines to be changed in this update. Set get polylinesToChange => objectsToChange; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/screen_coordinate.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart' show immutable, objectRuntimeType; /// Represents a point coordinate in the [GoogleMap]'s view. /// /// The screen location is specified in screen pixels (not display pixels) relative /// to the top left of the map, not top left of the whole screen. (x, y) = (0, 0) /// corresponds to top-left of the [GoogleMap] not the whole screen. @immutable class ScreenCoordinate { /// Creates an immutable representation of a point coordinate in the [GoogleMap]'s view. const ScreenCoordinate({ required this.x, required this.y, }); /// Represents the number of pixels from the left of the [GoogleMap]. final int x; /// Represents the number of pixels from the top of the [GoogleMap]. final int y; /// Converts this object to something serializable in JSON. Object toJson() { return { 'x': x, 'y': y, }; } @override String toString() => '${objectRuntimeType(this, 'ScreenCoordinate')}($x, $y)'; @override bool operator ==(Object other) { return other is ScreenCoordinate && other.x == x && other.y == y; } @override int get hashCode => Object.hash(x, y); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:typed_data'; import 'package:flutter/foundation.dart' show immutable; /// Contains information about a Tile that is returned by a [TileProvider]. @immutable class Tile { /// Creates an immutable representation of a [Tile] to draw by [TileProvider]. const Tile(this.width, this.height, this.data); /// The width of the image encoded by data in logical pixels. final int width; /// The height of the image encoded by data in logical pixels. final int height; /// A byte array containing the image data. /// /// The image data format must be natively supported for decoding by the platform. /// e.g on Android it can only be one of the [supported image formats for decoding](https://developer.android.com/guide/topics/media/media-formats#image-formats). final Uint8List? data; /// Converts this object to JSON. Object toJson() { final Map json = {}; void addIfPresent(String fieldName, Object? value) { if (value != null) { json[fieldName] = value; } } addIfPresent('width', width); addIfPresent('height', height); addIfPresent('data', data); return json; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart' show immutable; import 'types.dart'; /// Uniquely identifies a [TileOverlay] among [GoogleMap] tile overlays. @immutable class TileOverlayId extends MapsObjectId { /// Creates an immutable identifier for a [TileOverlay]. const TileOverlayId(String value) : super(value); } /// A set of images which are displayed on top of the base map tiles. /// /// These tiles may be transparent, allowing you to add features to existing maps. /// /// ## Tile Coordinates /// /// Note that the world is projected using the Mercator projection /// (see [Wikipedia](https://en.wikipedia.org/wiki/Mercator_projection)) with the left (west) side /// of the map corresponding to -180 degrees of longitude and the right (east) side of the map /// corresponding to 180 degrees of longitude. To make the map square, the top (north) side of the /// map corresponds to 85.0511 degrees of latitude and the bottom (south) side of the map /// corresponds to -85.0511 degrees of latitude. Areas outside this latitude range are not rendered. /// /// At each zoom level, the map is divided into tiles and only the tiles that overlap the screen are /// downloaded and rendered. Each tile is square and the map is divided into tiles as follows: /// /// * At zoom level 0, one tile represents the entire world. The coordinates of that tile are /// (x, y) = (0, 0). /// * At zoom level 1, the world is divided into 4 tiles arranged in a 2 x 2 grid. /// * ... /// * At zoom level N, the world is divided into 4N tiles arranged in a 2N x 2N grid. /// /// Note that the minimum zoom level that the camera supports (which can depend on various factors) /// is GoogleMap.getMinZoomLevel and the maximum zoom level is GoogleMap.getMaxZoomLevel. /// /// The coordinates of the tiles are measured from the top left (northwest) corner of the map. /// At zoom level N, the x values of the tile coordinates range from 0 to 2N - 1 and increase from /// west to east and the y values range from 0 to 2N - 1 and increase from north to south. @immutable class TileOverlay implements MapsObject { /// Creates an immutable representation of a [TileOverlay] to draw on [GoogleMap]. const TileOverlay({ required this.tileOverlayId, this.fadeIn = true, this.tileProvider, this.transparency = 0.0, this.zIndex = 0, this.visible = true, this.tileSize = 256, }) : assert(transparency >= 0.0 && transparency <= 1.0); /// Uniquely identifies a [TileOverlay]. final TileOverlayId tileOverlayId; @override TileOverlayId get mapsId => tileOverlayId; /// Whether the tiles should fade in. The default is true. final bool fadeIn; /// The tile provider to use for this tile overlay. final TileProvider? tileProvider; /// The transparency of the tile overlay. The default transparency is 0 (opaque). final double transparency; /// The tile overlay's zIndex, i.e., the order in which it will be drawn where /// overlays with larger values are drawn above those with lower values final int zIndex; /// The visibility for the tile overlay. The default visibility is true. final bool visible; /// Specifies the number of logical pixels (not points) that the returned tile images will prefer /// to display as. iOS only. /// /// Defaults to 256, which is the traditional size of Google Maps tiles. /// As an example, an application developer may wish to provide retina tiles (512 pixel edge length) /// on retina devices, to keep the same number of tiles per view as the default value of 256 /// would give on a non-retina device. final int tileSize; /// Creates a new [TileOverlay] object whose values are the same as this instance, /// unless overwritten by the specified parameters. TileOverlay copyWith({ bool? fadeInParam, TileProvider? tileProviderParam, double? transparencyParam, int? zIndexParam, bool? visibleParam, int? tileSizeParam, }) { return TileOverlay( tileOverlayId: tileOverlayId, fadeIn: fadeInParam ?? fadeIn, tileProvider: tileProviderParam ?? tileProvider, transparency: transparencyParam ?? transparency, zIndex: zIndexParam ?? zIndex, visible: visibleParam ?? visible, tileSize: tileSizeParam ?? tileSize, ); } @override TileOverlay clone() => copyWith(); /// Converts this object to JSON. @override Object toJson() { final Map json = {}; void addIfPresent(String fieldName, Object? value) { if (value != null) { json[fieldName] = value; } } addIfPresent('tileOverlayId', tileOverlayId.value); addIfPresent('fadeIn', fadeIn); addIfPresent('transparency', transparency); addIfPresent('zIndex', zIndex); addIfPresent('visible', visible); addIfPresent('tileSize', tileSize); return json; } @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) { return false; } return other is TileOverlay && tileOverlayId == other.tileOverlayId && fadeIn == other.fadeIn && tileProvider == other.tileProvider && transparency == other.transparency && zIndex == other.zIndex && visible == other.visible && tileSize == other.tileSize; } @override int get hashCode => Object.hash(tileOverlayId, fadeIn, tileProvider, transparency, zIndex, visible, tileSize); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay_updates.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'types.dart'; /// Update specification for a set of [TileOverlay]s. class TileOverlayUpdates extends MapsObjectUpdates { /// Computes [TileOverlayUpdates] given previous and current [TileOverlay]s. TileOverlayUpdates.from(Set previous, Set current) : super.from(previous, current, objectName: 'tileOverlay'); /// Set of TileOverlays to be added in this update. Set get tileOverlaysToAdd => objectsToAdd; /// Set of TileOverlayIds to be removed in this update. Set get tileOverlayIdsToRemove => objectIdsToRemove.cast(); /// Set of TileOverlays to be changed in this update. Set get tileOverlaysToChange => objectsToChange; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_provider.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'types.dart'; /// An interface for a class that provides the tile images for a TileOverlay. abstract class TileProvider { /// Stub tile that is used to indicate that no tile exists for a specific tile coordinate. static const Tile noTile = Tile(-1, -1, null); /// Returns the tile to be used for this tile coordinate. /// /// See [TileOverlay] for the specification of tile coordinates. Future getTile(int x, int y, int? zoom); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // All the public types exposed by this package. export 'bitmap.dart'; export 'callbacks.dart'; export 'camera.dart'; export 'cap.dart'; export 'circle.dart'; export 'circle_updates.dart'; export 'joint_type.dart'; export 'location.dart'; export 'map_configuration.dart'; export 'map_objects.dart'; export 'map_widget_configuration.dart'; export 'maps_object.dart'; export 'maps_object_updates.dart'; export 'marker.dart'; export 'marker_updates.dart'; export 'pattern_item.dart'; export 'polygon.dart'; export 'polygon_updates.dart'; export 'polyline.dart'; export 'polyline_updates.dart'; export 'screen_coordinate.dart'; export 'tile.dart'; export 'tile_overlay.dart'; export 'tile_provider.dart'; export 'ui.dart'; // Export the utils used by the Widget export 'utils/circle.dart'; export 'utils/marker.dart'; export 'utils/polygon.dart'; export 'utils/polyline.dart'; export 'utils/tile_overlay.dart'; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ui.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'types.dart'; /// Type of map tiles to display. // Enum constants must be indexed to match the corresponding int constants of // the Android platform API, see // enum MapType { /// Do not display map tiles. none, /// Normal tiles (traffic and labels, subtle terrain information). normal, /// Satellite imaging tiles (aerial photos) satellite, /// Terrain tiles (indicates type and height of terrain) terrain, /// Hybrid tiles (satellite images with some labels/overlays) hybrid, } /// Bounds for the map camera target. // Used with [GoogleMapOptions] to wrap a [LatLngBounds] value. This allows // distinguishing between specifying an unbounded target (null `LatLngBounds`) // from not specifying anything (null `CameraTargetBounds`). @immutable class CameraTargetBounds { /// Creates a camera target bounds with the specified bounding box, or null /// to indicate that the camera target is not bounded. const CameraTargetBounds(this.bounds); /// The geographical bounding box for the map camera target. /// /// A null value means the camera target is unbounded. final LatLngBounds? bounds; /// Unbounded camera target. static const CameraTargetBounds unbounded = CameraTargetBounds(null); /// Converts this object to something serializable in JSON. Object toJson() => [bounds?.toJson()]; @override bool operator ==(Object other) { if (identical(this, other)) { return true; } if (runtimeType != other.runtimeType) { return false; } return other is CameraTargetBounds && bounds == other.bounds; } @override int get hashCode => bounds.hashCode; @override String toString() { return 'CameraTargetBounds(bounds: $bounds)'; } } /// Preferred bounds for map camera zoom level. // Used with [GoogleMapOptions] to wrap min and max zoom. This allows // distinguishing between specifying unbounded zooming (null `minZoom` and // `maxZoom`) from not specifying anything (null `MinMaxZoomPreference`). @immutable class MinMaxZoomPreference { /// Creates a immutable representation of the preferred minimum and maximum zoom values for the map camera. /// /// [AssertionError] will be thrown if [minZoom] > [maxZoom]. const MinMaxZoomPreference(this.minZoom, this.maxZoom) : assert(minZoom == null || maxZoom == null || minZoom <= maxZoom); /// The preferred minimum zoom level or null, if unbounded from below. final double? minZoom; /// The preferred maximum zoom level or null, if unbounded from above. final double? maxZoom; /// Unbounded zooming. static const MinMaxZoomPreference unbounded = MinMaxZoomPreference(null, null); /// Converts this object to something serializable in JSON. Object toJson() => [minZoom, maxZoom]; @override bool operator ==(Object other) { if (identical(this, other)) { return true; } if (runtimeType != other.runtimeType) { return false; } return other is MinMaxZoomPreference && minZoom == other.minZoom && maxZoom == other.maxZoom; } @override int get hashCode => Object.hash(minZoom, maxZoom); @override String toString() { return 'MinMaxZoomPreference(minZoom: $minZoom, maxZoom: $maxZoom)'; } } /// Exception when a map style is invalid or was unable to be set. /// /// See also: `setStyle` on [GoogleMapController] for why this exception /// might be thrown. class MapStyleException implements Exception { /// Default constructor for [MapStyleException]. const MapStyleException(this.cause); /// The reason `GoogleMapController.setStyle` would throw this exception. final String cause; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/circle.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import '../types.dart'; import 'maps_object.dart'; /// Converts an [Iterable] of Circles in a Map of CircleId -> Circle. Map keyByCircleId(Iterable circles) { return keyByMapsObjectId(circles).cast(); } /// Converts a Set of Circles into something serializable in JSON. Object serializeCircleSet(Set circles) { return serializeMapsObjectSet(circles); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/map_configuration_serialization.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import '../map_configuration.dart'; /// Returns a JSON representation of [config]. /// /// This is intended for two purposes: /// - Conversion of [MapConfiguration] to the map options dictionary used by /// legacy platform interface methods. /// - Conversion of [MapConfiguration] to the default method channel /// implementation's representation. /// /// Both of these are parts of the public interface, so any change to the /// representation other than adding a new field requires a breaking change to /// the package. Map jsonForMapConfiguration(MapConfiguration config) { final EdgeInsets? padding = config.padding; return { if (config.compassEnabled != null) 'compassEnabled': config.compassEnabled!, if (config.mapToolbarEnabled != null) 'mapToolbarEnabled': config.mapToolbarEnabled!, if (config.cameraTargetBounds != null) 'cameraTargetBounds': config.cameraTargetBounds!.toJson(), if (config.mapType != null) 'mapType': config.mapType!.index, if (config.minMaxZoomPreference != null) 'minMaxZoomPreference': config.minMaxZoomPreference!.toJson(), if (config.rotateGesturesEnabled != null) 'rotateGesturesEnabled': config.rotateGesturesEnabled!, if (config.scrollGesturesEnabled != null) 'scrollGesturesEnabled': config.scrollGesturesEnabled!, if (config.tiltGesturesEnabled != null) 'tiltGesturesEnabled': config.tiltGesturesEnabled!, if (config.zoomControlsEnabled != null) 'zoomControlsEnabled': config.zoomControlsEnabled!, if (config.zoomGesturesEnabled != null) 'zoomGesturesEnabled': config.zoomGesturesEnabled!, if (config.liteModeEnabled != null) 'liteModeEnabled': config.liteModeEnabled!, if (config.trackCameraPosition != null) 'trackCameraPosition': config.trackCameraPosition!, if (config.myLocationEnabled != null) 'myLocationEnabled': config.myLocationEnabled!, if (config.myLocationButtonEnabled != null) 'myLocationButtonEnabled': config.myLocationButtonEnabled!, if (padding != null) 'padding': [ padding.top, padding.left, padding.bottom, padding.right, ], if (config.indoorViewEnabled != null) 'indoorEnabled': config.indoorViewEnabled!, if (config.trafficEnabled != null) 'trafficEnabled': config.trafficEnabled!, if (config.buildingsEnabled != null) 'buildingsEnabled': config.buildingsEnabled!, }; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/maps_object.dart ================================================ // Copyright 2013 The Flutter 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 '../maps_object.dart'; /// Converts an [Iterable] of [MapsObject]s in a Map of [MapObjectId] -> [MapObject]. Map, T> keyByMapsObjectId>( Iterable objects) { return Map, T>.fromEntries(objects.map((T object) => MapEntry, T>(object.mapsId, object.clone()))); } /// Converts a Set of [MapsObject]s into something serializable in JSON. Object serializeMapsObjectSet(Set> mapsObjects) { return mapsObjects.map((MapsObject p) => p.toJson()).toList(); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/marker.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import '../types.dart'; import 'maps_object.dart'; /// Converts an [Iterable] of Markers in a Map of MarkerId -> Marker. Map keyByMarkerId(Iterable markers) { return keyByMapsObjectId(markers).cast(); } /// Converts a Set of Markers into something serializable in JSON. Object serializeMarkerSet(Set markers) { return serializeMapsObjectSet(markers); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/polygon.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import '../types.dart'; import 'maps_object.dart'; /// Converts an [Iterable] of Polygons in a Map of PolygonId -> Polygon. Map keyByPolygonId(Iterable polygons) { return keyByMapsObjectId(polygons).cast(); } /// Converts a Set of Polygons into something serializable in JSON. Object serializePolygonSet(Set polygons) { return serializeMapsObjectSet(polygons); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/polyline.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import '../types.dart'; import 'maps_object.dart'; /// Converts an [Iterable] of Polylines in a Map of PolylineId -> Polyline. Map keyByPolylineId(Iterable polylines) { return keyByMapsObjectId(polylines).cast(); } /// Converts a Set of Polylines into something serializable in JSON. Object serializePolylineSet(Set polylines) { return serializeMapsObjectSet(polylines); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/tile_overlay.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import '../types.dart'; import 'maps_object.dart'; /// Converts an [Iterable] of TileOverlay in a Map of TileOverlayId -> TileOverlay. Map keyTileOverlayId( Iterable tileOverlays) { return keyByMapsObjectId(tileOverlays) .cast(); } /// Converts a Set of TileOverlays into something serializable in JSON. Object serializeTileOverlaySet(Set tileOverlays) { return serializeMapsObjectSet(tileOverlays); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml ================================================ name: google_maps_flutter_platform_interface description: A common platform interface for the google_maps_flutter plugin. repository: https://github.com/flutter/plugins/tree/main/packages/google_maps_flutter/google_maps_flutter_platform_interface 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.5 environment: sdk: '>=2.12.0 <3.0.0' flutter: ">=3.0.0" dependencies: collection: ^1.15.0 flutter: sdk: flutter plugin_platform_interface: ^2.1.0 stream_transform: ^2.0.0 dev_dependencies: async: ^2.5.0 flutter_test: sdk: flutter mockito: ^5.0.0 ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart ================================================ // Copyright 2013 The Flutter 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:async/async.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$MethodChannelGoogleMapsFlutter', () { late List log; setUp(() async { log = []; }); /// Initializes a map with the given ID and canned responses, logging all /// calls to [log]. void configureMockMap( MethodChannelGoogleMapsFlutter maps, { required int mapId, required Future? Function(MethodCall call) handler, }) { final MethodChannel channel = maps.ensureChannelInitialized(mapId); _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( channel, (MethodCall methodCall) { log.add(methodCall.method); return handler(methodCall); }, ); } Future sendPlatformMessage( int mapId, String method, Map data) async { final ByteData byteData = const StandardMethodCodec() .encodeMethodCall(MethodCall(method, data)); await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .handlePlatformMessage('plugins.flutter.io/google_maps_$mapId', byteData, (ByteData? data) {}); } // Calls each method that uses invokeMethod with a return type other than // void to ensure that the casting/nullability handling succeeds. // // TODO(stuartmorgan): Remove this once there is real test coverage of // each method, since that would cover this issue. test('non-void invokeMethods handle types correctly', () async { const int mapId = 0; final MethodChannelGoogleMapsFlutter maps = MethodChannelGoogleMapsFlutter(); configureMockMap(maps, mapId: mapId, handler: (MethodCall methodCall) async { switch (methodCall.method) { case 'map#getLatLng': return [1.0, 2.0]; case 'markers#isInfoWindowShown': return true; case 'map#getZoomLevel': return 2.5; case 'map#takeSnapshot': return null; } }); await maps.getLatLng(const ScreenCoordinate(x: 0, y: 0), mapId: mapId); await maps.isMarkerInfoWindowShown(const MarkerId(''), mapId: mapId); await maps.getZoomLevel(mapId: mapId); await maps.takeSnapshot(mapId: mapId); // Check that all the invokeMethod calls happened. expect(log, [ 'map#getLatLng', 'markers#isInfoWindowShown', 'map#getZoomLevel', 'map#takeSnapshot', ]); }); test('markers send drag event to correct streams', () async { const int mapId = 1; final Map jsonMarkerDragStartEvent = { 'mapId': mapId, 'markerId': 'drag-start-marker', 'position': [1.0, 1.0] }; final Map jsonMarkerDragEvent = { 'mapId': mapId, 'markerId': 'drag-marker', 'position': [1.0, 1.0] }; final Map jsonMarkerDragEndEvent = { 'mapId': mapId, 'markerId': 'drag-end-marker', 'position': [1.0, 1.0] }; final MethodChannelGoogleMapsFlutter maps = MethodChannelGoogleMapsFlutter(); maps.ensureChannelInitialized(mapId); final StreamQueue markerDragStartStream = StreamQueue( maps.onMarkerDragStart(mapId: mapId)); final StreamQueue markerDragStream = StreamQueue(maps.onMarkerDrag(mapId: mapId)); final StreamQueue markerDragEndStream = StreamQueue(maps.onMarkerDragEnd(mapId: mapId)); await sendPlatformMessage( mapId, 'marker#onDragStart', jsonMarkerDragStartEvent); await sendPlatformMessage(mapId, 'marker#onDrag', jsonMarkerDragEvent); await sendPlatformMessage( mapId, 'marker#onDragEnd', jsonMarkerDragEndEvent); expect((await markerDragStartStream.next).value.value, equals('drag-start-marker')); expect((await markerDragStream.next).value.value, equals('drag-marker')); expect((await markerDragEndStream.next).value.value, 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; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/test/platform_interface/google_maps_flutter_platform_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); // Store the initial instance before any tests change it. final GoogleMapsFlutterPlatform initialInstance = GoogleMapsFlutterPlatform.instance; group('$GoogleMapsFlutterPlatform', () { test('$MethodChannelGoogleMapsFlutter() is the default instance', () { expect(initialInstance, isInstanceOf()); }); test('Cannot be implemented with `implements`', () { expect(() { GoogleMapsFlutterPlatform.instance = ImplementsGoogleMapsFlutterPlatform(); // In versions of `package:plugin_platform_interface` prior to fixing // https://github.com/flutter/flutter/issues/109339, an attempt to // implement a platform interface using `implements` would sometimes // throw a `NoSuchMethodError` and other times throw an // `AssertionError`. After the issue is fixed, an `AssertionError` will // always be thrown. For the purpose of this test, we don't really care // what exception is thrown, so just allow any exception. }, throwsA(anything)); }); test('Can be mocked with `implements`', () { final GoogleMapsFlutterPlatformMock mock = GoogleMapsFlutterPlatformMock(); GoogleMapsFlutterPlatform.instance = mock; }); test('Can be extended', () { GoogleMapsFlutterPlatform.instance = ExtendsGoogleMapsFlutterPlatform(); }); test( 'default implementation of `buildViewWithTextDirection` delegates to `buildView`', () { final GoogleMapsFlutterPlatform platform = BuildViewGoogleMapsFlutterPlatform(); expect( platform.buildViewWithTextDirection( 0, (_) {}, initialCameraPosition: const CameraPosition(target: LatLng(0.0, 0.0)), textDirection: TextDirection.ltr, ), isA(), ); }, ); test( 'default implementation of `buildViewWithConfiguration` delegates to `buildViewWithTextDirection`', () { final GoogleMapsFlutterPlatform platform = BuildViewGoogleMapsFlutterPlatform(); expect( platform.buildViewWithConfiguration( 0, (_) {}, widgetConfiguration: const MapWidgetConfiguration( initialCameraPosition: CameraPosition(target: LatLng(0.0, 0.0)), textDirection: TextDirection.ltr, ), ), isA(), ); }, ); }); } class GoogleMapsFlutterPlatformMock extends Mock with MockPlatformInterfaceMixin implements GoogleMapsFlutterPlatform {} class ImplementsGoogleMapsFlutterPlatform extends Mock implements GoogleMapsFlutterPlatform {} class ExtendsGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform {} class BuildViewGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform { @override Widget buildView( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { required CameraPosition initialCameraPosition, Set markers = const {}, Set polygons = const {}, Set polylines = const {}, Set circles = const {}, Set tileOverlays = const {}, Set>? gestureRecognizers = const >{}, Map mapOptions = const {}, }) { return const Text(''); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/test/platform_interface/google_maps_inspector_platform_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:mockito/mockito.dart'; void main() { // Store the initial instance before any tests change it. final GoogleMapsInspectorPlatform? initialInstance = GoogleMapsInspectorPlatform.instance; test('default instance is null', () { expect(initialInstance, isNull); }); test('cannot be implemented with `implements`', () { expect(() { GoogleMapsInspectorPlatform.instance = ImplementsGoogleMapsInspectorPlatform(); }, throwsA(isInstanceOf())); }); test('can be implement with `extends`', () { GoogleMapsInspectorPlatform.instance = ExtendsGoogleMapsInspectorPlatform(); }); } class ImplementsGoogleMapsInspectorPlatform extends Mock implements GoogleMapsInspectorPlatform {} class ExtendsGoogleMapsInspectorPlatform extends GoogleMapsInspectorPlatform {} ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/bitmap_test.dart ================================================ // Copyright 2013 The Flutter 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:unnecessary_import import 'dart:typed_data'; import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$BitmapDescriptor', () { test('toJson / fromJson', () { final BitmapDescriptor descriptor = BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueCyan); final Object json = descriptor.toJson(); // Rehydrate a new bitmap descriptor... // ignore: deprecated_member_use_from_same_package final BitmapDescriptor descriptorFromJson = BitmapDescriptor.fromJson(json); expect(descriptorFromJson, isNot(descriptor)); // New instance expect(identical(descriptorFromJson.toJson(), json), isTrue); // Same JSON }); group('fromBytes constructor', () { test('with empty byte array, throws assertion error', () { expect(() { BitmapDescriptor.fromBytes(Uint8List.fromList([])); }, throwsAssertionError); }); test('with bytes', () { final BitmapDescriptor descriptor = BitmapDescriptor.fromBytes( Uint8List.fromList([1, 2, 3]), ); expect(descriptor, isA()); expect( descriptor.toJson(), equals([ 'fromBytes', [1, 2, 3], ])); }); test('with size, not on the web, size is ignored', () { final BitmapDescriptor descriptor = BitmapDescriptor.fromBytes( Uint8List.fromList([1, 2, 3]), size: const Size(40, 20), ); expect( descriptor.toJson(), equals([ 'fromBytes', [1, 2, 3], ])); }, skip: kIsWeb); test('with size, on the web, size is preserved', () { final BitmapDescriptor descriptor = BitmapDescriptor.fromBytes( Uint8List.fromList([1, 2, 3]), size: const Size(40, 20), ); expect( descriptor.toJson(), equals([ 'fromBytes', [1, 2, 3], [40, 20], ])); }, skip: !kIsWeb); }); group('fromJson validation', () { group('type validation', () { test('correct type', () { expect(BitmapDescriptor.fromJson(['defaultMarker']), isA()); }); test('wrong type', () { expect(() { BitmapDescriptor.fromJson(['bogusType']); }, throwsAssertionError); }); }); group('defaultMarker', () { test('hue is null', () { expect(BitmapDescriptor.fromJson(['defaultMarker']), isA()); }); test('hue is number', () { expect(BitmapDescriptor.fromJson(['defaultMarker', 158]), isA()); }); test('hue is not number', () { expect(() { BitmapDescriptor.fromJson(['defaultMarker', 'nope']); }, throwsAssertionError); }); test('hue is out of range', () { expect(() { BitmapDescriptor.fromJson(['defaultMarker', -1]); }, throwsAssertionError); expect(() { BitmapDescriptor.fromJson(['defaultMarker', 361]); }, throwsAssertionError); }); }); group('fromBytes', () { test('with bytes', () { expect( BitmapDescriptor.fromJson([ 'fromBytes', Uint8List.fromList([1, 2, 3]) ]), isA()); }); test('without bytes', () { expect(() { BitmapDescriptor.fromJson(['fromBytes', null]); }, throwsAssertionError); expect(() { BitmapDescriptor.fromJson(['fromBytes', []]); }, throwsAssertionError); }); }); group('fromAsset', () { test('name is passed', () { expect( BitmapDescriptor.fromJson( ['fromAsset', 'some/path.png']), isA()); }); test('name cannot be null or empty', () { expect(() { BitmapDescriptor.fromJson(['fromAsset', null]); }, throwsAssertionError); expect(() { BitmapDescriptor.fromJson(['fromAsset', '']); }, throwsAssertionError); }); test('package is passed', () { expect( BitmapDescriptor.fromJson( ['fromAsset', 'some/path.png', 'some_package']), isA()); }); test('package cannot be null or empty', () { expect(() { BitmapDescriptor.fromJson( ['fromAsset', 'some/path.png', null]); }, throwsAssertionError); expect(() { BitmapDescriptor.fromJson( ['fromAsset', 'some/path.png', '']); }, throwsAssertionError); }); }); group('fromAssetImage', () { test('name and dpi passed', () { expect( BitmapDescriptor.fromJson( ['fromAssetImage', 'some/path.png', 1.0]), isA()); }); test('name cannot be null or empty', () { expect(() { BitmapDescriptor.fromJson(['fromAssetImage', null, 1.0]); }, throwsAssertionError); expect(() { BitmapDescriptor.fromJson(['fromAssetImage', '', 1.0]); }, throwsAssertionError); }); test('dpi must be number', () { expect(() { BitmapDescriptor.fromJson( ['fromAssetImage', 'some/path.png', null]); }, throwsAssertionError); expect(() { BitmapDescriptor.fromJson( ['fromAssetImage', 'some/path.png', 'one']); }, throwsAssertionError); }); test('with optional [width, height] List', () { expect( BitmapDescriptor.fromJson([ 'fromAssetImage', 'some/path.png', 1.0, [640, 480] ]), isA()); }); test( 'optional [width, height] List cannot be null or not contain 2 elements', () { expect(() { BitmapDescriptor.fromJson( ['fromAssetImage', 'some/path.png', 1.0, null]); }, throwsAssertionError); expect(() { BitmapDescriptor.fromJson( ['fromAssetImage', 'some/path.png', 1.0, []]); }, throwsAssertionError); expect(() { BitmapDescriptor.fromJson([ 'fromAssetImage', 'some/path.png', 1.0, [640, 480, 1024] ]); }, throwsAssertionError); }); }); }); }); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/camera_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); test('toMap / fromMap', () { const CameraPosition cameraPosition = CameraPosition( target: LatLng(10.0, 15.0), bearing: 0.5, tilt: 30.0, zoom: 1.5); // Cast to to ensure that recreating from JSON, where // type information will have likely been lost, still works. final Map json = (cameraPosition.toMap() as Map) .cast(); final CameraPosition? cameraPositionFromJson = CameraPosition.fromMap(json); expect(cameraPosition, cameraPositionFromJson); }); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/location_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('LanLng constructor', () { test('Maintains longitude precision if within acceptable range', () async { const double lat = -34.509981; const double lng = 150.792384; const LatLng latLng = LatLng(lat, lng); expect(latLng.latitude, equals(lat)); expect(latLng.longitude, equals(lng)); }); test('Normalizes longitude that is below lower limit', () async { const double lat = -34.509981; const double lng = -270.0; const LatLng latLng = LatLng(lat, lng); expect(latLng.latitude, equals(lat)); expect(latLng.longitude, equals(90.0)); }); test('Normalizes longitude that is above upper limit', () async { const double lat = -34.509981; const double lng = 270.0; const LatLng latLng = LatLng(lat, lng); expect(latLng.latitude, equals(lat)); expect(latLng.longitude, equals(-90.0)); }); test('Includes longitude set to lower limit', () async { const double lat = -34.509981; const double lng = -180.0; const LatLng latLng = LatLng(lat, lng); expect(latLng.latitude, equals(lat)); expect(latLng.longitude, equals(-180.0)); }); test('Normalizes longitude set to upper limit', () async { const double lat = -34.509981; const double lng = 180.0; const LatLng latLng = LatLng(lat, lng); expect(latLng.latitude, equals(lat)); expect(latLng.longitude, equals(-180.0)); }); }); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/map_configuration_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; void main() { group('diffs', () { // A options instance with every field set, to test diffs against. final MapConfiguration diffBase = MapConfiguration( compassEnabled: false, mapToolbarEnabled: false, cameraTargetBounds: CameraTargetBounds(LatLngBounds( northeast: const LatLng(30, 20), southwest: const LatLng(10, 40))), mapType: MapType.normal, minMaxZoomPreference: const MinMaxZoomPreference(1.0, 10.0), rotateGesturesEnabled: false, scrollGesturesEnabled: false, tiltGesturesEnabled: false, trackCameraPosition: false, zoomControlsEnabled: false, zoomGesturesEnabled: false, liteModeEnabled: false, myLocationEnabled: false, myLocationButtonEnabled: false, padding: const EdgeInsets.all(5.0), indoorViewEnabled: false, trafficEnabled: false, buildingsEnabled: false, ); test('only include changed fields', () async { const MapConfiguration nullOptions = MapConfiguration(); // Everything should be null since nothing changed. expect(diffBase.diffFrom(diffBase), nullOptions); }); test('only apply non-null fields', () async { const MapConfiguration smallDiff = MapConfiguration(compassEnabled: true); final MapConfiguration updated = diffBase.applyDiff(smallDiff); // The diff should be updated. expect(updated.compassEnabled, true); // Spot check that other fields weren't stomped. expect(updated.mapToolbarEnabled, isNot(null)); expect(updated.cameraTargetBounds, isNot(null)); expect(updated.mapType, isNot(null)); expect(updated.zoomControlsEnabled, isNot(null)); expect(updated.liteModeEnabled, isNot(null)); expect(updated.padding, isNot(null)); expect(updated.trafficEnabled, isNot(null)); }); test('handle compassEnabled', () async { const MapConfiguration diff = MapConfiguration(compassEnabled: true); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); // A diff applied to empty options should be the diff itself. expect(empty.applyDiff(diff), diff); // A diff applied to non-empty options should update that field. expect(updated.compassEnabled, true); }); test('handle mapToolbarEnabled', () async { const MapConfiguration diff = MapConfiguration(mapToolbarEnabled: true); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); // A diff applied to empty options should be the diff itself. expect(empty.applyDiff(diff), diff); // A diff applied to non-empty options should update that field. expect(updated.mapToolbarEnabled, true); }); test('handle cameraTargetBounds', () async { final CameraTargetBounds newBounds = CameraTargetBounds(LatLngBounds( northeast: const LatLng(55, 15), southwest: const LatLng(5, 15))); final MapConfiguration diff = MapConfiguration(cameraTargetBounds: newBounds); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); // A diff applied to empty options should be the diff itself. expect(empty.applyDiff(diff), diff); // A diff applied to non-empty options should update that field. expect(updated.cameraTargetBounds, newBounds); }); test('handle mapType', () async { const MapConfiguration diff = MapConfiguration(mapType: MapType.satellite); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); // A diff applied to empty options should be the diff itself. expect(empty.applyDiff(diff), diff); // A diff applied to non-empty options should update that field. expect(updated.mapType, MapType.satellite); }); test('handle minMaxZoomPreference', () async { const MinMaxZoomPreference newZoomPref = MinMaxZoomPreference(3.3, 4.5); const MapConfiguration diff = MapConfiguration(minMaxZoomPreference: newZoomPref); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); // A diff applied to empty options should be the diff itself. expect(empty.applyDiff(diff), diff); // A diff applied to non-empty options should update that field. expect(updated.minMaxZoomPreference, newZoomPref); }); test('handle rotateGesturesEnabled', () async { const MapConfiguration diff = MapConfiguration(rotateGesturesEnabled: true); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); // A diff applied to empty options should be the diff itself. expect(empty.applyDiff(diff), diff); // A diff applied to non-empty options should update that field. expect(updated.rotateGesturesEnabled, true); }); test('handle scrollGesturesEnabled', () async { const MapConfiguration diff = MapConfiguration(scrollGesturesEnabled: true); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); // A diff applied to empty options should be the diff itself. expect(empty.applyDiff(diff), diff); // A diff applied to non-empty options should update that field. expect(updated.scrollGesturesEnabled, true); }); test('handle tiltGesturesEnabled', () async { const MapConfiguration diff = MapConfiguration(tiltGesturesEnabled: true); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); // A diff applied to empty options should be the diff itself. expect(empty.applyDiff(diff), diff); // A diff applied to non-empty options should update that field. expect(updated.tiltGesturesEnabled, true); }); test('handle trackCameraPosition', () async { const MapConfiguration diff = MapConfiguration(trackCameraPosition: true); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); // A diff applied to empty options should be the diff itself. expect(empty.applyDiff(diff), diff); // A diff applied to non-empty options should update that field. expect(updated.trackCameraPosition, true); }); test('handle zoomControlsEnabled', () async { const MapConfiguration diff = MapConfiguration(zoomControlsEnabled: true); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); // A diff applied to empty options should be the diff itself. expect(empty.applyDiff(diff), diff); // A diff applied to non-empty options should update that field. expect(updated.zoomControlsEnabled, true); }); test('handle zoomGesturesEnabled', () async { const MapConfiguration diff = MapConfiguration(zoomGesturesEnabled: true); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); // A diff applied to empty options should be the diff itself. expect(empty.applyDiff(diff), diff); // A diff applied to non-empty options should update that field. expect(updated.zoomGesturesEnabled, true); }); test('handle liteModeEnabled', () async { const MapConfiguration diff = MapConfiguration(liteModeEnabled: true); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); // A diff applied to empty options should be the diff itself. expect(empty.applyDiff(diff), diff); // A diff applied to non-empty options should update that field. expect(updated.liteModeEnabled, true); }); test('handle myLocationEnabled', () async { const MapConfiguration diff = MapConfiguration(myLocationEnabled: true); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); // A diff applied to empty options should be the diff itself. expect(empty.applyDiff(diff), diff); // A diff applied to non-empty options should update that field. expect(updated.myLocationEnabled, true); }); test('handle myLocationButtonEnabled', () async { const MapConfiguration diff = MapConfiguration(myLocationButtonEnabled: true); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); // A diff applied to empty options should be the diff itself. expect(empty.applyDiff(diff), diff); // A diff applied to non-empty options should update that field. expect(updated.myLocationButtonEnabled, true); }); test('handle padding', () async { const EdgeInsets newPadding = EdgeInsets.symmetric(vertical: 1.0, horizontal: 3.0); const MapConfiguration diff = MapConfiguration(padding: newPadding); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); // A diff applied to empty options should be the diff itself. expect(empty.applyDiff(diff), diff); // A diff applied to non-empty options should update that field. expect(updated.padding, newPadding); }); test('handle indoorViewEnabled', () async { const MapConfiguration diff = MapConfiguration(indoorViewEnabled: true); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); // A diff applied to empty options should be the diff itself. expect(empty.applyDiff(diff), diff); // A diff applied to non-empty options should update that field. expect(updated.indoorViewEnabled, true); }); test('handle trafficEnabled', () async { const MapConfiguration diff = MapConfiguration(trafficEnabled: true); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); // A diff applied to empty options should be the diff itself. expect(empty.applyDiff(diff), diff); // A diff applied to non-empty options should update that field. expect(updated.trafficEnabled, true); }); test('handle buildingsEnabled', () async { const MapConfiguration diff = MapConfiguration(buildingsEnabled: true); const MapConfiguration empty = MapConfiguration(); final MapConfiguration updated = diffBase.applyDiff(diff); // A diff applied to empty options should be the diff itself. expect(empty.applyDiff(diff), diff); // A diff applied to non-empty options should update that field. expect(updated.buildingsEnabled, true); }); }); group('isEmpty', () { test('is true for empty', () async { const MapConfiguration nullOptions = MapConfiguration(); expect(nullOptions.isEmpty, true); }); test('is false with compassEnabled', () async { const MapConfiguration diff = MapConfiguration(compassEnabled: true); expect(diff.isEmpty, false); }); test('is false with mapToolbarEnabled', () async { const MapConfiguration diff = MapConfiguration(mapToolbarEnabled: true); expect(diff.isEmpty, false); }); test('is false with cameraTargetBounds', () async { final CameraTargetBounds newBounds = CameraTargetBounds(LatLngBounds( northeast: const LatLng(55, 15), southwest: const LatLng(5, 15))); final MapConfiguration diff = MapConfiguration(cameraTargetBounds: newBounds); expect(diff.isEmpty, false); }); test('is false with mapType', () async { const MapConfiguration diff = MapConfiguration(mapType: MapType.satellite); expect(diff.isEmpty, false); }); test('is false with minMaxZoomPreference', () async { const MinMaxZoomPreference newZoomPref = MinMaxZoomPreference(3.3, 4.5); const MapConfiguration diff = MapConfiguration(minMaxZoomPreference: newZoomPref); expect(diff.isEmpty, false); }); test('is false with rotateGesturesEnabled', () async { const MapConfiguration diff = MapConfiguration(rotateGesturesEnabled: true); expect(diff.isEmpty, false); }); test('is false with scrollGesturesEnabled', () async { const MapConfiguration diff = MapConfiguration(scrollGesturesEnabled: true); expect(diff.isEmpty, false); }); test('is false with tiltGesturesEnabled', () async { const MapConfiguration diff = MapConfiguration(tiltGesturesEnabled: true); expect(diff.isEmpty, false); }); test('is false with trackCameraPosition', () async { const MapConfiguration diff = MapConfiguration(trackCameraPosition: true); expect(diff.isEmpty, false); }); test('is false with zoomControlsEnabled', () async { const MapConfiguration diff = MapConfiguration(zoomControlsEnabled: true); expect(diff.isEmpty, false); }); test('is false with zoomGesturesEnabled', () async { const MapConfiguration diff = MapConfiguration(zoomGesturesEnabled: true); expect(diff.isEmpty, false); }); test('is false with liteModeEnabled', () async { const MapConfiguration diff = MapConfiguration(liteModeEnabled: true); expect(diff.isEmpty, false); }); test('is false with myLocationEnabled', () async { const MapConfiguration diff = MapConfiguration(myLocationEnabled: true); expect(diff.isEmpty, false); }); test('is false with myLocationButtonEnabled', () async { const MapConfiguration diff = MapConfiguration(myLocationButtonEnabled: true); expect(diff.isEmpty, false); }); test('is false with padding', () async { const EdgeInsets newPadding = EdgeInsets.symmetric(vertical: 1.0, horizontal: 3.0); const MapConfiguration diff = MapConfiguration(padding: newPadding); expect(diff.isEmpty, false); }); test('is false with indoorViewEnabled', () async { const MapConfiguration diff = MapConfiguration(indoorViewEnabled: true); expect(diff.isEmpty, false); }); test('is false with trafficEnabled', () async { const MapConfiguration diff = MapConfiguration(trafficEnabled: true); expect(diff.isEmpty, false); }); test('is false with buildingsEnabled', () async { const MapConfiguration diff = MapConfiguration(buildingsEnabled: true); expect(diff.isEmpty, false); }); }); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/maps_object_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:google_maps_flutter_platform_interface/src/types/utils/maps_object.dart'; import 'test_maps_object.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); test('keyByMapsObjectId', () async { const MapsObjectId id1 = MapsObjectId('1'); const MapsObjectId id2 = MapsObjectId('2'); const MapsObjectId id3 = MapsObjectId('3'); const TestMapsObject object1 = TestMapsObject(id1); const TestMapsObject object2 = TestMapsObject(id2, data: 2); const TestMapsObject object3 = TestMapsObject(id3); expect( keyByMapsObjectId({object1, object2, object3}), , TestMapsObject>{ id1: object1, id2: object2, id3: object3, }); }); test('serializeMapsObjectSet', () async { const MapsObjectId id1 = MapsObjectId('1'); const MapsObjectId id2 = MapsObjectId('2'); const MapsObjectId id3 = MapsObjectId('3'); const TestMapsObject object1 = TestMapsObject(id1); const TestMapsObject object2 = TestMapsObject(id2, data: 2); const TestMapsObject object3 = TestMapsObject(id3); expect( serializeMapsObjectSet({object1, object2, object3}), >[ {'id': '1'}, {'id': '2'}, {'id': '3'} ]); }); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/maps_object_updates_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_platform_interface/src/types/maps_object.dart'; import 'package:google_maps_flutter_platform_interface/src/types/maps_object_updates.dart'; import 'package:google_maps_flutter_platform_interface/src/types/utils/maps_object.dart'; import 'test_maps_object.dart'; class TestMapsObjectUpdate extends MapsObjectUpdates { TestMapsObjectUpdate.from( Set previous, Set current) : super.from(previous, current, objectName: 'testObject'); } void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('tile overlay updates tests', () { test('Correctly set toRemove, toAdd and toChange', () async { const TestMapsObject to1 = TestMapsObject(MapsObjectId('id1')); const TestMapsObject to2 = TestMapsObject(MapsObjectId('id2')); const TestMapsObject to3 = TestMapsObject(MapsObjectId('id3')); const TestMapsObject to3Changed = TestMapsObject(MapsObjectId('id3'), data: 2); const TestMapsObject to4 = TestMapsObject(MapsObjectId('id4')); final Set previous = {to1, to2, to3}; final Set current = { to2, to3Changed, to4 }; final TestMapsObjectUpdate updates = TestMapsObjectUpdate.from(previous, current); final Set> toRemove = >{ const MapsObjectId('id1') }; expect(updates.objectIdsToRemove, toRemove); final Set toAdd = {to4}; expect(updates.objectsToAdd, toAdd); final Set toChange = {to3Changed}; expect(updates.objectsToChange, toChange); }); test('toJson', () async { const TestMapsObject to1 = TestMapsObject(MapsObjectId('id1')); const TestMapsObject to2 = TestMapsObject(MapsObjectId('id2')); const TestMapsObject to3 = TestMapsObject(MapsObjectId('id3')); const TestMapsObject to3Changed = TestMapsObject(MapsObjectId('id3'), data: 2); const TestMapsObject to4 = TestMapsObject(MapsObjectId('id4')); final Set previous = {to1, to2, to3}; final Set current = { to2, to3Changed, to4 }; final TestMapsObjectUpdate updates = TestMapsObjectUpdate.from(previous, current); final Object json = updates.toJson(); expect(json, { 'testObjectsToAdd': serializeMapsObjectSet(updates.objectsToAdd), 'testObjectsToChange': serializeMapsObjectSet(updates.objectsToChange), 'testObjectIdsToRemove': updates.objectIdsToRemove .map((MapsObjectId m) => m.value) .toList() }); }); test('equality', () async { const TestMapsObject to1 = TestMapsObject(MapsObjectId('id1')); const TestMapsObject to2 = TestMapsObject(MapsObjectId('id2')); const TestMapsObject to3 = TestMapsObject(MapsObjectId('id3')); const TestMapsObject to3Changed = TestMapsObject(MapsObjectId('id3'), data: 2); const TestMapsObject to4 = TestMapsObject(MapsObjectId('id4')); final Set previous = {to1, to2, to3}; final Set current1 = { to2, to3Changed, to4 }; final Set current2 = { to2, to3Changed, to4 }; final Set current3 = {to2, to4}; final TestMapsObjectUpdate updates1 = TestMapsObjectUpdate.from(previous, current1); final TestMapsObjectUpdate updates2 = TestMapsObjectUpdate.from(previous, current2); final TestMapsObjectUpdate updates3 = TestMapsObjectUpdate.from(previous, current3); expect(updates1, updates2); expect(updates1, isNot(updates3)); }); test('hashCode', () async { const TestMapsObject to1 = TestMapsObject(MapsObjectId('id1')); const TestMapsObject to2 = TestMapsObject(MapsObjectId('id2')); const TestMapsObject to3 = TestMapsObject(MapsObjectId('id3')); const TestMapsObject to3Changed = TestMapsObject(MapsObjectId('id3'), data: 2); const TestMapsObject to4 = TestMapsObject(MapsObjectId('id4')); final Set previous = {to1, to2, to3}; final Set current = { to2, to3Changed, to4 }; final TestMapsObjectUpdate updates = TestMapsObjectUpdate.from(previous, current); expect( updates.hashCode, Object.hash( Object.hashAll(updates.objectsToAdd), Object.hashAll(updates.objectIdsToRemove), Object.hashAll(updates.objectsToChange))); }); test('toString', () async { const TestMapsObject to1 = TestMapsObject(MapsObjectId('id1')); const TestMapsObject to2 = TestMapsObject(MapsObjectId('id2')); const TestMapsObject to3 = TestMapsObject(MapsObjectId('id3')); const TestMapsObject to3Changed = TestMapsObject(MapsObjectId('id3'), data: 2); const TestMapsObject to4 = TestMapsObject(MapsObjectId('id4')); final Set previous = {to1, to2, to3}; final Set current = { to2, to3Changed, to4 }; final TestMapsObjectUpdate updates = TestMapsObjectUpdate.from(previous, current); expect( updates.toString(), 'TestMapsObjectUpdate(add: ${updates.objectsToAdd}, ' 'remove: ${updates.objectIdsToRemove}, ' 'change: ${updates.objectsToChange})'); }); }); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$Marker', () { test('constructor defaults', () { const Marker marker = Marker(markerId: MarkerId('ABC123')); expect(marker.alpha, equals(1.0)); expect(marker.anchor, equals(const Offset(0.5, 1.0))); expect(marker.consumeTapEvents, equals(false)); expect(marker.draggable, equals(false)); expect(marker.flat, equals(false)); expect(marker.icon, equals(BitmapDescriptor.defaultMarker)); expect(marker.infoWindow, equals(InfoWindow.noText)); expect(marker.position, equals(const LatLng(0.0, 0.0))); expect(marker.rotation, equals(0.0)); expect(marker.visible, equals(true)); expect(marker.zIndex, equals(0.0)); expect(marker.onTap, equals(null)); expect(marker.onDrag, equals(null)); expect(marker.onDragStart, equals(null)); expect(marker.onDragEnd, equals(null)); }); test('constructor alpha is >= 0.0 and <= 1.0', () { void initWithAlpha(double alpha) { Marker(markerId: const MarkerId('ABC123'), alpha: alpha); } expect(() => initWithAlpha(-0.5), throwsAssertionError); expect(() => initWithAlpha(0.0), isNot(throwsAssertionError)); expect(() => initWithAlpha(0.5), isNot(throwsAssertionError)); expect(() => initWithAlpha(1.0), isNot(throwsAssertionError)); expect(() => initWithAlpha(100), throwsAssertionError); }); test('toJson', () { final BitmapDescriptor testDescriptor = BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueCyan); final Marker marker = Marker( markerId: const MarkerId('ABC123'), alpha: 0.12345, anchor: const Offset(100, 100), consumeTapEvents: true, draggable: true, flat: true, icon: testDescriptor, infoWindow: const InfoWindow( title: 'Test title', snippet: 'Test snippet', anchor: Offset(100, 200), ), position: const LatLng(50, 50), rotation: 100, visible: false, zIndex: 100, onTap: () {}, onDragStart: (LatLng latLng) {}, onDrag: (LatLng latLng) {}, onDragEnd: (LatLng latLng) {}, ); final Map json = marker.toJson() as Map; expect(json, { 'markerId': 'ABC123', 'alpha': 0.12345, 'anchor': [100, 100], 'consumeTapEvents': true, 'draggable': true, 'flat': true, 'icon': testDescriptor.toJson(), 'infoWindow': { 'title': 'Test title', 'snippet': 'Test snippet', 'anchor': [100.0, 200.0], }, 'position': [50, 50], 'rotation': 100.0, 'visible': false, 'zIndex': 100.0, }); }); test('clone', () { const Marker marker = Marker(markerId: MarkerId('ABC123')); final Marker clone = marker.clone(); expect(identical(clone, marker), isFalse); expect(clone, equals(marker)); }); test('copyWith', () { const Marker marker = Marker(markerId: MarkerId('ABC123')); final BitmapDescriptor testDescriptor = BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueCyan); const double testAlphaParam = 0.12345; const Offset testAnchorParam = Offset(100, 100); final bool testConsumeTapEventsParam = !marker.consumeTapEvents; final bool testDraggableParam = !marker.draggable; final bool testFlatParam = !marker.flat; final BitmapDescriptor testIconParam = testDescriptor; const InfoWindow testInfoWindowParam = InfoWindow(title: 'Test'); const LatLng testPositionParam = LatLng(100, 100); const double testRotationParam = 100; final bool testVisibleParam = !marker.visible; const double testZIndexParam = 100; final List log = []; final Marker copy = marker.copyWith( alphaParam: testAlphaParam, anchorParam: testAnchorParam, consumeTapEventsParam: testConsumeTapEventsParam, draggableParam: testDraggableParam, flatParam: testFlatParam, iconParam: testIconParam, infoWindowParam: testInfoWindowParam, positionParam: testPositionParam, rotationParam: testRotationParam, visibleParam: testVisibleParam, zIndexParam: testZIndexParam, onTapParam: () { log.add('onTapParam'); }, onDragStartParam: (LatLng latLng) { log.add('onDragStartParam'); }, onDragParam: (LatLng latLng) { log.add('onDragParam'); }, onDragEndParam: (LatLng latLng) { log.add('onDragEndParam'); }, ); expect(copy.alpha, equals(testAlphaParam)); expect(copy.anchor, equals(testAnchorParam)); expect(copy.consumeTapEvents, equals(testConsumeTapEventsParam)); expect(copy.draggable, equals(testDraggableParam)); expect(copy.flat, equals(testFlatParam)); expect(copy.icon, equals(testIconParam)); expect(copy.infoWindow, equals(testInfoWindowParam)); expect(copy.position, equals(testPositionParam)); expect(copy.rotation, equals(testRotationParam)); expect(copy.visible, equals(testVisibleParam)); expect(copy.zIndex, equals(testZIndexParam)); copy.onTap!(); expect(log, contains('onTapParam')); copy.onDragStart!(const LatLng(0, 1)); expect(log, contains('onDragStartParam')); copy.onDrag!(const LatLng(0, 1)); expect(log, contains('onDragParam')); copy.onDragEnd!(const LatLng(0, 1)); expect(log, contains('onDragEndParam')); }); }); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/test_maps_object.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:google_maps_flutter_platform_interface/src/types/maps_object.dart'; import 'package:google_maps_flutter_platform_interface/src/types/maps_object_updates.dart'; /// A trivial TestMapsObject implementation for testing updates with. @immutable class TestMapsObject implements MapsObject { const TestMapsObject(this.mapsId, {this.data = 1}); @override final MapsObjectId mapsId; final int data; @override TestMapsObject clone() { return TestMapsObject(mapsId, data: data); } @override Object toJson() { return {'id': mapsId.value}; } @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) { return false; } return other is TestMapsObject && mapsId == other.mapsId && data == other.data; } @override int get hashCode => Object.hash(mapsId, data); } class TestMapsObjectUpdate extends MapsObjectUpdates { TestMapsObjectUpdate.from( Set previous, Set current) : super.from(previous, current, objectName: 'testObject'); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; class _TestTileProvider extends TileProvider { @override Future getTile(int x, int y, int? zoom) async { return const Tile(0, 0, null); } } void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('tile overlay id tests', () { test('equality', () async { const TileOverlayId id1 = TileOverlayId('1'); const TileOverlayId id2 = TileOverlayId('1'); const TileOverlayId id3 = TileOverlayId('2'); expect(id1, id2); expect(id1, isNot(id3)); }); test('toString', () async { const TileOverlayId id1 = TileOverlayId('1'); expect(id1.toString(), 'TileOverlayId(1)'); }); }); group('tile overlay tests', () { test('toJson returns correct format', () async { const TileOverlay tileOverlay = TileOverlay( tileOverlayId: TileOverlayId('id'), fadeIn: false, transparency: 0.1, zIndex: 1, visible: false, tileSize: 128); final Object json = tileOverlay.toJson(); expect(json, { 'tileOverlayId': 'id', 'fadeIn': false, 'transparency': moreOrLessEquals(0.1), 'zIndex': 1, 'visible': false, 'tileSize': 128, }); }); test('invalid transparency throws', () async { expect( () => TileOverlay( tileOverlayId: const TileOverlayId('id1'), transparency: -0.1), throwsAssertionError); expect( () => TileOverlay( tileOverlayId: const TileOverlayId('id2'), transparency: 1.2), throwsAssertionError); }); test('equality', () async { final TileProvider tileProvider = _TestTileProvider(); final TileOverlay tileOverlay1 = TileOverlay( tileOverlayId: const TileOverlayId('id1'), fadeIn: false, tileProvider: tileProvider, transparency: 0.1, zIndex: 1, visible: false, tileSize: 128); final TileOverlay tileOverlaySameValues = TileOverlay( tileOverlayId: const TileOverlayId('id1'), fadeIn: false, tileProvider: tileProvider, transparency: 0.1, zIndex: 1, visible: false, tileSize: 128); final TileOverlay tileOverlayDifferentId = TileOverlay( tileOverlayId: const TileOverlayId('id2'), fadeIn: false, tileProvider: tileProvider, transparency: 0.1, zIndex: 1, visible: false, tileSize: 128); const TileOverlay tileOverlayDifferentProvider = TileOverlay( tileOverlayId: TileOverlayId('id1'), fadeIn: false, transparency: 0.1, zIndex: 1, visible: false, tileSize: 128); expect(tileOverlay1, tileOverlaySameValues); expect(tileOverlay1, isNot(tileOverlayDifferentId)); expect(tileOverlay1, isNot(tileOverlayDifferentProvider)); }); test('clone', () async { final TileProvider tileProvider = _TestTileProvider(); // Set non-default values for every parameter. final TileOverlay tileOverlay = TileOverlay( tileOverlayId: const TileOverlayId('id1'), fadeIn: false, tileProvider: tileProvider, transparency: 0.1, zIndex: 1, visible: false, tileSize: 128); expect(tileOverlay, tileOverlay.clone()); }); test('hashCode', () async { final TileProvider tileProvider = _TestTileProvider(); const TileOverlayId id = TileOverlayId('id1'); final TileOverlay tileOverlay = TileOverlay( tileOverlayId: id, fadeIn: false, tileProvider: tileProvider, transparency: 0.1, zIndex: 1, visible: false, tileSize: 128); expect( tileOverlay.hashCode, Object.hash( tileOverlay.tileOverlayId, tileOverlay.fadeIn, tileOverlay.tileProvider, tileOverlay.transparency, tileOverlay.zIndex, tileOverlay.visible, tileOverlay.tileSize)); }); }); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_updates_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_platform_interface/src/types/tile_overlay.dart'; import 'package:google_maps_flutter_platform_interface/src/types/tile_overlay_updates.dart'; import 'package:google_maps_flutter_platform_interface/src/types/utils/tile_overlay.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('tile overlay updates tests', () { test('Correctly set toRemove, toAdd and toChange', () async { const TileOverlay to1 = TileOverlay(tileOverlayId: TileOverlayId('id1')); const TileOverlay to2 = TileOverlay(tileOverlayId: TileOverlayId('id2')); const TileOverlay to3 = TileOverlay(tileOverlayId: TileOverlayId('id3')); const TileOverlay to3Changed = TileOverlay(tileOverlayId: TileOverlayId('id3'), transparency: 0.5); const TileOverlay to4 = TileOverlay(tileOverlayId: TileOverlayId('id4')); final Set previous = {to1, to2, to3}; final Set current = {to2, to3Changed, to4}; final TileOverlayUpdates updates = TileOverlayUpdates.from(previous, current); final Set toRemove = { const TileOverlayId('id1') }; expect(updates.tileOverlayIdsToRemove, toRemove); final Set toAdd = {to4}; expect(updates.tileOverlaysToAdd, toAdd); final Set toChange = {to3Changed}; expect(updates.tileOverlaysToChange, toChange); }); test('toJson', () async { const TileOverlay to1 = TileOverlay(tileOverlayId: TileOverlayId('id1')); const TileOverlay to2 = TileOverlay(tileOverlayId: TileOverlayId('id2')); const TileOverlay to3 = TileOverlay(tileOverlayId: TileOverlayId('id3')); const TileOverlay to3Changed = TileOverlay(tileOverlayId: TileOverlayId('id3'), transparency: 0.5); const TileOverlay to4 = TileOverlay(tileOverlayId: TileOverlayId('id4')); final Set previous = {to1, to2, to3}; final Set current = {to2, to3Changed, to4}; final TileOverlayUpdates updates = TileOverlayUpdates.from(previous, current); final Object json = updates.toJson(); expect(json, { 'tileOverlaysToAdd': serializeTileOverlaySet(updates.tileOverlaysToAdd), 'tileOverlaysToChange': serializeTileOverlaySet(updates.tileOverlaysToChange), 'tileOverlayIdsToRemove': updates.tileOverlayIdsToRemove .map((TileOverlayId m) => m.value) .toList() }); }); test('equality', () async { const TileOverlay to1 = TileOverlay(tileOverlayId: TileOverlayId('id1')); const TileOverlay to2 = TileOverlay(tileOverlayId: TileOverlayId('id2')); const TileOverlay to3 = TileOverlay(tileOverlayId: TileOverlayId('id3')); const TileOverlay to3Changed = TileOverlay(tileOverlayId: TileOverlayId('id3'), transparency: 0.5); const TileOverlay to4 = TileOverlay(tileOverlayId: TileOverlayId('id4')); final Set previous = {to1, to2, to3}; final Set current1 = {to2, to3Changed, to4}; final Set current2 = {to2, to3Changed, to4}; final Set current3 = {to2, to4}; final TileOverlayUpdates updates1 = TileOverlayUpdates.from(previous, current1); final TileOverlayUpdates updates2 = TileOverlayUpdates.from(previous, current2); final TileOverlayUpdates updates3 = TileOverlayUpdates.from(previous, current3); expect(updates1, updates2); expect(updates1, isNot(updates3)); }); test('hashCode', () async { const TileOverlay to1 = TileOverlay(tileOverlayId: TileOverlayId('id1')); const TileOverlay to2 = TileOverlay(tileOverlayId: TileOverlayId('id2')); const TileOverlay to3 = TileOverlay(tileOverlayId: TileOverlayId('id3')); const TileOverlay to3Changed = TileOverlay(tileOverlayId: TileOverlayId('id3'), transparency: 0.5); const TileOverlay to4 = TileOverlay(tileOverlayId: TileOverlayId('id4')); final Set previous = {to1, to2, to3}; final Set current = {to2, to3Changed, to4}; final TileOverlayUpdates updates = TileOverlayUpdates.from(previous, current); expect( updates.hashCode, Object.hash( Object.hashAll(updates.tileOverlaysToAdd), Object.hashAll(updates.tileOverlayIdsToRemove), Object.hashAll(updates.tileOverlaysToChange))); }); test('toString', () async { const TileOverlay to1 = TileOverlay(tileOverlayId: TileOverlayId('id1')); const TileOverlay to2 = TileOverlay(tileOverlayId: TileOverlayId('id2')); const TileOverlay to3 = TileOverlay(tileOverlayId: TileOverlayId('id3')); const TileOverlay to3Changed = TileOverlay(tileOverlayId: TileOverlayId('id3'), transparency: 0.5); const TileOverlay to4 = TileOverlay(tileOverlayId: TileOverlayId('id4')); final Set previous = {to1, to2, to3}; final Set current = {to2, to3Changed, to4}; final TileOverlayUpdates updates = TileOverlayUpdates.from(previous, current); expect( updates.toString(), 'TileOverlayUpdates(add: ${updates.tileOverlaysToAdd}, ' 'remove: ${updates.tileOverlayIdsToRemove}, ' 'change: ${updates.tileOverlaysToChange})'); }); }); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('tile tests', () { test('toJson returns correct format', () async { final Uint8List data = Uint8List.fromList([0, 1]); final Tile tile = Tile(100, 200, data); final Object json = tile.toJson(); expect(json, { 'width': 100, 'height': 200, 'data': data, }); }); test('toJson handles null data', () async { const Tile tile = Tile(0, 0, null); final Object json = tile.toJson(); expect(json, { 'width': 0, 'height': 0, }); }); }); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_platform_interface/test/utils/map_configuration_serialization_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:google_maps_flutter_platform_interface/src/types/utils/map_configuration_serialization.dart'; void main() { test('empty serialization', () async { const MapConfiguration config = MapConfiguration(); final Map json = jsonForMapConfiguration(config); expect(json.isEmpty, true); }); test('complete serialization', () async { final MapConfiguration config = MapConfiguration( compassEnabled: false, mapToolbarEnabled: false, cameraTargetBounds: CameraTargetBounds(LatLngBounds( northeast: const LatLng(30, 20), southwest: const LatLng(10, 40))), mapType: MapType.normal, minMaxZoomPreference: const MinMaxZoomPreference(1.0, 10.0), rotateGesturesEnabled: false, scrollGesturesEnabled: false, tiltGesturesEnabled: false, trackCameraPosition: false, zoomControlsEnabled: false, zoomGesturesEnabled: false, liteModeEnabled: false, myLocationEnabled: false, myLocationButtonEnabled: false, padding: const EdgeInsets.all(5.0), indoorViewEnabled: false, trafficEnabled: false, buildingsEnabled: false, ); final Map json = jsonForMapConfiguration(config); // This uses literals instead of toJson() for the expectations on // sub-objects, because if the serialization of any of those objects were // ever to change MapConfiguration would need to update to serialize those // objects manually to preserve the format, in order to avoid breaking // implementations. expect(json, { 'compassEnabled': false, 'mapToolbarEnabled': false, 'cameraTargetBounds': [ [ [10.0, 40.0], [30.0, 20.0] ] ], 'mapType': 1, 'minMaxZoomPreference': [1.0, 10.0], 'rotateGesturesEnabled': false, 'scrollGesturesEnabled': false, 'tiltGesturesEnabled': false, 'zoomControlsEnabled': false, 'zoomGesturesEnabled': false, 'liteModeEnabled': false, 'trackCameraPosition': false, 'myLocationEnabled': false, 'myLocationButtonEnabled': false, 'padding': [5.0, 5.0, 5.0, 5.0], 'indoorEnabled': false, 'trafficEnabled': false, 'buildingsEnabled': false }); }); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/AUTHORS ================================================ # 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> ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md ================================================ ## 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 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. ## 0.4.0+2 * Updates conversion of `BitmapDescriptor.fromBytes` marker icons to support the new `size` parameter. Issue [#73789](https://github.com/flutter/flutter/issues/73789). * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 0.4.0+1 * Updates `README.md` to describe a hit-testing issue when Flutter widgets are overlaid on top of the Map widget. ## 0.4.0 * Implements the new platform interface versions of `buildView` and `updateOptions` with structured option types. * **BREAKING CHANGE**: No longer implements the unstructured option dictionary versions of those methods, so this version can only be used with `google_maps_flutter` 2.1.8 or later. * Adds `const` constructor parameters in example tests. ## 0.3.3 * Removes custom `analysis_options.yaml` (and fixes code to comply with newest rules). * Updates `package:google_maps` dependency to latest (`^6.1.0`). * Ensures that `convert.dart` sanitizes user-created HTML before passing it to the Maps JS SDK with `sanitizeHtml` from `package:sanitize_html`. [More info](https://pub.dev/documentation/sanitize_html/latest/sanitize_html/sanitizeHtml.html). ## 0.3.2+2 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.3.2+1 * Removes dependency on `meta`. ## 0.3.2 * Add `onDragStart` and `onDrag` to `Marker` ## 0.3.1 * Fix the `getScreenCoordinate(LatLng)` method. [#80710](https://github.com/flutter/flutter/issues/80710) * Wait until the map tiles have loaded before calling `onPlatformViewCreated`, so the returned controller is 100% functional (has bounds, a projection, etc...) * Use zIndex property when initializing Circle objects. [#89374](https://github.com/flutter/flutter/issues/89374) ## 0.3.0+4 * Add `implements` to pubspec. ## 0.3.0+3 * Update the `README.md` usage instructions to not be tied to explicit package versions. ## 0.3.0+2 * Document `liteModeEnabled` is not available on the web. [#83737](https://github.com/flutter/flutter/issues/83737). ## 0.3.0+1 * Change sizing code of `GoogleMap` widget's `HtmlElementView` so it works well when slotted. ## 0.3.0 * Migrate package to null-safety. * **Breaking changes:** * The property `icon` of a `Marker` cannot be `null`. Defaults to `BitmapDescriptor.defaultMarker` * The property `initialCameraPosition` of a `GoogleMapController` can't be `null`. It is also marked as `required`. * The parameter `creationId` of the `buildView` method cannot be `null` (this should be handled internally for users of the plugin) * Most of the Controller methods can't be called after `remove`/`dispose`. Calling these methods now will throw an Assertion error. Before it'd be a no-op, or a null-pointer exception. ## 0.2.1 * Move integration tests to `example`. * Tweak pubspec dependencies for main package. ## 0.2.0 * Make this plugin compatible with the rest of null-safe plugins. * Noop tile overlays methods, so they don't crash on web. **NOTE**: This plugin is **not** null-safe yet! ## 0.1.2 * Update min Flutter SDK to 1.20.0. ## 0.1.1 * Auto-reverse holes if they're the same direction as the polygon. [Issue](https://github.com/flutter/flutter/issues/74096). ## 0.1.0+10 * Update `package:google_maps_flutter_platform_interface` to `^1.1.0`. * Add support for Polygon Holes. ## 0.1.0+9 * Update Flutter SDK constraint. ## 0.1.0+8 * Update `package:google_maps_flutter_platform_interface` to `^1.0.5`. * Add support for `fromBitmap` BitmapDescriptors. [Issue](https://github.com/flutter/flutter/issues/66622). ## 0.1.0+7 * Substitute `undefined_prefixed_name: ignore` analyzer setting by a `dart:ui` shim with conditional exports. [Issue](https://github.com/flutter/flutter/issues/69309). ## 0.1.0+6 * Ensure a single `InfoWindow` is shown at a time. [Issue](https://github.com/flutter/flutter/issues/67380). ## 0.1.0+5 * Update `package:google_maps` to `^3.4.5`. * Fix `GoogleMapController.getLatLng()`. [Issue](https://github.com/flutter/flutter/issues/67606). * Make `InfoWindow` contents clickable so `onTap` works as advertised. [Issue](https://github.com/flutter/flutter/issues/67289). * Fix `InfoWindow` snippets when converting initial markers. [Issue](https://github.com/flutter/flutter/issues/67854). ## 0.1.0+4 * Update `package:sanitize_html` to `^1.4.1` to prevent [a crash](https://github.com/flutter/flutter/issues/67854) when InfoWindow title/snippet have links. ## 0.1.0+3 * Fix crash when converting initial polylines and polygons. [Issue](https://github.com/flutter/flutter/issues/65152). * Correctly convert Colors when rendering polylines, polygons and circles. [Issue](https://github.com/flutter/flutter/issues/67032). ## 0.1.0+2 * Fix crash when converting Markers with icon explicitly set to null. [Issue](https://github.com/flutter/flutter/issues/64938). ## 0.1.0+1 * Port e2e tests to use the new integration_test package. ## 0.1.0 * First open-source version ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/LICENSE ================================================ google_maps_flutter_web 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. -------------------------------------------------------------------------------- to_screen_location The MIT License (MIT) Copyright (c) 2008 Krasimir Tsonev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/README.md ================================================ # google_maps_flutter_web This is an implementation of the [google_maps_flutter](https://pub.dev/packages/google_maps_flutter) plugin for web. Behind the scenes, it uses a14n's [google_maps](https://pub.dev/packages/google_maps) dart JS interop layer. ## Usage ### Depend on the package This package is not an endorsed implementation of the google_maps_flutter plugin yet, so you'll need to [add it explicitly](https://pub.dev/packages/google_maps_flutter_web/install). ### Modify web/index.html Get an API Key for Google Maps JavaScript API. Get started [here](https://developers.google.com/maps/documentation/javascript/get-api-key). Modify the `` tag of your `web/index.html` to load the Google Maps JavaScript API, like so: ```html ``` Now you should be able to use the Google Maps plugin normally. ## Limitations of the web version The following map options are not available in web, because the map doesn't rotate there: * `compassEnabled` * `rotateGesturesEnabled` * `tiltGesturesEnabled` There's no "Map Toolbar" in web, so the `mapToolbarEnabled` option is unused. There's no "My Location" widget in web ([tracking issue](https://github.com/flutter/flutter/issues/64073)), so the following options are ignored, for now: * `myLocationButtonEnabled` * `myLocationEnabled` There's no `defaultMarkerWithHue` in web. If you need colored pins/markers, you may need to use your own asset images. Indoor and building layers are still not available on the web. Traffic is. Only Android supports "[Lite Mode](https://developers.google.com/maps/documentation/android-sdk/lite)", so the `liteModeEnabled` constructor argument can't be set to `true` on web apps. Google Maps for web uses `HtmlElementView` to render maps. When a `GoogleMap` is stacked below other widgets, [`package:pointer_interceptor`](https://www.pub.dev/packages/pointer_interceptor) must be used to capture mouse events on the Flutter overlays. See issue [#73830](https://github.com/flutter/flutter/issues/73830). ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/example/README.md ================================================ # 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. ## Testing This package uses `package:integration_test` to run its tests in a web browser. See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/wiki/Plugin-Tests#web-tests) in the Flutter wiki for instructions to setup and run the tests in this package. Check [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) for more info. ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/example/build.yaml ================================================ targets: $default: sources: - integration_test/*.dart - lib/$lib$ - $package$ ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:html' as html; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'google_maps_controller_test.mocks.dart'; // This value is used when comparing long~num, like // LatLng values. const double _acceptableDelta = 0.0000000001; @GenerateMocks([], customMocks: >[ MockSpec(onMissingStub: OnMissingStub.returnDefault), MockSpec(onMissingStub: OnMissingStub.returnDefault), MockSpec(onMissingStub: OnMissingStub.returnDefault), MockSpec(onMissingStub: OnMissingStub.returnDefault), ]) /// Test Google Map Controller void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('GoogleMapController', () { const int mapId = 33930; late GoogleMapController controller; late StreamController> stream; // Creates a controller with the default mapId and stream controller, and any `options` needed. GoogleMapController createController({ CameraPosition initialCameraPosition = const CameraPosition(target: LatLng(0, 0)), MapObjects mapObjects = const MapObjects(), MapConfiguration mapConfiguration = const MapConfiguration(), }) { return GoogleMapController( mapId: mapId, streamController: stream, widgetConfiguration: MapWidgetConfiguration( initialCameraPosition: initialCameraPosition, textDirection: TextDirection.ltr), mapObjects: mapObjects, mapConfiguration: mapConfiguration, ); } setUp(() { stream = StreamController>.broadcast(); }); group('construct/dispose', () { setUp(() { controller = createController(); }); testWidgets('constructor creates widget', (WidgetTester tester) async { expect(controller.widget, isNotNull); expect(controller.widget, isA()); expect((controller.widget! as HtmlElementView).viewType, endsWith('$mapId')); }); testWidgets('widget is cached when reused', (WidgetTester tester) async { final Widget? first = controller.widget; final Widget? again = controller.widget; expect(identical(first, again), isTrue); }); group('dispose', () { testWidgets('closes the stream and removes the widget', (WidgetTester tester) async { controller.dispose(); expect(stream.isClosed, isTrue); expect(controller.widget, isNull); }); testWidgets('cannot call getVisibleRegion after dispose', (WidgetTester tester) async { controller.dispose(); expect(() async { await controller.getVisibleRegion(); }, throwsAssertionError); }); testWidgets('cannot call getScreenCoordinate after dispose', (WidgetTester tester) async { controller.dispose(); expect(() async { await controller.getScreenCoordinate( const LatLng(43.3072465, -5.6918241), ); }, throwsAssertionError); }); testWidgets('cannot call getLatLng after dispose', (WidgetTester tester) async { controller.dispose(); expect(() async { await controller.getLatLng( const ScreenCoordinate(x: 640, y: 480), ); }, throwsAssertionError); }); testWidgets('cannot call moveCamera after dispose', (WidgetTester tester) async { controller.dispose(); expect(() async { await controller.moveCamera(CameraUpdate.zoomIn()); }, throwsAssertionError); }); testWidgets('cannot call getZoomLevel after dispose', (WidgetTester tester) async { controller.dispose(); expect(() async { await controller.getZoomLevel(); }, throwsAssertionError); }); testWidgets('cannot updateCircles after dispose', (WidgetTester tester) async { controller.dispose(); expect(() { controller.updateCircles( CircleUpdates.from( const {}, const {}, ), ); }, throwsAssertionError); }); testWidgets('cannot updatePolygons after dispose', (WidgetTester tester) async { controller.dispose(); expect(() { controller.updatePolygons( PolygonUpdates.from( const {}, const {}, ), ); }, throwsAssertionError); }); testWidgets('cannot updatePolylines after dispose', (WidgetTester tester) async { controller.dispose(); expect(() { controller.updatePolylines( PolylineUpdates.from( const {}, const {}, ), ); }, throwsAssertionError); }); testWidgets('cannot updateMarkers after dispose', (WidgetTester tester) async { controller.dispose(); expect(() { controller.updateMarkers( MarkerUpdates.from( const {}, const {}, ), ); }, throwsAssertionError); expect(() { controller.showInfoWindow(const MarkerId('any')); }, throwsAssertionError); expect(() { controller.hideInfoWindow(const MarkerId('any')); }, throwsAssertionError); }); testWidgets('isInfoWindowShown defaults to false', (WidgetTester tester) async { controller.dispose(); expect(controller.isInfoWindowShown(const MarkerId('any')), false); }); }); }); group('init', () { late MockCirclesController circles; late MockMarkersController markers; late MockPolygonsController polygons; late MockPolylinesController polylines; late gmaps.GMap map; setUp(() { circles = MockCirclesController(); markers = MockMarkersController(); polygons = MockPolygonsController(); polylines = MockPolylinesController(); map = gmaps.GMap(html.DivElement()); }); testWidgets('listens to map events', (WidgetTester tester) async { controller = createController(); controller.debugSetOverrides( createMap: (_, __) => map, circles: circles, markers: markers, polygons: polygons, polylines: polylines, ); controller.init(); // Trigger events on the map, and verify they've been broadcast to the stream final Stream> capturedEvents = stream.stream.take(5); gmaps.Event.trigger( map, 'click', [gmaps.MapMouseEvent()..latLng = gmaps.LatLng(0, 0)], ); gmaps.Event.trigger( map, 'rightclick', [gmaps.MapMouseEvent()..latLng = gmaps.LatLng(0, 0)], ); // The following line causes 2 events gmaps.Event.trigger(map, 'bounds_changed', []); gmaps.Event.trigger(map, 'idle', []); final List> events = await capturedEvents.toList(); expect(events[0], isA()); expect(events[1], isA()); expect(events[2], isA()); expect(events[3], isA()); expect(events[4], isA()); }); testWidgets("binds geometry controllers to map's", (WidgetTester tester) async { controller = createController(); controller.debugSetOverrides( createMap: (_, __) => map, circles: circles, markers: markers, polygons: polygons, polylines: polylines, ); controller.init(); verify(circles.bindToMap(mapId, map)); verify(markers.bindToMap(mapId, map)); verify(polygons.bindToMap(mapId, map)); verify(polylines.bindToMap(mapId, map)); }); testWidgets('renders initial geometry', (WidgetTester tester) async { controller = createController( mapObjects: MapObjects(circles: { const Circle( circleId: CircleId('circle-1'), zIndex: 1234, ), }, markers: { const Marker( markerId: MarkerId('marker-1'), infoWindow: InfoWindow( title: 'title for test', snippet: 'snippet for test', ), ), }, polygons: { const Polygon(polygonId: PolygonId('polygon-1'), points: [ LatLng(43.355114, -5.851333), LatLng(43.354797, -5.851860), LatLng(43.354469, -5.851318), LatLng(43.354762, -5.850824), ]), const Polygon( polygonId: PolygonId('polygon-2-with-holes'), points: [ LatLng(43.355114, -5.851333), LatLng(43.354797, -5.851860), LatLng(43.354469, -5.851318), LatLng(43.354762, -5.850824), ], holes: >[ [ LatLng(41.354797, -6.851860), LatLng(41.354469, -6.851318), LatLng(41.354762, -6.850824), ] ], ), }, polylines: { const Polyline(polylineId: PolylineId('polyline-1'), points: [ LatLng(43.355114, -5.851333), LatLng(43.354797, -5.851860), LatLng(43.354469, -5.851318), LatLng(43.354762, -5.850824), ]) })); controller.debugSetOverrides( circles: circles, markers: markers, polygons: polygons, polylines: polylines, ); controller.init(); final Set capturedCircles = verify(circles.addCircles(captureAny)).captured[0] as Set; final Set capturedMarkers = verify(markers.addMarkers(captureAny)).captured[0] as Set; final Set capturedPolygons = verify(polygons.addPolygons(captureAny)).captured[0] as Set; final Set capturedPolylines = verify(polylines.addPolylines(captureAny)).captured[0] as Set; expect(capturedCircles.first.circleId.value, 'circle-1'); expect(capturedCircles.first.zIndex, 1234); expect(capturedMarkers.first.markerId.value, 'marker-1'); expect(capturedMarkers.first.infoWindow.snippet, 'snippet for test'); expect(capturedMarkers.first.infoWindow.title, 'title for test'); expect(capturedPolygons.first.polygonId.value, 'polygon-1'); expect(capturedPolygons.elementAt(1).polygonId.value, 'polygon-2-with-holes'); expect(capturedPolygons.elementAt(1).holes, isNot(null)); expect(capturedPolylines.first.polylineId.value, 'polyline-1'); }); testWidgets('empty infoWindow does not create InfoWindow instance.', (WidgetTester tester) async { controller = createController( mapObjects: MapObjects(markers: { const Marker(markerId: MarkerId('marker-1')), })); controller.debugSetOverrides( markers: markers, ); controller.init(); final Set capturedMarkers = verify(markers.addMarkers(captureAny)).captured[0] as Set; expect(capturedMarkers.first.infoWindow, InfoWindow.noText); }); group('Initialization options', () { gmaps.MapOptions? capturedOptions; setUp(() { capturedOptions = null; }); testWidgets('translates initial options', (WidgetTester tester) async { controller = createController( mapConfiguration: const MapConfiguration( mapType: MapType.satellite, zoomControlsEnabled: true, )); controller.debugSetOverrides( createMap: (_, gmaps.MapOptions options) { capturedOptions = options; return map; }); controller.init(); expect(capturedOptions, isNotNull); expect(capturedOptions!.mapTypeId, gmaps.MapTypeId.SATELLITE); expect(capturedOptions!.zoomControl, true); expect(capturedOptions!.gestureHandling, 'auto', reason: 'by default the map handles zoom/pan gestures internally'); }); testWidgets('disables gestureHandling with scrollGesturesEnabled false', (WidgetTester tester) async { controller = createController( mapConfiguration: const MapConfiguration( scrollGesturesEnabled: false, )); controller.debugSetOverrides( createMap: (_, gmaps.MapOptions options) { capturedOptions = options; return map; }); controller.init(); expect(capturedOptions, isNotNull); expect(capturedOptions!.gestureHandling, 'none', reason: 'disabling scroll gestures disables all gesture handling'); }); testWidgets('disables gestureHandling with zoomGesturesEnabled false', (WidgetTester tester) async { controller = createController( mapConfiguration: const MapConfiguration( zoomGesturesEnabled: false, )); controller.debugSetOverrides( createMap: (_, gmaps.MapOptions options) { capturedOptions = options; return map; }); controller.init(); expect(capturedOptions, isNotNull); expect(capturedOptions!.gestureHandling, 'none', reason: 'disabling scroll gestures disables all gesture handling'); }); testWidgets('sets initial position when passed', (WidgetTester tester) async { controller = createController( initialCameraPosition: const CameraPosition( target: LatLng(43.308, -5.6910), zoom: 12, ), ); controller.debugSetOverrides( createMap: (_, gmaps.MapOptions options) { capturedOptions = options; return map; }); controller.init(); expect(capturedOptions, isNotNull); expect(capturedOptions!.zoom, 12); expect(capturedOptions!.center, isNotNull); }); }); group('Traffic Layer', () { testWidgets('by default is disabled', (WidgetTester tester) async { controller = createController(); controller.init(); expect(controller.trafficLayer, isNull); }); testWidgets('initializes with traffic layer', (WidgetTester tester) async { controller = createController( mapConfiguration: const MapConfiguration( trafficEnabled: true, )); controller.debugSetOverrides(createMap: (_, __) => map); controller.init(); expect(controller.trafficLayer, isNotNull); }); }); }); // These are the methods that are delegated to the gmaps.GMap object, that we can mock... group('Map control methods', () { late gmaps.GMap map; setUp(() { map = gmaps.GMap( html.DivElement(), gmaps.MapOptions() ..zoom = 10 ..center = gmaps.LatLng(0, 0), ); controller = createController(); controller.debugSetOverrides(createMap: (_, __) => map); controller.init(); }); group('updateRawOptions', () { testWidgets('can update `options`', (WidgetTester tester) async { controller.updateMapConfiguration(const MapConfiguration( mapType: MapType.satellite, )); expect(map.mapTypeId, gmaps.MapTypeId.SATELLITE); }); testWidgets('can turn on/off traffic', (WidgetTester tester) async { expect(controller.trafficLayer, isNull); controller.updateMapConfiguration(const MapConfiguration( trafficEnabled: true, )); expect(controller.trafficLayer, isNotNull); controller.updateMapConfiguration(const MapConfiguration( trafficEnabled: false, )); expect(controller.trafficLayer, isNull); }); }); group('viewport getters', () { testWidgets('getVisibleRegion', (WidgetTester tester) async { final gmaps.LatLng gmCenter = map.center!; final LatLng center = LatLng(gmCenter.lat.toDouble(), gmCenter.lng.toDouble()); final LatLngBounds bounds = await controller.getVisibleRegion(); expect(bounds.contains(center), isTrue, reason: 'The computed visible region must contain the center of the created map.'); }); testWidgets('getZoomLevel', (WidgetTester tester) async { expect(await controller.getZoomLevel(), map.zoom); }); }); group('moveCamera', () { testWidgets('newLatLngZoom', (WidgetTester tester) async { await controller.moveCamera( CameraUpdate.newLatLngZoom( const LatLng(19, 26), 12, ), ); final gmaps.LatLng gmCenter = map.center!; expect(map.zoom, 12); expect(gmCenter.lat, closeTo(19, _acceptableDelta)); expect(gmCenter.lng, closeTo(26, _acceptableDelta)); }); }); group('map.projection methods', () { // Tested in projection_test.dart }); }); // These are the methods that get forwarded to other controllers, so we just verify calls. group('Pass-through methods', () { setUp(() { controller = createController(); }); testWidgets('updateCircles', (WidgetTester tester) async { final MockCirclesController mock = MockCirclesController(); controller.debugSetOverrides(circles: mock); final Set previous = { const Circle(circleId: CircleId('to-be-updated')), const Circle(circleId: CircleId('to-be-removed')), }; final Set current = { const Circle(circleId: CircleId('to-be-updated'), visible: false), const Circle(circleId: CircleId('to-be-added')), }; controller.updateCircles(CircleUpdates.from(previous, current)); verify(mock.removeCircles({ const CircleId('to-be-removed'), })); verify(mock.addCircles({ const Circle(circleId: CircleId('to-be-added')), })); verify(mock.changeCircles({ const Circle(circleId: CircleId('to-be-updated'), visible: false), })); }); testWidgets('updateMarkers', (WidgetTester tester) async { final MockMarkersController mock = MockMarkersController(); controller.debugSetOverrides(markers: mock); final Set previous = { const Marker(markerId: MarkerId('to-be-updated')), const Marker(markerId: MarkerId('to-be-removed')), }; final Set current = { const Marker(markerId: MarkerId('to-be-updated'), visible: false), const Marker(markerId: MarkerId('to-be-added')), }; controller.updateMarkers(MarkerUpdates.from(previous, current)); verify(mock.removeMarkers({ const MarkerId('to-be-removed'), })); verify(mock.addMarkers({ const Marker(markerId: MarkerId('to-be-added')), })); verify(mock.changeMarkers({ const Marker(markerId: MarkerId('to-be-updated'), visible: false), })); }); testWidgets('updatePolygons', (WidgetTester tester) async { final MockPolygonsController mock = MockPolygonsController(); controller.debugSetOverrides(polygons: mock); final Set previous = { const Polygon(polygonId: PolygonId('to-be-updated')), const Polygon(polygonId: PolygonId('to-be-removed')), }; final Set current = { const Polygon(polygonId: PolygonId('to-be-updated'), visible: false), const Polygon(polygonId: PolygonId('to-be-added')), }; controller.updatePolygons(PolygonUpdates.from(previous, current)); verify(mock.removePolygons({ const PolygonId('to-be-removed'), })); verify(mock.addPolygons({ const Polygon(polygonId: PolygonId('to-be-added')), })); verify(mock.changePolygons({ const Polygon(polygonId: PolygonId('to-be-updated'), visible: false), })); }); testWidgets('updatePolylines', (WidgetTester tester) async { final MockPolylinesController mock = MockPolylinesController(); controller.debugSetOverrides(polylines: mock); final Set previous = { const Polyline(polylineId: PolylineId('to-be-updated')), const Polyline(polylineId: PolylineId('to-be-removed')), }; final Set current = { const Polyline( polylineId: PolylineId('to-be-updated'), visible: false, ), const Polyline(polylineId: PolylineId('to-be-added')), }; controller.updatePolylines(PolylineUpdates.from(previous, current)); verify(mock.removePolylines({ const PolylineId('to-be-removed'), })); verify(mock.addPolylines({ const Polyline(polylineId: PolylineId('to-be-added')), })); verify(mock.changePolylines({ const Polyline( polylineId: PolylineId('to-be-updated'), visible: false, ), })); }); testWidgets('infoWindow visibility', (WidgetTester tester) async { final MockMarkersController mock = MockMarkersController(); const MarkerId markerId = MarkerId('marker-with-infowindow'); when(mock.isInfoWindowShown(markerId)).thenReturn(true); controller.debugSetOverrides(markers: mock); controller.showInfoWindow(markerId); verify(mock.showMarkerInfoWindow(markerId)); controller.hideInfoWindow(markerId); verify(mock.hideMarkerInfoWindow(markerId)); controller.isInfoWindowShown(markerId); verify(mock.isInfoWindowShown(markerId)); }); }); }); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in google_maps_flutter_web_integration_tests/integration_test/google_maps_controller_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:google_maps/google_maps.dart' as _i2; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart' as _i4; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class class _FakeGMap_0 extends _i1.SmartFake implements _i2.GMap { _FakeGMap_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [CirclesController]. /// /// See the documentation for Mockito's code generation for more information. class MockCirclesController extends _i1.Mock implements _i3.CirclesController { @override Map<_i4.CircleId, _i3.CircleController> get circles => (super.noSuchMethod( Invocation.getter(#circles), returnValue: <_i4.CircleId, _i3.CircleController>{}, returnValueForMissingStub: <_i4.CircleId, _i3.CircleController>{}, ) as Map<_i4.CircleId, _i3.CircleController>); @override _i2.GMap get googleMap => (super.noSuchMethod( Invocation.getter(#googleMap), returnValue: _FakeGMap_0( this, Invocation.getter(#googleMap), ), returnValueForMissingStub: _FakeGMap_0( this, Invocation.getter(#googleMap), ), ) as _i2.GMap); @override set googleMap(_i2.GMap? _googleMap) => super.noSuchMethod( Invocation.setter( #googleMap, _googleMap, ), returnValueForMissingStub: null, ); @override int get mapId => (super.noSuchMethod( Invocation.getter(#mapId), returnValue: 0, returnValueForMissingStub: 0, ) as int); @override set mapId(int? _mapId) => super.noSuchMethod( Invocation.setter( #mapId, _mapId, ), returnValueForMissingStub: null, ); @override void addCircles(Set<_i4.Circle>? circlesToAdd) => super.noSuchMethod( Invocation.method( #addCircles, [circlesToAdd], ), returnValueForMissingStub: null, ); @override void changeCircles(Set<_i4.Circle>? circlesToChange) => super.noSuchMethod( Invocation.method( #changeCircles, [circlesToChange], ), returnValueForMissingStub: null, ); @override void removeCircles(Set<_i4.CircleId>? circleIdsToRemove) => super.noSuchMethod( Invocation.method( #removeCircles, [circleIdsToRemove], ), returnValueForMissingStub: null, ); @override void bindToMap( int? mapId, _i2.GMap? googleMap, ) => super.noSuchMethod( Invocation.method( #bindToMap, [ mapId, googleMap, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [PolygonsController]. /// /// See the documentation for Mockito's code generation for more information. class MockPolygonsController extends _i1.Mock implements _i3.PolygonsController { @override Map<_i4.PolygonId, _i3.PolygonController> get polygons => (super.noSuchMethod( Invocation.getter(#polygons), returnValue: <_i4.PolygonId, _i3.PolygonController>{}, returnValueForMissingStub: <_i4.PolygonId, _i3.PolygonController>{}, ) as Map<_i4.PolygonId, _i3.PolygonController>); @override _i2.GMap get googleMap => (super.noSuchMethod( Invocation.getter(#googleMap), returnValue: _FakeGMap_0( this, Invocation.getter(#googleMap), ), returnValueForMissingStub: _FakeGMap_0( this, Invocation.getter(#googleMap), ), ) as _i2.GMap); @override set googleMap(_i2.GMap? _googleMap) => super.noSuchMethod( Invocation.setter( #googleMap, _googleMap, ), returnValueForMissingStub: null, ); @override int get mapId => (super.noSuchMethod( Invocation.getter(#mapId), returnValue: 0, returnValueForMissingStub: 0, ) as int); @override set mapId(int? _mapId) => super.noSuchMethod( Invocation.setter( #mapId, _mapId, ), returnValueForMissingStub: null, ); @override void addPolygons(Set<_i4.Polygon>? polygonsToAdd) => super.noSuchMethod( Invocation.method( #addPolygons, [polygonsToAdd], ), returnValueForMissingStub: null, ); @override void changePolygons(Set<_i4.Polygon>? polygonsToChange) => super.noSuchMethod( Invocation.method( #changePolygons, [polygonsToChange], ), returnValueForMissingStub: null, ); @override void removePolygons(Set<_i4.PolygonId>? polygonIdsToRemove) => super.noSuchMethod( Invocation.method( #removePolygons, [polygonIdsToRemove], ), returnValueForMissingStub: null, ); @override void bindToMap( int? mapId, _i2.GMap? googleMap, ) => super.noSuchMethod( Invocation.method( #bindToMap, [ mapId, googleMap, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [PolylinesController]. /// /// See the documentation for Mockito's code generation for more information. class MockPolylinesController extends _i1.Mock implements _i3.PolylinesController { @override Map<_i4.PolylineId, _i3.PolylineController> get lines => (super.noSuchMethod( Invocation.getter(#lines), returnValue: <_i4.PolylineId, _i3.PolylineController>{}, returnValueForMissingStub: <_i4.PolylineId, _i3.PolylineController>{}, ) as Map<_i4.PolylineId, _i3.PolylineController>); @override _i2.GMap get googleMap => (super.noSuchMethod( Invocation.getter(#googleMap), returnValue: _FakeGMap_0( this, Invocation.getter(#googleMap), ), returnValueForMissingStub: _FakeGMap_0( this, Invocation.getter(#googleMap), ), ) as _i2.GMap); @override set googleMap(_i2.GMap? _googleMap) => super.noSuchMethod( Invocation.setter( #googleMap, _googleMap, ), returnValueForMissingStub: null, ); @override int get mapId => (super.noSuchMethod( Invocation.getter(#mapId), returnValue: 0, returnValueForMissingStub: 0, ) as int); @override set mapId(int? _mapId) => super.noSuchMethod( Invocation.setter( #mapId, _mapId, ), returnValueForMissingStub: null, ); @override void addPolylines(Set<_i4.Polyline>? polylinesToAdd) => super.noSuchMethod( Invocation.method( #addPolylines, [polylinesToAdd], ), returnValueForMissingStub: null, ); @override void changePolylines(Set<_i4.Polyline>? polylinesToChange) => super.noSuchMethod( Invocation.method( #changePolylines, [polylinesToChange], ), returnValueForMissingStub: null, ); @override void removePolylines(Set<_i4.PolylineId>? polylineIdsToRemove) => super.noSuchMethod( Invocation.method( #removePolylines, [polylineIdsToRemove], ), returnValueForMissingStub: null, ); @override void bindToMap( int? mapId, _i2.GMap? googleMap, ) => super.noSuchMethod( Invocation.method( #bindToMap, [ mapId, googleMap, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [MarkersController]. /// /// See the documentation for Mockito's code generation for more information. class MockMarkersController extends _i1.Mock implements _i3.MarkersController { @override Map<_i4.MarkerId, _i3.MarkerController> get markers => (super.noSuchMethod( Invocation.getter(#markers), returnValue: <_i4.MarkerId, _i3.MarkerController>{}, returnValueForMissingStub: <_i4.MarkerId, _i3.MarkerController>{}, ) as Map<_i4.MarkerId, _i3.MarkerController>); @override _i2.GMap get googleMap => (super.noSuchMethod( Invocation.getter(#googleMap), returnValue: _FakeGMap_0( this, Invocation.getter(#googleMap), ), returnValueForMissingStub: _FakeGMap_0( this, Invocation.getter(#googleMap), ), ) as _i2.GMap); @override set googleMap(_i2.GMap? _googleMap) => super.noSuchMethod( Invocation.setter( #googleMap, _googleMap, ), returnValueForMissingStub: null, ); @override int get mapId => (super.noSuchMethod( Invocation.getter(#mapId), returnValue: 0, returnValueForMissingStub: 0, ) as int); @override set mapId(int? _mapId) => super.noSuchMethod( Invocation.setter( #mapId, _mapId, ), returnValueForMissingStub: null, ); @override void addMarkers(Set<_i4.Marker>? markersToAdd) => super.noSuchMethod( Invocation.method( #addMarkers, [markersToAdd], ), returnValueForMissingStub: null, ); @override void changeMarkers(Set<_i4.Marker>? markersToChange) => super.noSuchMethod( Invocation.method( #changeMarkers, [markersToChange], ), returnValueForMissingStub: null, ); @override void removeMarkers(Set<_i4.MarkerId>? markerIdsToRemove) => super.noSuchMethod( Invocation.method( #removeMarkers, [markerIdsToRemove], ), returnValueForMissingStub: null, ); @override void showMarkerInfoWindow(_i4.MarkerId? markerId) => super.noSuchMethod( Invocation.method( #showMarkerInfoWindow, [markerId], ), returnValueForMissingStub: null, ); @override void hideMarkerInfoWindow(_i4.MarkerId? markerId) => super.noSuchMethod( Invocation.method( #hideMarkerInfoWindow, [markerId], ), returnValueForMissingStub: null, ); @override bool isInfoWindowShown(_i4.MarkerId? markerId) => (super.noSuchMethod( Invocation.method( #isInfoWindowShown, [markerId], ), returnValue: false, returnValueForMissingStub: false, ) as bool); @override void bindToMap( int? mapId, _i2.GMap? googleMap, ) => super.noSuchMethod( Invocation.method( #bindToMap, [ mapId, googleMap, ], ), returnValueForMissingStub: null, ); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:js_util' show getProperty; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'google_maps_plugin_test.mocks.dart'; @GenerateMocks([], customMocks: >[ MockSpec(onMissingStub: OnMissingStub.returnDefault), ]) /// Test GoogleMapsPlugin void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('GoogleMapsPlugin', () { late MockGoogleMapController controller; late GoogleMapsPlugin plugin; late Completer reportedMapIdCompleter; int numberOnPlatformViewCreatedCalls = 0; void onPlatformViewCreated(int id) { reportedMapIdCompleter.complete(id); numberOnPlatformViewCreatedCalls++; } setUp(() { controller = MockGoogleMapController(); plugin = GoogleMapsPlugin(); reportedMapIdCompleter = Completer(); }); group('init/dispose', () { group('before buildWidget', () { testWidgets('init throws assertion', (WidgetTester tester) async { expect(() => plugin.init(0), throwsAssertionError); }); }); group('after buildWidget', () { setUp(() { plugin.debugSetMapById({0: controller}); }); testWidgets('cannot call methods after dispose', (WidgetTester tester) async { plugin.dispose(mapId: 0); verify(controller.dispose()); expect( () => plugin.init(0), throwsAssertionError, reason: 'Method calls should fail after dispose.', ); }); }); }); group('buildView', () { const int testMapId = 33930; const CameraPosition initialCameraPosition = CameraPosition(target: LatLng(0, 0)); testWidgets( 'returns an HtmlElementView and caches the controller for later', (WidgetTester tester) async { final Map cache = {}; plugin.debugSetMapById(cache); final Widget widget = plugin.buildViewWithConfiguration( testMapId, onPlatformViewCreated, widgetConfiguration: const MapWidgetConfiguration( initialCameraPosition: initialCameraPosition, textDirection: TextDirection.ltr, ), ); expect(widget, isA()); expect( (widget as HtmlElementView).viewType, contains('$testMapId'), reason: 'view type should contain the mapId passed when creating the map.', ); expect(cache, contains(testMapId)); expect( cache[testMapId], isNotNull, reason: 'cached controller cannot be null.', ); expect( cache[testMapId]!.isInitialized, isTrue, reason: 'buildView calls init on the controller', ); }); testWidgets('returns cached instance if it already exists', (WidgetTester tester) async { const HtmlElementView expected = HtmlElementView(viewType: 'only-for-testing'); when(controller.widget).thenReturn(expected); plugin.debugSetMapById({ testMapId: controller, }); final Widget widget = plugin.buildViewWithConfiguration( testMapId, onPlatformViewCreated, widgetConfiguration: const MapWidgetConfiguration( initialCameraPosition: initialCameraPosition, textDirection: TextDirection.ltr, ), ); expect(widget, equals(expected)); }); testWidgets( 'asynchronously reports onPlatformViewCreated the first time it happens', (WidgetTester tester) async { final Map cache = {}; plugin.debugSetMapById(cache); plugin.buildViewWithConfiguration( testMapId, onPlatformViewCreated, widgetConfiguration: const MapWidgetConfiguration( initialCameraPosition: initialCameraPosition, textDirection: TextDirection.ltr, ), ); // Simulate Google Maps JS SDK being "ready" cache[testMapId]!.stream.add(WebMapReadyEvent(testMapId)); expect( cache[testMapId]!.isInitialized, isTrue, reason: 'buildView calls init on the controller', ); expect( await reportedMapIdCompleter.future, testMapId, reason: 'Should call onPlatformViewCreated with the mapId', ); // Fire repeated event again... cache[testMapId]!.stream.add(WebMapReadyEvent(testMapId)); expect( numberOnPlatformViewCreatedCalls, equals(1), reason: 'Should not call onPlatformViewCreated for the same controller multiple times', ); }); }); group('setMapStyles', () { const String mapStyle = ''' [{ "featureType": "poi.park", "elementType": "labels.text.fill", "stylers": [{"color": "#6b9a76"}] }]'''; testWidgets('translates styles for controller', (WidgetTester tester) async { plugin.debugSetMapById({0: controller}); await plugin.setMapStyle(mapStyle, mapId: 0); final dynamic captured = verify(controller.updateStyles(captureThat(isList))).captured[0]; final List styles = captured as List; expect(styles.length, 1); // Let's peek inside the styles... final gmaps.MapTypeStyle style = styles[0]; expect(style.featureType, 'poi.park'); expect(style.elementType, 'labels.text.fill'); expect(style.stylers?.length, 1); expect(getProperty(style.stylers![0]!, 'color'), '#6b9a76'); }); }); group('Noop methods:', () { const int mapId = 0; setUp(() { plugin.debugSetMapById({mapId: controller}); }); // Options testWidgets('updateTileOverlays', (WidgetTester tester) async { final Future update = plugin.updateTileOverlays( mapId: mapId, newTileOverlays: {}, ); expect(update, completion(null)); }); testWidgets('updateTileOverlays', (WidgetTester tester) async { final Future update = plugin.clearTileCache( const TileOverlayId('any'), mapId: mapId, ); expect(update, completion(null)); }); }); // These methods only pass-through values from the plugin to the controller // so we verify them all together here... group('Pass-through methods:', () { const int mapId = 0; setUp(() { plugin.debugSetMapById({mapId: controller}); }); // Options testWidgets('updateMapConfiguration', (WidgetTester tester) async { const MapConfiguration configuration = MapConfiguration(mapType: MapType.satellite); await plugin.updateMapConfiguration(configuration, mapId: mapId); verify(controller.updateMapConfiguration(configuration)); }); // Geometry testWidgets('updateMarkers', (WidgetTester tester) async { final MarkerUpdates expectedUpdates = MarkerUpdates.from( const {}, const {}, ); await plugin.updateMarkers(expectedUpdates, mapId: mapId); verify(controller.updateMarkers(expectedUpdates)); }); testWidgets('updatePolygons', (WidgetTester tester) async { final PolygonUpdates expectedUpdates = PolygonUpdates.from( const {}, const {}, ); await plugin.updatePolygons(expectedUpdates, mapId: mapId); verify(controller.updatePolygons(expectedUpdates)); }); testWidgets('updatePolylines', (WidgetTester tester) async { final PolylineUpdates expectedUpdates = PolylineUpdates.from( const {}, const {}, ); await plugin.updatePolylines(expectedUpdates, mapId: mapId); verify(controller.updatePolylines(expectedUpdates)); }); testWidgets('updateCircles', (WidgetTester tester) async { final CircleUpdates expectedUpdates = CircleUpdates.from( const {}, const {}, ); await plugin.updateCircles(expectedUpdates, mapId: mapId); verify(controller.updateCircles(expectedUpdates)); }); // Camera testWidgets('animateCamera', (WidgetTester tester) async { final CameraUpdate expectedUpdates = CameraUpdate.newLatLng( const LatLng(43.3626, -5.8433), ); await plugin.animateCamera(expectedUpdates, mapId: mapId); verify(controller.moveCamera(expectedUpdates)); }); testWidgets('moveCamera', (WidgetTester tester) async { final CameraUpdate expectedUpdates = CameraUpdate.newLatLng( const LatLng(43.3628, -5.8478), ); await plugin.moveCamera(expectedUpdates, mapId: mapId); verify(controller.moveCamera(expectedUpdates)); }); // Viewport testWidgets('getVisibleRegion', (WidgetTester tester) async { when(controller.getVisibleRegion()) .thenAnswer((_) async => LatLngBounds( northeast: const LatLng(47.2359634, -68.0192019), southwest: const LatLng(34.5019594, -120.4974629), )); await plugin.getVisibleRegion(mapId: mapId); verify(controller.getVisibleRegion()); }); testWidgets('getZoomLevel', (WidgetTester tester) async { when(controller.getZoomLevel()).thenAnswer((_) async => 10); await plugin.getZoomLevel(mapId: mapId); verify(controller.getZoomLevel()); }); testWidgets('getScreenCoordinate', (WidgetTester tester) async { when(controller.getScreenCoordinate(any)).thenAnswer( (_) async => const ScreenCoordinate(x: 320, y: 240) // fake return ); const LatLng latLng = LatLng(43.3613, -5.8499); await plugin.getScreenCoordinate(latLng, mapId: mapId); verify(controller.getScreenCoordinate(latLng)); }); testWidgets('getLatLng', (WidgetTester tester) async { when(controller.getLatLng(any)).thenAnswer( (_) async => const LatLng(43.3613, -5.8499) // fake return ); const ScreenCoordinate coordinates = ScreenCoordinate(x: 19, y: 26); await plugin.getLatLng(coordinates, mapId: mapId); verify(controller.getLatLng(coordinates)); }); // InfoWindows testWidgets('showMarkerInfoWindow', (WidgetTester tester) async { const MarkerId markerId = MarkerId('testing-123'); await plugin.showMarkerInfoWindow(markerId, mapId: mapId); verify(controller.showInfoWindow(markerId)); }); testWidgets('hideMarkerInfoWindow', (WidgetTester tester) async { const MarkerId markerId = MarkerId('testing-123'); await plugin.hideMarkerInfoWindow(markerId, mapId: mapId); verify(controller.hideInfoWindow(markerId)); }); testWidgets('isMarkerInfoWindowShown', (WidgetTester tester) async { when(controller.isInfoWindowShown(any)).thenReturn(true); const MarkerId markerId = MarkerId('testing-123'); await plugin.isMarkerInfoWindowShown(markerId, mapId: mapId); verify(controller.isInfoWindowShown(markerId)); }); }); // Verify all event streams are filtered correctly from the main one... group('Event Streams', () { const int mapId = 0; late StreamController> streamController; setUp(() { streamController = StreamController>.broadcast(); when(controller.events) .thenAnswer((Invocation realInvocation) => streamController.stream); plugin.debugSetMapById({mapId: controller}); }); // Dispatches a few events in the global streamController, and expects *only* the passed event to be there. Future testStreamFiltering( Stream> stream, MapEvent event) async { Timer.run(() { streamController.add(_OtherMapEvent(mapId)); streamController.add(event); streamController.add(_OtherMapEvent(mapId)); streamController.close(); }); final List> events = await stream.toList(); expect(events.length, 1); expect(events[0], event); } // Camera events testWidgets('onCameraMoveStarted', (WidgetTester tester) async { final CameraMoveStartedEvent event = CameraMoveStartedEvent(mapId); final Stream stream = plugin.onCameraMoveStarted(mapId: mapId); await testStreamFiltering(stream, event); }); testWidgets('onCameraMoveStarted', (WidgetTester tester) async { final CameraMoveEvent event = CameraMoveEvent( mapId, const CameraPosition( target: LatLng(43.3790, -5.8660), ), ); final Stream stream = plugin.onCameraMove(mapId: mapId); await testStreamFiltering(stream, event); }); testWidgets('onCameraIdle', (WidgetTester tester) async { final CameraIdleEvent event = CameraIdleEvent(mapId); final Stream stream = plugin.onCameraIdle(mapId: mapId); await testStreamFiltering(stream, event); }); // Marker events testWidgets('onMarkerTap', (WidgetTester tester) async { final MarkerTapEvent event = MarkerTapEvent( mapId, const MarkerId('test-123'), ); final Stream stream = plugin.onMarkerTap(mapId: mapId); await testStreamFiltering(stream, event); }); testWidgets('onInfoWindowTap', (WidgetTester tester) async { final InfoWindowTapEvent event = InfoWindowTapEvent( mapId, const MarkerId('test-123'), ); final Stream stream = plugin.onInfoWindowTap(mapId: mapId); await testStreamFiltering(stream, event); }); testWidgets('onMarkerDragStart', (WidgetTester tester) async { final MarkerDragStartEvent event = MarkerDragStartEvent( mapId, const LatLng(43.3677, -5.8372), const MarkerId('test-123'), ); final Stream stream = plugin.onMarkerDragStart(mapId: mapId); await testStreamFiltering(stream, event); }); testWidgets('onMarkerDrag', (WidgetTester tester) async { final MarkerDragEvent event = MarkerDragEvent( mapId, const LatLng(43.3677, -5.8372), const MarkerId('test-123'), ); final Stream stream = plugin.onMarkerDrag(mapId: mapId); await testStreamFiltering(stream, event); }); testWidgets('onMarkerDragEnd', (WidgetTester tester) async { final MarkerDragEndEvent event = MarkerDragEndEvent( mapId, const LatLng(43.3677, -5.8372), const MarkerId('test-123'), ); final Stream stream = plugin.onMarkerDragEnd(mapId: mapId); await testStreamFiltering(stream, event); }); // Geometry testWidgets('onPolygonTap', (WidgetTester tester) async { final PolygonTapEvent event = PolygonTapEvent( mapId, const PolygonId('test-123'), ); final Stream stream = plugin.onPolygonTap(mapId: mapId); await testStreamFiltering(stream, event); }); testWidgets('onPolylineTap', (WidgetTester tester) async { final PolylineTapEvent event = PolylineTapEvent( mapId, const PolylineId('test-123'), ); final Stream stream = plugin.onPolylineTap(mapId: mapId); await testStreamFiltering(stream, event); }); testWidgets('onCircleTap', (WidgetTester tester) async { final CircleTapEvent event = CircleTapEvent( mapId, const CircleId('test-123'), ); final Stream stream = plugin.onCircleTap(mapId: mapId); await testStreamFiltering(stream, event); }); // Map taps testWidgets('onTap', (WidgetTester tester) async { final MapTapEvent event = MapTapEvent( mapId, const LatLng(43.3597, -5.8458), ); final Stream stream = plugin.onTap(mapId: mapId); await testStreamFiltering(stream, event); }); testWidgets('onLongPress', (WidgetTester tester) async { final MapLongPressEvent event = MapLongPressEvent( mapId, const LatLng(43.3608, -5.8425), ); final Stream stream = plugin.onLongPress(mapId: mapId); await testStreamFiltering(stream, event); }); }); }); } class _OtherMapEvent extends MapEvent { _OtherMapEvent(int mapId) : super(mapId, null); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in google_maps_flutter_web_integration_tests/integration_test/google_maps_plugin_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i2; import 'package:google_maps/google_maps.dart' as _i5; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart' as _i3; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class class _FakeStreamController_0 extends _i1.SmartFake implements _i2.StreamController { _FakeStreamController_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeLatLngBounds_1 extends _i1.SmartFake implements _i3.LatLngBounds { _FakeLatLngBounds_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeScreenCoordinate_2 extends _i1.SmartFake implements _i3.ScreenCoordinate { _FakeScreenCoordinate_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeLatLng_3 extends _i1.SmartFake implements _i3.LatLng { _FakeLatLng_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [GoogleMapController]. /// /// See the documentation for Mockito's code generation for more information. class MockGoogleMapController extends _i1.Mock implements _i4.GoogleMapController { @override _i2.StreamController<_i3.MapEvent> get stream => (super.noSuchMethod( Invocation.getter(#stream), returnValue: _FakeStreamController_0<_i3.MapEvent>( this, Invocation.getter(#stream), ), returnValueForMissingStub: _FakeStreamController_0<_i3.MapEvent>( this, Invocation.getter(#stream), ), ) as _i2.StreamController<_i3.MapEvent>); @override _i2.Stream<_i3.MapEvent> get events => (super.noSuchMethod( Invocation.getter(#events), returnValue: _i2.Stream<_i3.MapEvent>.empty(), returnValueForMissingStub: _i2.Stream<_i3.MapEvent>.empty(), ) as _i2.Stream<_i3.MapEvent>); @override bool get isInitialized => (super.noSuchMethod( Invocation.getter(#isInitialized), returnValue: false, returnValueForMissingStub: false, ) as bool); @override void debugSetOverrides({ _i4.DebugCreateMapFunction? createMap, _i4.MarkersController? markers, _i4.CirclesController? circles, _i4.PolygonsController? polygons, _i4.PolylinesController? polylines, }) => super.noSuchMethod( Invocation.method( #debugSetOverrides, [], { #createMap: createMap, #markers: markers, #circles: circles, #polygons: polygons, #polylines: polylines, }, ), returnValueForMissingStub: null, ); @override void init() => super.noSuchMethod( Invocation.method( #init, [], ), returnValueForMissingStub: null, ); @override void updateMapConfiguration(_i3.MapConfiguration? update) => super.noSuchMethod( Invocation.method( #updateMapConfiguration, [update], ), returnValueForMissingStub: null, ); @override void updateStyles(List<_i5.MapTypeStyle>? styles) => super.noSuchMethod( Invocation.method( #updateStyles, [styles], ), returnValueForMissingStub: null, ); @override _i2.Future<_i3.LatLngBounds> getVisibleRegion() => (super.noSuchMethod( Invocation.method( #getVisibleRegion, [], ), returnValue: _i2.Future<_i3.LatLngBounds>.value(_FakeLatLngBounds_1( this, Invocation.method( #getVisibleRegion, [], ), )), returnValueForMissingStub: _i2.Future<_i3.LatLngBounds>.value(_FakeLatLngBounds_1( this, Invocation.method( #getVisibleRegion, [], ), )), ) as _i2.Future<_i3.LatLngBounds>); @override _i2.Future<_i3.ScreenCoordinate> getScreenCoordinate(_i3.LatLng? latLng) => (super.noSuchMethod( Invocation.method( #getScreenCoordinate, [latLng], ), returnValue: _i2.Future<_i3.ScreenCoordinate>.value(_FakeScreenCoordinate_2( this, Invocation.method( #getScreenCoordinate, [latLng], ), )), returnValueForMissingStub: _i2.Future<_i3.ScreenCoordinate>.value(_FakeScreenCoordinate_2( this, Invocation.method( #getScreenCoordinate, [latLng], ), )), ) as _i2.Future<_i3.ScreenCoordinate>); @override _i2.Future<_i3.LatLng> getLatLng(_i3.ScreenCoordinate? screenCoordinate) => (super.noSuchMethod( Invocation.method( #getLatLng, [screenCoordinate], ), returnValue: _i2.Future<_i3.LatLng>.value(_FakeLatLng_3( this, Invocation.method( #getLatLng, [screenCoordinate], ), )), returnValueForMissingStub: _i2.Future<_i3.LatLng>.value(_FakeLatLng_3( this, Invocation.method( #getLatLng, [screenCoordinate], ), )), ) as _i2.Future<_i3.LatLng>); @override _i2.Future moveCamera(_i3.CameraUpdate? cameraUpdate) => (super.noSuchMethod( Invocation.method( #moveCamera, [cameraUpdate], ), returnValue: _i2.Future.value(), returnValueForMissingStub: _i2.Future.value(), ) as _i2.Future); @override _i2.Future getZoomLevel() => (super.noSuchMethod( Invocation.method( #getZoomLevel, [], ), returnValue: _i2.Future.value(0.0), returnValueForMissingStub: _i2.Future.value(0.0), ) as _i2.Future); @override void updateCircles(_i3.CircleUpdates? updates) => super.noSuchMethod( Invocation.method( #updateCircles, [updates], ), returnValueForMissingStub: null, ); @override void updatePolygons(_i3.PolygonUpdates? updates) => super.noSuchMethod( Invocation.method( #updatePolygons, [updates], ), returnValueForMissingStub: null, ); @override void updatePolylines(_i3.PolylineUpdates? updates) => super.noSuchMethod( Invocation.method( #updatePolylines, [updates], ), returnValueForMissingStub: null, ); @override void updateMarkers(_i3.MarkerUpdates? updates) => super.noSuchMethod( Invocation.method( #updateMarkers, [updates], ), returnValueForMissingStub: null, ); @override void showInfoWindow(_i3.MarkerId? markerId) => super.noSuchMethod( Invocation.method( #showInfoWindow, [markerId], ), returnValueForMissingStub: null, ); @override void hideInfoWindow(_i3.MarkerId? markerId) => super.noSuchMethod( Invocation.method( #hideInfoWindow, [markerId], ), returnValueForMissingStub: null, ); @override bool isInfoWindowShown(_i3.MarkerId? markerId) => (super.noSuchMethod( Invocation.method( #isInfoWindowShown, [markerId], ), returnValue: false, returnValueForMissingStub: false, ) as bool); @override void dispose() => super.noSuchMethod( Invocation.method( #dispose, [], ), returnValueForMissingStub: null, ); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/marker_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:html' as html; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'; import 'package:integration_test/integration_test.dart'; /// Test Markers void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); // Since onTap/DragEnd events happen asynchronously, we need to store when the event // is fired. We use a completer so the test can wait for the future to be completed. late Completer methodCalledCompleter; /// This is the future value of the [methodCalledCompleter]. Reinitialized /// in the [setUp] method, and completed (as `true`) by [onTap] and [onDragEnd] /// when those methods are called from the MarkerController. late Future methodCalled; void onTap() { methodCalledCompleter.complete(true); } void onDragStart(gmaps.LatLng _) { methodCalledCompleter.complete(true); } void onDrag(gmaps.LatLng _) { methodCalledCompleter.complete(true); } void onDragEnd(gmaps.LatLng _) { methodCalledCompleter.complete(true); } setUp(() { methodCalledCompleter = Completer(); methodCalled = methodCalledCompleter.future; }); group('MarkerController', () { late gmaps.Marker marker; setUp(() { marker = gmaps.Marker(); }); testWidgets('onTap gets called', (WidgetTester tester) async { MarkerController(marker: marker, onTap: onTap); // Trigger a click event... gmaps.Event.trigger(marker, 'click', [gmaps.MapMouseEvent()]); // The event handling is now truly async. Wait for it... expect(await methodCalled, isTrue); }); testWidgets('onDragStart gets called', (WidgetTester tester) async { MarkerController(marker: marker, onDragStart: onDragStart); // Trigger a drag end event... gmaps.Event.trigger(marker, 'dragstart', [gmaps.MapMouseEvent()..latLng = gmaps.LatLng(0, 0)]); expect(await methodCalled, isTrue); }); testWidgets('onDrag gets called', (WidgetTester tester) async { MarkerController(marker: marker, onDrag: onDrag); // Trigger a drag end event... gmaps.Event.trigger( marker, 'drag', [gmaps.MapMouseEvent()..latLng = gmaps.LatLng(0, 0)], ); expect(await methodCalled, isTrue); }); testWidgets('onDragEnd gets called', (WidgetTester tester) async { MarkerController(marker: marker, onDragEnd: onDragEnd); // Trigger a drag end event... gmaps.Event.trigger( marker, 'dragend', [gmaps.MapMouseEvent()..latLng = gmaps.LatLng(0, 0)], ); expect(await methodCalled, isTrue); }); testWidgets('update', (WidgetTester tester) async { final MarkerController controller = MarkerController(marker: marker); final gmaps.MarkerOptions options = gmaps.MarkerOptions() ..draggable = true; expect(marker.draggable, isNull); controller.update(options); expect(marker.draggable, isTrue); }); testWidgets('infoWindow null, showInfoWindow.', (WidgetTester tester) async { final MarkerController controller = MarkerController(marker: marker); controller.showInfoWindow(); expect(controller.infoWindowShown, isFalse); }); testWidgets('showInfoWindow', (WidgetTester tester) async { final gmaps.InfoWindow infoWindow = gmaps.InfoWindow(); final gmaps.GMap map = gmaps.GMap(html.DivElement()); marker.set('map', map); final MarkerController controller = MarkerController( marker: marker, infoWindow: infoWindow, ); controller.showInfoWindow(); expect(infoWindow.get('map'), map); expect(controller.infoWindowShown, isTrue); }); testWidgets('hideInfoWindow', (WidgetTester tester) async { final gmaps.InfoWindow infoWindow = gmaps.InfoWindow(); final gmaps.GMap map = gmaps.GMap(html.DivElement()); marker.set('map', map); final MarkerController controller = MarkerController( marker: marker, infoWindow: infoWindow, ); controller.hideInfoWindow(); expect(infoWindow.get('map'), isNull); expect(controller.infoWindowShown, isFalse); }); group('remove', () { late MarkerController controller; setUp(() { final gmaps.InfoWindow infoWindow = gmaps.InfoWindow(); final gmaps.GMap map = gmaps.GMap(html.DivElement()); marker.set('map', map); controller = MarkerController(marker: marker, infoWindow: infoWindow); }); testWidgets('drops gmaps instance', (WidgetTester tester) async { controller.remove(); expect(controller.marker, isNull); }); testWidgets('cannot call update after remove', (WidgetTester tester) async { final gmaps.MarkerOptions options = gmaps.MarkerOptions() ..draggable = true; controller.remove(); expect(() { controller.update(options); }, throwsAssertionError); }); testWidgets('cannot call showInfoWindow after remove', (WidgetTester tester) async { controller.remove(); expect(() { controller.showInfoWindow(); }, throwsAssertionError); }); testWidgets('cannot call hideInfoWindow after remove', (WidgetTester tester) async { controller.remove(); expect(() { controller.hideInfoWindow(); }, throwsAssertionError); }); }); }); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/markers_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:convert'; import 'dart:html' as html; import 'dart:typed_data'; import 'dart:ui'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'; import 'package:http/http.dart' as http; import 'package:integration_test/integration_test.dart'; import 'resources/icon_image_base64.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('MarkersController', () { late StreamController> events; late MarkersController controller; late gmaps.GMap map; setUp(() { events = StreamController>(); controller = MarkersController(stream: events); map = gmaps.GMap(html.DivElement()); controller.bindToMap(123, map); }); testWidgets('addMarkers', (WidgetTester tester) async { final Set markers = { const Marker(markerId: MarkerId('1')), const Marker(markerId: MarkerId('2')), }; controller.addMarkers(markers); expect(controller.markers.length, 2); expect(controller.markers, contains(const MarkerId('1'))); expect(controller.markers, contains(const MarkerId('2'))); expect(controller.markers, isNot(contains(const MarkerId('66')))); }); testWidgets('changeMarkers', (WidgetTester tester) async { final Set markers = { const Marker(markerId: MarkerId('1')), }; controller.addMarkers(markers); expect( controller.markers[const MarkerId('1')]?.marker?.draggable, isFalse); // Update the marker with radius 10 final Set updatedMarkers = { const Marker(markerId: MarkerId('1'), draggable: true), }; controller.changeMarkers(updatedMarkers); expect(controller.markers.length, 1); expect( controller.markers[const MarkerId('1')]?.marker?.draggable, isTrue); }); testWidgets('removeMarkers', (WidgetTester tester) async { final Set markers = { const Marker(markerId: MarkerId('1')), const Marker(markerId: MarkerId('2')), const Marker(markerId: MarkerId('3')), }; controller.addMarkers(markers); expect(controller.markers.length, 3); // Remove some markers... final Set markerIdsToRemove = { const MarkerId('1'), const MarkerId('3'), }; controller.removeMarkers(markerIdsToRemove); expect(controller.markers.length, 1); expect(controller.markers, isNot(contains(const MarkerId('1')))); expect(controller.markers, contains(const MarkerId('2'))); expect(controller.markers, isNot(contains(const MarkerId('3')))); }); testWidgets('InfoWindow show/hide', (WidgetTester tester) async { final Set markers = { const Marker( markerId: MarkerId('1'), infoWindow: InfoWindow(title: 'Title', snippet: 'Snippet'), ), }; controller.addMarkers(markers); expect(controller.markers[const MarkerId('1')]?.infoWindowShown, isFalse); controller.showMarkerInfoWindow(const MarkerId('1')); expect(controller.markers[const MarkerId('1')]?.infoWindowShown, isTrue); controller.hideMarkerInfoWindow(const MarkerId('1')); expect(controller.markers[const MarkerId('1')]?.infoWindowShown, isFalse); }); // https://github.com/flutter/flutter/issues/67380 testWidgets('only single InfoWindow is visible', (WidgetTester tester) async { final Set markers = { const Marker( markerId: MarkerId('1'), infoWindow: InfoWindow(title: 'Title', snippet: 'Snippet'), ), const Marker( markerId: MarkerId('2'), infoWindow: InfoWindow(title: 'Title', snippet: 'Snippet'), ), }; controller.addMarkers(markers); expect(controller.markers[const MarkerId('1')]?.infoWindowShown, isFalse); expect(controller.markers[const MarkerId('2')]?.infoWindowShown, isFalse); controller.showMarkerInfoWindow(const MarkerId('1')); expect(controller.markers[const MarkerId('1')]?.infoWindowShown, isTrue); expect(controller.markers[const MarkerId('2')]?.infoWindowShown, isFalse); controller.showMarkerInfoWindow(const MarkerId('2')); expect(controller.markers[const MarkerId('1')]?.infoWindowShown, isFalse); expect(controller.markers[const MarkerId('2')]?.infoWindowShown, isTrue); }); // https://github.com/flutter/flutter/issues/66622 testWidgets('markers with custom bitmap icon work', (WidgetTester tester) async { final Uint8List bytes = const Base64Decoder().convert(iconImageBase64); final Set markers = { Marker( markerId: const MarkerId('1'), icon: BitmapDescriptor.fromBytes(bytes), ), }; controller.addMarkers(markers); expect(controller.markers.length, 1); final gmaps.Icon? icon = controller.markers[const MarkerId('1')]?.marker?.icon as gmaps.Icon?; expect(icon, isNotNull); final String blobUrl = icon!.url!; expect(blobUrl, startsWith('blob:')); final http.Response response = await http.get(Uri.parse(blobUrl)); expect(response.bodyBytes, bytes, reason: 'Bytes from the Icon blob must match bytes used to create Marker'); }); // https://github.com/flutter/flutter/issues/73789 testWidgets('markers with custom bitmap icon pass size to sdk', (WidgetTester tester) async { final Uint8List bytes = const Base64Decoder().convert(iconImageBase64); final Set markers = { Marker( markerId: const MarkerId('1'), icon: BitmapDescriptor.fromBytes(bytes, size: const Size(20, 30)), ), }; controller.addMarkers(markers); expect(controller.markers.length, 1); final gmaps.Icon? icon = controller.markers[const MarkerId('1')]?.marker?.icon as gmaps.Icon?; expect(icon, isNotNull); final gmaps.Size size = icon!.size!; final gmaps.Size scaledSize = icon.scaledSize!; expect(size.width, 20); expect(size.height, 30); expect(scaledSize.width, 20); expect(scaledSize.height, 30); }); // https://github.com/flutter/flutter/issues/67854 testWidgets('InfoWindow snippet can have links', (WidgetTester tester) async { final Set markers = { const Marker( markerId: MarkerId('1'), infoWindow: InfoWindow( title: 'title for test', snippet: 'Go to Google >>>', ), ), }; controller.addMarkers(markers); expect(controller.markers.length, 1); final html.HtmlElement? content = controller.markers[const MarkerId('1')] ?.infoWindow?.content as html.HtmlElement?; expect(content?.innerHtml, contains('title for test')); expect( content?.innerHtml, contains( 'Go to Google >>>', )); }); // https://github.com/flutter/flutter/issues/67289 testWidgets('InfoWindow content is clickable', (WidgetTester tester) async { final Set markers = { const Marker( markerId: MarkerId('1'), infoWindow: InfoWindow( title: 'title for test', snippet: 'some snippet', ), ), }; controller.addMarkers(markers); expect(controller.markers.length, 1); final html.HtmlElement? content = controller.markers[const MarkerId('1')] ?.infoWindow?.content as html.HtmlElement?; content?.click(); final MapEvent event = await events.stream.first; expect(event, isA()); expect((event as InfoWindowTapEvent).value, equals(const MarkerId('1'))); }); }); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/projection_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // These tests render an app with a small map widget, and use its map controller // to compute values of the default projection. // (Tests methods that can't be mocked in `google_maps_controller_test.dart`) import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart' show GoogleMap, GoogleMapController; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:integration_test/integration_test.dart'; // This value is used when comparing long~num, like LatLng values. const double _acceptableLatLngDelta = 0.0000000001; // This value is used when comparing pixel measurements, mostly to gloss over // browser rounding errors. const int _acceptablePixelDelta = 1; /// Test Google Map Controller void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('Methods that require a proper Projection', () { const LatLng center = LatLng(43.3078, -5.6958); const Size size = Size(320, 240); const CameraPosition initialCamera = CameraPosition( target: center, zoom: 14, ); late Completer controllerCompleter; late void Function(GoogleMapController) onMapCreated; setUp(() { controllerCompleter = Completer(); onMapCreated = (GoogleMapController mapController) { controllerCompleter.complete(mapController); }; }); group('getScreenCoordinate', () { testWidgets('target of map is in center of widget', (WidgetTester tester) async { await pumpCenteredMap( tester, initialCamera: initialCamera, size: size, onMapCreated: onMapCreated, ); final GoogleMapController controller = await controllerCompleter.future; final ScreenCoordinate screenPosition = await controller.getScreenCoordinate(center); expect( screenPosition.x, closeTo(size.width / 2, _acceptablePixelDelta), ); expect( screenPosition.y, closeTo(size.height / 2, _acceptablePixelDelta), ); }); testWidgets('NorthWest of visible region corresponds to x:0, y:0', (WidgetTester tester) async { await pumpCenteredMap( tester, initialCamera: initialCamera, size: size, onMapCreated: onMapCreated, ); final GoogleMapController controller = await controllerCompleter.future; final LatLngBounds bounds = await controller.getVisibleRegion(); final LatLng northWest = LatLng( bounds.northeast.latitude, bounds.southwest.longitude, ); final ScreenCoordinate screenPosition = await controller.getScreenCoordinate(northWest); expect(screenPosition.x, closeTo(0, _acceptablePixelDelta)); expect(screenPosition.y, closeTo(0, _acceptablePixelDelta)); }); testWidgets( 'SouthEast of visible region corresponds to x:size.width, y:size.height', (WidgetTester tester) async { await pumpCenteredMap( tester, initialCamera: initialCamera, size: size, onMapCreated: onMapCreated, ); final GoogleMapController controller = await controllerCompleter.future; final LatLngBounds bounds = await controller.getVisibleRegion(); final LatLng southEast = LatLng( bounds.southwest.latitude, bounds.northeast.longitude, ); final ScreenCoordinate screenPosition = await controller.getScreenCoordinate(southEast); expect(screenPosition.x, closeTo(size.width, _acceptablePixelDelta)); expect(screenPosition.y, closeTo(size.height, _acceptablePixelDelta)); }); }); group('getLatLng', () { testWidgets('Center of widget is the target of map', (WidgetTester tester) async { await pumpCenteredMap( tester, initialCamera: initialCamera, size: size, onMapCreated: onMapCreated, ); final GoogleMapController controller = await controllerCompleter.future; final LatLng coords = await controller.getLatLng( ScreenCoordinate(x: size.width ~/ 2, y: size.height ~/ 2), ); expect( coords.latitude, closeTo(center.latitude, _acceptableLatLngDelta), ); expect( coords.longitude, closeTo(center.longitude, _acceptableLatLngDelta), ); }); testWidgets('Top-left of widget is NorthWest bound of map', (WidgetTester tester) async { await pumpCenteredMap( tester, initialCamera: initialCamera, size: size, onMapCreated: onMapCreated, ); final GoogleMapController controller = await controllerCompleter.future; final LatLngBounds bounds = await controller.getVisibleRegion(); final LatLng northWest = LatLng( bounds.northeast.latitude, bounds.southwest.longitude, ); final LatLng coords = await controller.getLatLng( const ScreenCoordinate(x: 0, y: 0), ); expect( coords.latitude, closeTo(northWest.latitude, _acceptableLatLngDelta), ); expect( coords.longitude, closeTo(northWest.longitude, _acceptableLatLngDelta), ); }); testWidgets('Bottom-right of widget is SouthWest bound of map', (WidgetTester tester) async { await pumpCenteredMap( tester, initialCamera: initialCamera, size: size, onMapCreated: onMapCreated, ); final GoogleMapController controller = await controllerCompleter.future; final LatLngBounds bounds = await controller.getVisibleRegion(); final LatLng southEast = LatLng( bounds.southwest.latitude, bounds.northeast.longitude, ); final LatLng coords = await controller.getLatLng( ScreenCoordinate(x: size.width.toInt(), y: size.height.toInt()), ); expect( coords.latitude, closeTo(southEast.latitude, _acceptableLatLngDelta), ); expect( coords.longitude, closeTo(southEast.longitude, _acceptableLatLngDelta), ); }); }); }); } // Pumps a CenteredMap Widget into a given tester, with some parameters Future pumpCenteredMap( WidgetTester tester, { required CameraPosition initialCamera, Size? size, void Function(GoogleMapController)? onMapCreated, }) async { await tester.pumpWidget( CenteredMap( initialCamera: initialCamera, size: size ?? const Size(320, 240), onMapCreated: onMapCreated, ), ); // This is needed to kick-off the rendering of the JS Map flutter widget await tester.pump(); } /// Renders a Map widget centered on the screen. /// This depends in `package:google_maps_flutter` to work. class CenteredMap extends StatelessWidget { const CenteredMap({ required this.initialCamera, required this.size, required this.onMapCreated, Key? key, }) : super(key: key); /// A function that receives the [GoogleMapController] of the Map widget once initialized. final void Function(GoogleMapController)? onMapCreated; /// The size of the rendered map widget. final Size size; /// The initial camera position (center + zoom level) of the Map widget. final CameraPosition initialCamera; @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Center( child: SizedBox.fromSize( size: size, child: GoogleMap( initialCameraPosition: initialCamera, onMapCreated: onMapCreated, ), ), ), ), ); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/resources/icon_image_base64.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. const String iconImageBase64 = 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAIRlWElmTU' '0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIA' 'AIdpAAQAAAABAAAAWgAAAAAAAABIAAAAAQAAAEgAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQ' 'AAABCgAwAEAAAAAQAAABAAAAAAx28c8QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAVlpVFh0WE1M' 'OmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIH' 'g6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8v' 'd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcm' 'lwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFk' 'b2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk' '9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6' 'eG1wbWV0YT4KTMInWQAAAplJREFUOBF1k01ME1EQx2fe7tIPoGgTE6AJgQQSPaiH9oAtkFbsgX' 'jygFcT0XjSkxcTDxtPJh6MR28ePMHBBA8cNLSIony0oBhEMVETP058tE132+7uG3cW24DAXN57' '2fn9/zPz3iIcEdEl0nIxtNLr1IlVeoMadkubKmoL+u2SzAV8IjV5Ekt4GN+A8+VOUPwLarOI2G' 'Vpqq0i4JQorwQxPtWHVZ1IKP8LNGDXGaSyqARFxDGo7MJBy4XVf3AyQ+qTHnTEXoF9cFUy3OkY' '0oWxmWFtD5xNoc1sQ6AOn1+hCNTkkhKow8KFZV77tVs2O9dhFvBm0IA/U0RhZ7/ocEx23oUDlh' 'h8HkNjZIN8Lb3gOU8gOp7AKJHCB2/aNZkTftHumNzzbtl2CBPZHqxw8mHhVZBeoz6w5DvhE2FZ' 'lQYPjKdd2/qRyKZ6KsPv7TEk7EYEk0A0EUmJduHRy1i4oLKqgmC59ZggAdwrC9pFuWy1iUT2rA' 'uv0h2UdNtNqxCBBkgqorjOMOgksN7CxQ90vEb00U3c3LIwyo9o8FXxQVNr8Coqyk+S5EPBXnjt' 'xRmc4TegI7qWbvBkeeUbGMnTCd4nZnYeDOWIEtlC6cKK/JJepY3hZSvN33jovO6L0XFqPKqBTO' 'FuapUoPr1lxDM7cmC2TAOz25cYSGa++feBew/cjpc0V+mNT29/HZp3KDFTNLvuTRPEHy5065lj' 'Xn4y41XM+wP/AlcycRmdc3MUhvLm/J/ceu/3qUVT62oP2EZpjSylHybHSpDUVcjq9gEBVo0+Xt' 'JyN2IWRO+3QUforRoKnZLVsglaMECW+YmMSj9M3SrC6Lg71CMiqWfUrJ6ywzefhnZ+G69BaKdB' 'WhXQAn6wzDUpfUPw7MrmX/WhbfmKblw+AAAAAElFTkSuQmCC'; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shape_test.dart ================================================ // Copyright 2013 The Flutter 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_test/flutter_test.dart'; import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'; import 'package:integration_test/integration_test.dart'; /// Test Shapes (Circle, Polygon, Polyline) void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); // Since onTap events happen asynchronously, we need to store when the event // is fired. We use a completer so the test can wait for the future to be completed. late Completer methodCalledCompleter; /// This is the future value of the [methodCalledCompleter]. Reinitialized /// in the [setUp] method, and completed (as `true`) by [onTap], when it gets /// called by the corresponding Shape Controller. late Future methodCalled; void onTap() { methodCalledCompleter.complete(true); } setUp(() { methodCalledCompleter = Completer(); methodCalled = methodCalledCompleter.future; }); group('CircleController', () { late gmaps.Circle circle; setUp(() { circle = gmaps.Circle(); }); testWidgets('onTap gets called', (WidgetTester tester) async { CircleController(circle: circle, consumeTapEvents: true, onTap: onTap); // Trigger a click event... gmaps.Event.trigger(circle, 'click', [gmaps.MapMouseEvent()]); // The event handling is now truly async. Wait for it... expect(await methodCalled, isTrue); }); testWidgets('update', (WidgetTester tester) async { final CircleController controller = CircleController(circle: circle); final gmaps.CircleOptions options = gmaps.CircleOptions() ..draggable = true; expect(circle.draggable, isNull); controller.update(options); expect(circle.draggable, isTrue); }); group('remove', () { late CircleController controller; setUp(() { controller = CircleController(circle: circle); }); testWidgets('drops gmaps instance', (WidgetTester tester) async { controller.remove(); expect(controller.circle, isNull); }); testWidgets('cannot call update after remove', (WidgetTester tester) async { final gmaps.CircleOptions options = gmaps.CircleOptions() ..draggable = true; controller.remove(); expect(() { controller.update(options); }, throwsAssertionError); }); }); }); group('PolygonController', () { late gmaps.Polygon polygon; setUp(() { polygon = gmaps.Polygon(); }); testWidgets('onTap gets called', (WidgetTester tester) async { PolygonController(polygon: polygon, consumeTapEvents: true, onTap: onTap); // Trigger a click event... gmaps.Event.trigger(polygon, 'click', [gmaps.MapMouseEvent()]); // The event handling is now truly async. Wait for it... expect(await methodCalled, isTrue); }); testWidgets('update', (WidgetTester tester) async { final PolygonController controller = PolygonController(polygon: polygon); final gmaps.PolygonOptions options = gmaps.PolygonOptions() ..draggable = true; expect(polygon.draggable, isNull); controller.update(options); expect(polygon.draggable, isTrue); }); group('remove', () { late PolygonController controller; setUp(() { controller = PolygonController(polygon: polygon); }); testWidgets('drops gmaps instance', (WidgetTester tester) async { controller.remove(); expect(controller.polygon, isNull); }); testWidgets('cannot call update after remove', (WidgetTester tester) async { final gmaps.PolygonOptions options = gmaps.PolygonOptions() ..draggable = true; controller.remove(); expect(() { controller.update(options); }, throwsAssertionError); }); }); }); group('PolylineController', () { late gmaps.Polyline polyline; setUp(() { polyline = gmaps.Polyline(); }); testWidgets('onTap gets called', (WidgetTester tester) async { PolylineController( polyline: polyline, consumeTapEvents: true, onTap: onTap, ); // Trigger a click event... gmaps.Event.trigger(polyline, 'click', [gmaps.MapMouseEvent()]); // The event handling is now truly async. Wait for it... expect(await methodCalled, isTrue); }); testWidgets('update', (WidgetTester tester) async { final PolylineController controller = PolylineController( polyline: polyline, ); final gmaps.PolylineOptions options = gmaps.PolylineOptions() ..draggable = true; expect(polyline.draggable, isNull); controller.update(options); expect(polyline.draggable, isTrue); }); group('remove', () { late PolylineController controller; setUp(() { controller = PolylineController(polyline: polyline); }); testWidgets('drops gmaps instance', (WidgetTester tester) async { controller.remove(); expect(controller.line, isNull); }); testWidgets('cannot call update after remove', (WidgetTester tester) async { final gmaps.PolylineOptions options = gmaps.PolylineOptions() ..draggable = true; controller.remove(); expect(() { controller.update(options); }, throwsAssertionError); }); }); }); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/shapes_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:html' as html; import 'dart:ui'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps/google_maps_geometry.dart' as geometry; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:google_maps_flutter_web/google_maps_flutter_web.dart'; import 'package:integration_test/integration_test.dart'; // This value is used when comparing the results of // converting from a byte value to a double between 0 and 1. // (For Color opacity values, for example) const double _acceptableDelta = 0.01; /// Test Shapes (Circle, Polygon, Polyline) void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); late gmaps.GMap map; setUp(() { map = gmaps.GMap(html.DivElement()); }); group('CirclesController', () { late StreamController> events; late CirclesController controller; setUp(() { events = StreamController>(); controller = CirclesController(stream: events); controller.bindToMap(123, map); }); testWidgets('addCircles', (WidgetTester tester) async { final Set circles = { const Circle(circleId: CircleId('1')), const Circle(circleId: CircleId('2')), }; controller.addCircles(circles); expect(controller.circles.length, 2); expect(controller.circles, contains(const CircleId('1'))); expect(controller.circles, contains(const CircleId('2'))); expect(controller.circles, isNot(contains(const CircleId('66')))); }); testWidgets('changeCircles', (WidgetTester tester) async { final Set circles = { const Circle(circleId: CircleId('1')), }; controller.addCircles(circles); expect(controller.circles[const CircleId('1')]?.circle?.visible, isTrue); final Set updatedCircles = { const Circle(circleId: CircleId('1'), visible: false), }; controller.changeCircles(updatedCircles); expect(controller.circles.length, 1); expect(controller.circles[const CircleId('1')]?.circle?.visible, isFalse); }); testWidgets('removeCircles', (WidgetTester tester) async { final Set circles = { const Circle(circleId: CircleId('1')), const Circle(circleId: CircleId('2')), const Circle(circleId: CircleId('3')), }; controller.addCircles(circles); expect(controller.circles.length, 3); // Remove some circles... final Set circleIdsToRemove = { const CircleId('1'), const CircleId('3'), }; controller.removeCircles(circleIdsToRemove); expect(controller.circles.length, 1); expect(controller.circles, isNot(contains(const CircleId('1')))); expect(controller.circles, contains(const CircleId('2'))); expect(controller.circles, isNot(contains(const CircleId('3')))); }); testWidgets('Converts colors to CSS', (WidgetTester tester) async { final Set circles = { const Circle( circleId: CircleId('1'), fillColor: Color(0x7FFABADA), strokeColor: Color(0xFFC0FFEE), ), }; controller.addCircles(circles); final gmaps.Circle circle = controller.circles.values.first.circle!; expect(circle.get('fillColor'), '#fabada'); expect(circle.get('fillOpacity'), closeTo(0.5, _acceptableDelta)); expect(circle.get('strokeColor'), '#c0ffee'); expect(circle.get('strokeOpacity'), closeTo(1, _acceptableDelta)); }); }); group('PolygonsController', () { late StreamController> events; late PolygonsController controller; setUp(() { events = StreamController>(); controller = PolygonsController(stream: events); controller.bindToMap(123, map); }); testWidgets('addPolygons', (WidgetTester tester) async { final Set polygons = { const Polygon(polygonId: PolygonId('1')), const Polygon(polygonId: PolygonId('2')), }; controller.addPolygons(polygons); expect(controller.polygons.length, 2); expect(controller.polygons, contains(const PolygonId('1'))); expect(controller.polygons, contains(const PolygonId('2'))); expect(controller.polygons, isNot(contains(const PolygonId('66')))); }); testWidgets('changePolygons', (WidgetTester tester) async { final Set polygons = { const Polygon(polygonId: PolygonId('1')), }; controller.addPolygons(polygons); expect( controller.polygons[const PolygonId('1')]?.polygon?.visible, isTrue); // Update the polygon final Set updatedPolygons = { const Polygon(polygonId: PolygonId('1'), visible: false), }; controller.changePolygons(updatedPolygons); expect(controller.polygons.length, 1); expect( controller.polygons[const PolygonId('1')]?.polygon?.visible, isFalse); }); testWidgets('removePolygons', (WidgetTester tester) async { final Set polygons = { const Polygon(polygonId: PolygonId('1')), const Polygon(polygonId: PolygonId('2')), const Polygon(polygonId: PolygonId('3')), }; controller.addPolygons(polygons); expect(controller.polygons.length, 3); // Remove some polygons... final Set polygonIdsToRemove = { const PolygonId('1'), const PolygonId('3'), }; controller.removePolygons(polygonIdsToRemove); expect(controller.polygons.length, 1); expect(controller.polygons, isNot(contains(const PolygonId('1')))); expect(controller.polygons, contains(const PolygonId('2'))); expect(controller.polygons, isNot(contains(const PolygonId('3')))); }); testWidgets('Converts colors to CSS', (WidgetTester tester) async { final Set polygons = { const Polygon( polygonId: PolygonId('1'), fillColor: Color(0x7FFABADA), strokeColor: Color(0xFFC0FFEE), ), }; controller.addPolygons(polygons); final gmaps.Polygon polygon = controller.polygons.values.first.polygon!; expect(polygon.get('fillColor'), '#fabada'); expect(polygon.get('fillOpacity'), closeTo(0.5, _acceptableDelta)); expect(polygon.get('strokeColor'), '#c0ffee'); expect(polygon.get('strokeOpacity'), closeTo(1, _acceptableDelta)); }); testWidgets('Handle Polygons with holes', (WidgetTester tester) async { final Set polygons = { const Polygon( polygonId: PolygonId('BermudaTriangle'), points: [ LatLng(25.774, -80.19), LatLng(18.466, -66.118), LatLng(32.321, -64.757), ], holes: >[ [ LatLng(28.745, -70.579), LatLng(29.57, -67.514), LatLng(27.339, -66.668), ], ], ), }; controller.addPolygons(polygons); expect(controller.polygons.length, 1); expect(controller.polygons, contains(const PolygonId('BermudaTriangle'))); expect(controller.polygons, isNot(contains(const PolygonId('66')))); }); testWidgets('Polygon with hole has a hole', (WidgetTester tester) async { final Set polygons = { const Polygon( polygonId: PolygonId('BermudaTriangle'), points: [ LatLng(25.774, -80.19), LatLng(18.466, -66.118), LatLng(32.321, -64.757), ], holes: >[ [ LatLng(28.745, -70.579), LatLng(29.57, -67.514), LatLng(27.339, -66.668), ], ], ), }; controller.addPolygons(polygons); final gmaps.Polygon? polygon = controller.polygons.values.first.polygon; final gmaps.LatLng pointInHole = gmaps.LatLng(28.632, -68.401); expect(geometry.Poly.containsLocation(pointInHole, polygon), false); }); testWidgets('Hole Path gets reversed to display correctly', (WidgetTester tester) async { final Set polygons = { const Polygon( polygonId: PolygonId('BermudaTriangle'), points: [ LatLng(25.774, -80.19), LatLng(18.466, -66.118), LatLng(32.321, -64.757), ], holes: >[ [ LatLng(27.339, -66.668), LatLng(29.57, -67.514), LatLng(28.745, -70.579), ], ], ), }; controller.addPolygons(polygons); final gmaps.MVCArray?> paths = controller.polygons.values.first.polygon!.paths!; expect(paths.getAt(1)?.getAt(0)?.lat, 28.745); expect(paths.getAt(1)?.getAt(1)?.lat, 29.57); expect(paths.getAt(1)?.getAt(2)?.lat, 27.339); }); }); group('PolylinesController', () { late StreamController> events; late PolylinesController controller; setUp(() { events = StreamController>(); controller = PolylinesController(stream: events); controller.bindToMap(123, map); }); testWidgets('addPolylines', (WidgetTester tester) async { final Set polylines = { const Polyline(polylineId: PolylineId('1')), const Polyline(polylineId: PolylineId('2')), }; controller.addPolylines(polylines); expect(controller.lines.length, 2); expect(controller.lines, contains(const PolylineId('1'))); expect(controller.lines, contains(const PolylineId('2'))); expect(controller.lines, isNot(contains(const PolylineId('66')))); }); testWidgets('changePolylines', (WidgetTester tester) async { final Set polylines = { const Polyline(polylineId: PolylineId('1')), }; controller.addPolylines(polylines); expect(controller.lines[const PolylineId('1')]?.line?.visible, isTrue); final Set updatedPolylines = { const Polyline(polylineId: PolylineId('1'), visible: false), }; controller.changePolylines(updatedPolylines); expect(controller.lines.length, 1); expect(controller.lines[const PolylineId('1')]?.line?.visible, isFalse); }); testWidgets('removePolylines', (WidgetTester tester) async { final Set polylines = { const Polyline(polylineId: PolylineId('1')), const Polyline(polylineId: PolylineId('2')), const Polyline(polylineId: PolylineId('3')), }; controller.addPolylines(polylines); expect(controller.lines.length, 3); // Remove some polylines... final Set polylineIdsToRemove = { const PolylineId('1'), const PolylineId('3'), }; controller.removePolylines(polylineIdsToRemove); expect(controller.lines.length, 1); expect(controller.lines, isNot(contains(const PolylineId('1')))); expect(controller.lines, contains(const PolylineId('2'))); expect(controller.lines, isNot(contains(const PolylineId('3')))); }); testWidgets('Converts colors to CSS', (WidgetTester tester) async { final Set lines = { const Polyline( polylineId: PolylineId('1'), color: Color(0x7FFABADA), ), }; controller.addPolylines(lines); final gmaps.Polyline line = controller.lines.values.first.line!; expect(line.get('strokeColor'), '#fabada'); expect(line.get('strokeOpacity'), closeTo(0.5, _acceptableDelta)); }); }); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } /// App for testing class MyApp extends StatefulWidget { /// Constructor with key const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override Widget build(BuildContext context) { return const Text('Testing... Look at the console output for results!'); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml ================================================ name: google_maps_flutter_web_integration_tests publish_to: none # Tests require flutter beta or greater to run. environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter google_maps_flutter_platform_interface: ^2.2.1 google_maps_flutter_web: path: ../ dev_dependencies: build_runner: ^2.1.1 flutter_driver: sdk: flutter flutter_test: sdk: flutter google_maps: ^6.1.0 google_maps_flutter: # Used for projection_test.dart path: ../../google_maps_flutter http: ^0.13.0 integration_test: sdk: flutter mockito: ^5.3.2 ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/example/regen_mocks.sh ================================================ #!/usr/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. flutter pub get echo "(Re)generating mocks." flutter pub run build_runner build --delete-conflicting-outputs ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/example/run_test.sh ================================================ #!/usr/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. if pgrep -lf chromedriver > /dev/null; then echo "chromedriver is running." ./regen_mocks.sh if [ $# -eq 0 ]; then echo "No target specified, running all tests..." find integration_test/ -iname *_test.dart | xargs -n1 -i -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target='{}' else echo "Running test target: $1..." set -x flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target=$1 fi else echo "chromedriver is not running." echo "Please, check the README.md for instructions on how to use run_test.sh" fi ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/example/web/index.html ================================================ Browser Tests ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. library google_maps_flutter_web; import 'dart:async'; import 'dart:convert'; import 'dart:html'; import 'dart:js_util'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:google_maps/google_maps.dart' as gmaps; import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; import 'package:sanitize_html/sanitize_html.dart'; import 'package:stream_transform/stream_transform.dart'; import 'src/shims/dart_ui.dart' as ui; // Conditionally imports dart:ui in web import 'src/third_party/to_screen_location/to_screen_location.dart'; import 'src/types.dart'; part 'src/circle.dart'; part 'src/circles.dart'; part 'src/convert.dart'; part 'src/google_maps_controller.dart'; part 'src/google_maps_flutter_web.dart'; part 'src/marker.dart'; part 'src/markers.dart'; part 'src/polygon.dart'; part 'src/polygons.dart'; part 'src/polyline.dart'; part 'src/polylines.dart'; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/src/circle.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. part of google_maps_flutter_web; /// The `CircleController` class wraps a [gmaps.Circle] and its `onTap` behavior. class CircleController { /// Creates a `CircleController`, which wraps a [gmaps.Circle] object and its `onTap` behavior. CircleController({ required gmaps.Circle circle, bool consumeTapEvents = false, ui.VoidCallback? onTap, }) : _circle = circle, _consumeTapEvents = consumeTapEvents { if (onTap != null) { circle.onClick.listen((_) { onTap.call(); }); } } gmaps.Circle? _circle; final bool _consumeTapEvents; /// Returns the wrapped [gmaps.Circle]. Only used for testing. @visibleForTesting gmaps.Circle? get circle => _circle; /// Returns `true` if this Controller will use its own `onTap` handler to consume events. bool get consumeTapEvents => _consumeTapEvents; /// Updates the options of the wrapped [gmaps.Circle] object. /// /// This cannot be called after [remove]. void update(gmaps.CircleOptions options) { assert(_circle != null, 'Cannot `update` Circle after calling `remove`.'); _circle!.options = options; } /// Disposes of the currently wrapped [gmaps.Circle]. void remove() { if (_circle != null) { _circle!.visible = false; _circle!.radius = 0; _circle!.map = null; _circle = null; } } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/src/circles.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. part of google_maps_flutter_web; /// This class manages all the [CircleController]s associated to a [GoogleMapController]. class CirclesController extends GeometryController { /// Initialize the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers. CirclesController({ required StreamController> stream, }) : _streamController = stream, _circleIdToController = {}; // A cache of [CircleController]s indexed by their [CircleId]. final Map _circleIdToController; // The stream over which circles broadcast their events final StreamController> _streamController; /// Returns the cache of [CircleController]s. Test only. @visibleForTesting Map get circles => _circleIdToController; /// Adds a set of [Circle] objects to the cache. /// /// Wraps each [Circle] into its corresponding [CircleController]. void addCircles(Set circlesToAdd) { circlesToAdd.forEach(_addCircle); } void _addCircle(Circle circle) { if (circle == null) { return; } final gmaps.CircleOptions circleOptions = _circleOptionsFromCircle(circle); final gmaps.Circle gmCircle = gmaps.Circle(circleOptions)..map = googleMap; final CircleController controller = CircleController( circle: gmCircle, consumeTapEvents: circle.consumeTapEvents, onTap: () { _onCircleTap(circle.circleId); }); _circleIdToController[circle.circleId] = controller; } /// Updates a set of [Circle] objects with new options. void changeCircles(Set circlesToChange) { circlesToChange.forEach(_changeCircle); } void _changeCircle(Circle circle) { final CircleController? circleController = _circleIdToController[circle.circleId]; circleController?.update(_circleOptionsFromCircle(circle)); } /// Removes a set of [CircleId]s from the cache. void removeCircles(Set circleIdsToRemove) { circleIdsToRemove.forEach(_removeCircle); } // Removes a circle and its controller by its [CircleId]. void _removeCircle(CircleId circleId) { final CircleController? circleController = _circleIdToController[circleId]; circleController?.remove(); _circleIdToController.remove(circleId); } // Handles the global onCircleTap function to funnel events from circles into the stream. bool _onCircleTap(CircleId circleId) { // Have you ended here on your debugging? Is this wrong? // Comment here: https://github.com/flutter/flutter/issues/64084 _streamController.add(CircleTapEvent(mapId, circleId)); return _circleIdToController[circleId]?.consumeTapEvents ?? false; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. part of google_maps_flutter_web; // Default values for when the gmaps objects return null/undefined values. final gmaps.LatLng _nullGmapsLatLng = gmaps.LatLng(0, 0); final gmaps.LatLngBounds _nullGmapsLatLngBounds = gmaps.LatLngBounds(_nullGmapsLatLng, _nullGmapsLatLng); // Defaults taken from the Google Maps Platform SDK documentation. const String _defaultCssColor = '#000000'; const double _defaultCssOpacity = 0.0; // Converts a [Color] into a valid CSS value #RRGGBB. String _getCssColor(Color color) { if (color == null) { return _defaultCssColor; } return '#${color.value.toRadixString(16).padLeft(8, '0').substring(2)}'; } // Extracts the opacity from a [Color]. double _getCssOpacity(Color color) { if (color == null) { return _defaultCssOpacity; } return color.opacity; } // Converts options from the plugin into gmaps.MapOptions that can be used by the JS SDK. // The following options are not handled here, for various reasons: // The following are not available in web, because the map doesn't rotate there: // compassEnabled // rotateGesturesEnabled // tiltGesturesEnabled // mapToolbarEnabled is unused in web, there's no "map toolbar" // myLocationButtonEnabled Widget not available in web yet, it needs to be built on top of the maps widget // See: https://developers.google.com/maps/documentation/javascript/examples/control-custom // myLocationEnabled needs to be built through dart:html navigator.geolocation // See: https://api.dart.dev/stable/2.8.4/dart-html/Geolocation-class.html // trafficEnabled is handled when creating the GMap object, since it needs to be added as a layer. // trackCameraPosition is just a boolan value that indicates if the map has an onCameraMove handler. // indoorViewEnabled seems to not have an equivalent in web // buildingsEnabled seems to not have an equivalent in web // padding seems to behave differently in web than mobile. You can't move UI elements in web. gmaps.MapOptions _configurationAndStyleToGmapsOptions( MapConfiguration configuration, List styles) { final gmaps.MapOptions options = gmaps.MapOptions(); if (configuration.mapType != null) { options.mapTypeId = _gmapTypeIDForPluginType(configuration.mapType!); } final MinMaxZoomPreference? zoomPreference = configuration.minMaxZoomPreference; if (zoomPreference != null) { options ..minZoom = zoomPreference.minZoom ..maxZoom = zoomPreference.maxZoom; } if (configuration.cameraTargetBounds != null) { // Needs gmaps.MapOptions.restriction and gmaps.MapRestriction // see: https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions.restriction } if (configuration.zoomControlsEnabled != null) { options.zoomControl = configuration.zoomControlsEnabled; } if (configuration.scrollGesturesEnabled == false || configuration.zoomGesturesEnabled == false) { options.gestureHandling = 'none'; } else { options.gestureHandling = 'auto'; } // These don't have any configuration entries, but they seem to be off in the // native maps. options.mapTypeControl = false; options.fullscreenControl = false; options.streetViewControl = false; options.styles = styles; return options; } gmaps.MapTypeId _gmapTypeIDForPluginType(MapType type) { switch (type) { case MapType.satellite: return gmaps.MapTypeId.SATELLITE; case MapType.terrain: return gmaps.MapTypeId.TERRAIN; case MapType.hybrid: return gmaps.MapTypeId.HYBRID; case MapType.normal: case MapType.none: 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( CameraPosition initialPosition, gmaps.MapOptions options, ) { // Adjust the initial position, if passed... if (initialPosition != null) { options.zoom = initialPosition.zoom; options.center = gmaps.LatLng( initialPosition.target.latitude, initialPosition.target.longitude); } return options; } // The keys we'd expect to see in a serialized MapTypeStyle JSON object. final Set _mapStyleKeys = { 'elementType', 'featureType', 'stylers', }; // Checks if the passed in Map contains some of the _mapStyleKeys. bool _isJsonMapStyle(Map value) { return _mapStyleKeys.intersection(value.keys.toSet()).isNotEmpty; } // Converts an incoming JSON-encoded Style info, into the correct gmaps array. List _mapStyles(String? mapStyleJson) { List styles = []; if (mapStyleJson != null) { styles = (json.decode(mapStyleJson, reviver: (Object? key, Object? value) { if (value is Map && _isJsonMapStyle(value as Map)) { List stylers = []; if (value['stylers'] != null) { stylers = (value['stylers']! as List) .map((Object? e) => e != null ? jsify(e) : null) .toList(); } return gmaps.MapTypeStyle() ..elementType = value['elementType'] as String? ..featureType = value['featureType'] as String? ..stylers = stylers; } return value; }) as List) .where((Object? element) => element != null) .cast() .toList(); // .toList calls are required so the JS API understands the underlying data structure. } return styles; } gmaps.LatLng _latLngToGmLatLng(LatLng latLng) { return gmaps.LatLng(latLng.latitude, latLng.longitude); } LatLng _gmLatLngToLatLng(gmaps.LatLng latLng) { return LatLng(latLng.lat.toDouble(), latLng.lng.toDouble()); } LatLngBounds _gmLatLngBoundsTolatLngBounds(gmaps.LatLngBounds latLngBounds) { return LatLngBounds( southwest: _gmLatLngToLatLng(latLngBounds.southWest), northeast: _gmLatLngToLatLng(latLngBounds.northEast), ); } CameraPosition _gmViewportToCameraPosition(gmaps.GMap map) { return CameraPosition( target: _gmLatLngToLatLng(map.center ?? _nullGmapsLatLng), bearing: map.heading?.toDouble() ?? 0, tilt: map.tilt?.toDouble() ?? 0, zoom: map.zoom?.toDouble() ?? 0, ); } // Convert plugin objects to gmaps.Options objects // TODO(ditman): Move to their appropriate objects, maybe make them copy constructors? // Marker.fromMarker(anotherMarker, moreOptions); gmaps.InfoWindowOptions? _infoWindowOptionsFromMarker(Marker marker) { final String markerTitle = marker.infoWindow.title ?? ''; final String markerSnippet = marker.infoWindow.snippet ?? ''; // If both the title and snippet of an infowindow are empty, we don't really // want an infowindow... if ((markerTitle.isEmpty) && (markerSnippet.isEmpty)) { return null; } // Add an outer wrapper to the contents of the infowindow, we need it to listen // to click events... final HtmlElement container = DivElement() ..id = 'gmaps-marker-${marker.markerId.value}-infowindow'; if (markerTitle.isNotEmpty) { final HtmlElement title = HeadingElement.h3() ..className = 'infowindow-title' ..innerText = markerTitle; container.children.add(title); } if (markerSnippet.isNotEmpty) { final HtmlElement snippet = DivElement() ..className = 'infowindow-snippet' // `sanitizeHtml` is used to clean the (potential) user input from (potential) // XSS attacks through the contents of the marker InfoWindow. // See: https://pub.dev/documentation/sanitize_html/latest/sanitize_html/sanitizeHtml.html // See: b/159137885, b/159598165 // The NodeTreeSanitizer.trusted just tells setInnerHtml to leave the output // of `sanitizeHtml` untouched. // ignore: unsafe_html ..setInnerHtml( sanitizeHtml(markerSnippet), treeSanitizer: NodeTreeSanitizer.trusted, ); container.children.add(snippet); } return gmaps.InfoWindowOptions() ..content = container ..zIndex = marker.zIndex; // TODO(ditman): Compute the pixelOffset of the infoWindow, from the size of the Marker, // and the marker.infoWindow.anchor property. } // Attempts to extract a [gmaps.Size] from `iconConfig[sizeIndex]`. gmaps.Size? _gmSizeFromIconConfig(List iconConfig, int sizeIndex) { gmaps.Size? size; if (iconConfig.length >= sizeIndex + 1) { final List? rawIconSize = iconConfig[sizeIndex] as List?; if (rawIconSize != null) { size = gmaps.Size( rawIconSize[0] as num?, rawIconSize[1] as num?, ); } } return size; } // Converts a [BitmapDescriptor] into a [gmaps.Icon] that can be used in Markers. gmaps.Icon? _gmIconFromBitmapDescriptor(BitmapDescriptor bitmapDescriptor) { final List iconConfig = bitmapDescriptor.toJson() as List; gmaps.Icon? icon; if (iconConfig != null) { if (iconConfig[0] == 'fromAssetImage') { assert(iconConfig.length >= 2); // iconConfig[2] contains the DPIs of the screen, but that information is // already encoded in the iconConfig[1] icon = gmaps.Icon() ..url = ui.webOnlyAssetManager.getAssetUrl(iconConfig[1]! as String); final gmaps.Size? size = _gmSizeFromIconConfig(iconConfig, 3); if (size != null) { icon ..size = size ..scaledSize = size; } } else if (iconConfig[0] == 'fromBytes') { // Grab the bytes, and put them into a blob final List bytes = iconConfig[1]! as List; // Create a Blob from bytes, but let the browser figure out the encoding final Blob blob = Blob([bytes]); icon = gmaps.Icon()..url = Url.createObjectUrlFromBlob(blob); final gmaps.Size? size = _gmSizeFromIconConfig(iconConfig, 2); if (size != null) { icon ..size = size ..scaledSize = size; } } } return icon; } // Computes the options for a new [gmaps.Marker] from an incoming set of options // [marker], and the existing marker registered with the map: [currentMarker]. // Preserves the position from the [currentMarker], if set. gmaps.MarkerOptions _markerOptionsFromMarker( Marker marker, gmaps.Marker? currentMarker, ) { return gmaps.MarkerOptions() ..position = currentMarker?.position ?? gmaps.LatLng( marker.position.latitude, marker.position.longitude, ) ..title = sanitizeHtml(marker.infoWindow.title ?? '') ..zIndex = marker.zIndex ..visible = marker.visible ..opacity = marker.alpha ..draggable = marker.draggable ..icon = _gmIconFromBitmapDescriptor(marker.icon); // TODO(ditman): Compute anchor properly, otherwise infowindows attach to the wrong spot. // Flat and Rotation are not supported directly on the web. } gmaps.CircleOptions _circleOptionsFromCircle(Circle circle) { final gmaps.CircleOptions circleOptions = gmaps.CircleOptions() ..strokeColor = _getCssColor(circle.strokeColor) ..strokeOpacity = _getCssOpacity(circle.strokeColor) ..strokeWeight = circle.strokeWidth ..fillColor = _getCssColor(circle.fillColor) ..fillOpacity = _getCssOpacity(circle.fillColor) ..center = gmaps.LatLng(circle.center.latitude, circle.center.longitude) ..radius = circle.radius ..visible = circle.visible ..zIndex = circle.zIndex; return circleOptions; } gmaps.PolygonOptions _polygonOptionsFromPolygon( gmaps.GMap googleMap, Polygon polygon) { // Convert all points to GmLatLng final List path = polygon.points.map(_latLngToGmLatLng).toList(); final bool isClockwisePolygon = _isPolygonClockwise(path); final List> paths = >[path]; for (int i = 0; i < polygon.holes.length; i++) { final List hole = polygon.holes[i]; final List correctHole = _ensureHoleHasReverseWinding( hole, isClockwisePolygon, holeId: i, polygonId: polygon.polygonId, ); paths.add(correctHole); } return gmaps.PolygonOptions() ..paths = paths ..strokeColor = _getCssColor(polygon.strokeColor) ..strokeOpacity = _getCssOpacity(polygon.strokeColor) ..strokeWeight = polygon.strokeWidth ..fillColor = _getCssColor(polygon.fillColor) ..fillOpacity = _getCssOpacity(polygon.fillColor) ..visible = polygon.visible ..zIndex = polygon.zIndex ..geodesic = polygon.geodesic; } List _ensureHoleHasReverseWinding( List hole, bool polyIsClockwise, { required int holeId, required PolygonId polygonId, }) { List holePath = hole.map(_latLngToGmLatLng).toList(); final bool holeIsClockwise = _isPolygonClockwise(holePath); if (holeIsClockwise == polyIsClockwise) { holePath = holePath.reversed.toList(); if (kDebugMode) { print('Hole [$holeId] in Polygon [${polygonId.value}] has been reversed.' ' Ensure holes in polygons are "wound in the opposite direction to the outer path."' ' More info: https://github.com/flutter/flutter/issues/74096'); } } return holePath; } /// Calculates the direction of a given Polygon /// based on: https://stackoverflow.com/a/1165943 /// /// returns [true] if clockwise [false] if counterclockwise /// /// This method expects that the incoming [path] is a `List` of well-formed, /// non-null [gmaps.LatLng] objects. /// /// Currently, this method is only called from [_polygonOptionsFromPolygon], and /// the `path` is a transformed version of [Polygon.points] or each of the /// [Polygon.holes], guaranteeing that `lat` and `lng` can be accessed with `!`. bool _isPolygonClockwise(List path) { double direction = 0.0; for (int i = 0; i < path.length; i++) { direction = direction + ((path[(i + 1) % path.length].lat - path[i].lat) * (path[(i + 1) % path.length].lng + path[i].lng)); } return direction >= 0; } gmaps.PolylineOptions _polylineOptionsFromPolyline( gmaps.GMap googleMap, Polyline polyline) { final List paths = polyline.points.map(_latLngToGmLatLng).toList(); return gmaps.PolylineOptions() ..path = paths ..strokeWeight = polyline.width ..strokeColor = _getCssColor(polyline.color) ..strokeOpacity = _getCssOpacity(polyline.color) ..visible = polyline.visible ..zIndex = polyline.zIndex ..geodesic = polyline.geodesic; // this.endCap = Cap.buttCap, // this.jointType = JointType.mitered, // this.patterns = const [], // this.startCap = Cap.buttCap, // this.width = 10, } // 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': 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(latLng[0] as num?, latLng[1] as num?), ); map.tilt = position['tilt'] as num?; break; case 'newLatLng': 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(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(latLng1[0] as num?, latLng1[1] as num?), gmaps.LatLng(latLng2[0] as num?, latLng2[1] as num?), ), ); // padding = json[2]; // Needs package:google_maps ^4.0.0 to adjust the padding in fitBounds break; case 'scrollBy': map.panBy(json[1] as num?, json[2] as num?); break; case 'zoomBy': gmaps.LatLng? focusLatLng; final double zoomDelta = json[1] as double? ?? 0; // Web only supports integer changes... 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, 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()); } } map.zoom = (map.zoom ?? 0) + newZoomDelta; if (focusLatLng != null) { map.panTo(focusLatLng); } break; case 'zoomIn': map.zoom = (map.zoom ?? 0) + 1; break; case 'zoomOut': map.zoom = (map.zoom ?? 0) - 1; break; case 'zoomTo': map.zoom = json[1] as num?; break; default: throw UnimplementedError('Unimplemented CameraMove: ${json[0]}.'); } } // original JS by: Byron Singh (https://stackoverflow.com/a/30541162) gmaps.LatLng _pixelToLatLng(gmaps.GMap map, int x, int y) { final gmaps.LatLngBounds? bounds = map.bounds; final gmaps.Projection? projection = map.projection; final num? zoom = map.zoom; assert( bounds != null, 'Map Bounds required to compute LatLng of screen x/y.'); assert(projection != null, 'Map Projection required to compute LatLng of screen x/y'); assert(zoom != null, 'Current map zoom level required to compute LatLng of screen x/y'); final gmaps.LatLng ne = bounds!.northEast; final gmaps.LatLng sw = bounds.southWest; final gmaps.Point topRight = projection!.fromLatLngToPoint!(ne)!; final gmaps.Point bottomLeft = projection.fromLatLngToPoint!(sw)!; final int scale = 1 << (zoom!.toInt()); // 2 ^ zoom final gmaps.Point point = gmaps.Point((x / scale) + bottomLeft.x!, (y / scale) + topRight.y!); return projection.fromPointToLatLng!(point)!; } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. part of google_maps_flutter_web; /// Type used when passing an override to the _createMap function. @visibleForTesting typedef DebugCreateMapFunction = gmaps.GMap Function( HtmlElement div, gmaps.MapOptions options); /// Encapsulates a [gmaps.GMap], its events, and where in the DOM it's rendered. class GoogleMapController { /// Initializes the GMap, and the sub-controllers related to it. Wires events. GoogleMapController({ required int mapId, required StreamController> streamController, required MapWidgetConfiguration widgetConfiguration, MapObjects mapObjects = const MapObjects(), MapConfiguration mapConfiguration = const MapConfiguration(), }) : _mapId = mapId, _streamController = streamController, _initialCameraPosition = widgetConfiguration.initialCameraPosition, _markers = mapObjects.markers, _polygons = mapObjects.polygons, _polylines = mapObjects.polylines, _circles = mapObjects.circles, _lastMapConfiguration = mapConfiguration { _circlesController = CirclesController(stream: _streamController); _polygonsController = PolygonsController(stream: _streamController); _polylinesController = PolylinesController(stream: _streamController); _markersController = MarkersController(stream: _streamController); // Register the view factory that will hold the `_div` that holds the map in the DOM. // The `_div` needs to be created outside of the ViewFactory (and cached!) so we can // use it to create the [gmaps.GMap] in the `init()` method of this class. _div = DivElement() ..id = _getViewType(mapId) ..style.width = '100%' ..style.height = '100%'; ui.platformViewRegistry.registerViewFactory( _getViewType(mapId), (int viewId) => _div, ); } // The internal ID of the map. Used to broadcast events, DOM IDs and everything where a unique ID is needed. final int _mapId; final CameraPosition _initialCameraPosition; final Set _markers; final Set _polygons; final Set _polylines; final Set _circles; // The configuraiton passed by the user, before converting to gmaps. // Caching this allows us to re-create the map faithfully when needed. MapConfiguration _lastMapConfiguration = const MapConfiguration(); List _lastStyles = const []; // Creates the 'viewType' for the _widget String _getViewType(int mapId) => 'plugins.flutter.io/google_maps_$mapId'; // The Flutter widget that contains the rendered Map. HtmlElementView? _widget; late HtmlElement _div; /// The Flutter widget that will contain the rendered Map. Used for caching. Widget? get widget { if (_widget == null && !_streamController.isClosed) { _widget = HtmlElementView( viewType: _getViewType(_mapId), ); } return _widget; } // The currently-enabled traffic layer. gmaps.TrafficLayer? _trafficLayer; /// A getter for the current traffic layer. Only for tests. @visibleForTesting gmaps.TrafficLayer? get trafficLayer => _trafficLayer; // The underlying GMap instance. This is the interface with the JS SDK. gmaps.GMap? _googleMap; // The StreamController used by this controller and the geometry ones. final StreamController> _streamController; /// The StreamController for the events of this Map. Only for integration testing. @visibleForTesting StreamController> get stream => _streamController; /// The Stream over which this controller broadcasts events. Stream> get events => _streamController.stream; // Geometry controllers, for different features of the map. CirclesController? _circlesController; PolygonsController? _polygonsController; PolylinesController? _polylinesController; MarkersController? _markersController; // Keeps track if _attachGeometryControllers has been called or not. bool _controllersBoundToMap = false; // Keeps track if the map is moving or not. bool _mapIsMoving = false; /// Overrides certain properties to install mocks defined during testing. @visibleForTesting void debugSetOverrides({ DebugCreateMapFunction? createMap, MarkersController? markers, CirclesController? circles, PolygonsController? polygons, PolylinesController? polylines, }) { _overrideCreateMap = createMap; _markersController = markers ?? _markersController; _circlesController = circles ?? _circlesController; _polygonsController = polygons ?? _polygonsController; _polylinesController = polylines ?? _polylinesController; } DebugCreateMapFunction? _overrideCreateMap; gmaps.GMap _createMap(HtmlElement div, gmaps.MapOptions options) { if (_overrideCreateMap != null) { return _overrideCreateMap!(div, options); } return gmaps.GMap(div, options); } /// A flag that returns true if the controller has been initialized or not. @visibleForTesting bool get isInitialized => _googleMap != null; /// Starts the JS Maps SDK into the target [_div] with `rawOptions`. /// /// (Also initializes the geometry/traffic layers.) /// /// The first part of this method starts the rendering of a [gmaps.GMap] inside /// of the target [_div], with configuration from `rawOptions`. It then stores /// the created GMap in the [_googleMap] attribute. /// /// Not *everything* is rendered with the initial `rawOptions` configuration, /// geometry and traffic layers (and possibly others in the future) have their /// own configuration and are rendered on top of a GMap instance later. This /// happens in the second half of this method. /// /// This method is eagerly called from the [GoogleMapsPlugin.buildView] method /// so the internal [GoogleMapsController] of a Web Map initializes as soon as /// possible. Check [_attachMapEvents] to see how this controller notifies the /// plugin of it being fully ready (through the `onTilesloaded.first` event). /// /// Failure to call this method would result in the GMap not rendering at all, /// and most of the public methods on this class no-op'ing. void init() { gmaps.MapOptions options = _configurationAndStyleToGmapsOptions( _lastMapConfiguration, _lastStyles); // Initial position can only to be set here! options = _applyInitialPosition(_initialCameraPosition, options); // Create the map... final gmaps.GMap map = _createMap(_div, options); _googleMap = map; _attachMapEvents(map); _attachGeometryControllers(map); // Now attach the geometry, traffic and any other layers... _renderInitialGeometry( markers: _markers, circles: _circles, polygons: _polygons, polylines: _polylines, ); _setTrafficLayer(map, _lastMapConfiguration.trafficEnabled ?? false); } // Funnels map gmap events into the plugin's stream controller. void _attachMapEvents(gmaps.GMap map) { map.onTilesloaded.first.then((void _) { // Report the map as ready to go the first time the tiles load _streamController.add(WebMapReadyEvent(_mapId)); }); map.onClick.listen((gmaps.IconMouseEvent event) { assert(event.latLng != null); _streamController.add( MapTapEvent(_mapId, _gmLatLngToLatLng(event.latLng!)), ); }); map.onRightclick.listen((gmaps.MapMouseEvent event) { assert(event.latLng != null); _streamController.add( MapLongPressEvent(_mapId, _gmLatLngToLatLng(event.latLng!)), ); }); map.onBoundsChanged.listen((void _) { if (!_mapIsMoving) { _mapIsMoving = true; _streamController.add(CameraMoveStartedEvent(_mapId)); } _streamController.add( CameraMoveEvent(_mapId, _gmViewportToCameraPosition(map)), ); }); map.onIdle.listen((void _) { _mapIsMoving = false; _streamController.add(CameraIdleEvent(_mapId)); }); } // Binds the Geometry controllers to a map instance void _attachGeometryControllers(gmaps.GMap map) { // Now we can add the initial geometry. // And bind the (ready) map instance to the other geometry controllers. // // These controllers are either created in the constructor of this class, or // overriden (for testing) by the [debugSetOverrides] method. They can't be // null. assert(_circlesController != null, 'Cannot attach a map to a null CirclesController instance.'); assert(_polygonsController != null, 'Cannot attach a map to a null PolygonsController instance.'); assert(_polylinesController != null, 'Cannot attach a map to a null PolylinesController instance.'); assert(_markersController != null, 'Cannot attach a map to a null MarkersController instance.'); _circlesController!.bindToMap(_mapId, map); _polygonsController!.bindToMap(_mapId, map); _polylinesController!.bindToMap(_mapId, map); _markersController!.bindToMap(_mapId, map); _controllersBoundToMap = true; } // Renders the initial sets of geometry. void _renderInitialGeometry({ Set markers = const {}, Set circles = const {}, Set polygons = const {}, Set polylines = const {}, }) { assert( _controllersBoundToMap, 'Geometry controllers must be bound to a map before any geometry can ' 'be added to them. Ensure _attachGeometryControllers is called first.'); // The above assert will only succeed if the controllers have been bound to a map // in the [_attachGeometryControllers] method, which ensures that all these // controllers below are *not* null. _markersController!.addMarkers(markers); _circlesController!.addCircles(circles); _polygonsController!.addPolygons(polygons); _polylinesController!.addPolylines(polylines); } // Merges new options coming from the plugin into _lastConfiguration. // // Returns the updated _lastConfiguration object. MapConfiguration _mergeConfigurations(MapConfiguration update) { _lastMapConfiguration = _lastMapConfiguration.applyDiff(update); return _lastMapConfiguration; } /// Updates the map options from a [MapConfiguration]. /// /// This method converts the map into the proper [gmaps.MapOptions]. void updateMapConfiguration(MapConfiguration update) { assert(_googleMap != null, 'Cannot update options on a null map.'); final MapConfiguration newConfiguration = _mergeConfigurations(update); final gmaps.MapOptions newOptions = _configurationAndStyleToGmapsOptions(newConfiguration, _lastStyles); _setOptions(newOptions); _setTrafficLayer(_googleMap!, newConfiguration.trafficEnabled ?? false); } /// Updates the map options with a new list of [styles]. void updateStyles(List styles) { _lastStyles = styles; _setOptions( _configurationAndStyleToGmapsOptions(_lastMapConfiguration, styles)); } // Sets new [gmaps.MapOptions] on the wrapped map. // ignore: use_setters_to_change_properties void _setOptions(gmaps.MapOptions options) { _googleMap?.options = options; } // Attaches/detaches a Traffic Layer on the passed `map` if `attach` is true/false. void _setTrafficLayer(gmaps.GMap map, bool attach) { if (attach && _trafficLayer == null) { _trafficLayer = gmaps.TrafficLayer()..set('map', map); } if (!attach && _trafficLayer != null) { _trafficLayer!.set('map', null); _trafficLayer = null; } } // _googleMap manipulation // Viewport /// Returns the [LatLngBounds] of the current viewport. Future getVisibleRegion() async { assert(_googleMap != null, 'Cannot get the visible region of a null map.'); final gmaps.LatLngBounds bounds = await Future.value(_googleMap!.bounds) ?? _nullGmapsLatLngBounds; return _gmLatLngBoundsTolatLngBounds(bounds); } /// Returns the [ScreenCoordinate] for a given viewport [LatLng]. Future getScreenCoordinate(LatLng latLng) async { assert(_googleMap != null, 'Cannot get the screen coordinates with a null map.'); final gmaps.Point point = toScreenLocation(_googleMap!, _latLngToGmLatLng(latLng)); return ScreenCoordinate(x: point.x!.toInt(), y: point.y!.toInt()); } /// Returns the [LatLng] for a `screenCoordinate` (in pixels) of the viewport. Future getLatLng(ScreenCoordinate screenCoordinate) async { assert(_googleMap != null, 'Cannot get the lat, lng of a screen coordinate with a null map.'); final gmaps.LatLng latLng = _pixelToLatLng(_googleMap!, screenCoordinate.x, screenCoordinate.y); return _gmLatLngToLatLng(latLng); } /// Applies a `cameraUpdate` to the current viewport. Future moveCamera(CameraUpdate cameraUpdate) async { assert(_googleMap != null, 'Cannot update the camera of a null map.'); return _applyCameraUpdate(_googleMap!, cameraUpdate); } /// Returns the zoom level of the current viewport. Future getZoomLevel() async { assert(_googleMap != null, 'Cannot get zoom level of a null map.'); assert(_googleMap!.zoom != null, 'Zoom level should not be null. Is the map correctly initialized?'); return _googleMap!.zoom!.toDouble(); } // Geometry manipulation /// Applies [CircleUpdates] to the currently managed circles. void updateCircles(CircleUpdates updates) { assert( _circlesController != null, 'Cannot update circles after dispose().'); _circlesController?.addCircles(updates.circlesToAdd); _circlesController?.changeCircles(updates.circlesToChange); _circlesController?.removeCircles(updates.circleIdsToRemove); } /// Applies [PolygonUpdates] to the currently managed polygons. void updatePolygons(PolygonUpdates updates) { assert( _polygonsController != null, 'Cannot update polygons after dispose().'); _polygonsController?.addPolygons(updates.polygonsToAdd); _polygonsController?.changePolygons(updates.polygonsToChange); _polygonsController?.removePolygons(updates.polygonIdsToRemove); } /// Applies [PolylineUpdates] to the currently managed lines. void updatePolylines(PolylineUpdates updates) { assert(_polylinesController != null, 'Cannot update polylines after dispose().'); _polylinesController?.addPolylines(updates.polylinesToAdd); _polylinesController?.changePolylines(updates.polylinesToChange); _polylinesController?.removePolylines(updates.polylineIdsToRemove); } /// Applies [MarkerUpdates] to the currently managed markers. void updateMarkers(MarkerUpdates updates) { assert( _markersController != null, 'Cannot update markers after dispose().'); _markersController?.addMarkers(updates.markersToAdd); _markersController?.changeMarkers(updates.markersToChange); _markersController?.removeMarkers(updates.markerIdsToRemove); } /// Shows the [InfoWindow] of the marker identified by its [MarkerId]. void showInfoWindow(MarkerId markerId) { assert(_markersController != null, 'Cannot show infowindow of marker [${markerId.value}] after dispose().'); _markersController?.showMarkerInfoWindow(markerId); } /// Hides the [InfoWindow] of the marker identified by its [MarkerId]. void hideInfoWindow(MarkerId markerId) { assert(_markersController != null, 'Cannot hide infowindow of marker [${markerId.value}] after dispose().'); _markersController?.hideMarkerInfoWindow(markerId); } /// Returns true if the [InfoWindow] of the marker identified by [MarkerId] is shown. bool isInfoWindowShown(MarkerId markerId) { return _markersController?.isInfoWindowShown(markerId) ?? false; } // Cleanup /// Disposes of this controller and its resources. /// /// You won't be able to call many of the methods on this controller after /// calling `dispose`! void dispose() { _widget = null; _googleMap = null; _circlesController = null; _polygonsController = null; _polylinesController = null; _markersController = null; _streamController.close(); } } /// A MapEvent event fired when a [mapId] on web is interactive. class WebMapReadyEvent extends MapEvent { /// Build a WebMapReady Event for the map represented by `mapId`. WebMapReadyEvent(int mapId) : super(mapId, null); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. part of google_maps_flutter_web; /// The web implementation of [GoogleMapsFlutterPlatform]. /// /// This class implements the `package:google_maps_flutter` functionality for the web. class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { /// Registers this class as the default instance of [GoogleMapsFlutterPlatform]. static void registerWith(Registrar registrar) { GoogleMapsFlutterPlatform.instance = GoogleMapsPlugin(); } // A cache of map controllers by map Id. Map _mapById = {}; /// Allows tests to inject controllers without going through the buildView flow. @visibleForTesting // ignore: use_setters_to_change_properties void debugSetMapById(Map mapById) { _mapById = mapById; } // Convenience getter for a stream of events filtered by their mapId. Stream> _events(int mapId) => _map(mapId).events; // Convenience getter for a map controller by its mapId. GoogleMapController _map(int mapId) { final GoogleMapController? controller = _mapById[mapId]; assert(controller != null, 'Maps cannot be retrieved before calling buildView!'); return controller!; } @override Future init(int mapId) async { // The internal instance of our controller is initialized eagerly in `buildView`, // so we don't have to do anything in this method, which is left intentionally // blank. assert(_map(mapId) != null, 'Must call buildWidget before init!'); } /// Updates the options of a given `mapId`. /// /// This attempts to merge the new `optionsUpdate` passed in, with the previous /// options passed to the map (in other updates, or when creating it). @override Future updateMapConfiguration( MapConfiguration update, { required int mapId, }) async { _map(mapId).updateMapConfiguration(update); } /// Applies the passed in `markerUpdates` to the `mapId`. @override Future updateMarkers( MarkerUpdates markerUpdates, { required int mapId, }) async { _map(mapId).updateMarkers(markerUpdates); } /// Applies the passed in `polygonUpdates` to the `mapId`. @override Future updatePolygons( PolygonUpdates polygonUpdates, { required int mapId, }) async { _map(mapId).updatePolygons(polygonUpdates); } /// Applies the passed in `polylineUpdates` to the `mapId`. @override Future updatePolylines( PolylineUpdates polylineUpdates, { required int mapId, }) async { _map(mapId).updatePolylines(polylineUpdates); } /// Applies the passed in `circleUpdates` to the `mapId`. @override Future updateCircles( CircleUpdates circleUpdates, { required int mapId, }) async { _map(mapId).updateCircles(circleUpdates); } @override Future updateTileOverlays({ required Set newTileOverlays, required int mapId, }) async { return; // Noop for now! } @override Future clearTileCache( TileOverlayId tileOverlayId, { required int mapId, }) async { return; // Noop for now! } /// Applies the given `cameraUpdate` to the current viewport (with animation). @override Future animateCamera( CameraUpdate cameraUpdate, { required int mapId, }) async { return moveCamera(cameraUpdate, mapId: mapId); } /// Applies the given `cameraUpdate` to the current viewport. @override Future moveCamera( CameraUpdate cameraUpdate, { required int mapId, }) async { return _map(mapId).moveCamera(cameraUpdate); } /// Sets the passed-in `mapStyle` to the map. /// /// This function just adds a 'styles' option to the current map options. /// /// Subsequent calls to this method override previous calls, you need to /// pass full styles. @override Future setMapStyle( String? mapStyle, { required int mapId, }) async { _map(mapId).updateStyles(_mapStyles(mapStyle)); } /// Returns the bounds of the current viewport. @override Future getVisibleRegion({ required int mapId, }) { return _map(mapId).getVisibleRegion(); } /// Returns the screen coordinate (in pixels) of a given `latLng`. @override Future getScreenCoordinate( LatLng latLng, { required int mapId, }) { return _map(mapId).getScreenCoordinate(latLng); } /// Returns the [LatLng] of a [ScreenCoordinate] of the viewport. @override Future getLatLng( ScreenCoordinate screenCoordinate, { required int mapId, }) { return _map(mapId).getLatLng(screenCoordinate); } /// Shows the [InfoWindow] (if any) of the [Marker] identified by `markerId`. /// /// See also: /// * [hideMarkerInfoWindow] to hide the info window. /// * [isMarkerInfoWindowShown] to check if the info window is visible/hidden. @override Future showMarkerInfoWindow( MarkerId markerId, { required int mapId, }) async { _map(mapId).showInfoWindow(markerId); } /// Hides the [InfoWindow] (if any) of the [Marker] identified by `markerId`. /// /// See also: /// * [showMarkerInfoWindow] to show the info window. /// * [isMarkerInfoWindowShown] to check if the info window is shown. @override Future hideMarkerInfoWindow( MarkerId markerId, { required int mapId, }) async { _map(mapId).hideInfoWindow(markerId); } /// Returns true if the [InfoWindow] of the [Marker] identified by `markerId` is shown. /// /// See also: /// * [showMarkerInfoWindow] to show the info window. /// * [hideMarkerInfoWindow] to hide the info window. @override Future isMarkerInfoWindowShown( MarkerId markerId, { required int mapId, }) async { return _map(mapId).isInfoWindowShown(markerId); } /// Returns the zoom level of the `mapId`. @override Future getZoomLevel({ required int mapId, }) { return _map(mapId).getZoomLevel(); } // The following are the 11 possible streams of data from the native side // into the plugin @override Stream onCameraMoveStarted({required int mapId}) { return _events(mapId).whereType(); } @override Stream onCameraMove({required int mapId}) { return _events(mapId).whereType(); } @override Stream onCameraIdle({required int mapId}) { return _events(mapId).whereType(); } @override Stream onMarkerTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onInfoWindowTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onMarkerDragStart({required int mapId}) { return _events(mapId).whereType(); } @override Stream onMarkerDrag({required int mapId}) { return _events(mapId).whereType(); } @override Stream onMarkerDragEnd({required int mapId}) { return _events(mapId).whereType(); } @override Stream onPolylineTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onPolygonTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onCircleTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onTap({required int mapId}) { return _events(mapId).whereType(); } @override Stream onLongPress({required int mapId}) { return _events(mapId).whereType(); } /// Disposes of the current map. It can't be used afterwards! @override void dispose({required int mapId}) { _map(mapId).dispose(); _mapById.remove(mapId); } @override Widget buildViewWithConfiguration( int creationId, PlatformViewCreatedCallback onPlatformViewCreated, { required MapWidgetConfiguration widgetConfiguration, MapObjects mapObjects = const MapObjects(), MapConfiguration mapConfiguration = const MapConfiguration(), }) { // Bail fast if we've already rendered this map ID... if (_mapById[creationId]?.widget != null) { return _mapById[creationId]!.widget!; } final StreamController> controller = StreamController>.broadcast(); final GoogleMapController mapController = GoogleMapController( mapId: creationId, streamController: controller, widgetConfiguration: widgetConfiguration, mapObjects: mapObjects, mapConfiguration: mapConfiguration, )..init(); // Initialize the controller _mapById[creationId] = mapController; mapController.events .whereType() .first .then((WebMapReadyEvent event) { assert(creationId == event.mapId, 'Received WebMapReadyEvent for the wrong map'); // Notify the plugin now that there's a fully initialized controller. onPlatformViewCreated.call(event.mapId); }); assert(mapController.widget != null, 'The widget of a GoogleMapController cannot be null before calling dispose on it.'); return mapController.widget!; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/src/marker.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. part of google_maps_flutter_web; /// The `MarkerController` class wraps a [gmaps.Marker], how it handles events, and its associated (optional) [gmaps.InfoWindow] widget. class MarkerController { /// Creates a `MarkerController`, which wraps a [gmaps.Marker] object, its `onTap`/`onDrag` behavior, and its associated [gmaps.InfoWindow]. MarkerController({ required gmaps.Marker marker, gmaps.InfoWindow? infoWindow, bool consumeTapEvents = false, LatLngCallback? onDragStart, LatLngCallback? onDrag, LatLngCallback? onDragEnd, ui.VoidCallback? onTap, }) : _marker = marker, _infoWindow = infoWindow, _consumeTapEvents = consumeTapEvents { if (onTap != null) { marker.onClick.listen((gmaps.MapMouseEvent event) { onTap.call(); }); } if (onDragStart != null) { marker.onDragstart.listen((gmaps.MapMouseEvent event) { if (marker != null) { marker.position = event.latLng; } onDragStart.call(event.latLng ?? _nullGmapsLatLng); }); } if (onDrag != null) { marker.onDrag.listen((gmaps.MapMouseEvent event) { if (marker != null) { marker.position = event.latLng; } onDrag.call(event.latLng ?? _nullGmapsLatLng); }); } if (onDragEnd != null) { marker.onDragend.listen((gmaps.MapMouseEvent event) { if (marker != null) { marker.position = event.latLng; } onDragEnd.call(event.latLng ?? _nullGmapsLatLng); }); } } gmaps.Marker? _marker; final bool _consumeTapEvents; final gmaps.InfoWindow? _infoWindow; bool _infoWindowShown = false; /// Returns `true` if this Controller will use its own `onTap` handler to consume events. bool get consumeTapEvents => _consumeTapEvents; /// Returns `true` if the [gmaps.InfoWindow] associated to this marker is being shown. bool get infoWindowShown => _infoWindowShown; /// Returns the [gmaps.Marker] associated to this controller. gmaps.Marker? get marker => _marker; /// Returns the [gmaps.InfoWindow] associated to the marker. @visibleForTesting gmaps.InfoWindow? get infoWindow => _infoWindow; /// Updates the options of the wrapped [gmaps.Marker] object. /// /// This cannot be called after [remove]. void update( gmaps.MarkerOptions options, { HtmlElement? newInfoWindowContent, }) { assert(_marker != null, 'Cannot `update` Marker after calling `remove`.'); _marker!.options = options; if (_infoWindow != null && newInfoWindowContent != null) { _infoWindow!.content = newInfoWindowContent; } } /// Disposes of the currently wrapped [gmaps.Marker]. void remove() { if (_marker != null) { _infoWindowShown = false; _marker!.visible = false; _marker!.map = null; _marker = null; } } /// Hide the associated [gmaps.InfoWindow]. /// /// This cannot be called after [remove]. void hideInfoWindow() { assert(_marker != null, 'Cannot `hideInfoWindow` on a `remove`d Marker.'); if (_infoWindow != null) { _infoWindow!.close(); _infoWindowShown = false; } } /// Show the associated [gmaps.InfoWindow]. /// /// This cannot be called after [remove]. void showInfoWindow() { assert(_marker != null, 'Cannot `showInfoWindow` on a `remove`d Marker.'); if (_infoWindow != null) { _infoWindow!.open(_marker!.map, _marker); _infoWindowShown = true; } } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/src/markers.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. part of google_maps_flutter_web; /// This class manages a set of [MarkerController]s associated to a [GoogleMapController]. class MarkersController extends GeometryController { /// Initialize the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers. MarkersController({ required StreamController> stream, }) : _streamController = stream, _markerIdToController = {}; // A cache of [MarkerController]s indexed by their [MarkerId]. final Map _markerIdToController; // The stream over which markers broadcast their events final StreamController> _streamController; /// Returns the cache of [MarkerController]s. Test only. @visibleForTesting Map get markers => _markerIdToController; /// Adds a set of [Marker] objects to the cache. /// /// Wraps each [Marker] into its corresponding [MarkerController]. void addMarkers(Set markersToAdd) { markersToAdd.forEach(_addMarker); } void _addMarker(Marker marker) { if (marker == null) { return; } final gmaps.InfoWindowOptions? infoWindowOptions = _infoWindowOptionsFromMarker(marker); gmaps.InfoWindow? gmInfoWindow; if (infoWindowOptions != null) { gmInfoWindow = gmaps.InfoWindow(infoWindowOptions); // Google Maps' JS SDK does not have a click event on the InfoWindow, so // we make one... if (infoWindowOptions.content != null && infoWindowOptions.content is HtmlElement) { final HtmlElement content = infoWindowOptions.content! as HtmlElement; content.onClick.listen((_) { _onInfoWindowTap(marker.markerId); }); } } final gmaps.Marker? currentMarker = _markerIdToController[marker.markerId]?.marker; final gmaps.MarkerOptions markerOptions = _markerOptionsFromMarker(marker, currentMarker); final gmaps.Marker gmMarker = gmaps.Marker(markerOptions)..map = googleMap; final MarkerController controller = MarkerController( marker: gmMarker, infoWindow: gmInfoWindow, consumeTapEvents: marker.consumeTapEvents, onTap: () { showMarkerInfoWindow(marker.markerId); _onMarkerTap(marker.markerId); }, onDragStart: (gmaps.LatLng latLng) { _onMarkerDragStart(marker.markerId, latLng); }, onDrag: (gmaps.LatLng latLng) { _onMarkerDrag(marker.markerId, latLng); }, onDragEnd: (gmaps.LatLng latLng) { _onMarkerDragEnd(marker.markerId, latLng); }, ); _markerIdToController[marker.markerId] = controller; } /// Updates a set of [Marker] objects with new options. void changeMarkers(Set markersToChange) { markersToChange.forEach(_changeMarker); } void _changeMarker(Marker marker) { final MarkerController? markerController = _markerIdToController[marker.markerId]; if (markerController != null) { final gmaps.MarkerOptions markerOptions = _markerOptionsFromMarker( marker, markerController.marker, ); final gmaps.InfoWindowOptions? infoWindow = _infoWindowOptionsFromMarker(marker); markerController.update( markerOptions, newInfoWindowContent: infoWindow?.content as HtmlElement?, ); } } /// Removes a set of [MarkerId]s from the cache. void removeMarkers(Set markerIdsToRemove) { markerIdsToRemove.forEach(_removeMarker); } void _removeMarker(MarkerId markerId) { final MarkerController? markerController = _markerIdToController[markerId]; markerController?.remove(); _markerIdToController.remove(markerId); } // InfoWindow... /// Shows the [InfoWindow] of a [MarkerId]. /// /// See also [hideMarkerInfoWindow] and [isInfoWindowShown]. void showMarkerInfoWindow(MarkerId markerId) { _hideAllMarkerInfoWindow(); final MarkerController? markerController = _markerIdToController[markerId]; markerController?.showInfoWindow(); } /// Hides the [InfoWindow] of a [MarkerId]. /// /// See also [showMarkerInfoWindow] and [isInfoWindowShown]. void hideMarkerInfoWindow(MarkerId markerId) { final MarkerController? markerController = _markerIdToController[markerId]; markerController?.hideInfoWindow(); } /// Returns whether or not the [InfoWindow] of a [MarkerId] is shown. /// /// See also [showMarkerInfoWindow] and [hideMarkerInfoWindow]. bool isInfoWindowShown(MarkerId markerId) { final MarkerController? markerController = _markerIdToController[markerId]; return markerController?.infoWindowShown ?? false; } // Handle internal events bool _onMarkerTap(MarkerId markerId) { // Have you ended here on your debugging? Is this wrong? // Comment here: https://github.com/flutter/flutter/issues/64084 _streamController.add(MarkerTapEvent(mapId, markerId)); return _markerIdToController[markerId]?.consumeTapEvents ?? false; } void _onInfoWindowTap(MarkerId markerId) { _streamController.add(InfoWindowTapEvent(mapId, markerId)); } void _onMarkerDragStart(MarkerId markerId, gmaps.LatLng latLng) { _streamController.add(MarkerDragStartEvent( mapId, _gmLatLngToLatLng(latLng), markerId, )); } void _onMarkerDrag(MarkerId markerId, gmaps.LatLng latLng) { _streamController.add(MarkerDragEvent( mapId, _gmLatLngToLatLng(latLng), markerId, )); } void _onMarkerDragEnd(MarkerId markerId, gmaps.LatLng latLng) { _streamController.add(MarkerDragEndEvent( mapId, _gmLatLngToLatLng(latLng), markerId, )); } void _hideAllMarkerInfoWindow() { _markerIdToController.values .where((MarkerController? controller) => controller?.infoWindowShown ?? false) .forEach((MarkerController controller) { controller.hideInfoWindow(); }); } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/src/polygon.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. part of google_maps_flutter_web; /// The `PolygonController` class wraps a [gmaps.Polygon] and its `onTap` behavior. class PolygonController { /// Creates a `PolygonController` that wraps a [gmaps.Polygon] object and its `onTap` behavior. PolygonController({ required gmaps.Polygon polygon, bool consumeTapEvents = false, ui.VoidCallback? onTap, }) : _polygon = polygon, _consumeTapEvents = consumeTapEvents { if (onTap != null) { polygon.onClick.listen((gmaps.PolyMouseEvent event) { onTap.call(); }); } } gmaps.Polygon? _polygon; final bool _consumeTapEvents; /// Returns the wrapped [gmaps.Polygon]. Only used for testing. @visibleForTesting gmaps.Polygon? get polygon => _polygon; /// Returns `true` if this Controller will use its own `onTap` handler to consume events. bool get consumeTapEvents => _consumeTapEvents; /// Updates the options of the wrapped [gmaps.Polygon] object. /// /// This cannot be called after [remove]. void update(gmaps.PolygonOptions options) { assert(_polygon != null, 'Cannot `update` Polygon after calling `remove`.'); _polygon!.options = options; } /// Disposes of the currently wrapped [gmaps.Polygon]. void remove() { if (_polygon != null) { _polygon!.visible = false; _polygon!.map = null; _polygon = null; } } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/src/polygons.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. part of google_maps_flutter_web; /// This class manages a set of [PolygonController]s associated to a [GoogleMapController]. class PolygonsController extends GeometryController { /// Initializes the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers. PolygonsController({ required StreamController> stream, }) : _streamController = stream, _polygonIdToController = {}; // A cache of [PolygonController]s indexed by their [PolygonId]. final Map _polygonIdToController; // The stream over which polygons broadcast events final StreamController> _streamController; /// Returns the cache of [PolygonController]s. Test only. @visibleForTesting Map get polygons => _polygonIdToController; /// Adds a set of [Polygon] objects to the cache. /// /// Wraps each Polygon into its corresponding [PolygonController]. void addPolygons(Set polygonsToAdd) { if (polygonsToAdd != null) { polygonsToAdd.forEach(_addPolygon); } } void _addPolygon(Polygon polygon) { if (polygon == null) { return; } final gmaps.PolygonOptions polygonOptions = _polygonOptionsFromPolygon(googleMap, polygon); final gmaps.Polygon gmPolygon = gmaps.Polygon(polygonOptions) ..map = googleMap; final PolygonController controller = PolygonController( polygon: gmPolygon, consumeTapEvents: polygon.consumeTapEvents, onTap: () { _onPolygonTap(polygon.polygonId); }); _polygonIdToController[polygon.polygonId] = controller; } /// Updates a set of [Polygon] objects with new options. void changePolygons(Set polygonsToChange) { if (polygonsToChange != null) { polygonsToChange.forEach(_changePolygon); } } void _changePolygon(Polygon polygon) { final PolygonController? polygonController = _polygonIdToController[polygon.polygonId]; polygonController?.update(_polygonOptionsFromPolygon(googleMap, polygon)); } /// Removes a set of [PolygonId]s from the cache. void removePolygons(Set polygonIdsToRemove) { polygonIdsToRemove.forEach(_removePolygon); } // Removes a polygon and its controller by its [PolygonId]. void _removePolygon(PolygonId polygonId) { final PolygonController? polygonController = _polygonIdToController[polygonId]; polygonController?.remove(); _polygonIdToController.remove(polygonId); } // Handle internal events bool _onPolygonTap(PolygonId polygonId) { // Have you ended here on your debugging? Is this wrong? // Comment here: https://github.com/flutter/flutter/issues/64084 _streamController.add(PolygonTapEvent(mapId, polygonId)); return _polygonIdToController[polygonId]?.consumeTapEvents ?? false; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/src/polyline.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. part of google_maps_flutter_web; /// The `PolygonController` class wraps a [gmaps.Polyline] and its `onTap` behavior. class PolylineController { /// Creates a `PolylineController` that wraps a [gmaps.Polyline] object and its `onTap` behavior. PolylineController({ required gmaps.Polyline polyline, bool consumeTapEvents = false, ui.VoidCallback? onTap, }) : _polyline = polyline, _consumeTapEvents = consumeTapEvents { if (onTap != null) { polyline.onClick.listen((gmaps.PolyMouseEvent event) { onTap.call(); }); } } gmaps.Polyline? _polyline; final bool _consumeTapEvents; /// Returns the wrapped [gmaps.Polyline]. Only used for testing. @visibleForTesting gmaps.Polyline? get line => _polyline; /// Returns `true` if this Controller will use its own `onTap` handler to consume events. bool get consumeTapEvents => _consumeTapEvents; /// Updates the options of the wrapped [gmaps.Polyline] object. /// /// This cannot be called after [remove]. void update(gmaps.PolylineOptions options) { assert( _polyline != null, 'Cannot `update` Polyline after calling `remove`.'); _polyline!.options = options; } /// Disposes of the currently wrapped [gmaps.Polyline]. void remove() { if (_polyline != null) { _polyline!.visible = false; _polyline!.map = null; _polyline = null; } } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/src/polylines.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. part of google_maps_flutter_web; /// This class manages a set of [PolylinesController]s associated to a [GoogleMapController]. class PolylinesController extends GeometryController { /// Initializes the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers. PolylinesController({ required StreamController> stream, }) : _streamController = stream, _polylineIdToController = {}; // A cache of [PolylineController]s indexed by their [PolylineId]. final Map _polylineIdToController; // The stream over which polylines broadcast their events final StreamController> _streamController; /// Returns the cache of [PolylineContrller]s. Test only. @visibleForTesting Map get lines => _polylineIdToController; /// Adds a set of [Polyline] objects to the cache. /// /// Wraps each line into its corresponding [PolylineController]. void addPolylines(Set polylinesToAdd) { polylinesToAdd.forEach(_addPolyline); } void _addPolyline(Polyline polyline) { if (polyline == null) { return; } final gmaps.PolylineOptions polylineOptions = _polylineOptionsFromPolyline(googleMap, polyline); final gmaps.Polyline gmPolyline = gmaps.Polyline(polylineOptions) ..map = googleMap; final PolylineController controller = PolylineController( polyline: gmPolyline, consumeTapEvents: polyline.consumeTapEvents, onTap: () { _onPolylineTap(polyline.polylineId); }); _polylineIdToController[polyline.polylineId] = controller; } /// Updates a set of [Polyline] objects with new options. void changePolylines(Set polylinesToChange) { polylinesToChange.forEach(_changePolyline); } void _changePolyline(Polyline polyline) { final PolylineController? polylineController = _polylineIdToController[polyline.polylineId]; polylineController ?.update(_polylineOptionsFromPolyline(googleMap, polyline)); } /// Removes a set of [PolylineId]s from the cache. void removePolylines(Set polylineIdsToRemove) { polylineIdsToRemove.forEach(_removePolyline); } // Removes a polyline and its controller by its [PolylineId]. void _removePolyline(PolylineId polylineId) { final PolylineController? polylineController = _polylineIdToController[polylineId]; polylineController?.remove(); _polylineIdToController.remove(polylineId); } // Handle internal events bool _onPolylineTap(PolylineId polylineId) { // Have you ended here on your debugging? Is this wrong? // Comment here: https://github.com/flutter/flutter/issues/64084 _streamController.add(PolylineTapEvent(mapId, polylineId)); return _polylineIdToController[polylineId]?.consumeTapEvents ?? false; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/src/shims/dart_ui.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// This file shims dart:ui in web-only scenarios, getting rid of the need to /// suppress analyzer warnings. // TODO(ditman): Remove this file once web-only dart:ui APIs, https://github.com/flutter/flutter/issues/55000 // are exposed from a dedicated place. export 'dart_ui_fake.dart' if (dart.library.html) 'dart_ui_real.dart'; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/src/shims/dart_ui_fake.dart ================================================ // Copyright 2013 The Flutter 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:html' as html; // Fake interface for the logic that this package needs from (web-only) dart:ui. // This is conditionally exported so the analyzer sees these methods as available. // ignore_for_file: avoid_classes_with_only_static_members // ignore_for_file: camel_case_types /// Shim for web_ui engine.PlatformViewRegistry /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/ui.dart#L62 class platformViewRegistry { /// Shim for registerViewFactory /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/ui.dart#L72 static bool registerViewFactory( String viewTypeId, html.Element Function(int viewId) viewFactory) { return false; } } /// Shim for web_ui engine.AssetManager. /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/src/engine/assets.dart#L12 class webOnlyAssetManager { /// Shim for getAssetUrl. /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/src/engine/assets.dart#L45 static String getAssetUrl(String asset) => ''; } /// Signature of callbacks that have no arguments and return no data. typedef VoidCallback = void Function(); ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/src/shims/dart_ui_real.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'dart:ui'; ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2008 Krasimir Tsonev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/README.md ================================================ # to_screen_location The code in this directory is a Dart re-implementation of Krasimir Tsonev's blog post: [GoogleMaps API v3: convert LatLng object to actual pixels][blog-post]. The blog post describes a way to implement the [`toScreenLocation` method][method] of the Google Maps Platform SDK for the web. Used under license (MIT), [available here][blog-license], and in the accompanying LICENSE file. [blog-license]: https://krasimirtsonev.com/license [blog-post]: https://krasimirtsonev.com/blog/article/google-maps-api-v3-convert-latlng-object-to-actual-pixels-point-object [method]: https://developers.google.com/maps/documentation/android-sdk/reference/com/google/android/libraries/maps/Projection#toScreenLocation(com.google.android.libraries.maps.model.LatLng) ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/src/third_party/to_screen_location/to_screen_location.dart ================================================ // The MIT License (MIT) // // Copyright (c) 2008 Krasimir Tsonev // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. import 'package:google_maps/google_maps.dart' as gmaps; /// Returns a screen location that corresponds to a geographical coordinate ([gmaps.LatLng]). /// /// The screen location is in pixels relative to the top left of the Map widget /// (not of the whole screen/app). /// /// See: https://developers.google.com/maps/documentation/android-sdk/reference/com/google/android/libraries/maps/Projection#public-point-toscreenlocation-latlng-location gmaps.Point toScreenLocation(gmaps.GMap map, gmaps.LatLng coords) { final num? zoom = map.zoom; final gmaps.LatLngBounds? bounds = map.bounds; final gmaps.Projection? projection = map.projection; assert( bounds != null, 'Map Bounds required to compute screen x/y of LatLng.'); assert(projection != null, 'Map Projection required to compute screen x/y of LatLng.'); assert(zoom != null, 'Current map zoom level required to compute screen x/y of LatLng.'); final gmaps.LatLng ne = bounds!.northEast; final gmaps.LatLng sw = bounds.southWest; final gmaps.Point topRight = projection!.fromLatLngToPoint!(ne)!; final gmaps.Point bottomLeft = projection.fromLatLngToPoint!(sw)!; final int scale = 1 << (zoom!.toInt()); // 2 ^ zoom final gmaps.Point worldPoint = projection.fromLatLngToPoint!(coords)!; return gmaps.Point( ((worldPoint.x! - bottomLeft.x!) * scale).toInt(), ((worldPoint.y! - topRight.y!) * scale).toInt(), ); } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/lib/src/types.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:google_maps/google_maps.dart' as gmaps; import '../../google_maps_flutter_web.dart'; /// A void function that handles a [gmaps.LatLng] as a parameter. /// /// Similar to [ui.VoidCallback], but specific for Marker drag events. typedef LatLngCallback = void Function(gmaps.LatLng latLng); /// The base class for all "geometry" group controllers. /// /// This lets all Geometry controllers ([MarkersController], [CirclesController], /// [PolygonsController], [PolylinesController]) to be bound to a [gmaps.GMap] /// instance and our internal `mapId` value. abstract class GeometryController { /// The GMap instance that this controller operates on. late gmaps.GMap googleMap; /// The map ID for events. late int mapId; /// Binds a `mapId` and the [gmaps.GMap] instance to this controller. void bindToMap(int mapId, gmaps.GMap googleMap) { this.mapId = mapId; this.googleMap = googleMap; } } ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml ================================================ 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+5 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: google_maps_flutter platforms: web: pluginClass: GoogleMapsPlugin fileName: google_maps_flutter_web.dart dependencies: flutter: sdk: flutter flutter_web_plugins: sdk: flutter google_maps: ^6.1.0 google_maps_flutter_platform_interface: ^2.2.2 sanitize_html: ^2.0.0 stream_transform: ^2.0.0 dev_dependencies: flutter_test: sdk: flutter # The example deliberately includes limited-use secrets. false_secrets: - /example/web/index.html ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/test/README.md ================================================ ## test This package uses integration tests for testing. See `example/README.md` for more info. ================================================ FILE: packages/google_maps_flutter/google_maps_flutter_web/test/tests_exist_elsewhere_test.dart ================================================ // Copyright 2013 The Flutter 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: avoid_print import 'package:flutter_test/flutter_test.dart'; void main() { test('Tell the user where to find the real tests', () { print('---'); print('This package uses integration_test for its tests.'); print('See `example/README.md` for more info.'); print('---'); }); } ================================================ FILE: packages/google_sign_in/google_sign_in/AUTHORS ================================================ # 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> Twin Sun, LLC ================================================ FILE: packages/google_sign_in/google_sign_in/CHANGELOG.md ================================================ ## 6.0.0 * **Breaking change** for platform `web`: * Endorses `google_sign_in_web: ^0.11.0` as the web implementation of the plugin. * The web package is now backed by the **Google Identity Services (GIS) SDK**, instead of the **Google Sign-In for Web JS SDK**, which is set to be deprecated after March 31, 2023. * Migration information can be found in the [`google_sign_in_web` package README](https://pub.dev/packages/google_sign_in_web). For every platform other than `web`, this version should be identical to `5.4.4`. ## 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. * Adds override for `GoogleSignInPlatform.initWithParams`. * Fixes tests to recognize new default `forceCodeForRefreshToken` request attribute. ## 5.4.1 * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 5.4.0 * Adds support for configuring `serverClientId` through `GoogleSignIn` constructor. * Adds support for Dart-based configuration as alternative to `GoogleService-Info.plist` for iOS. ## 5.3.3 * Updates references to the obsolete master branch. ## 5.3.2 * Enables mocking models by changing overridden operator == parameter type from `dynamic` to `Object`. * Updates tests to use a mock platform instead of relying on default method channel implementation internals. * Removes example workaround to build for arm64 iOS simulators. ## 5.3.1 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 5.3.0 * Moves Android and iOS implementations to federated packages. ## 5.2.5 * Migrates from `ui.hash*` to `Object.hash*`. * Adds OS version support information to README. ## 5.2.4 * Internal code cleanup for stricter analysis options. ## 5.2.3 * Bumps the Android dependency on `com.google.android.gms:play-services-auth` and therefore removes the need for `jetifier`. ## 5.2.2 * Updates Android compileSdkVersion to 31. * Removes dependency on `meta`. ## 5.2.1 Change the placeholder of the GoogleUserCircleAvatar to a transparent image. ## 5.2.0 * Add `GoogleSignInAccount.serverAuthCode`. Mark `GoogleSignInAuthentication.serverAuthCode` as deprecated. ## 5.1.1 * Update minimum Flutter SDK to 2.5 and iOS deployment target to 9.0. ## 5.1.0 * Add reAuthenticate option to signInSilently to allow re-authentication to be requested * Updated Android lint settings. ## 5.0.7 * Mark iOS arm64 simulators as unsupported. ## 5.0.6 * Remove references to the Android V1 embedding. ## 5.0.5 * Add iOS unit and UI integration test targets. * Add iOS unit test module map. * Exclude arm64 simulators in example app. ## 5.0.4 * Migrate maven repo from jcenter to mavenCentral. ## 5.0.3 * Fixed links in `README.md`. * Added documentation for usage on the web. ## 5.0.2 * Fix flutter/flutter#48602 iOS flow shows account selection, if user is signed in to Google on the device. ## 5.0.1 * Update platforms `init` function to prioritize `clientId` property when available; * Updates `google_sign_in_platform_interface` version. ## 5.0.0 * Migrate to null safety. ## 4.5.9 * Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets. ## 4.5.8 * Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) ## 4.5.7 * Update Flutter SDK constraint. ## 4.5.6 * Fix deprecated member warning in tests. ## 4.5.5 * Update android compileSdkVersion to 29. ## 4.5.4 * Keep handling deprecated Android v1 classes for backward compatibility. ## 4.5.3 * Update package:e2e -> package:integration_test ## 4.5.2 * Update package:e2e reference to use the local version in the flutter/plugins repository. ## 4.5.1 * Add note on Apple sign in requirement in README. ## 4.5.0 * Add support for getting `serverAuthCode`. ## 4.4.6 * Update lower bound of dart dependency to 2.1.0. ## 4.4.5 * Fix requestScopes to allow subsequent calls on Android. ## 4.4.4 * OCMock module import -> #import, unit tests compile generated as library. * Fix CocoaPods podspec lint warnings. ## 4.4.3 * Upgrade google_sign_in_web to version ^0.9.1 ## 4.4.2 * Android: make the Delegate non-final to allow overriding. ## 4.4.1 * Android: Move `GoogleSignInWrapper` to a separate class. ## 4.4.0 * Migrate to Android v2 embedder. ## 4.3.0 * Add support for method introduced in `google_sign_in_platform_interface` 1.1.0. ## 4.2.0 * Migrate to AndroidX. ## 4.1.5 * Remove unused variable. ## 4.1.4 * Make the pedantic dev_dependency explicit. ## 4.1.3 * Make plugin example meet naming convention. ## 4.1.2 * Added a new error code `network_error`, and return it when a network error occurred. ## 4.1.1 * Support passing `clientId` to the web plugin programmatically. ## 4.1.0 * Support web by default. * Require Flutter SDK `v1.12.13+hotfix.4` or greater. ## 4.0.17 * Add missing documentation and fix an unawaited future in the example app. ## 4.0.16 * Remove the deprecated `author:` field from pubspec.yaml * Migrate the plugin to the pubspec platforms manifest. * Require Flutter SDK 1.10.0 or greater. ## 4.0.15 * Export SignInOption from interface since it is used in the frontend as a type. ## 4.0.14 * Port plugin code to use the federated Platform Interface, instead of a MethodChannel directly. ## 4.0.13 * Fix `GoogleUserCircleAvatar` to handle new style profile image URLs. ## 4.0.12 * Move google_sign_in plugin to google_sign_in/google_sign_in to prepare for federated implementations. ## 4.0.11 * Update iOS CocoaPod dependency to 5.0 to fix deprecated API usage issue. ## 4.0.10 * Remove AndroidX warning. ## 4.0.9 * Update and migrate iOS example project. * Define clang module for iOS. ## 4.0.8 * Get rid of `MethodCompleter` and serialize async actions using chained futures. This prevents a bug when sign in methods are being used in error handling zones. ## 4.0.7 * Switch from using `api` to `implementation` for dependency on `play-services-auth`, preventing version mismatch build failures in some Android configurations. ## 4.0.6 * Fixed the `PlatformException` leaking from `catchError()` in debug mode. ## 4.0.5 * Update README with solution to `APIException` errors. ## 4.0.4 * Revert changes in 4.0.3. ## 4.0.3 * Update guava to `27.0.1-android`. * Add correct @NonNull annotations to reduce compiler warnings. ## 4.0.2 * Add missing template type parameter to `invokeMethod` calls. * Bump minimum Flutter version to 1.5.0. * Replace invokeMethod with invokeMapMethod wherever necessary. ## 4.0.1+3 * Update example to gracefully handle null user information. ## 4.0.1+2 * Fix README.md to correctly spell `GoogleService-Info.plist`. ## 4.0.1+1 * Remove categories. ## 4.0.1 * Log a more detailed warning at build time about the previous AndroidX migration. ## 4.0.0+1 * Added a better error message for iOS when the app is missing necessary URL schemes. ## 4.0.0 * **Breaking change**. Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to [also migrate](https://developer.android.com/jetpack/androidx/migrate) if they're using the original support library. This was originally incorrectly pushed in the `3.3.0` update. ## 3.3.0+1 * **Revert the breaking 3.3.0 update**. 3.3.0 was known to be breaking and should have incremented the major version number instead of the minor. This revert is in and of itself breaking for anyone that has already migrated however. Anyone who has already migrated their app to AndroidX should immediately update to `4.0.0` instead. That's the correctly versioned new push of `3.3.0`. ## 3.3.0 * **BAD**. This was a breaking change that was incorrectly published on a minor version upgrade, should never have happened. Reverted by 3.3.0+1. * **Breaking change**. Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to [also migrate](https://developer.android.com/jetpack/androidx/migrate) if they're using the original support library. ## 3.2.4 * Increase play-services-auth version to 16.0.1 ## 3.2.3 * Change google-services.json and GoogleService-Info.plist of example. ## 3.2.2 * Don't use the result code when handling signin. This results in better error codes because result code always returns "cancelled". ## 3.2.1 * Set http version to be compatible with flutter_test. ## 3.2.0 * Add support for clearing authentication cache for Android. ## 3.1.0 * Add support to recover authentication for Android. ## 3.0.6 * Remove flaky displayName assertion ## 3.0.5 * Added missing http package dependency. ## 3.0.4 * Updated Gradle tooling to match Android Studio 3.1.2. ## 3.0.3+1 * Added documentation on where to find the list of available scopes. ## 3.0.3 * Added support for games sign in on Android. ## 3.0.2 * Updated Google Play Services dependency to version 15.0.0. ## 3.0.1 * Simplified podspec for Cocoapods 1.5.0, avoiding link issues in app archives. ## 3.0.0 * **Breaking change**. Set SDK constraints to match the Flutter beta release. ## 2.1.2 * Added a Delegate interface (IDelegate) that can be implemented by clients in order to override the functionality (for testing purposes for example). ## 2.1.1 * Fixed Dart 2 type errors. ## 2.1.0 * Enabled use in Swift projects. ## 2.0.1 * Simplified and upgraded Android project template to Android SDK 27. * Updated package description. ## 2.0.0 * **Breaking change**. Upgraded to Gradle 4.1 and Android Studio Gradle plugin 3.0.1. Older Flutter projects need to upgrade their Gradle setup as well in order to use this version of the plugin. Instructions can be found [here](https://github.com/flutter/flutter/wiki/Updating-Flutter-projects-to-Gradle-4.1-and-Android-Studio-Gradle-plugin-3.0.1). * Relaxed GMS dependency to [11.4.0,12.0[ ## 1.0.3 * Add FLT prefix to iOS types ## 1.0.2 * Support setting foregroundColor in the avatar. ## 1.0.1 * Change GMS dependency to 11.+ ## 1.0.0 * Make GoogleUserCircleAvatar fade profile image over the top of placeholder * Bump to released version ## 0.3.1 * Updated GMS to always use latest patch version for 11.0.x builds ## 0.3.0 * Add a new `GoogleIdentity` interface, implemented by `GoogleSignInAccount`. * Move `GoogleUserCircleAvatar` to "widgets" library (exported by base library for backwards compatibility) and make it take an instance of `GoogleIdentity`, thus allowing it to be used by other packages that provide implementations of `GoogleIdentity`. ## 0.2.1 * Plugin can (once again) be used in apps that extend `FlutterActivity` * `signInSilently` is guaranteed to never throw * A failed sign-in (caused by a failing `init` step) will no longer block subsequent sign-in attempts ## 0.2.0 * Updated dependencies * **Breaking Change**: You need to add a maven section with the "https://maven.google.com" endpoint to the repository section of your `android/build.gradle`. For example: ```gradle allprojects { repositories { jcenter() maven { // NEW url "https://maven.google.com" // NEW } // NEW } } ``` ## 0.1.0 * Update to use `GoogleSignIn` CocoaPod ## 0.0.6 * Fix crash on iOS when signing in caused by nil uiDelegate ## 0.0.5 * Require the use of `support-v4` library on Android. This is an API change in that plugin users will need their activity class to be an instance of `android.support.v4.app.FragmentActivity`. Flutter framework provides such an activity out of the box: `io.flutter.app.FlutterFragmentActivity` * Ignore "Broken pipe" errors affecting iOS simulator * Update to non-deprecated `application:openURL:options:` on iOS ## 0.0.4 * Prevent race conditions when GoogleSignIn methods are called concurrently (#94) ## 0.0.3 * Fix signOut and disconnect (they were silently ignored) * Fix test (#10050) ## 0.0.2 * Don't try to sign in again if user is already signed in ## 0.0.1 * Initial Release ================================================ FILE: packages/google_sign_in/google_sign_in/LICENSE ================================================ 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. ================================================ FILE: packages/google_sign_in/google_sign_in/README.md ================================================ [![pub package](https://img.shields.io/pub/v/google_sign_in.svg)](https://pub.dev/packages/google_sign_in) A Flutter plugin for [Google Sign In](https://developers.google.com/identity/). _Note_: This plugin is still under development, and some APIs might not be available yet. [Feedback](https://github.com/flutter/flutter/issues) and [Pull Requests](https://github.com/flutter/plugins/pulls) are most welcome! | | Android | iOS | Web | |-------------|---------|--------|-----| | **Support** | SDK 16+ | iOS 9+ | Any | ## Platform integration ### Android integration To access Google Sign-In, you'll need to make sure to [register your application](https://firebase.google.com/docs/android/setup). You don't need to include the google-services.json file in your app unless you are using Google services that require it. You do need to enable the OAuth APIs that you want, using the [Google Cloud Platform API manager](https://console.developers.google.com/). For example, if you want to mimic the behavior of the Google Sign-In sample app, you'll need to enable the [Google People API](https://developers.google.com/people/). Make sure you've filled out all required fields in the console for [OAuth consent screen](https://console.developers.google.com/apis/credentials/consent). Otherwise, you may encounter `APIException` errors. ### iOS integration This plugin requires iOS 9.0 or higher. 1. [First register your application](https://firebase.google.com/docs/ios/setup). 2. Make sure the file you download in step 1 is named `GoogleService-Info.plist`. 3. Move or copy `GoogleService-Info.plist` into the `[my_project]/ios/Runner` directory. 4. Open Xcode, then right-click on `Runner` directory and select `Add Files to "Runner"`. 5. Select `GoogleService-Info.plist` from the file manager. 6. A dialog will show up and ask you to select the targets, select the `Runner` target. 7. If you need to authenticate to a backend server you can add a `SERVER_CLIENT_ID` key value pair in your `GoogleService-Info.plist`. ```xml SERVER_CLIENT_ID [YOUR SERVER CLIENT ID] ``` 8. Then add the `CFBundleURLTypes` attributes below into the `[my_project]/ios/Runner/Info.plist` file. ```xml CFBundleURLTypes CFBundleTypeRole Editor CFBundleURLSchemes com.googleusercontent.apps.861823949799-vc35cprkp249096uujjn0vvnmcvjppkn ``` As an alternative to adding `GoogleService-Info.plist` to your Xcode project, you can instead configure your app in Dart code. In this case, skip steps 3 to 7 and pass `clientId` and `serverClientId` to the `GoogleSignIn` constructor: ```dart GoogleSignIn _googleSignIn = GoogleSignIn( ... // The OAuth client id of your app. This is required. clientId: ..., // If you need to authenticate to a backend server, specify its OAuth client. This is optional. serverClientId: ..., ); ``` Note that step 8 is still required. #### iOS additional requirement Note that according to https://developer.apple.com/sign-in-with-apple/get-started, starting June 30, 2020, apps that use login services must also offer a "Sign in with Apple" option when submitting to the Apple App Store. Consider also using an Apple sign in plugin from pub.dev. The Flutter Favorite [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) plugin could be an option. ### Web integration For web integration details, see the [`google_sign_in_web` package](https://pub.dev/packages/google_sign_in_web). ## Usage ### Import the package To use this plugin, follow the [plugin installation instructions](https://pub.dev/packages/google_sign_in/install). ### Use the plugin Add the following import to your Dart code: ```dart import 'package:google_sign_in/google_sign_in.dart'; ``` Initialize GoogleSignIn with the scopes you want: ```dart GoogleSignIn _googleSignIn = GoogleSignIn( scopes: [ 'email', 'https://www.googleapis.com/auth/contacts.readonly', ], ); ``` [Full list of available scopes](https://developers.google.com/identity/protocols/googlescopes). You can now use the `GoogleSignIn` class to authenticate in your Dart code, e.g. ```dart Future _handleSignIn() async { try { await _googleSignIn.signIn(); } catch (error) { print(error); } } ``` ## Example Find the example wiring in the [Google sign-in example application](https://github.com/flutter/plugins/blob/main/packages/google_sign_in/google_sign_in/example/lib/main.dart). ================================================ FILE: packages/google_sign_in/google_sign_in/example/README.md ================================================ # google_sign_in_example Demonstrates how to use the google_sign_in plugin. ================================================ FILE: packages/google_sign_in/google_sign_in/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 lintOptions { disable 'InvalidPackage' } defaultConfig { applicationId "io.flutter.plugins.googlesigninexample" minSdkVersion 16 targetSdkVersion 28 multiDexEnabled true versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } testOptions { unitTests.returnDefaultValues = true } } flutter { source '../..' } dependencies { implementation 'com.google.android.gms:play-services-auth:16.0.1' testImplementation'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' api 'androidx.test:core:1.2.0' } ================================================ FILE: packages/google_sign_in/google_sign_in/example/android/app/google-services.json ================================================ { "project_info": { "project_number": "479882132969", "firebase_url": "https://my-flutter-proj.firebaseio.com", "project_id": "my-flutter-proj", "storage_bucket": "my-flutter-proj.appspot.com" }, "client": [ { "client_info": { "mobilesdk_app_id": "1:479882132969:android:c73fd19ff7e2c0be", "android_client_info": { "package_name": "io.flutter.plugins.cameraexample" } }, "oauth_client": [ { "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { "current_key": "AIzaSyCrZz9T0Pg0rDnpxfNuPBrOxGhXskfebXs" } ], "services": { "analytics_service": { "status": 1 }, "appinvite_service": { "status": 1, "other_platform_oauth_client": [] }, "ads_service": { "status": 2 } } }, { "client_info": { "mobilesdk_app_id": "1:479882132969:android:632cdf3fc0a17139", "android_client_info": { "package_name": "io.flutter.plugins.firebasedynamiclinksexample" } }, "oauth_client": [ { "client_id": "479882132969-32qusitiag53931ck80h121ajhlc5a7e.apps.googleusercontent.com", "client_type": 1, "android_info": { "package_name": "io.flutter.plugins.firebasedynamiclinksexample", "certificate_hash": "e733b7a303250b63e06de6f7c9767c517d69cfa0" } }, { "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { "current_key": "AIzaSyCrZz9T0Pg0rDnpxfNuPBrOxGhXskfebXs" } ], "services": { "analytics_service": { "status": 1 }, "appinvite_service": { "status": 2, "other_platform_oauth_client": [ { "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", "client_type": 3 }, { "client_id": "479882132969-gjp4e63ogu2h6guttj2ie6t3f10ic7i8.apps.googleusercontent.com", "client_type": 2, "ios_info": { "bundle_id": "io.flutter.plugins.firebaseMlVisionExample" } } ] }, "ads_service": { "status": 2 } } }, { "client_info": { "mobilesdk_app_id": "1:479882132969:android:ae50362b4bc06086", "android_client_info": { "package_name": "io.flutter.plugins.firebasemlvisionexample" } }, "oauth_client": [ { "client_id": "479882132969-9pp74fkgmtvt47t9rikc1p861v7n85tn.apps.googleusercontent.com", "client_type": 1, "android_info": { "package_name": "io.flutter.plugins.firebasemlvisionexample", "certificate_hash": "e733b7a303250b63e06de6f7c9767c517d69cfa0" } }, { "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { "current_key": "AIzaSyCrZz9T0Pg0rDnpxfNuPBrOxGhXskfebXs" } ], "services": { "analytics_service": { "status": 1 }, "appinvite_service": { "status": 2, "other_platform_oauth_client": [ { "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", "client_type": 3 }, { "client_id": "479882132969-gjp4e63ogu2h6guttj2ie6t3f10ic7i8.apps.googleusercontent.com", "client_type": 2, "ios_info": { "bundle_id": "io.flutter.plugins.firebaseMlVisionExample" } } ] }, "ads_service": { "status": 2 } } }, { "client_info": { "mobilesdk_app_id": "1:479882132969:android:215a22700e1b466b", "android_client_info": { "package_name": "io.flutter.plugins.firebaseperformanceexample" } }, "oauth_client": [ { "client_id": "479882132969-8h4kiv8m7ho4tvn6uuujsfcrf69unuf7.apps.googleusercontent.com", "client_type": 1, "android_info": { "package_name": "io.flutter.plugins.firebaseperformanceexample", "certificate_hash": "e733b7a303250b63e06de6f7c9767c517d69cfa0" } }, { "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { "current_key": "AIzaSyCrZz9T0Pg0rDnpxfNuPBrOxGhXskfebXs" } ], "services": { "analytics_service": { "status": 1 }, "appinvite_service": { "status": 2, "other_platform_oauth_client": [ { "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", "client_type": 3 }, { "client_id": "479882132969-gjp4e63ogu2h6guttj2ie6t3f10ic7i8.apps.googleusercontent.com", "client_type": 2, "ios_info": { "bundle_id": "io.flutter.plugins.firebaseMlVisionExample" } } ] }, "ads_service": { "status": 2 } } }, { "client_info": { "mobilesdk_app_id": "1:479882132969:android:5e9f1f89e134dc86", "android_client_info": { "package_name": "io.flutter.plugins.googlesigninexample" } }, "oauth_client": [ { "client_id": "479882132969-90ml692hkonp587sl0v0rurmnvkekgrg.apps.googleusercontent.com", "client_type": 1, "android_info": { "package_name": "io.flutter.plugins.googlesigninexample", "certificate_hash": "e733b7a303250b63e06de6f7c9767c517d69cfa0" } }, { "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { "current_key": "AIzaSyCrZz9T0Pg0rDnpxfNuPBrOxGhXskfebXs" } ], "services": { "analytics_service": { "status": 1 }, "appinvite_service": { "status": 2, "other_platform_oauth_client": [ { "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", "client_type": 3 }, { "client_id": "479882132969-gjp4e63ogu2h6guttj2ie6t3f10ic7i8.apps.googleusercontent.com", "client_type": 2, "ios_info": { "bundle_id": "io.flutter.plugins.firebaseMlVisionExample" } } ] }, "ads_service": { "status": 2 } } } ], "configuration_version": "1" } ================================================ FILE: packages/google_sign_in/google_sign_in/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/google_sign_in/google_sign_in/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/google_sign_in/google_sign_in/example/android/app/src/androidTest/java/io/flutter/plugins/googlesigninexample/FlutterActivityTest.java ================================================ // Copyright 2013 The Flutter 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.googlesigninexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class FlutterActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/google_sign_in/google_sign_in/example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: packages/google_sign_in/google_sign_in/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/google_sign_in/google_sign_in/example/android/app/src/main/java/io/flutter/plugins/.gitignore ================================================ GeneratedPluginRegistrant.java ================================================ FILE: packages/google_sign_in/google_sign_in/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/GoogleSignInTestActivity.java ================================================ // Copyright 2013 The Flutter 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.googlesigninexample; import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; // Makes the FlutterEngine accessible for testing. public class GoogleSignInTestActivity extends FlutterActivity { public FlutterEngine engine; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); engine = flutterEngine; } } ================================================ FILE: packages/google_sign_in/google_sign_in/example/android/app/src/main/res/values/strings.xml ================================================ YOUR_WEB_CLIENT_ID ================================================ FILE: packages/google_sign_in/google_sign_in/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/google_sign_in/google_sign_in/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/google_sign_in/google_sign_in/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true ================================================ FILE: packages/google_sign_in/google_sign_in/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withInputStream { stream -> plugins.load(stream) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/google_sign_in/google_sign_in/example/integration_test/google_sign_in_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:integration_test/integration_test.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('Can initialize the plugin', (WidgetTester tester) async { final GoogleSignIn signIn = GoogleSignIn(); expect(signIn, isNotNull); }); } ================================================ FILE: packages/google_sign_in/google_sign_in/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 UIRequiredDeviceCapabilities arm64 MinimumOSVersion 9.0 ================================================ FILE: packages/google_sign_in/google_sign_in/example/ios/Flutter/Debug.xcconfig ================================================ #include "Generated.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" ================================================ FILE: packages/google_sign_in/google_sign_in/example/ios/Flutter/Release.xcconfig ================================================ #include "Generated.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" ================================================ FILE: packages/google_sign_in/google_sign_in/example/ios/GoogleSignInPluginTest/Info.plist ================================================ 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 ================================================ FILE: packages/google_sign_in/google_sign_in/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/google_sign_in/google_sign_in/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/google_sign_in/google_sign_in/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end ================================================ FILE: packages/google_sign_in/google_sign_in/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/google_sign_in/google_sign_in/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/google_sign_in/google_sign_in/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/google_sign_in/google_sign_in/example/ios/Runner/GoogleService-Info.plist ================================================ AD_UNIT_ID_FOR_BANNER_TEST ca-app-pub-3940256099942544/2934735716 AD_UNIT_ID_FOR_INTERSTITIAL_TEST ca-app-pub-3940256099942544/4411468910 CLIENT_ID 479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com REVERSED_CLIENT_ID com.googleusercontent.apps.479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u ANDROID_CLIENT_ID 479882132969-jie8r1me6dsra60pal6ejaj8dgme3tg0.apps.googleusercontent.com API_KEY AIzaSyBECOwLTAN6PU4Aet1b2QLGIb3kRK8Xjew GCM_SENDER_ID 479882132969 PLIST_VERSION 1 BUNDLE_ID io.flutter.plugins.googleSignInExample PROJECT_ID my-flutter-proj STORAGE_BUCKET my-flutter-proj.appspot.com IS_ADS_ENABLED IS_ANALYTICS_ENABLED IS_APPINVITE_ENABLED IS_GCM_ENABLED IS_SIGNIN_ENABLED GOOGLE_APP_ID 1:479882132969:ios:2643f950e0a0da08 DATABASE_URL https://my-flutter-proj.firebaseio.com SERVER_CLIENT_ID YOUR_SERVER_CLIENT_ID ================================================ FILE: packages/google_sign_in/google_sign_in/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName Google Sign-In Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName GoogleSignInExample CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleURLTypes CFBundleTypeRole Editor CFBundleURLSchemes com.googleusercontent.apps.479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: packages/google_sign_in/google_sign_in/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 5C6F5A6E1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C6F5A6D1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m */; }; 7A303C2E1E89D76400B1F19E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */; }; 7ACDFB0E1E8944C400BE2D00 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7ACDFB0D1E8944C400BE2D00 /* AppFrameworkInfo.plist */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 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 */; }; C2FB9CBA01DB0A2DE5F31E12 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0263E28FA425D1CE928BDE15 /* libPods-Runner.a */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0263E28FA425D1CE928BDE15 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 18AD6475292B9C45B529DDC9 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 37E582FF620A90D0EB2C0851 /* 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 = ""; }; 45D93D4513839BFEA2AA74FE /* 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 = ""; }; 5A76713E622F06379AEDEBFA /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 5C6F5A6C1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 5C6F5A6D1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 7ACDFB0D1E8944C400BE2D00 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F582639B44581540871D9BB0 /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( C2FB9CBA01DB0A2DE5F31E12 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 840012C8B5EDBCF56B0E4AC1 /* Pods */ = { isa = PBXGroup; children = ( 5A76713E622F06379AEDEBFA /* Pods-Runner.debug.xcconfig */, F582639B44581540871D9BB0 /* Pods-Runner.release.xcconfig */, 37E582FF620A90D0EB2C0851 /* Pods-RunnerTests.debug.xcconfig */, 45D93D4513839BFEA2AA74FE /* Pods-RunnerTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 7ACDFB0D1E8944C400BE2D00 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 840012C8B5EDBCF56B0E4AC1 /* Pods */, CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 5C6F5A6C1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.h */, 5C6F5A6D1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m */, 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = { isa = PBXGroup; children = ( 0263E28FA425D1CE928BDE15 /* libPods-Runner.a */, 18AD6475292B9C45B529DDC9 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1100; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 7A303C2E1E89D76400B1F19E /* GoogleService-Info.plist in Resources */, 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 7ACDFB0E1E8944C400BE2D00 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n"; }; 532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", "${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\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"; }; AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 5C6F5A6E1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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 = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.googleSignInExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.googleSignInExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/google_sign_in/google_sign_in/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/google_sign_in/google_sign_in/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/google_sign_in/google_sign_in/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/google_sign_in/google_sign_in/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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, avoid_print import 'dart:async'; import 'dart:convert' show json; import 'package:flutter/material.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:http/http.dart' as http; GoogleSignIn _googleSignIn = GoogleSignIn( // Optional clientId // clientId: '479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com', scopes: [ 'email', 'https://www.googleapis.com/auth/contacts.readonly', ], ); void main() { runApp( const MaterialApp( title: 'Google Sign In', home: SignInDemo(), ), ); } class SignInDemo extends StatefulWidget { const SignInDemo({Key? key}) : super(key: key); @override State createState() => SignInDemoState(); } class SignInDemoState extends State { GoogleSignInAccount? _currentUser; String _contactText = ''; @override void initState() { super.initState(); _googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount? account) { setState(() { _currentUser = account; }); if (_currentUser != null) { _handleGetContact(_currentUser!); } }); _googleSignIn.signInSilently(); } Future _handleGetContact(GoogleSignInAccount user) async { setState(() { _contactText = 'Loading contact info...'; }); final http.Response response = await http.get( Uri.parse('https://people.googleapis.com/v1/people/me/connections' '?requestMask.includeField=person.names'), headers: await user.authHeaders, ); if (response.statusCode != 200) { setState(() { _contactText = 'People API gave a ${response.statusCode} ' 'response. Check logs for details.'; }); print('People API ${response.statusCode} response: ${response.body}'); return; } final Map data = json.decode(response.body) as Map; final String? namedContact = _pickFirstNamedContact(data); setState(() { if (namedContact != null) { _contactText = 'I see you know $namedContact!'; } else { _contactText = 'No contacts to display.'; } }); } String? _pickFirstNamedContact(Map data) { final List? connections = data['connections'] as List?; final Map? contact = connections?.firstWhere( (dynamic contact) => (contact as Map)['names'] != null, orElse: () => null, ) as Map?; if (contact != 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) { return name['displayName'] as String?; } } return null; } Future _handleSignIn() async { try { await _googleSignIn.signIn(); } catch (error) { print(error); } } Future _handleSignOut() => _googleSignIn.disconnect(); Widget _buildBody() { final GoogleSignInAccount? user = _currentUser; if (user != null) { return Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ ListTile( leading: GoogleUserCircleAvatar( identity: user, ), title: Text(user.displayName ?? ''), subtitle: Text(user.email), ), const Text('Signed in successfully.'), Text(_contactText), ElevatedButton( onPressed: _handleSignOut, child: const Text('SIGN OUT'), ), ElevatedButton( child: const Text('REFRESH'), onPressed: () => _handleGetContact(user), ), ], ); } else { return Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ const Text('You are not currently signed in.'), ElevatedButton( onPressed: _handleSignIn, child: const Text('SIGN IN'), ), ], ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Google Sign In'), ), body: ConstrainedBox( constraints: const BoxConstraints.expand(), child: _buildBody(), )); } } ================================================ FILE: packages/google_sign_in/google_sign_in/example/pubspec.yaml ================================================ name: google_sign_in_example description: Example of Google Sign-In plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter google_sign_in: # When depending on this package from a real application you should use: # google_sign_in: ^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: ../ http: ^0.13.0 dev_dependencies: espresso: ^0.2.0 flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/google_sign_in/google_sign_in/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/google_sign_in/google_sign_in/example/web/index.html ================================================ Google Sign-in Example ================================================ FILE: packages/google_sign_in/google_sign_in/lib/google_sign_in.dart ================================================ // Copyright 2013 The Flutter 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:flutter/services.dart' show PlatformException; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'src/common.dart'; export 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart' show SignInOption; export 'src/common.dart'; export 'widgets.dart'; /// Holds authentication tokens after sign in. class GoogleSignInAuthentication { GoogleSignInAuthentication._(this._data); final GoogleSignInTokenData _data; /// An OpenID Connect ID token that identifies the user. String? get idToken => _data.idToken; /// The OAuth2 access token to access Google services. String? get accessToken => _data.accessToken; /// Server auth code used to access Google Login @Deprecated('Use the `GoogleSignInAccount.serverAuthCode` property instead') String? get serverAuthCode => _data.serverAuthCode; @override String toString() => 'GoogleSignInAuthentication:$_data'; } /// Holds fields describing a signed in user's identity, following /// [GoogleSignInUserData]. /// /// [id] is guaranteed to be non-null. @immutable class GoogleSignInAccount implements GoogleIdentity { GoogleSignInAccount._(this._googleSignIn, GoogleSignInUserData data) : displayName = data.displayName, email = data.email, id = data.id, photoUrl = data.photoUrl, serverAuthCode = data.serverAuthCode, _idToken = data.idToken { assert(id != null); } // These error codes must match with ones declared on Android and iOS sides. /// Error code indicating there was a failed attempt to recover user authentication. static const String kFailedToRecoverAuthError = 'failed_to_recover_auth'; /// Error indicating that authentication can be recovered with user action; static const String kUserRecoverableAuthError = 'user_recoverable_auth'; @override final String? displayName; @override final String email; @override final String id; @override final String? photoUrl; @override final String? serverAuthCode; final String? _idToken; final GoogleSignIn _googleSignIn; /// Retrieve [GoogleSignInAuthentication] for this account. /// /// [shouldRecoverAuth] sets whether to attempt to recover authentication if /// user action is needed. If an attempt to recover authentication fails a /// [PlatformException] is thrown with possible error code /// [kFailedToRecoverAuthError]. /// /// Otherwise, if [shouldRecoverAuth] is false and the authentication can be /// recovered by user action a [PlatformException] is thrown with error code /// [kUserRecoverableAuthError]. Future get authentication async { if (_googleSignIn.currentUser != this) { throw StateError('User is no longer signed in.'); } final GoogleSignInTokenData response = await GoogleSignInPlatform.instance.getTokens( email: email, shouldRecoverAuth: true, ); // On Android, there isn't an API for refreshing the idToken, so re-use // the one we obtained on login. response.idToken ??= _idToken; return GoogleSignInAuthentication._(response); } /// Convenience method returning a `` map of HTML Authorization /// headers, containing the current `authentication.accessToken`. /// /// See also https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization. Future> get authHeaders async { final String? token = (await authentication).accessToken; return { 'Authorization': 'Bearer $token', // TODO(kevmoo): Use the correct value once it's available from authentication // See https://github.com/flutter/flutter/issues/80905 'X-Goog-AuthUser': '0', }; } /// Clears any client side cache that might be holding invalid tokens. /// /// If client runs into 401 errors using a token, it is expected to call /// this method and grab `authHeaders` once again. Future clearAuthCache() async { final String token = (await authentication).accessToken!; await GoogleSignInPlatform.instance.clearAuthCache(token: token); } @override bool operator ==(Object other) { if (identical(this, other)) { return true; } if (other is! GoogleSignInAccount) { return false; } final GoogleSignInAccount otherAccount = other; return displayName == otherAccount.displayName && email == otherAccount.email && id == otherAccount.id && photoUrl == otherAccount.photoUrl && serverAuthCode == otherAccount.serverAuthCode && _idToken == otherAccount._idToken; } @override int get hashCode => Object.hash(displayName, email, id, photoUrl, _idToken, serverAuthCode); @override String toString() { final Map data = { 'displayName': displayName, 'email': email, 'id': id, 'photoUrl': photoUrl, 'serverAuthCode': serverAuthCode }; return 'GoogleSignInAccount:$data'; } } /// GoogleSignIn allows you to authenticate Google users. class GoogleSignIn { /// Initializes global sign-in configuration settings. /// /// The [signInOption] determines the user experience. [SigninOption.games] /// is only supported on Android. /// /// The list of [scopes] are OAuth scope codes to request when signing in. /// These scope codes will determine the level of data access that is granted /// to your application by the user. The full list of available scopes can /// be found here: /// /// /// The [hostedDomain] argument specifies a hosted domain restriction. By /// setting this, sign in will be restricted to accounts of the user in the /// specified domain. By default, the list of accounts will not be restricted. /// /// The [forceCodeForRefreshToken] is used on Android to ensure the authentication /// code can be exchanged for a refresh token after the first request. GoogleSignIn({ this.signInOption = SignInOption.standard, this.scopes = const [], this.hostedDomain, this.clientId, this.serverClientId, this.forceCodeForRefreshToken = false, }); /// Factory for creating default sign in user experience. factory GoogleSignIn.standard({ List scopes = const [], String? hostedDomain, }) { return GoogleSignIn(scopes: scopes, hostedDomain: hostedDomain); } /// Factory for creating sign in suitable for games. This option is only /// supported on Android. factory GoogleSignIn.games() { return GoogleSignIn(signInOption: SignInOption.games); } // These error codes must match with ones declared on Android and iOS sides. /// Error code indicating there is no signed in user and interactive sign in /// flow is required. static const String kSignInRequiredError = 'sign_in_required'; /// Error code indicating that interactive sign in process was canceled by the /// user. static const String kSignInCanceledError = 'sign_in_canceled'; /// Error code indicating network error. Retrying should resolve the problem. static const String kNetworkError = 'network_error'; /// Error code indicating that attempt to sign in failed. static const String kSignInFailedError = 'sign_in_failed'; /// Option to determine the sign in user experience. [SignInOption.games] is /// only supported on Android. final SignInOption signInOption; /// The list of [scopes] are OAuth scope codes requested when signing in. final List scopes; /// Domain to restrict sign-in to. final String? hostedDomain; /// Client ID being used to connect to google sign-in. /// /// This option is not supported on all platforms (e.g. Android). It is /// optional if file-based configuration is used. /// /// The value specified here has precedence over a value from a configuration /// file. final String? clientId; /// Client ID of the backend server to which the app needs to authenticate /// itself. /// /// Optional and not supported on all platforms (e.g. web). By default, it /// is initialized from a configuration file if available. /// /// The value specified here has precedence over a value from a configuration /// file. /// /// [GoogleSignInAuthentication.idToken] and /// [GoogleSignInAccount.serverAuthCode] will be specific to the backend /// server. final String? serverClientId; /// Force the authorization code to be valid for a refresh token every time. Only needed on Android. final bool forceCodeForRefreshToken; final StreamController _currentUserController = StreamController.broadcast(); /// Subscribe to this stream to be notified when the current user changes. Stream get onCurrentUserChanged => _currentUserController.stream; // Future that completes when we've finished calling `init` on the native side Future? _initialization; Future _callMethod( Future Function() method) async { await _ensureInitialized(); final dynamic response = await method(); return _setCurrentUser(response != null && response is GoogleSignInUserData ? GoogleSignInAccount._(this, response) : null); } GoogleSignInAccount? _setCurrentUser(GoogleSignInAccount? currentUser) { if (currentUser != _currentUser) { _currentUser = currentUser; _currentUserController.add(_currentUser); } return _currentUser; } Future _ensureInitialized() { return _initialization ??= GoogleSignInPlatform.instance.initWithParams(SignInInitParameters( signInOption: signInOption, scopes: scopes, hostedDomain: hostedDomain, clientId: clientId, serverClientId: serverClientId, forceCodeForRefreshToken: forceCodeForRefreshToken, )) ..catchError((dynamic _) { // Invalidate initialization if it errors out. _initialization = null; }); } /// The most recently scheduled method call. Future? _lastMethodCall; /// Returns a [Future] that completes with a success after [future], whether /// it completed with a value or an error. static Future _waitFor(Future future) { final Completer completer = Completer(); future.whenComplete(completer.complete).catchError((dynamic _) { // Ignore if previous call completed with an error. // TODO(ditman): Should we log errors here, if debug or similar? }); return completer.future; } /// Adds call to [method] in a queue for execution. /// /// At most one in flight call is allowed to prevent concurrent (out of order) /// updates to [currentUser] and [onCurrentUserChanged]. /// /// The optional, named parameter [canSkipCall] lets the plugin know that the /// method call may be skipped, if there's already [_currentUser] information. /// This is used from the [signIn] and [signInSilently] methods. Future _addMethodCall( Future Function() method, { bool canSkipCall = false, }) async { Future response; if (_lastMethodCall == null) { response = _callMethod(method); } else { response = _lastMethodCall!.then((_) { // If after the last completed call `currentUser` is not `null` and requested // method can be skipped (`canSkipCall`), re-use the same authenticated user // instead of making extra call to the native side. if (canSkipCall && _currentUser != null) { return _currentUser; } return _callMethod(method); }); } // Add the current response to the currently running Promise of all pending responses _lastMethodCall = _waitFor(response); return response; } /// The currently signed in account, or null if the user is signed out. GoogleSignInAccount? get currentUser => _currentUser; GoogleSignInAccount? _currentUser; /// Attempts to sign in a previously authenticated user without interaction. /// /// Returned Future resolves to an instance of [GoogleSignInAccount] for a /// successful sign in or `null` if there is no previously authenticated user. /// Use [signIn] method to trigger interactive sign in process. /// /// Authentication is triggered if there is no currently signed in /// user (that is when `currentUser == null`), otherwise this method returns /// a Future which resolves to the same user instance. /// /// Re-authentication can be triggered after [signOut] or [disconnect]. It can /// also be triggered by setting [reAuthenticate] to `true` if a new ID token /// is required. /// /// When [suppressErrors] is set to `false` and an error occurred during sign in /// returned Future completes with [PlatformException] whose `code` can be /// one of [kSignInRequiredError] (when there is no authenticated user) , /// [kNetworkError] (when a network error occurred) or [kSignInFailedError] /// (when an unknown error occurred). Future signInSilently({ bool suppressErrors = true, bool reAuthenticate = false, }) async { try { return await _addMethodCall(GoogleSignInPlatform.instance.signInSilently, canSkipCall: !reAuthenticate); } catch (_) { if (suppressErrors) { return null; } else { rethrow; } } } /// Returns a future that resolves to whether a user is currently signed in. Future isSignedIn() async { await _ensureInitialized(); return GoogleSignInPlatform.instance.isSignedIn(); } /// Starts the interactive sign-in process. /// /// Returned Future resolves to an instance of [GoogleSignInAccount] for a /// successful sign in or `null` in case sign in process was aborted. /// /// Authentication process is triggered only if there is no currently signed in /// user (that is when `currentUser == null`), otherwise this method returns /// a Future which resolves to the same user instance. /// /// Re-authentication can be triggered only after [signOut] or [disconnect]. Future signIn() { final Future result = _addMethodCall(GoogleSignInPlatform.instance.signIn, canSkipCall: true); bool isCanceled(dynamic error) => error is PlatformException && error.code == kSignInCanceledError; return result.catchError((dynamic _) => null, test: isCanceled); } /// Marks current user as being in the signed out state. Future signOut() => _addMethodCall(GoogleSignInPlatform.instance.signOut); /// Disconnects the current user from the app and revokes previous /// authentication. Future disconnect() => _addMethodCall(GoogleSignInPlatform.instance.disconnect); /// Requests the user grants additional Oauth [scopes]. Future requestScopes(List scopes) async { await _ensureInitialized(); return GoogleSignInPlatform.instance.requestScopes(scopes); } } ================================================ FILE: packages/google_sign_in/google_sign_in/lib/src/common.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Encapsulation of the fields that represent a Google user's identity. abstract class GoogleIdentity { /// The unique ID for the Google account. /// /// This is the preferred unique key to use for a user record. /// /// _Important_: Do not use this returned Google ID to communicate the /// currently signed in user to your backend server. Instead, send an ID token /// which can be securely validated on the server. /// `GoogleSignInAccount.authentication.idToken` provides such an ID token. String get id; /// The email address of the signed in user. /// /// Applications should not key users by email address since a Google /// account's email address can change. Use [id] as a key instead. /// /// _Important_: Do not use this returned email address to communicate the /// currently signed in user to your backend server. Instead, send an ID token /// which can be securely validated on the server. /// `GoogleSignInAccount.authentication.idToken` provides such an ID token. String get email; /// The display name of the signed in user. /// /// Not guaranteed to be present for all users, even when configured. String? get displayName; /// The photo url of the signed in user if the user has a profile picture. /// /// Not guaranteed to be present for all users, even when configured. String? get photoUrl; /// Server auth code used to access Google Login String? get serverAuthCode; } ================================================ FILE: packages/google_sign_in/google_sign_in/lib/src/fife.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// A regular expression that matches against the "size directive" path /// segment of Google profile image URLs. /// /// The format is is "`/sNN-c/`", where `NN` is the max width/height of the /// image, and "`c`" indicates we want the image cropped. final RegExp sizeDirective = RegExp(r'^s[0-9]{1,5}(-c)?$'); /// Adds [size] (and crop) directive to [photoUrl]. /// /// There are two formats for photoUrls coming from the Sign In backend. /// /// The two formats can be told apart by the number of path segments in the /// URL (path segments: parts of the URL separated by slashes "/"): /// /// * If the URL has 2 or less path segments, it is a *new* style URL. /// * If the URL has more than 2 path segments, it is an old style URL. /// /// Old style URLs encode the image transformation directives as the last /// path segment. Look at the [sizeDirective] Regular Expression for more /// information about these URLs. /// /// New style URLs carry the same directives at the end of the URL, /// after an = sign, like: "`=s120-c-fSoften=1,50,0`". /// /// Directives may contain the "=" sign (`fSoften=1,50,0`), but it seems the /// base URL of the images don't. "Everything after the first = sign" is a /// good heuristic to split new style URLs. /// /// Each directive is separated from others by dashes. Directives are the same /// as described in the [sizeDirective] RegExp. /// /// Modified image URLs are recomposed by performing the parsing steps in reverse. String addSizeDirectiveToUrl(String photoUrl, double size) { final Uri profileUri = Uri.parse(photoUrl); final List pathSegments = List.from(profileUri.pathSegments); if (pathSegments.length <= 2) { final String imagePath = pathSegments.last; // Does this have any existing transformation directives? final int directiveSeparator = imagePath.indexOf('='); if (directiveSeparator >= 0) { // Split the baseUrl from the sizing directive by the first "=" final String baseUrl = imagePath.substring(0, directiveSeparator); final String directive = imagePath.substring(directiveSeparator + 1); // Split the directive by "-" final Set directives = Set.from(directive.split('-')) // Remove the size directive, if present, and any empty values ..removeWhere((String s) => s.isEmpty || sizeDirective.hasMatch(s)) // Add the size and crop directives ..addAll(['c', 's${size.round()}']); // Recompose the URL by performing the reverse of the parsing pathSegments.last = '$baseUrl=${directives.join("-")}'; } else { pathSegments.last = '${pathSegments.last}=c-s${size.round()}'; } } else { // Old style URLs pathSegments ..removeWhere(sizeDirective.hasMatch) ..insert(pathSegments.length - 1, 's${size.round()}-c'); } return Uri( scheme: profileUri.scheme, host: profileUri.host, pathSegments: pathSegments, ).toString(); } ================================================ FILE: packages/google_sign_in/google_sign_in/lib/testing.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:flutter/services.dart' show MethodCall; /// A fake backend that can be used to test components that require a valid /// [GoogleSignInAccount]. /// /// Example usage: /// /// ``` /// GoogleSignIn googleSignIn; /// FakeSignInBackend fakeSignInBackend; /// /// setUp(() { /// googleSignIn = GoogleSignIn(); /// fakeSignInBackend = FakeSignInBackend(); /// fakeSignInBackend.user = FakeUser( /// id: 123, /// email: 'jdoe@example.org', /// ); /// googleSignIn.channel.setMockMethodCallHandler( /// fakeSignInBackend.handleMethodCall); /// }); /// ``` /// class FakeSignInBackend { /// A [FakeUser] object. /// /// This does not represent the signed-in user, but rather an object that will /// be returned when [GoogleSignIn.signIn] or [GoogleSignIn.signInSilently] is /// called. late FakeUser user; /// Handles method calls that would normally be sent to the native backend. /// Returns with the expected values based on the current [user]. Future handleMethodCall(MethodCall methodCall) async { switch (methodCall.method) { case 'init': // do nothing return null; case 'getTokens': return { 'idToken': user.idToken, 'accessToken': user.accessToken, }; case 'signIn': return user._asMap; case 'signInSilently': return user._asMap; case 'signOut': return {}; case 'disconnect': return {}; } } } /// Represents a fake user that can be used with the [FakeSignInBackend] to /// obtain a [GoogleSignInAccount] and simulate authentication. class FakeUser { /// Any of the given parameters can be null. const FakeUser({ this.id, this.email, this.displayName, this.photoUrl, this.serverAuthCode, this.idToken, this.accessToken, }); /// Will be converted into [GoogleSignInUserData.id]. final String? id; /// Will be converted into [GoogleSignInUserData.email]. final String? email; /// Will be converted into [GoogleSignInUserData.displayName]. final String? displayName; /// Will be converted into [GoogleSignInUserData.photoUrl]. final String? photoUrl; /// Will be converted into [GoogleSignInUserData.serverAuthCode]. final String? serverAuthCode; /// Will be converted into [GoogleSignInTokenData.idToken]. final String? idToken; /// Will be converted into [GoogleSignInTokenData.accessToken]. final String? accessToken; Map get _asMap => { 'id': id, 'email': email, 'displayName': displayName, 'photoUrl': photoUrl, 'serverAuthCode': serverAuthCode, 'idToken': idToken, }; } ================================================ FILE: packages/google_sign_in/google_sign_in/lib/widgets.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'src/common.dart'; import 'src/fife.dart' as fife; /// Builds a CircleAvatar profile image of the appropriate resolution class GoogleUserCircleAvatar extends StatelessWidget { /// Creates a new widget based on the specified [identity]. /// /// If [identity] does not contain a `photoUrl` and [placeholderPhotoUrl] is /// specified, then the given URL will be used as the user's photo URL. The /// URL must be able to handle a [sizeDirective] path segment. /// /// If [identity] does not contain a `photoUrl` and [placeholderPhotoUrl] is /// *not* specified, then the widget will render the user's first initial /// in place of a profile photo, or a default profile photo if the user's /// identity does not specify a `displayName`. const GoogleUserCircleAvatar({ Key? key, required this.identity, this.placeholderPhotoUrl, this.foregroundColor, this.backgroundColor, }) : assert(identity != null), super(key: key); /// A regular expression that matches against the "size directive" path /// segment of Google profile image URLs. /// /// The format is is "`/sNN-c/`", where `NN` is the max width/height of the /// image, and "`c`" indicates we want the image cropped. static final RegExp sizeDirective = fife.sizeDirective; /// The Google user's identity; guaranteed to be non-null. final GoogleIdentity identity; /// The color of the text to be displayed if photo is not available. /// /// If a foreground color is not specified, the theme's text color is used. final Color? foregroundColor; /// The color with which to fill the circle. Changing the background color /// will cause the avatar to animate to the new color. /// /// If a background color is not specified, the theme's primary color is used. final Color? backgroundColor; /// The URL of a photo to use if the user's [identity] does not specify a /// `photoUrl`. /// /// If this is `null` and the user's [identity] does not contain a photo URL, /// then this widget will attempt to display the user's first initial as /// determined from the identity's [displayName] field. If that is `null` a /// default (generic) Google profile photo will be displayed. final String? placeholderPhotoUrl; @override Widget build(BuildContext context) { return CircleAvatar( backgroundColor: backgroundColor, foregroundColor: foregroundColor, child: LayoutBuilder(builder: _buildClippedImage), ); } Widget _buildClippedImage(BuildContext context, BoxConstraints constraints) { assert(constraints.maxWidth == constraints.maxHeight); // Placeholder to use when there is no photo URL, and while the photo is // loading. Uses the first character of the display name (if it has one), // or the first letter of the email address if it does not. final List placeholderCharSources = [ identity.displayName, identity.email, '-', ]; final String placeholderChar = placeholderCharSources .firstWhere((String? str) => str != null && str.trimLeft().isNotEmpty)! .trimLeft()[0] .toUpperCase(); final Widget placeholder = Center( child: Text(placeholderChar, textAlign: TextAlign.center), ); final String? photoUrl = identity.photoUrl ?? placeholderPhotoUrl; if (photoUrl == null) { return placeholder; } // Add a sizing directive to the profile photo URL. final double size = MediaQuery.of(context).devicePixelRatio * constraints.maxWidth; final String sizedPhotoUrl = fife.addSizeDirectiveToUrl(photoUrl, size); // Fade the photo in over the top of the placeholder. return SizedBox( width: size, height: size, child: ClipOval( child: Stack(fit: StackFit.expand, children: [ placeholder, FadeInImage.memoryNetwork( // This creates a transparent placeholder image, so that // [placeholder] shows through. placeholder: _transparentImage, image: sizedPhotoUrl, ) ]), )); } } /// This is an transparent 1x1 gif image. /// /// Those bytes come from `resources/transparentImage.gif`. final Uint8List _transparentImage = Uint8List.fromList( [ 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0x80, 0x00, // 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x21, 0xf9, 0x04, 0x01, 0x00, // 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, // 0x00, 0x02, 0x01, 0x44, 0x00, 0x3B ], ); ================================================ FILE: packages/google_sign_in/google_sign_in/pubspec.yaml ================================================ name: google_sign_in 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: 6.0.0 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: platforms: android: default_package: google_sign_in_android ios: default_package: google_sign_in_ios web: default_package: google_sign_in_web dependencies: flutter: sdk: flutter google_sign_in_android: ^6.1.0 google_sign_in_ios: ^5.5.0 google_sign_in_platform_interface: ^2.2.0 google_sign_in_web: ^0.11.0 dev_dependencies: build_runner: ^2.1.10 flutter_driver: sdk: flutter flutter_test: sdk: flutter http: ^0.13.0 integration_test: sdk: flutter mockito: ^5.1.0 # The example deliberately includes limited-use secrets. false_secrets: - /example/android/app/google-services.json - /example/ios/Runner/GoogleService-Info.plist - /example/ios/RunnerTests/GoogleSignInTests.m - /example/lib/main.dart - /example/web/index.html ================================================ FILE: packages/google_sign_in/google_sign_in/resources/README.md ================================================ `transparentImage.gif` is a 1x1 transparent gif which comes from [this wikimedia page](https://commons.wikimedia.org/wiki/File:Transparent.gif): ![](transparentImage.gif) This is the image used a placeholder for the `GoogleCircleAvatar` widget. The variable `_transparentImage` in `lib/widgets.dart` is the list of bytes of `transparentImage.gif`. ================================================ FILE: packages/google_sign_in/google_sign_in/test/fife_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:google_sign_in/src/fife.dart'; void main() { group('addSizeDirectiveToUrl', () { const double size = 20; group('Old style URLs', () { const String base = 'https://lh3.googleusercontent.com/-ukEAtRyRhw8/AAAAAAAAAAI/AAAAAAAAAAA/ACHi3rfhID9XACtdb9q_xK43VSXQvBV11Q.CMID'; const String expected = '$base/s20-c/photo.jpg'; test('with directives, sets size', () { const String url = '$base/s64-c/photo.jpg'; expect(addSizeDirectiveToUrl(url, size), expected); }); test('no directives, sets size and crop', () { const String url = '$base/photo.jpg'; expect(addSizeDirectiveToUrl(url, size), expected); }); test('no crop, sets size and crop', () { const String url = '$base/s64/photo.jpg'; expect(addSizeDirectiveToUrl(url, size), expected); }); }); group('New style URLs', () { const String base = 'https://lh3.googleusercontent.com/a-/AAuE7mC0Lh4F4uDtEaY7hpe-GIsbDpqfMZ3_2UhBQ8Qk'; const String expected = '$base=c-s20'; test('with directives, sets size', () { const String url = '$base=s120-c'; expect(addSizeDirectiveToUrl(url, size), expected); }); test('no directives, sets size and crop', () { const String url = base; expect(addSizeDirectiveToUrl(url, size), expected); }); test('no directives, but with an equals sign, sets size and crop', () { const String url = '$base='; expect(addSizeDirectiveToUrl(url, size), expected); }); test('no crop, adds crop', () { const String url = '$base=s120'; expect(addSizeDirectiveToUrl(url, size), expected); }); test('many directives, sets size and crop, preserves other directives', () { const String url = '$base=s120-c-fSoften=1,50,0'; const String expected = '$base=c-fSoften=1,50,0-s20'; expect(addSizeDirectiveToUrl(url, size), expected); }); }); }); } ================================================ FILE: packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart ================================================ // Copyright 2013 The Flutter 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_test/flutter_test.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'google_sign_in_test.mocks.dart'; /// Verify that [GoogleSignInAccount] can be mocked even though it's unused // ignore: avoid_implementing_value_types, must_be_immutable class MockGoogleSignInAccount extends Mock implements GoogleSignInAccount {} @GenerateMocks([GoogleSignInPlatform]) void main() { late MockGoogleSignInPlatform mockPlatform; group('GoogleSignIn', () { final GoogleSignInUserData kDefaultUser = GoogleSignInUserData( email: 'john.doe@gmail.com', id: '8162538176523816253123', photoUrl: 'https://lh5.googleusercontent.com/photo.jpg', displayName: 'John Doe', serverAuthCode: '789'); setUp(() { mockPlatform = MockGoogleSignInPlatform(); when(mockPlatform.isMock).thenReturn(true); when(mockPlatform.signInSilently()) .thenAnswer((Invocation _) async => kDefaultUser); when(mockPlatform.signIn()) .thenAnswer((Invocation _) async => kDefaultUser); GoogleSignInPlatform.instance = mockPlatform; }); test('signInSilently', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); await googleSignIn.signInSilently(); expect(googleSignIn.currentUser, isNotNull); _verifyInit(mockPlatform); verify(mockPlatform.signInSilently()); }); test('signIn', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); await googleSignIn.signIn(); expect(googleSignIn.currentUser, isNotNull); _verifyInit(mockPlatform); verify(mockPlatform.signIn()); }); test('clientId parameter is forwarded to implementation', () async { const String fakeClientId = 'fakeClientId'; final GoogleSignIn googleSignIn = GoogleSignIn(clientId: fakeClientId); await googleSignIn.signIn(); _verifyInit(mockPlatform, clientId: fakeClientId); verify(mockPlatform.signIn()); }); test('serverClientId parameter is forwarded to implementation', () async { const String fakeServerClientId = 'fakeServerClientId'; final GoogleSignIn googleSignIn = GoogleSignIn(serverClientId: fakeServerClientId); await googleSignIn.signIn(); _verifyInit(mockPlatform, serverClientId: fakeServerClientId); verify(mockPlatform.signIn()); }); test('forceCodeForRefreshToken sent with init method call', () async { final GoogleSignIn googleSignIn = GoogleSignIn(forceCodeForRefreshToken: true); await googleSignIn.signIn(); _verifyInit(mockPlatform, forceCodeForRefreshToken: true); verify(mockPlatform.signIn()); }); test('signOut', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); await googleSignIn.signOut(); _verifyInit(mockPlatform); verify(mockPlatform.signOut()); }); test('disconnect; null response', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); await googleSignIn.disconnect(); expect(googleSignIn.currentUser, isNull); _verifyInit(mockPlatform); verify(mockPlatform.disconnect()); }); test('isSignedIn', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); when(mockPlatform.isSignedIn()).thenAnswer((Invocation _) async => true); final bool result = await googleSignIn.isSignedIn(); expect(result, isTrue); _verifyInit(mockPlatform); verify(mockPlatform.isSignedIn()); }); test('signIn works even if a previous call throws error in other zone', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); when(mockPlatform.signInSilently()).thenThrow(Exception('Not a user')); await runZonedGuarded(() async { expect(await googleSignIn.signInSilently(), isNull); }, (Object e, StackTrace st) {}); expect(await googleSignIn.signIn(), isNotNull); _verifyInit(mockPlatform); verify(mockPlatform.signInSilently()); verify(mockPlatform.signIn()); }); test('concurrent calls of the same method trigger sign in once', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); final List> futures = >[ googleSignIn.signInSilently(), googleSignIn.signInSilently(), ]; expect(futures.first, isNot(futures.last), reason: 'Must return new Future'); final List users = await Future.wait(futures); expect(googleSignIn.currentUser, isNotNull); expect(users, [ googleSignIn.currentUser, googleSignIn.currentUser ]); _verifyInit(mockPlatform); verify(mockPlatform.signInSilently()).called(1); }); test('can sign in after previously failed attempt', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); when(mockPlatform.signInSilently()).thenThrow(Exception('Not a user')); expect(await googleSignIn.signInSilently(), isNull); expect(await googleSignIn.signIn(), isNotNull); _verifyInit(mockPlatform); verify(mockPlatform.signInSilently()); verify(mockPlatform.signIn()); }); test('concurrent calls of different signIn methods', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); final List> futures = >[ googleSignIn.signInSilently(), googleSignIn.signIn(), ]; expect(futures.first, isNot(futures.last)); final List users = await Future.wait(futures); expect(users.first, users.last, reason: 'Must return the same user'); expect(googleSignIn.currentUser, users.last); _verifyInit(mockPlatform); verify(mockPlatform.signInSilently()); verifyNever(mockPlatform.signIn()); }); test('can sign in after aborted flow', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); when(mockPlatform.signIn()).thenAnswer((Invocation _) async => null); expect(await googleSignIn.signIn(), isNull); when(mockPlatform.signIn()) .thenAnswer((Invocation _) async => kDefaultUser); expect(await googleSignIn.signIn(), isNotNull); }); test('signOut/disconnect methods always trigger native calls', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); final List> futures = >[ googleSignIn.signOut(), googleSignIn.signOut(), googleSignIn.disconnect(), googleSignIn.disconnect(), ]; await Future.wait(futures); _verifyInit(mockPlatform); verify(mockPlatform.signOut()).called(2); verify(mockPlatform.disconnect()).called(2); }); test('queue of many concurrent calls', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); final List> futures = >[ googleSignIn.signInSilently(), googleSignIn.signOut(), googleSignIn.signIn(), googleSignIn.disconnect(), ]; await Future.wait(futures); _verifyInit(mockPlatform); verifyInOrder([ mockPlatform.signInSilently(), mockPlatform.signOut(), mockPlatform.signIn(), mockPlatform.disconnect(), ]); }); test('signInSilently suppresses errors by default', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); when(mockPlatform.signInSilently()).thenThrow(Exception('I am an error')); expect(await googleSignIn.signInSilently(), isNull); // should not throw }); test('signInSilently forwards exceptions', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); when(mockPlatform.signInSilently()).thenThrow(Exception('I am an error')); expect(googleSignIn.signInSilently(suppressErrors: false), throwsA(isInstanceOf())); }); test('signInSilently allows re-authentication to be requested', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); await googleSignIn.signInSilently(); expect(googleSignIn.currentUser, isNotNull); await googleSignIn.signInSilently(reAuthenticate: true); _verifyInit(mockPlatform); verify(mockPlatform.signInSilently()).called(2); }); test('can sign in after init failed before', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); when(mockPlatform.initWithParams(any)) .thenThrow(Exception('First init fails')); expect(googleSignIn.signIn(), throwsA(isInstanceOf())); when(mockPlatform.initWithParams(any)) .thenAnswer((Invocation _) async {}); expect(await googleSignIn.signIn(), isNotNull); }); test('created with standard factory uses correct options', () async { final GoogleSignIn googleSignIn = GoogleSignIn.standard(); await googleSignIn.signInSilently(); expect(googleSignIn.currentUser, isNotNull); _verifyInit(mockPlatform); verify(mockPlatform.signInSilently()); }); test('created with defaultGamesSignIn factory uses correct options', () async { final GoogleSignIn googleSignIn = GoogleSignIn.games(); await googleSignIn.signInSilently(); expect(googleSignIn.currentUser, isNotNull); _verifyInit(mockPlatform, signInOption: SignInOption.games); verify(mockPlatform.signInSilently()); }); test('authentication', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); when(mockPlatform.getTokens( email: anyNamed('email'), shouldRecoverAuth: anyNamed('shouldRecoverAuth'))) .thenAnswer((Invocation _) async => GoogleSignInTokenData( idToken: '123', accessToken: '456', serverAuthCode: '789', )); await googleSignIn.signIn(); final GoogleSignInAccount user = googleSignIn.currentUser!; final GoogleSignInAuthentication auth = await user.authentication; expect(auth.accessToken, '456'); expect(auth.idToken, '123'); verify(mockPlatform.getTokens( email: 'john.doe@gmail.com', shouldRecoverAuth: true)); }); test('requestScopes returns true once new scope is granted', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); when(mockPlatform.requestScopes(any)) .thenAnswer((Invocation _) async => true); await googleSignIn.signIn(); final bool result = await googleSignIn.requestScopes(['testScope']); expect(result, isTrue); _verifyInit(mockPlatform); verify(mockPlatform.signIn()); verify(mockPlatform.requestScopes(['testScope'])); }); test('user starts as null', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); expect(googleSignIn.currentUser, isNull); }); test('can sign in and sign out', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); await googleSignIn.signIn(); final GoogleSignInAccount user = googleSignIn.currentUser!; expect(user.displayName, equals(kDefaultUser.displayName)); expect(user.email, equals(kDefaultUser.email)); expect(user.id, equals(kDefaultUser.id)); expect(user.photoUrl, equals(kDefaultUser.photoUrl)); expect(user.serverAuthCode, equals(kDefaultUser.serverAuthCode)); await googleSignIn.disconnect(); expect(googleSignIn.currentUser, isNull); }); test('disconnect when signout already succeeds', () async { final GoogleSignIn googleSignIn = GoogleSignIn(); await googleSignIn.disconnect(); expect(googleSignIn.currentUser, isNull); }); }); } void _verifyInit( MockGoogleSignInPlatform mockSignIn, { List scopes = const [], SignInOption signInOption = SignInOption.standard, String? hostedDomain, String? clientId, String? serverClientId, bool forceCodeForRefreshToken = false, }) { verify(mockSignIn.initWithParams(argThat( isA() .having( (SignInInitParameters p) => p.scopes, 'scopes', scopes, ) .having( (SignInInitParameters p) => p.signInOption, 'signInOption', signInOption, ) .having( (SignInInitParameters p) => p.hostedDomain, 'hostedDomain', hostedDomain, ) .having( (SignInInitParameters p) => p.clientId, 'clientId', clientId, ) .having( (SignInInitParameters p) => p.serverClientId, 'serverClientId', serverClientId, ) .having( (SignInInitParameters p) => p.forceCodeForRefreshToken, 'forceCodeForRefreshToken', forceCodeForRefreshToken, ), ))); } ================================================ FILE: packages/google_sign_in/google_sign_in/test/google_sign_in_test.mocks.dart ================================================ // Mocks generated by Mockito 5.1.0 from annotations // in google_sign_in/test/google_sign_in_test.dart. // Do not manually edit this file. import 'dart:async' as _i4; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart' as _i3; import 'package:google_sign_in_platform_interface/src/types.dart' as _i2; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: 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 class _FakeGoogleSignInTokenData_0 extends _i1.Fake implements _i2.GoogleSignInTokenData {} /// A class which mocks [GoogleSignInPlatform]. /// /// See the documentation for Mockito's code generation for more information. class MockGoogleSignInPlatform extends _i1.Mock implements _i3.GoogleSignInPlatform { MockGoogleSignInPlatform() { _i1.throwOnMissingStub(this); } @override bool get isMock => (super.noSuchMethod(Invocation.getter(#isMock), returnValue: false) as bool); @override _i4.Future init( {List? scopes = const [], _i2.SignInOption? signInOption = _i2.SignInOption.standard, String? hostedDomain, String? clientId}) => (super.noSuchMethod( Invocation.method(#init, [], { #scopes: scopes, #signInOption: signInOption, #hostedDomain: hostedDomain, #clientId: clientId }), returnValue: Future.value(), returnValueForMissingStub: Future.value()) as _i4.Future); @override _i4.Future initWithParams(_i2.SignInInitParameters? params) => (super.noSuchMethod(Invocation.method(#initWithParams, [params]), returnValue: Future.value(), returnValueForMissingStub: Future.value()) as _i4.Future); @override _i4.Future<_i2.GoogleSignInUserData?> signInSilently() => (super.noSuchMethod(Invocation.method(#signInSilently, []), returnValue: Future<_i2.GoogleSignInUserData?>.value()) as _i4.Future<_i2.GoogleSignInUserData?>); @override _i4.Future<_i2.GoogleSignInUserData?> signIn() => (super.noSuchMethod(Invocation.method(#signIn, []), returnValue: Future<_i2.GoogleSignInUserData?>.value()) as _i4.Future<_i2.GoogleSignInUserData?>); @override _i4.Future<_i2.GoogleSignInTokenData> getTokens( {String? email, bool? shouldRecoverAuth}) => (super.noSuchMethod( Invocation.method(#getTokens, [], {#email: email, #shouldRecoverAuth: shouldRecoverAuth}), returnValue: Future<_i2.GoogleSignInTokenData>.value( _FakeGoogleSignInTokenData_0())) as _i4.Future<_i2.GoogleSignInTokenData>); @override _i4.Future signOut() => (super.noSuchMethod(Invocation.method(#signOut, []), returnValue: Future.value(), returnValueForMissingStub: Future.value()) as _i4.Future); @override _i4.Future disconnect() => (super.noSuchMethod(Invocation.method(#disconnect, []), returnValue: Future.value(), returnValueForMissingStub: Future.value()) as _i4.Future); @override _i4.Future isSignedIn() => (super.noSuchMethod(Invocation.method(#isSignedIn, []), returnValue: Future.value(false)) as _i4.Future); @override _i4.Future clearAuthCache({String? token}) => (super.noSuchMethod( Invocation.method(#clearAuthCache, [], {#token: token}), returnValue: Future.value(), returnValueForMissingStub: Future.value()) as _i4.Future); @override _i4.Future requestScopes(List? scopes) => (super.noSuchMethod(Invocation.method(#requestScopes, [scopes]), returnValue: Future.value(false)) as _i4.Future); } ================================================ FILE: packages/google_sign_in/google_sign_in/test/widgets_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_sign_in/google_sign_in.dart'; /// A instantiable class that extends [GoogleIdentity] class _TestGoogleIdentity extends GoogleIdentity { _TestGoogleIdentity({ required this.id, required this.email, this.photoUrl, }); @override final String id; @override final String email; @override final String? photoUrl; @override String? get displayName => null; @override String? get serverAuthCode => null; } /// A mocked [HttpClient] which always returns a [_MockHttpRequest]. class _MockHttpClient extends Fake implements HttpClient { @override bool autoUncompress = true; @override Future getUrl(Uri url) { return Future.value(_MockHttpRequest()); } } /// A mocked [HttpClientRequest] which always returns a [_MockHttpClientResponse]. class _MockHttpRequest extends Fake implements HttpClientRequest { @override Future close() { return Future.value(_MockHttpResponse()); } } /// Arbitrary valid image returned by the [_MockHttpResponse]. /// /// This is an transparent 1x1 gif image. /// It doesn't have to match the placeholder used in [GoogleUserCircleAvatar]. /// /// Those bytes come from `resources/transparentImage.gif`. final Uint8List _transparentImage = Uint8List.fromList( [ 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0x80, 0x00, // 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x21, 0xf9, 0x04, 0x01, 0x00, // 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, // 0x00, 0x02, 0x01, 0x44, 0x00, 0x3B ], ); /// A mocked [HttpClientResponse] which is empty and has a [statusCode] of 200 /// and returns valid image. class _MockHttpResponse extends Fake implements HttpClientResponse { final Stream _delegate = Stream.value(_transparentImage); @override int get contentLength => -1; @override HttpClientResponseCompressionState get compressionState { return HttpClientResponseCompressionState.decompressed; } @override StreamSubscription listen(void Function(Uint8List event)? onData, {Function? onError, void Function()? onDone, bool? cancelOnError}) { return _delegate.listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError); } @override int get statusCode => 200; } void main() { testWidgets('It should build the GoogleUserCircleAvatar successfully', (WidgetTester tester) async { final GoogleIdentity identity = _TestGoogleIdentity( email: 'email@email.com', id: 'userId', photoUrl: 'photoUrl', ); tester.binding.window.physicalSizeTestValue = const Size(100, 100); await HttpOverrides.runZoned( () async { await tester.pumpWidget(MaterialApp( home: SizedBox( height: 100, width: 100, child: GoogleUserCircleAvatar( identity: identity, ), ), )); }, createHttpClient: (SecurityContext? c) => _MockHttpClient(), ); }); } ================================================ FILE: packages/google_sign_in/google_sign_in_android/AUTHORS ================================================ # 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> Twin Sun, LLC ================================================ FILE: packages/google_sign_in/google_sign_in_android/CHANGELOG.md ================================================ ## 6.1.6 * Minor implementation cleanup * 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. ## 6.1.3 * Updates play-services-auth version to 20.4.0. ## 6.1.2 * Fixes passing `serverClientId` via the channelled `init` call ## 6.1.1 * Corrects typos in plugin error logs and removes not actionable warnings. * Updates minimum Flutter version to 2.10. * Updates play-services-auth version to 20.3.0. ## 6.1.0 * Adds override for `GoogleSignIn.initWithParams` to handle new `forceCodeForRefreshToken` parameter. ## 6.0.1 * Updates gradle version to 7.2.1 on Android. ## 6.0.0 * Deprecates `clientId` and adds support for `serverClientId` instead. Historically `clientId` was interpreted as `serverClientId`, but only on Android. On other platforms it was interpreted as the OAuth `clientId` of the app. For backwards-compatibility `clientId` will still be used as a server client ID if `serverClientId` is not provided. * **BREAKING CHANGES**: * Adds `serverClientId` parameter to `IDelegate.init` (Java). ## 5.2.8 * Suppresses `deprecation` warnings (for using Android V1 embedding). ## 5.2.7 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 5.2.6 * Switches to an internal method channel, rather than the default. ## 5.2.5 * Splits from `google_sign_in` as a federated implementation. ================================================ FILE: packages/google_sign_in/google_sign_in_android/LICENSE ================================================ 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. ================================================ FILE: packages/google_sign_in/google_sign_in_android/README.md ================================================ # google\_sign\_in\_android The Android implementation of [`google_sign_in`][1]. ## Usage This package is [endorsed][2], which means you can simply use `google_sign_in` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/google_sign_in [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/google_sign_in/google_sign_in_android/android/build.gradle ================================================ group 'io.flutter.plugins.googlesignin' version '1.0-SNAPSHOT' buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.2.1' } } rootProject.allprojects { repositories { google() mavenCentral() } } apply plugin: 'com.android.library' android { compileSdkVersion 31 defaultConfig { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } testOptions { unitTests.includeAndroidResources = true unitTests.returnDefaultValues = true unitTests.all { testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" outputs.upToDateWhen {false} showStandardStreams = true } } } } dependencies { 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:5.0.0' } ================================================ FILE: packages/google_sign_in/google_sign_in_android/android/settings.gradle ================================================ rootProject.name = 'google_sign_in_android' ================================================ FILE: packages/google_sign_in/google_sign_in_android/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/BackgroundTaskRunner.java ================================================ // Copyright 2013 The Flutter 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.googlesignin; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * A class for running tasks in a background thread. * *

TODO(jackson): If this class is useful for other plugins, consider including it in a shared * library or in the Flutter engine */ public final class BackgroundTaskRunner { /** * Interface that callers of this API can implement to be notified when a {@link * #runInBackground(Callable,Callback) background task} has completed. */ public interface Callback { /** * Invoked on the UI thread when the specified future has completed (calling {@code get()} on * the future is guaranteed not to block). If the future completed with an exception, then * {@code get()} will throw an {@code ExecutionException}. */ void run(Future future); } private final ThreadPoolExecutor executor; /** * Creates a new background processor with the given number of threads. * * @param threads The fixed number of threads in ther pool. */ public BackgroundTaskRunner(int threads) { BlockingQueue workQueue = new LinkedBlockingQueue<>(); // Only keeps idle threads open for 1 second if we've got more threads than cores. executor = new ThreadPoolExecutor(threads, threads, 1, TimeUnit.SECONDS, workQueue); } /** * Executes the specified task in a background thread and notifies the specified callback once the * task has completed (either successfully or with an exception). * *

The callback will be notified on the UI thread. */ public void runInBackground(Callable task, final Callback callback) { final ListenableFuture future = runInBackground(task); future.addListener( new Runnable() { @Override public void run() { callback.run(future); } }, Executors.uiThreadExecutor()); } /** * Executes the specified task in a background thread and returns a future with which the caller * can be notified of task completion. * *

Note: the future will be notified on the background thread. To be notified on the UI thread, * use {@link #runInBackground(Callable,Callback)}. */ public ListenableFuture runInBackground(final Callable task) { final SettableFuture future = SettableFuture.create(); executor.execute( new Runnable() { @Override public void run() { if (!future.isCancelled()) { try { future.set(task.call()); } catch (Throwable t) { future.setException(t); } } } }); return future; } } ================================================ FILE: packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/Executors.java ================================================ // Copyright 2013 The Flutter 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.googlesignin; import android.os.Handler; import android.os.Looper; import java.util.concurrent.Executor; /** * Factory and utility methods for {@code Executor}. * *

TODO(jackson): If this class is useful for other plugins, consider including it in a shared * library or in the Flutter engine */ public final class Executors { private static final class UiThreadExecutor implements Executor { private static final Handler UI_THREAD = new Handler(Looper.getMainLooper()); @Override public void execute(Runnable command) { UI_THREAD.post(command); } } /** Returns an {@code Executor} that will post commands to the UI thread. */ public static Executor uiThreadExecutor() { return new UiThreadExecutor(); } // Should never be instantiated. private Executors() {} } ================================================ FILE: packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java ================================================ // Copyright 2013 The Flutter 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.googlesignin; import android.accounts.Account; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.google.android.gms.auth.GoogleAuthUtil; import com.google.android.gms.auth.UserRecoverableAuthException; import com.google.android.gms.auth.api.signin.GoogleSignIn; import com.google.android.gms.auth.api.signin.GoogleSignInAccount; import com.google.android.gms.auth.api.signin.GoogleSignInClient; import com.google.android.gms.auth.api.signin.GoogleSignInOptions; import com.google.android.gms.auth.api.signin.GoogleSignInStatusCodes; import com.google.android.gms.common.api.ApiException; import com.google.android.gms.common.api.CommonStatusCodes; import com.google.android.gms.common.api.Scope; import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.RuntimeExecutionException; import com.google.android.gms.tasks.Task; import com.google.common.base.Joiner; import com.google.common.base.Strings; 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.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.PluginRegistry; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; /** Google sign-in plugin for Flutter. */ public class GoogleSignInPlugin implements MethodCallHandler, FlutterPlugin, ActivityAware { private static final String CHANNEL_NAME = "plugins.flutter.io/google_sign_in_android"; private static final String METHOD_INIT = "init"; private static final String METHOD_SIGN_IN_SILENTLY = "signInSilently"; private static final String METHOD_SIGN_IN = "signIn"; private static final String METHOD_GET_TOKENS = "getTokens"; private static final String METHOD_SIGN_OUT = "signOut"; private static final String METHOD_DISCONNECT = "disconnect"; private static final String METHOD_IS_SIGNED_IN = "isSignedIn"; private static final String METHOD_CLEAR_AUTH_CACHE = "clearAuthCache"; private static final String METHOD_REQUEST_SCOPES = "requestScopes"; private Delegate delegate; private MethodChannel channel; private ActivityPluginBinding activityPluginBinding; @SuppressWarnings("deprecation") public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { GoogleSignInPlugin instance = new GoogleSignInPlugin(); instance.initInstance(registrar.messenger(), registrar.context(), new GoogleSignInWrapper()); instance.setUpRegistrar(registrar); } @VisibleForTesting public void initInstance( BinaryMessenger messenger, Context context, GoogleSignInWrapper googleSignInWrapper) { channel = new MethodChannel(messenger, CHANNEL_NAME); delegate = new Delegate(context, googleSignInWrapper); channel.setMethodCallHandler(this); } @VisibleForTesting @SuppressWarnings("deprecation") public void setUpRegistrar(PluginRegistry.Registrar registrar) { delegate.setUpRegistrar(registrar); } private void dispose() { delegate = null; channel.setMethodCallHandler(null); channel = null; } private void attachToActivity(ActivityPluginBinding activityPluginBinding) { this.activityPluginBinding = activityPluginBinding; activityPluginBinding.addActivityResultListener(delegate); delegate.setActivity(activityPluginBinding.getActivity()); } private void disposeActivity() { activityPluginBinding.removeActivityResultListener(delegate); delegate.setActivity(null); activityPluginBinding = null; } @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { initInstance( binding.getBinaryMessenger(), binding.getApplicationContext(), new GoogleSignInWrapper()); } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { dispose(); } @Override public void onAttachedToActivity(ActivityPluginBinding activityPluginBinding) { attachToActivity(activityPluginBinding); } @Override public void onDetachedFromActivityForConfigChanges() { disposeActivity(); } @Override public void onReattachedToActivityForConfigChanges(ActivityPluginBinding activityPluginBinding) { attachToActivity(activityPluginBinding); } @Override public void onDetachedFromActivity() { disposeActivity(); } @Override public void onMethodCall(MethodCall call, Result result) { switch (call.method) { case METHOD_INIT: String signInOption = call.argument("signInOption"); List requestedScopes = call.argument("scopes"); String hostedDomain = call.argument("hostedDomain"); String clientId = call.argument("clientId"); String serverClientId = call.argument("serverClientId"); boolean forceCodeForRefreshToken = call.argument("forceCodeForRefreshToken"); delegate.init( result, signInOption, requestedScopes, hostedDomain, clientId, serverClientId, forceCodeForRefreshToken); break; case METHOD_SIGN_IN_SILENTLY: delegate.signInSilently(result); break; case METHOD_SIGN_IN: delegate.signIn(result); break; case METHOD_GET_TOKENS: String email = call.argument("email"); boolean shouldRecoverAuth = call.argument("shouldRecoverAuth"); delegate.getTokens(result, email, shouldRecoverAuth); break; case METHOD_SIGN_OUT: delegate.signOut(result); break; case METHOD_CLEAR_AUTH_CACHE: String token = call.argument("token"); delegate.clearAuthCache(result, token); break; case METHOD_DISCONNECT: delegate.disconnect(result); break; case METHOD_IS_SIGNED_IN: delegate.isSignedIn(result); break; case METHOD_REQUEST_SCOPES: List scopes = call.argument("scopes"); delegate.requestScopes(result, scopes); break; default: result.notImplemented(); } } /** * A delegate interface that exposes all of the sign-in functionality for other plugins to use. * The below {@link Delegate} implementation should be used by any clients unless they need to * override some of these functions, such as for testing. */ public interface IDelegate { /** Initializes this delegate so that it is ready to perform other operations. */ public void init( Result result, String signInOption, List requestedScopes, String hostedDomain, String clientId, String serverClientId, boolean forceCodeForRefreshToken); /** * Returns the account information for the user who is signed in to this app. If no user is * signed in, tries to sign the user in without displaying any user interface. */ public void signInSilently(Result result); /** * Signs the user in via the sign-in user interface, including the OAuth consent flow if scopes * were requested. */ public void signIn(Result result); /** * Gets an OAuth access token with the scopes that were specified during initialization for the * user with the specified email address. * *

If shouldRecoverAuth is set to true and user needs to recover authentication for method to * complete, the method will attempt to recover authentication and rerun method. */ public void getTokens(final Result result, final String email, final boolean shouldRecoverAuth); /** * Clears the token from any client cache forcing the next {@link #getTokens} call to fetch a * new one. */ public void clearAuthCache(final Result result, final String token); /** * Signs the user out. Their credentials may remain valid, meaning they'll be able to silently * sign back in. */ public void signOut(Result result); /** Signs the user out, and revokes their credentials. */ public void disconnect(Result result); /** Checks if there is a signed in user. */ public void isSignedIn(Result result); /** Prompts the user to grant an additional Oauth scopes. */ public void requestScopes(final Result result, final List scopes); } /** * Delegate class that does the work for the Google sign-in plugin. This is exposed as a dedicated * class for use in other plugins that wrap basic sign-in functionality. * *

All methods in this class assume that they are run to completion before any other method is * invoked. In this context, "run to completion" means that their {@link Result} argument has been * completed (either successfully or in error). This class provides no synchronization constructs * to guarantee such behavior; callers are responsible for providing such guarantees. */ public static class Delegate implements IDelegate, PluginRegistry.ActivityResultListener { private static final int REQUEST_CODE_SIGNIN = 53293; private static final int REQUEST_CODE_RECOVER_AUTH = 53294; @VisibleForTesting static final int REQUEST_CODE_REQUEST_SCOPE = 53295; private static final String ERROR_REASON_EXCEPTION = "exception"; private static final String ERROR_REASON_STATUS = "status"; // These error codes must match with ones declared on iOS and Dart sides. private static final String ERROR_REASON_SIGN_IN_CANCELED = "sign_in_canceled"; private static final String ERROR_REASON_SIGN_IN_REQUIRED = "sign_in_required"; private static final String ERROR_REASON_NETWORK_ERROR = "network_error"; private static final String ERROR_REASON_SIGN_IN_FAILED = "sign_in_failed"; private static final String ERROR_FAILURE_TO_RECOVER_AUTH = "failed_to_recover_auth"; private static final String ERROR_USER_RECOVERABLE_AUTH = "user_recoverable_auth"; private static final String DEFAULT_SIGN_IN = "SignInOption.standard"; private static final String DEFAULT_GAMES_SIGN_IN = "SignInOption.games"; private final Context context; // Only set registrar for v1 embedder. @SuppressWarnings("deprecation") private PluginRegistry.Registrar registrar; // Only set activity for v2 embedder. Always access activity from getActivity() method. private Activity activity; private final BackgroundTaskRunner backgroundTaskRunner = new BackgroundTaskRunner(1); private final GoogleSignInWrapper googleSignInWrapper; private GoogleSignInClient signInClient; private List requestedScopes; private PendingOperation pendingOperation; public Delegate(Context context, GoogleSignInWrapper googleSignInWrapper) { this.context = context; this.googleSignInWrapper = googleSignInWrapper; } @SuppressWarnings("deprecation") public void setUpRegistrar(PluginRegistry.Registrar registrar) { this.registrar = registrar; registrar.addActivityResultListener(this); } public void setActivity(Activity activity) { this.activity = activity; } // Only access activity with this method. public Activity getActivity() { return registrar != null ? registrar.activity() : activity; } private void checkAndSetPendingOperation(String method, Result result) { checkAndSetPendingOperation(method, result, null); } private void checkAndSetPendingOperation(String method, Result result, Object data) { if (pendingOperation != null) { throw new IllegalStateException( "Concurrent operations detected: " + pendingOperation.method + ", " + method); } pendingOperation = new PendingOperation(method, result, data); } /** * Initializes this delegate so that it is ready to perform other operations. The Dart code * guarantees that this will be called and completed before any other methods are invoked. */ @Override public void init( Result result, String signInOption, List requestedScopes, String hostedDomain, String clientId, String serverClientId, boolean forceCodeForRefreshToken) { try { GoogleSignInOptions.Builder optionsBuilder; switch (signInOption) { case DEFAULT_GAMES_SIGN_IN: optionsBuilder = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN); break; case DEFAULT_SIGN_IN: optionsBuilder = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN).requestEmail(); break; default: throw new IllegalStateException("Unknown signInOption"); } // The clientId parameter is not supported on Android. // Android apps are identified by their package name and the SHA-1 of their signing key. // https://developers.google.com/android/guides/client-auth // https://developers.google.com/identity/sign-in/android/start#configure-a-google-api-project if (!Strings.isNullOrEmpty(clientId) && Strings.isNullOrEmpty(serverClientId)) { Log.w( "google_sign_in", "clientId is not supported on Android and is interpreted as serverClientId. " + "Use serverClientId instead to suppress this warning."); serverClientId = clientId; } if (Strings.isNullOrEmpty(serverClientId)) { // Only requests a clientId if google-services.json was present and parsed // by the google-services Gradle script. // TODO(jackson): Perhaps we should provide a mechanism to override this // behavior. int webClientIdIdentifier = context .getResources() .getIdentifier("default_web_client_id", "string", context.getPackageName()); if (webClientIdIdentifier != 0) { serverClientId = context.getString(webClientIdIdentifier); } } if (!Strings.isNullOrEmpty(serverClientId)) { optionsBuilder.requestIdToken(serverClientId); optionsBuilder.requestServerAuthCode(serverClientId, forceCodeForRefreshToken); } for (String scope : requestedScopes) { optionsBuilder.requestScopes(new Scope(scope)); } if (!Strings.isNullOrEmpty(hostedDomain)) { optionsBuilder.setHostedDomain(hostedDomain); } this.requestedScopes = requestedScopes; signInClient = googleSignInWrapper.getClient(context, optionsBuilder.build()); result.success(null); } catch (Exception e) { result.error(ERROR_REASON_EXCEPTION, e.getMessage(), null); } } /** * Returns the account information for the user who is signed in to this app. If no user is * signed in, tries to sign the user in without displaying any user interface. */ @Override public void signInSilently(Result result) { checkAndSetPendingOperation(METHOD_SIGN_IN_SILENTLY, result); Task task = signInClient.silentSignIn(); if (task.isComplete()) { // There's immediate result available. onSignInResult(task); } else { task.addOnCompleteListener( new OnCompleteListener() { @Override public void onComplete(Task task) { onSignInResult(task); } }); } } /** * Signs the user in via the sign-in user interface, including the OAuth consent flow if scopes * were requested. */ @Override public void signIn(Result result) { if (getActivity() == null) { throw new IllegalStateException("signIn needs a foreground activity"); } checkAndSetPendingOperation(METHOD_SIGN_IN, result); Intent signInIntent = signInClient.getSignInIntent(); getActivity().startActivityForResult(signInIntent, REQUEST_CODE_SIGNIN); } /** * Signs the user out. Their credentials may remain valid, meaning they'll be able to silently * sign back in. */ @Override public void signOut(Result result) { checkAndSetPendingOperation(METHOD_SIGN_OUT, result); signInClient .signOut() .addOnCompleteListener( new OnCompleteListener() { @Override public void onComplete(Task task) { if (task.isSuccessful()) { finishWithSuccess(null); } else { finishWithError(ERROR_REASON_STATUS, "Failed to signout."); } } }); } /** Signs the user out, and revokes their credentials. */ @Override public void disconnect(Result result) { checkAndSetPendingOperation(METHOD_DISCONNECT, result); signInClient .revokeAccess() .addOnCompleteListener( new OnCompleteListener() { @Override public void onComplete(Task task) { if (task.isSuccessful()) { finishWithSuccess(null); } else { finishWithError(ERROR_REASON_STATUS, "Failed to disconnect."); } } }); } /** Checks if there is a signed in user. */ @Override public void isSignedIn(final Result result) { boolean value = GoogleSignIn.getLastSignedInAccount(context) != null; result.success(value); } @Override public void requestScopes(Result result, List scopes) { checkAndSetPendingOperation(METHOD_REQUEST_SCOPES, result); GoogleSignInAccount account = googleSignInWrapper.getLastSignedInAccount(context); if (account == null) { finishWithError(ERROR_REASON_SIGN_IN_REQUIRED, "No account to grant scopes."); return; } List wrappedScopes = new ArrayList<>(); for (String scope : scopes) { Scope wrappedScope = new Scope(scope); if (!googleSignInWrapper.hasPermissions(account, wrappedScope)) { wrappedScopes.add(wrappedScope); } } if (wrappedScopes.isEmpty()) { finishWithSuccess(true); return; } googleSignInWrapper.requestPermissions( getActivity(), REQUEST_CODE_REQUEST_SCOPE, account, wrappedScopes.toArray(new Scope[0])); } private void onSignInResult(Task completedTask) { try { GoogleSignInAccount account = completedTask.getResult(ApiException.class); onSignInAccount(account); } catch (ApiException e) { // Forward all errors and let Dart decide how to handle. String errorCode = errorCodeForStatus(e.getStatusCode()); finishWithError(errorCode, e.toString()); } catch (RuntimeExecutionException e) { finishWithError(ERROR_REASON_EXCEPTION, e.toString()); } } private void onSignInAccount(GoogleSignInAccount account) { Map response = new HashMap<>(); response.put("email", account.getEmail()); response.put("id", account.getId()); response.put("idToken", account.getIdToken()); response.put("serverAuthCode", account.getServerAuthCode()); response.put("displayName", account.getDisplayName()); if (account.getPhotoUrl() != null) { response.put("photoUrl", account.getPhotoUrl().toString()); } finishWithSuccess(response); } private String errorCodeForStatus(int statusCode) { switch (statusCode) { case GoogleSignInStatusCodes.SIGN_IN_CANCELLED: return ERROR_REASON_SIGN_IN_CANCELED; case CommonStatusCodes.SIGN_IN_REQUIRED: return ERROR_REASON_SIGN_IN_REQUIRED; case CommonStatusCodes.NETWORK_ERROR: return ERROR_REASON_NETWORK_ERROR; case GoogleSignInStatusCodes.SIGN_IN_CURRENTLY_IN_PROGRESS: case GoogleSignInStatusCodes.SIGN_IN_FAILED: case CommonStatusCodes.INVALID_ACCOUNT: case CommonStatusCodes.INTERNAL_ERROR: return ERROR_REASON_SIGN_IN_FAILED; default: return ERROR_REASON_SIGN_IN_FAILED; } } private void finishWithSuccess(Object data) { pendingOperation.result.success(data); pendingOperation = null; } private void finishWithError(String errorCode, String errorMessage) { pendingOperation.result.error(errorCode, errorMessage, null); pendingOperation = null; } private static class PendingOperation { final String method; final Result result; final Object data; PendingOperation(String method, Result result, Object data) { this.method = method; this.result = result; this.data = data; } } /** Clears the token kept in the client side cache. */ @Override public void clearAuthCache(final Result result, final String token) { Callable clearTokenTask = new Callable() { @Override public Void call() throws Exception { GoogleAuthUtil.clearToken(context, token); return null; } }; backgroundTaskRunner.runInBackground( clearTokenTask, new BackgroundTaskRunner.Callback() { @Override public void run(Future clearTokenFuture) { try { result.success(clearTokenFuture.get()); } catch (ExecutionException e) { result.error(ERROR_REASON_EXCEPTION, e.getCause().getMessage(), null); } catch (InterruptedException e) { result.error(ERROR_REASON_EXCEPTION, e.getMessage(), null); Thread.currentThread().interrupt(); } } }); } /** * Gets an OAuth access token with the scopes that were specified during initialization for the * user with the specified email address. * *

If shouldRecoverAuth is set to true and user needs to recover authentication for method to * complete, the method will attempt to recover authentication and rerun method. */ @Override public void getTokens( final Result result, final String email, final boolean shouldRecoverAuth) { if (email == null) { result.error(ERROR_REASON_EXCEPTION, "Email is null", null); return; } Callable getTokenTask = new Callable() { @Override public String call() throws Exception { Account account = new Account(email, "com.google"); String scopesStr = "oauth2:" + Joiner.on(' ').join(requestedScopes); return GoogleAuthUtil.getToken(context, account, scopesStr); } }; // Background task runner has a single thread effectively serializing // the getToken calls. 1p apps can then enjoy the token cache if multiple // getToken calls are coming in. backgroundTaskRunner.runInBackground( getTokenTask, new BackgroundTaskRunner.Callback() { @Override public void run(Future tokenFuture) { try { String token = tokenFuture.get(); HashMap tokenResult = new HashMap<>(); tokenResult.put("accessToken", token); result.success(tokenResult); } catch (ExecutionException e) { if (e.getCause() instanceof UserRecoverableAuthException) { if (shouldRecoverAuth && pendingOperation == null) { Activity activity = getActivity(); if (activity == null) { result.error( ERROR_USER_RECOVERABLE_AUTH, "Cannot recover auth because app is not in foreground. " + e.getLocalizedMessage(), null); } else { checkAndSetPendingOperation(METHOD_GET_TOKENS, result, email); Intent recoveryIntent = ((UserRecoverableAuthException) e.getCause()).getIntent(); activity.startActivityForResult(recoveryIntent, REQUEST_CODE_RECOVER_AUTH); } } else { result.error(ERROR_USER_RECOVERABLE_AUTH, e.getLocalizedMessage(), null); } } else { result.error(ERROR_REASON_EXCEPTION, e.getCause().getMessage(), null); } } catch (InterruptedException e) { result.error(ERROR_REASON_EXCEPTION, e.getMessage(), null); Thread.currentThread().interrupt(); } } }); } @Override public boolean onActivityResult(int requestCode, int resultCode, Intent data) { if (pendingOperation == null) { return false; } switch (requestCode) { case REQUEST_CODE_RECOVER_AUTH: if (resultCode == Activity.RESULT_OK) { // Recover the previous result and data and attempt to get tokens again. Result result = pendingOperation.result; String email = (String) pendingOperation.data; pendingOperation = null; getTokens(result, email, false); } else { finishWithError( ERROR_FAILURE_TO_RECOVER_AUTH, "Failed attempt to recover authentication"); } return true; case REQUEST_CODE_SIGNIN: // Whether resultCode is OK or not, the Task returned by GoogleSigIn will determine // failure with better specifics which are extracted in onSignInResult method. if (data != null) { onSignInResult(GoogleSignIn.getSignedInAccountFromIntent(data)); } else { // data is null which is highly unusual for a sign in result. finishWithError(ERROR_REASON_SIGN_IN_FAILED, "Signin failed"); } return true; case REQUEST_CODE_REQUEST_SCOPE: finishWithSuccess(resultCode == Activity.RESULT_OK); return true; default: return false; } } } } ================================================ FILE: packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInWrapper.java ================================================ // Copyright 2013 The Flutter 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.googlesignin; import android.app.Activity; import android.content.Context; import com.google.android.gms.auth.api.signin.GoogleSignIn; import com.google.android.gms.auth.api.signin.GoogleSignInAccount; import com.google.android.gms.auth.api.signin.GoogleSignInClient; import com.google.android.gms.auth.api.signin.GoogleSignInOptions; import com.google.android.gms.common.api.Scope; /** * A wrapper object that calls static method in GoogleSignIn. * *

Because GoogleSignIn uses static method mostly, which is hard for unit testing. We use this * wrapper class to use instance method which calls the corresponding GoogleSignIn static methods. * *

Warning! This class should stay true that each method calls a GoogleSignIn static method with * the same name and same parameters. */ public class GoogleSignInWrapper { GoogleSignInClient getClient(Context context, GoogleSignInOptions options) { return GoogleSignIn.getClient(context, options); } GoogleSignInAccount getLastSignedInAccount(Context context) { return GoogleSignIn.getLastSignedInAccount(context); } boolean hasPermissions(GoogleSignInAccount account, Scope scope) { return GoogleSignIn.hasPermissions(account, scope); } void requestPermissions( Activity activity, int requestCode, GoogleSignInAccount account, Scope[] scopes) { GoogleSignIn.requestPermissions(activity, requestCode, account, scopes); } } ================================================ FILE: packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInTest.java ================================================ // Copyright 2013 The Flutter 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.googlesignin; import static org.mockito.ArgumentMatchers.any; 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 android.content.Context; import android.content.Intent; import android.content.res.Resources; import com.google.android.gms.auth.api.signin.GoogleSignInAccount; import com.google.android.gms.auth.api.signin.GoogleSignInClient; import com.google.android.gms.auth.api.signin.GoogleSignInOptions; import com.google.android.gms.common.api.ApiException; import com.google.android.gms.common.api.CommonStatusCodes; import com.google.android.gms.common.api.Scope; import com.google.android.gms.common.api.Status; import com.google.android.gms.tasks.Task; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.PluginRegistry; import java.util.Collections; import java.util.HashMap; import java.util.List; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; public class GoogleSignInTest { @Mock Context mockContext; @Mock Resources mockResources; @Mock Activity mockActivity; @Mock PluginRegistry.Registrar mockRegistrar; @Mock BinaryMessenger mockMessenger; @Spy MethodChannel.Result result; @Mock GoogleSignInWrapper mockGoogleSignIn; @Mock GoogleSignInAccount account; @Mock GoogleSignInClient mockClient; @Mock Task mockSignInTask; private GoogleSignInPlugin plugin; @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mockRegistrar.messenger()).thenReturn(mockMessenger); when(mockRegistrar.context()).thenReturn(mockContext); when(mockRegistrar.activity()).thenReturn(mockActivity); when(mockContext.getResources()).thenReturn(mockResources); plugin = new GoogleSignInPlugin(); plugin.initInstance(mockRegistrar.messenger(), mockRegistrar.context(), mockGoogleSignIn); plugin.setUpRegistrar(mockRegistrar); } @Test public void requestScopes_ResultErrorIfAccountIsNull() { MethodCall methodCall = new MethodCall("requestScopes", null); when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(null); plugin.onMethodCall(methodCall, result); verify(result).error("sign_in_required", "No account to grant scopes.", null); } @Test public void requestScopes_ResultTrueIfAlreadyGranted() { HashMap> arguments = new HashMap<>(); arguments.put("scopes", Collections.singletonList("requestedScope")); MethodCall methodCall = new MethodCall("requestScopes", arguments); Scope requestedScope = new Scope("requestedScope"); when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(account); when(account.getGrantedScopes()).thenReturn(Collections.singleton(requestedScope)); when(mockGoogleSignIn.hasPermissions(account, requestedScope)).thenReturn(true); plugin.onMethodCall(methodCall, result); verify(result).success(true); } @Test public void requestScopes_RequestsPermissionIfNotGranted() { HashMap> arguments = new HashMap<>(); arguments.put("scopes", Collections.singletonList("requestedScope")); MethodCall methodCall = new MethodCall("requestScopes", arguments); Scope requestedScope = new Scope("requestedScope"); when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(account); when(account.getGrantedScopes()).thenReturn(Collections.singleton(requestedScope)); when(mockGoogleSignIn.hasPermissions(account, requestedScope)).thenReturn(false); plugin.onMethodCall(methodCall, result); verify(mockGoogleSignIn) .requestPermissions(mockActivity, 53295, account, new Scope[] {requestedScope}); } @Test public void requestScopes_ReturnsFalseIfPermissionDenied() { HashMap> arguments = new HashMap<>(); arguments.put("scopes", Collections.singletonList("requestedScope")); MethodCall methodCall = new MethodCall("requestScopes", arguments); Scope requestedScope = new Scope("requestedScope"); ArgumentCaptor captor = ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener.class); verify(mockRegistrar).addActivityResultListener(captor.capture()); PluginRegistry.ActivityResultListener listener = captor.getValue(); when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(account); when(account.getGrantedScopes()).thenReturn(Collections.singleton(requestedScope)); when(mockGoogleSignIn.hasPermissions(account, requestedScope)).thenReturn(false); plugin.onMethodCall(methodCall, result); listener.onActivityResult( GoogleSignInPlugin.Delegate.REQUEST_CODE_REQUEST_SCOPE, Activity.RESULT_CANCELED, new Intent()); verify(result).success(false); } @Test public void requestScopes_ReturnsTrueIfPermissionGranted() { HashMap> arguments = new HashMap<>(); arguments.put("scopes", Collections.singletonList("requestedScope")); MethodCall methodCall = new MethodCall("requestScopes", arguments); Scope requestedScope = new Scope("requestedScope"); ArgumentCaptor captor = ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener.class); verify(mockRegistrar).addActivityResultListener(captor.capture()); PluginRegistry.ActivityResultListener listener = captor.getValue(); when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(account); when(account.getGrantedScopes()).thenReturn(Collections.singleton(requestedScope)); when(mockGoogleSignIn.hasPermissions(account, requestedScope)).thenReturn(false); plugin.onMethodCall(methodCall, result); listener.onActivityResult( GoogleSignInPlugin.Delegate.REQUEST_CODE_REQUEST_SCOPE, Activity.RESULT_OK, new Intent()); verify(result).success(true); } @Test public void requestScopes_mayBeCalledRepeatedly_ifAlreadyGranted() { HashMap> arguments = new HashMap<>(); arguments.put("scopes", Collections.singletonList("requestedScope")); MethodCall methodCall = new MethodCall("requestScopes", arguments); Scope requestedScope = new Scope("requestedScope"); ArgumentCaptor captor = ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener.class); verify(mockRegistrar).addActivityResultListener(captor.capture()); PluginRegistry.ActivityResultListener listener = captor.getValue(); when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(account); when(account.getGrantedScopes()).thenReturn(Collections.singleton(requestedScope)); when(mockGoogleSignIn.hasPermissions(account, requestedScope)).thenReturn(false); plugin.onMethodCall(methodCall, result); listener.onActivityResult( GoogleSignInPlugin.Delegate.REQUEST_CODE_REQUEST_SCOPE, Activity.RESULT_OK, new Intent()); plugin.onMethodCall(methodCall, result); listener.onActivityResult( GoogleSignInPlugin.Delegate.REQUEST_CODE_REQUEST_SCOPE, Activity.RESULT_OK, new Intent()); verify(result, times(2)).success(true); } @Test public void requestScopes_mayBeCalledRepeatedly_ifNotSignedIn() { HashMap> arguments = new HashMap<>(); arguments.put("scopes", Collections.singletonList("requestedScope")); MethodCall methodCall = new MethodCall("requestScopes", arguments); Scope requestedScope = new Scope("requestedScope"); ArgumentCaptor captor = ArgumentCaptor.forClass(PluginRegistry.ActivityResultListener.class); verify(mockRegistrar).addActivityResultListener(captor.capture()); PluginRegistry.ActivityResultListener listener = captor.getValue(); when(mockGoogleSignIn.getLastSignedInAccount(mockContext)).thenReturn(null); plugin.onMethodCall(methodCall, result); listener.onActivityResult( GoogleSignInPlugin.Delegate.REQUEST_CODE_REQUEST_SCOPE, Activity.RESULT_OK, new Intent()); plugin.onMethodCall(methodCall, result); listener.onActivityResult( GoogleSignInPlugin.Delegate.REQUEST_CODE_REQUEST_SCOPE, Activity.RESULT_OK, new Intent()); verify(result, times(2)).error("sign_in_required", "No account to grant scopes.", null); } @Test(expected = IllegalStateException.class) public void signInThrowsWithoutActivity() { final GoogleSignInPlugin plugin = new GoogleSignInPlugin(); plugin.initInstance( mock(BinaryMessenger.class), mock(Context.class), mock(GoogleSignInWrapper.class)); plugin.onMethodCall(new MethodCall("signIn", null), null); } @Test public void signInSilentlyThatImmediatelyCompletesWithoutResultFinishesWithError() throws ApiException { final String clientId = "fakeClientId"; MethodCall methodCall = buildInitMethodCall(clientId, null); initAndAssertServerClientId(methodCall, clientId); ApiException exception = new ApiException(new Status(CommonStatusCodes.SIGN_IN_REQUIRED, "Error text")); when(mockClient.silentSignIn()).thenReturn(mockSignInTask); when(mockSignInTask.isComplete()).thenReturn(true); when(mockSignInTask.getResult(ApiException.class)).thenThrow(exception); plugin.onMethodCall(new MethodCall("signInSilently", null), result); verify(result) .error( "sign_in_required", "com.google.android.gms.common.api.ApiException: 4: Error text", null); } @Test public void init_LoadsServerClientIdFromResources() { final String packageName = "fakePackageName"; final String serverClientId = "fakeServerClientId"; final int resourceId = 1; MethodCall methodCall = buildInitMethodCall(null, null); when(mockContext.getPackageName()).thenReturn(packageName); when(mockResources.getIdentifier("default_web_client_id", "string", packageName)) .thenReturn(resourceId); when(mockContext.getString(resourceId)).thenReturn(serverClientId); initAndAssertServerClientId(methodCall, serverClientId); } @Test public void init_InterpretsClientIdAsServerClientId() { final String clientId = "fakeClientId"; MethodCall methodCall = buildInitMethodCall(clientId, null); initAndAssertServerClientId(methodCall, clientId); } @Test public void init_ForwardsServerClientId() { final String serverClientId = "fakeServerClientId"; MethodCall methodCall = buildInitMethodCall(null, serverClientId); initAndAssertServerClientId(methodCall, serverClientId); } @Test public void init_IgnoresClientIdIfServerClientIdIsProvided() { final String clientId = "fakeClientId"; final String serverClientId = "fakeServerClientId"; MethodCall methodCall = buildInitMethodCall(clientId, serverClientId); initAndAssertServerClientId(methodCall, serverClientId); } @Test public void init_PassesForceCodeForRefreshTokenFalseWithServerClientIdParameter() { MethodCall methodCall = buildInitMethodCall("fakeClientId", "fakeServerClientId", false); initAndAssertForceCodeForRefreshToken(methodCall, false); } @Test public void init_PassesForceCodeForRefreshTokenTrueWithServerClientIdParameter() { MethodCall methodCall = buildInitMethodCall("fakeClientId", "fakeServerClientId", true); initAndAssertForceCodeForRefreshToken(methodCall, true); } @Test public void init_PassesForceCodeForRefreshTokenFalseWithServerClientIdFromResources() { final String packageName = "fakePackageName"; final String serverClientId = "fakeServerClientId"; final int resourceId = 1; MethodCall methodCall = buildInitMethodCall(null, null, false); when(mockContext.getPackageName()).thenReturn(packageName); when(mockResources.getIdentifier("default_web_client_id", "string", packageName)) .thenReturn(resourceId); when(mockContext.getString(resourceId)).thenReturn(serverClientId); initAndAssertForceCodeForRefreshToken(methodCall, false); } @Test public void init_PassesForceCodeForRefreshTokenTrueWithServerClientIdFromResources() { final String packageName = "fakePackageName"; final String serverClientId = "fakeServerClientId"; final int resourceId = 1; MethodCall methodCall = buildInitMethodCall(null, null, true); when(mockContext.getPackageName()).thenReturn(packageName); when(mockResources.getIdentifier("default_web_client_id", "string", packageName)) .thenReturn(resourceId); when(mockContext.getString(resourceId)).thenReturn(serverClientId); initAndAssertForceCodeForRefreshToken(methodCall, true); } public void initAndAssertServerClientId(MethodCall methodCall, String serverClientId) { ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(GoogleSignInOptions.class); when(mockGoogleSignIn.getClient(any(Context.class), optionsCaptor.capture())) .thenReturn(mockClient); plugin.onMethodCall(methodCall, result); verify(result).success(null); Assert.assertEquals(serverClientId, optionsCaptor.getValue().getServerClientId()); } public void initAndAssertForceCodeForRefreshToken( MethodCall methodCall, boolean forceCodeForRefreshToken) { ArgumentCaptor optionsCaptor = ArgumentCaptor.forClass(GoogleSignInOptions.class); when(mockGoogleSignIn.getClient(any(Context.class), optionsCaptor.capture())) .thenReturn(mockClient); plugin.onMethodCall(methodCall, result); verify(result).success(null); Assert.assertEquals( forceCodeForRefreshToken, optionsCaptor.getValue().isForceCodeForRefreshToken()); } private static MethodCall buildInitMethodCall(String clientId, String serverClientId) { return buildInitMethodCall( "SignInOption.standard", Collections.emptyList(), clientId, serverClientId, false); } private static MethodCall buildInitMethodCall( String clientId, String serverClientId, boolean forceCodeForRefreshToken) { return buildInitMethodCall( "SignInOption.standard", Collections.emptyList(), clientId, serverClientId, forceCodeForRefreshToken); } private static MethodCall buildInitMethodCall( String signInOption, List scopes, String clientId, String serverClientId, boolean forceCodeForRefreshToken) { HashMap arguments = new HashMap<>(); arguments.put("signInOption", signInOption); arguments.put("scopes", scopes); if (clientId != null) { arguments.put("clientId", clientId); } if (serverClientId != null) { arguments.put("serverClientId", serverClientId); } arguments.put("forceCodeForRefreshToken", forceCodeForRefreshToken); return new MethodCall("init", arguments); } } ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/README.md ================================================ # 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. ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 lintOptions { disable 'InvalidPackage' } defaultConfig { applicationId "io.flutter.plugins.googlesigninexample" minSdkVersion 16 targetSdkVersion 28 multiDexEnabled true versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } testOptions { unitTests.returnDefaultValues = true } } flutter { source '../..' } dependencies { implementation 'com.google.android.gms:play-services-auth:16.0.1' testImplementation'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' api 'androidx.test:core:1.2.0' } ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/android/app/google-services.json ================================================ { "project_info": { "project_number": "479882132969", "firebase_url": "https://my-flutter-proj.firebaseio.com", "project_id": "my-flutter-proj", "storage_bucket": "my-flutter-proj.appspot.com" }, "client": [ { "client_info": { "mobilesdk_app_id": "1:479882132969:android:c73fd19ff7e2c0be", "android_client_info": { "package_name": "io.flutter.plugins.cameraexample" } }, "oauth_client": [ { "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { "current_key": "AIzaSyCrZz9T0Pg0rDnpxfNuPBrOxGhXskfebXs" } ], "services": { "analytics_service": { "status": 1 }, "appinvite_service": { "status": 1, "other_platform_oauth_client": [] }, "ads_service": { "status": 2 } } }, { "client_info": { "mobilesdk_app_id": "1:479882132969:android:632cdf3fc0a17139", "android_client_info": { "package_name": "io.flutter.plugins.firebasedynamiclinksexample" } }, "oauth_client": [ { "client_id": "479882132969-32qusitiag53931ck80h121ajhlc5a7e.apps.googleusercontent.com", "client_type": 1, "android_info": { "package_name": "io.flutter.plugins.firebasedynamiclinksexample", "certificate_hash": "e733b7a303250b63e06de6f7c9767c517d69cfa0" } }, { "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { "current_key": "AIzaSyCrZz9T0Pg0rDnpxfNuPBrOxGhXskfebXs" } ], "services": { "analytics_service": { "status": 1 }, "appinvite_service": { "status": 2, "other_platform_oauth_client": [ { "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", "client_type": 3 }, { "client_id": "479882132969-gjp4e63ogu2h6guttj2ie6t3f10ic7i8.apps.googleusercontent.com", "client_type": 2, "ios_info": { "bundle_id": "io.flutter.plugins.firebaseMlVisionExample" } } ] }, "ads_service": { "status": 2 } } }, { "client_info": { "mobilesdk_app_id": "1:479882132969:android:ae50362b4bc06086", "android_client_info": { "package_name": "io.flutter.plugins.firebasemlvisionexample" } }, "oauth_client": [ { "client_id": "479882132969-9pp74fkgmtvt47t9rikc1p861v7n85tn.apps.googleusercontent.com", "client_type": 1, "android_info": { "package_name": "io.flutter.plugins.firebasemlvisionexample", "certificate_hash": "e733b7a303250b63e06de6f7c9767c517d69cfa0" } }, { "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { "current_key": "AIzaSyCrZz9T0Pg0rDnpxfNuPBrOxGhXskfebXs" } ], "services": { "analytics_service": { "status": 1 }, "appinvite_service": { "status": 2, "other_platform_oauth_client": [ { "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", "client_type": 3 }, { "client_id": "479882132969-gjp4e63ogu2h6guttj2ie6t3f10ic7i8.apps.googleusercontent.com", "client_type": 2, "ios_info": { "bundle_id": "io.flutter.plugins.firebaseMlVisionExample" } } ] }, "ads_service": { "status": 2 } } }, { "client_info": { "mobilesdk_app_id": "1:479882132969:android:215a22700e1b466b", "android_client_info": { "package_name": "io.flutter.plugins.firebaseperformanceexample" } }, "oauth_client": [ { "client_id": "479882132969-8h4kiv8m7ho4tvn6uuujsfcrf69unuf7.apps.googleusercontent.com", "client_type": 1, "android_info": { "package_name": "io.flutter.plugins.firebaseperformanceexample", "certificate_hash": "e733b7a303250b63e06de6f7c9767c517d69cfa0" } }, { "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { "current_key": "AIzaSyCrZz9T0Pg0rDnpxfNuPBrOxGhXskfebXs" } ], "services": { "analytics_service": { "status": 1 }, "appinvite_service": { "status": 2, "other_platform_oauth_client": [ { "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", "client_type": 3 }, { "client_id": "479882132969-gjp4e63ogu2h6guttj2ie6t3f10ic7i8.apps.googleusercontent.com", "client_type": 2, "ios_info": { "bundle_id": "io.flutter.plugins.firebaseMlVisionExample" } } ] }, "ads_service": { "status": 2 } } }, { "client_info": { "mobilesdk_app_id": "1:479882132969:android:5e9f1f89e134dc86", "android_client_info": { "package_name": "io.flutter.plugins.googlesigninexample" } }, "oauth_client": [ { "client_id": "479882132969-90ml692hkonp587sl0v0rurmnvkekgrg.apps.googleusercontent.com", "client_type": 1, "android_info": { "package_name": "io.flutter.plugins.googlesigninexample", "certificate_hash": "e733b7a303250b63e06de6f7c9767c517d69cfa0" } }, { "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", "client_type": 3 } ], "api_key": [ { "current_key": "AIzaSyCrZz9T0Pg0rDnpxfNuPBrOxGhXskfebXs" } ], "services": { "analytics_service": { "status": 1 }, "appinvite_service": { "status": 2, "other_platform_oauth_client": [ { "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", "client_type": 3 }, { "client_id": "479882132969-gjp4e63ogu2h6guttj2ie6t3f10ic7i8.apps.googleusercontent.com", "client_type": 2, "ios_info": { "bundle_id": "io.flutter.plugins.firebaseMlVisionExample" } } ] }, "ads_service": { "status": 2 } } } ], "configuration_version": "1" } ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/android/app/src/androidTest/java/io/flutter/plugins/googlesigninexample/FlutterActivityTest.java ================================================ // Copyright 2013 The Flutter 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.googlesigninexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class FlutterActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/android/app/src/androidTest/java/io/flutter/plugins/googlesigninexample/GoogleSignInTest.java ================================================ // Copyright 2013 The Flutter 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.googlesigninexample; import static org.junit.Assert.assertTrue; import androidx.test.core.app.ActivityScenario; import io.flutter.plugins.googlesignin.GoogleSignInPlugin; import org.junit.Test; public class GoogleSignInTest { @Test public void googleSignInPluginIsAdded() { final ActivityScenario scenario = ActivityScenario.launch(GoogleSignInTestActivity.class); scenario.onActivity( activity -> { assertTrue(activity.engine.getPlugins().has(GoogleSignInPlugin.class)); }); } } ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/android/app/src/main/java/io/flutter/plugins/.gitignore ================================================ GeneratedPluginRegistrant.java ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/GoogleSignInTestActivity.java ================================================ // Copyright 2013 The Flutter 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.googlesigninexample; import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; // Makes the FlutterEngine accessible for testing. public class GoogleSignInTestActivity extends FlutterActivity { public FlutterEngine engine; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); engine = flutterEngine; } } ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/android/app/src/main/res/values/strings.xml ================================================ YOUR_WEB_CLIENT_ID ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withInputStream { stream -> plugins.load(stream) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/integration_test/google_sign_in_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'package:integration_test/integration_test.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('Can initialize the plugin', (WidgetTester tester) async { final GoogleSignInPlatform signIn = GoogleSignInPlatform.instance; expect(signIn, isNotNull); }); testWidgets('Method channel handler is present', (WidgetTester tester) async { // isSignedIn can be called without initialization, so use it to validate // that the native method handler is present (e.g., that the channel name // is correct). final GoogleSignInPlatform signIn = GoogleSignInPlatform.instance; await expectLater(signIn.isSignedIn(), completes); }); } ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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, avoid_print import 'dart:async'; import 'dart:convert' show json; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'package:http/http.dart' as http; void main() { runApp( const MaterialApp( title: 'Google Sign In', home: SignInDemo(), ), ); } class SignInDemo extends StatefulWidget { const SignInDemo({Key? key}) : super(key: key); @override State createState() => SignInDemoState(); } class SignInDemoState extends State { GoogleSignInUserData? _currentUser; String _contactText = ''; // Future that completes when `init` has completed on the sign in instance. Future? _initialization; @override void initState() { super.initState(); _signIn(); } Future _ensureInitialized() { return _initialization ??= GoogleSignInPlatform.instance.initWithParams(const SignInInitParameters( scopes: [ 'email', 'https://www.googleapis.com/auth/contacts.readonly', ], )) ..catchError((dynamic _) { _initialization = null; }); } void _setUser(GoogleSignInUserData? user) { setState(() { _currentUser = user; if (user != null) { _handleGetContact(user); } }); } Future _signIn() async { await _ensureInitialized(); final GoogleSignInUserData? newUser = await GoogleSignInPlatform.instance.signInSilently(); _setUser(newUser); } Future> _getAuthHeaders() async { final GoogleSignInUserData? user = _currentUser; if (user == null) { throw StateError('No user signed in'); } final GoogleSignInTokenData response = await GoogleSignInPlatform.instance.getTokens( email: user.email, shouldRecoverAuth: true, ); return { 'Authorization': 'Bearer ${response.accessToken}', // TODO(kevmoo): Use the correct value once it's available. // See https://github.com/flutter/flutter/issues/80905 'X-Goog-AuthUser': '0', }; } Future _handleGetContact(GoogleSignInUserData user) async { setState(() { _contactText = 'Loading contact info...'; }); final http.Response response = await http.get( Uri.parse('https://people.googleapis.com/v1/people/me/connections' '?requestMask.includeField=person.names'), headers: await _getAuthHeaders(), ); if (response.statusCode != 200) { setState(() { _contactText = 'People API gave a ${response.statusCode} ' 'response. Check logs for details.'; }); print('People API ${response.statusCode} response: ${response.body}'); return; } final Map data = json.decode(response.body) as Map; final int contactCount = (data['connections'] as List?)?.length ?? 0; setState(() { _contactText = '$contactCount contacts found'; }); } Future _handleSignIn() async { try { await _ensureInitialized(); _setUser(await GoogleSignInPlatform.instance.signIn()); } catch (error) { final bool canceled = error is PlatformException && error.code == 'sign_in_canceled'; if (!canceled) { print(error); } } } Future _handleSignOut() async { await _ensureInitialized(); await GoogleSignInPlatform.instance.disconnect(); } Widget _buildBody() { final GoogleSignInUserData? user = _currentUser; if (user != null) { return Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ ListTile( title: Text(user.displayName ?? ''), subtitle: Text(user.email), ), const Text('Signed in successfully.'), Text(_contactText), ElevatedButton( onPressed: _handleSignOut, child: const Text('SIGN OUT'), ), ElevatedButton( child: const Text('REFRESH'), onPressed: () => _handleGetContact(user), ), ], ); } else { return Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ const Text('You are not currently signed in.'), ElevatedButton( onPressed: _handleSignIn, child: const Text('SIGN IN'), ), ], ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Google Sign In'), ), body: ConstrainedBox( constraints: const BoxConstraints.expand(), child: _buildBody(), )); } } ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/pubspec.yaml ================================================ name: google_sign_in_example description: Example of Google Sign-In plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter google_sign_in_android: # When depending on this package from a real application you should use: # google_sign_in_android: ^x.y.z # See https://dart.dev/tools/pub/dependencies#version-constraints # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ google_sign_in_platform_interface: ^2.2.0 http: ^0.13.0 dev_dependencies: espresso: ^0.2.0 flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/google_sign_in/google_sign_in_android/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/google_sign_in/google_sign_in_android/lib/google_sign_in_android.dart ================================================ // Copyright 2013 The Flutter 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' show visibleForTesting; import 'package:flutter/services.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'src/utils.dart'; /// Android implementation of [GoogleSignInPlatform]. class GoogleSignInAndroid extends GoogleSignInPlatform { /// This is only exposed for test purposes. It shouldn't be used by clients of /// the plugin as it may break or change at any time. @visibleForTesting MethodChannel channel = const MethodChannel('plugins.flutter.io/google_sign_in_android'); /// Registers this class as the default instance of [GoogleSignInPlatform]. static void registerWith() { GoogleSignInPlatform.instance = GoogleSignInAndroid(); } @override Future init({ List scopes = const [], SignInOption signInOption = SignInOption.standard, String? hostedDomain, String? clientId, }) { return initWithParams(SignInInitParameters( signInOption: signInOption, scopes: scopes, hostedDomain: hostedDomain, clientId: clientId, )); } @override Future initWithParams(SignInInitParameters params) { return channel.invokeMethod('init', { 'signInOption': params.signInOption.toString(), 'scopes': params.scopes, 'hostedDomain': params.hostedDomain, 'clientId': params.clientId, 'serverClientId': params.serverClientId, 'forceCodeForRefreshToken': params.forceCodeForRefreshToken, }); } @override Future signInSilently() { return channel .invokeMapMethod('signInSilently') .then(getUserDataFromMap); } @override Future signIn() { return channel .invokeMapMethod('signIn') .then(getUserDataFromMap); } @override Future getTokens( {required String email, bool? shouldRecoverAuth = true}) { return channel .invokeMapMethod('getTokens', { 'email': email, 'shouldRecoverAuth': shouldRecoverAuth, }).then((Map? result) => getTokenDataFromMap(result!)); } @override Future signOut() { return channel.invokeMapMethod('signOut'); } @override Future disconnect() { return channel.invokeMapMethod('disconnect'); } @override Future isSignedIn() async { return (await channel.invokeMethod('isSignedIn'))!; } @override Future clearAuthCache({String? token}) { return channel.invokeMethod( 'clearAuthCache', {'token': token}, ); } @override Future requestScopes(List scopes) async { return (await channel.invokeMethod( 'requestScopes', >{'scopes': scopes}, ))!; } } ================================================ FILE: packages/google_sign_in/google_sign_in_android/lib/src/utils.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; /// Converts user data coming from native code into the proper platform interface type. GoogleSignInUserData? getUserDataFromMap(Map? data) { if (data == null) { return null; } return GoogleSignInUserData( email: data['email']! as String, id: data['id']! as String, displayName: data['displayName'] as String?, photoUrl: data['photoUrl'] as String?, idToken: data['idToken'] as String?, serverAuthCode: data['serverAuthCode'] as String?); } /// Converts token data coming from native code into the proper platform interface type. GoogleSignInTokenData getTokenDataFromMap(Map data) { return GoogleSignInTokenData( idToken: data['idToken'] as String?, accessToken: data['accessToken'] as String?, serverAuthCode: data['serverAuthCode'] as String?, ); } ================================================ FILE: packages/google_sign_in/google_sign_in_android/pubspec.yaml ================================================ 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.6 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: google_sign_in platforms: android: dartPluginClass: GoogleSignInAndroid package: io.flutter.plugins.googlesignin pluginClass: GoogleSignInPlugin dependencies: flutter: sdk: flutter google_sign_in_platform_interface: ^2.2.0 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter # The example deliberately includes limited-use secrets. false_secrets: - /example/android/app/google-services.json - /example/lib/main.dart ================================================ FILE: packages/google_sign_in/google_sign_in_android/test/google_sign_in_android_test.dart ================================================ // Copyright 2013 The Flutter 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:google_sign_in_android/google_sign_in_android.dart'; import 'package:google_sign_in_android/src/utils.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; const Map kUserData = { 'email': 'john.doe@gmail.com', 'id': '8162538176523816253123', 'photoUrl': 'https://lh5.googleusercontent.com/photo.jpg', 'displayName': 'John Doe', 'idToken': '123', 'serverAuthCode': '789', }; const Map kTokenData = { 'idToken': '123', 'accessToken': '456', 'serverAuthCode': '789', }; const Map kDefaultResponses = { 'init': null, 'signInSilently': kUserData, 'signIn': kUserData, 'signOut': null, 'disconnect': null, 'isSignedIn': true, 'getTokens': kTokenData, 'requestScopes': true, }; final GoogleSignInUserData? kUser = getUserDataFromMap(kUserData); final GoogleSignInTokenData kToken = getTokenDataFromMap(kTokenData as Map); void main() { TestWidgetsFlutterBinding.ensureInitialized(); final GoogleSignInAndroid googleSignIn = GoogleSignInAndroid(); final MethodChannel channel = googleSignIn.channel; final List log = []; late Map responses; // Some tests mutate some kDefaultResponses setUp(() { responses = Map.from(kDefaultResponses); _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( channel, (MethodCall methodCall) { log.add(methodCall); final dynamic response = responses[methodCall.method]; if (response != null && response is Exception) { return Future.error('$response'); } return Future.value(response); }, ); log.clear(); }); test('registered instance', () { GoogleSignInAndroid.registerWith(); expect(GoogleSignInPlatform.instance, isA()); }); test('signInSilently transforms platform data to GoogleSignInUserData', () async { final dynamic response = await googleSignIn.signInSilently(); expect(response, kUser); }); test('signInSilently Exceptions -> throws', () async { responses['signInSilently'] = Exception('Not a user'); expect(googleSignIn.signInSilently(), throwsA(isInstanceOf())); }); test('signIn transforms platform data to GoogleSignInUserData', () async { final dynamic response = await googleSignIn.signIn(); expect(response, kUser); }); test('signIn Exceptions -> throws', () async { responses['signIn'] = Exception('Not a user'); expect(googleSignIn.signIn(), throwsA(isInstanceOf())); }); test('getTokens transforms platform data to GoogleSignInTokenData', () async { final dynamic response = await googleSignIn.getTokens( email: 'example@example.com', shouldRecoverAuth: false); expect(response, kToken); expect( log[0], isMethodCall('getTokens', arguments: { 'email': 'example@example.com', 'shouldRecoverAuth': false, })); }); test('Other functions pass through arguments to the channel', () async { final Map tests = { () { googleSignIn.init( hostedDomain: 'example.com', scopes: ['two', 'scopes'], signInOption: SignInOption.games, clientId: 'fakeClientId'); }: isMethodCall('init', arguments: { 'hostedDomain': 'example.com', 'scopes': ['two', 'scopes'], 'signInOption': 'SignInOption.games', 'clientId': 'fakeClientId', 'serverClientId': null, 'forceCodeForRefreshToken': false, }), () { googleSignIn.initWithParams(const SignInInitParameters( hostedDomain: 'example.com', scopes: ['two', 'scopes'], signInOption: SignInOption.games, clientId: 'fakeClientId', serverClientId: 'fakeServerClientId', forceCodeForRefreshToken: true)); }: isMethodCall('init', arguments: { 'hostedDomain': 'example.com', 'scopes': ['two', 'scopes'], 'signInOption': 'SignInOption.games', 'clientId': 'fakeClientId', 'serverClientId': 'fakeServerClientId', 'forceCodeForRefreshToken': true, }), () { googleSignIn.getTokens( email: 'example@example.com', shouldRecoverAuth: false); }: isMethodCall('getTokens', arguments: { 'email': 'example@example.com', 'shouldRecoverAuth': false, }), () { googleSignIn.clearAuthCache(token: 'abc'); }: isMethodCall('clearAuthCache', arguments: { 'token': 'abc', }), () { googleSignIn.requestScopes(['newScope', 'anotherScope']); }: isMethodCall('requestScopes', arguments: { 'scopes': ['newScope', 'anotherScope'], }), googleSignIn.signOut: isMethodCall('signOut', arguments: null), googleSignIn.disconnect: isMethodCall('disconnect', arguments: null), googleSignIn.isSignedIn: isMethodCall('isSignedIn', arguments: null), }; for (final void Function() f in tests.keys) { f(); } expect(log, tests.values); }); } /// 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; ================================================ FILE: packages/google_sign_in/google_sign_in_ios/AUTHORS ================================================ # 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> Twin Sun, LLC ================================================ FILE: packages/google_sign_in/google_sign_in_ios/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 5.5.1 * Fixes passing `serverClientId` via the channelled `init` call * Updates minimum Flutter version to 2.10. ## 5.5.0 * Adds override for `GoogleSignInPlatform.initWithParams`. ## 5.4.0 * Adds support for `serverClientId` configuration option. * Makes `Google-Services.info` file optional. ## 5.3.1 * Suppresses warnings for pre-iOS-13 codepaths. ## 5.3.0 * Supports arm64 iOS simulators by increasing GoogleSignIn dependency to version 6.2. ## 5.2.7 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 5.2.6 * Switches to an internal method channel, rather than the default. ## 5.2.5 * Splits from `google_sign_in` as a federated implementation. ================================================ FILE: packages/google_sign_in/google_sign_in_ios/LICENSE ================================================ 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. ================================================ FILE: packages/google_sign_in/google_sign_in_ios/README.md ================================================ # google\_sign\_in\_ios The iOS implementation of [`google_sign_in`][1]. ## Usage This package is [endorsed][2], which means you can simply use `google_sign_in` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/google_sign_in [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/README.md ================================================ # 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. ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/integration_test/google_sign_in_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'package:integration_test/integration_test.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('Can initialize the plugin', (WidgetTester tester) async { final GoogleSignInPlatform signIn = GoogleSignInPlatform.instance; expect(signIn, isNotNull); }); testWidgets('Method channel handler is present', (WidgetTester tester) async { // isSignedIn can be called without initialization, so use it to validate // that the native method handler is present (e.g., that the channel name // is correct). final GoogleSignInPlatform signIn = GoogleSignInPlatform.instance; await expectLater(signIn.isSignedIn(), completes); }); } ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 UIRequiredDeviceCapabilities arm64 MinimumOSVersion 9.0 ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/Flutter/Debug.xcconfig ================================================ #include "Generated.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/Flutter/Release.xcconfig ================================================ #include "Generated.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/GoogleSignInPluginTest/Info.plist ================================================ 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 ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) # Suppress warnings from transitive dependencies that cause analysis to fail. pod 'AppAuth', :inhibit_warnings => true pod 'GTMAppAuth', :inhibit_warnings => true flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths pod 'OCMock','3.5' end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/Runner/GoogleService-Info.plist ================================================ AD_UNIT_ID_FOR_BANNER_TEST ca-app-pub-3940256099942544/2934735716 AD_UNIT_ID_FOR_INTERSTITIAL_TEST ca-app-pub-3940256099942544/4411468910 CLIENT_ID 479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com REVERSED_CLIENT_ID com.googleusercontent.apps.479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u ANDROID_CLIENT_ID 479882132969-jie8r1me6dsra60pal6ejaj8dgme3tg0.apps.googleusercontent.com API_KEY AIzaSyBECOwLTAN6PU4Aet1b2QLGIb3kRK8Xjew GCM_SENDER_ID 479882132969 PLIST_VERSION 1 BUNDLE_ID io.flutter.plugins.googleSignInExample PROJECT_ID my-flutter-proj STORAGE_BUCKET my-flutter-proj.appspot.com IS_ADS_ENABLED IS_ANALYTICS_ENABLED IS_APPINVITE_ENABLED IS_GCM_ENABLED IS_SIGNIN_ENABLED GOOGLE_APP_ID 1:479882132969:ios:2643f950e0a0da08 DATABASE_URL https://my-flutter-proj.firebaseio.com SERVER_CLIENT_ID YOUR_SERVER_CLIENT_ID ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName Google Sign-In Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName GoogleSignInExample CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleURLTypes CFBundleTypeRole Editor CFBundleURLSchemes com.googleusercontent.apps.479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance CADisableMinimumFrameDurationOnPhone ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 5C6F5A6E1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C6F5A6D1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m */; }; 7A303C2E1E89D76400B1F19E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */; }; 7ACDFB0E1E8944C400BE2D00 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7ACDFB0D1E8944C400BE2D00 /* AppFrameworkInfo.plist */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 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 */; }; C2FB9CBA01DB0A2DE5F31E12 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0263E28FA425D1CE928BDE15 /* libPods-Runner.a */; }; C56D3B06A42F3B35C1F47A43 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 18AD6475292B9C45B529DDC9 /* libPods-RunnerTests.a */; }; F76AC1A52666D0540040C8BC /* GoogleSignInTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F76AC1A42666D0540040C8BC /* GoogleSignInTests.m */; }; F76AC1B32666D0610040C8BC /* GoogleSignInUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F76AC1B22666D0610040C8BC /* GoogleSignInUITests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ F76AC1A72666D0540040C8BC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; F76AC1B52666D0610040C8BC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0263E28FA425D1CE928BDE15 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 18AD6475292B9C45B529DDC9 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 37E582FF620A90D0EB2C0851 /* 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 = ""; }; 45D93D4513839BFEA2AA74FE /* 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 = ""; }; 5A76713E622F06379AEDEBFA /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 5C6F5A6C1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 5C6F5A6D1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 7ACDFB0D1E8944C400BE2D00 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F582639B44581540871D9BB0 /* 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 = ""; }; F76AC1A22666D0540040C8BC /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F76AC1A42666D0540040C8BC /* GoogleSignInTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GoogleSignInTests.m; sourceTree = ""; }; F76AC1A62666D0540040C8BC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F76AC1B02666D0610040C8BC /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F76AC1B22666D0610040C8BC /* GoogleSignInUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GoogleSignInUITests.m; sourceTree = ""; }; F76AC1B42666D0610040C8BC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( C2FB9CBA01DB0A2DE5F31E12 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; F76AC19F2666D0540040C8BC /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( C56D3B06A42F3B35C1F47A43 /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; F76AC1AD2666D0610040C8BC /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 840012C8B5EDBCF56B0E4AC1 /* Pods */ = { isa = PBXGroup; children = ( 5A76713E622F06379AEDEBFA /* Pods-Runner.debug.xcconfig */, F582639B44581540871D9BB0 /* Pods-Runner.release.xcconfig */, 37E582FF620A90D0EB2C0851 /* Pods-RunnerTests.debug.xcconfig */, 45D93D4513839BFEA2AA74FE /* Pods-RunnerTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 7ACDFB0D1E8944C400BE2D00 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, F76AC1A32666D0540040C8BC /* RunnerTests */, F76AC1B12666D0610040C8BC /* RunnerUITests */, 97C146EF1CF9000F007C117D /* Products */, 840012C8B5EDBCF56B0E4AC1 /* Pods */, CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, F76AC1A22666D0540040C8BC /* RunnerTests.xctest */, F76AC1B02666D0610040C8BC /* RunnerUITests.xctest */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 5C6F5A6C1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.h */, 5C6F5A6D1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m */, 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = { isa = PBXGroup; children = ( 0263E28FA425D1CE928BDE15 /* libPods-Runner.a */, 18AD6475292B9C45B529DDC9 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; F76AC1A32666D0540040C8BC /* RunnerTests */ = { isa = PBXGroup; children = ( F76AC1A42666D0540040C8BC /* GoogleSignInTests.m */, F76AC1A62666D0540040C8BC /* Info.plist */, ); path = RunnerTests; sourceTree = ""; }; F76AC1B12666D0610040C8BC /* RunnerUITests */ = { isa = PBXGroup; children = ( F76AC1B22666D0610040C8BC /* GoogleSignInUITests.m */, F76AC1B42666D0610040C8BC /* Info.plist */, ); path = RunnerUITests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; F76AC1A12666D0540040C8BC /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = F76AC1AB2666D0540040C8BC /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( 27975964E48117AA65B1D6C7 /* [CP] Check Pods Manifest.lock */, F76AC19E2666D0540040C8BC /* Sources */, F76AC19F2666D0540040C8BC /* Frameworks */, F76AC1A02666D0540040C8BC /* Resources */, ); buildRules = ( ); dependencies = ( F76AC1A82666D0540040C8BC /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = F76AC1A22666D0540040C8BC /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; F76AC1AF2666D0610040C8BC /* RunnerUITests */ = { isa = PBXNativeTarget; buildConfigurationList = F76AC1B72666D0610040C8BC /* Build configuration list for PBXNativeTarget "RunnerUITests" */; buildPhases = ( F76AC1AC2666D0610040C8BC /* Sources */, F76AC1AD2666D0610040C8BC /* Frameworks */, F76AC1AE2666D0610040C8BC /* Resources */, ); buildRules = ( ); dependencies = ( F76AC1B62666D0610040C8BC /* PBXTargetDependency */, ); name = RunnerUITests; productName = RunnerUITests; productReference = F76AC1B02666D0610040C8BC /* RunnerUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; }; F76AC1A12666D0540040C8BC = { CreatedOnToolsVersion = 12.5; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; F76AC1AF2666D0610040C8BC = { CreatedOnToolsVersion = 12.5; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, F76AC1A12666D0540040C8BC /* RunnerTests */, F76AC1AF2666D0610040C8BC /* RunnerUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 7A303C2E1E89D76400B1F19E /* GoogleService-Info.plist in Resources */, 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 7ACDFB0E1E8944C400BE2D00 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; F76AC1A02666D0540040C8BC /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; F76AC1AE2666D0610040C8BC /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 27975964E48117AA65B1D6C7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n"; }; 532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/GoogleSignIn/GoogleSignIn.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\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"; }; AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 5C6F5A6E1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F76AC19E2666D0540040C8BC /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F76AC1A52666D0540040C8BC /* GoogleSignInTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F76AC1AC2666D0610040C8BC /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F76AC1B32666D0610040C8BC /* GoogleSignInUITests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ F76AC1A82666D0540040C8BC /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = F76AC1A72666D0540040C8BC /* PBXContainerItemProxy */; }; F76AC1B62666D0610040C8BC /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = F76AC1B52666D0610040C8BC /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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 = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.googleSignInExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.googleSignInExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; F76AC1A92666D0540040C8BC /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 37E582FF620A90D0EB2C0851 /* 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; }; F76AC1AA2666D0540040C8BC /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 45D93D4513839BFEA2AA74FE /* 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_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; }; F76AC1B82666D0610040C8BC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = Runner; }; name = Debug; }; F76AC1B92666D0610040C8BC /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = Runner; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F76AC1AB2666D0540040C8BC /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( F76AC1A92666D0540040C8BC /* Debug */, F76AC1AA2666D0540040C8BC /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F76AC1B72666D0610040C8BC /* Build configuration list for PBXNativeTarget "RunnerUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( F76AC1B82666D0610040C8BC /* Debug */, F76AC1B92666D0610040C8BC /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleSignInTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import Flutter; @import XCTest; @import google_sign_in_ios; @import google_sign_in_ios.Test; @import GoogleSignIn; // OCMock library doesn't generate a valid modulemap. #import @interface FLTGoogleSignInPluginTest : XCTestCase @property(strong, nonatomic) NSObject *mockBinaryMessenger; @property(strong, nonatomic) NSObject *mockPluginRegistrar; @property(strong, nonatomic) FLTGoogleSignInPlugin *plugin; @property(strong, nonatomic) id mockSignIn; @end @implementation FLTGoogleSignInPluginTest - (void)setUp { [super setUp]; self.mockBinaryMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); self.mockPluginRegistrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); id mockSignIn = OCMClassMock([GIDSignIn class]); self.mockSignIn = mockSignIn; OCMStub(self.mockPluginRegistrar.messenger).andReturn(self.mockBinaryMessenger); self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:mockSignIn]; [FLTGoogleSignInPlugin registerWithRegistrar:self.mockPluginRegistrar]; } - (void)testUnimplementedMethod { FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"bogus" arguments:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(id result) { XCTAssertEqualObjects(result, FlutterMethodNotImplemented); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testSignOut { FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signOut" arguments:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(id result) { XCTAssertNil(result); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; OCMVerify([self.mockSignIn signOut]); } - (void)testDisconnect { [[self.mockSignIn stub] disconnectWithCallback:[OCMArg invokeBlockWithArgs:[NSNull null], nil]]; FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"disconnect" arguments:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(NSDictionary *result) { XCTAssertEqualObjects(result, @{}); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testDisconnectIgnoresError { NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeHasNoAuthInKeychain userInfo:nil]; [[self.mockSignIn stub] disconnectWithCallback:[OCMArg invokeBlockWithArgs:error, nil]]; FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"disconnect" arguments:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(NSDictionary *result) { XCTAssertEqualObjects(result, @{}); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } #pragma mark - Init - (void)testInitNoClientIdError { // Init plugin without GoogleService-Info.plist. self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.mockSignIn withGoogleServiceProperties:nil]; // init call does not provide a clientId. FlutterMethodCall *initMethodCall = [FlutterMethodCall methodCallWithMethodName:@"init" arguments:@{}]; XCTestExpectation *initExpectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:initMethodCall result:^(FlutterError *result) { XCTAssertEqualObjects(result.code, @"missing-config"); [initExpectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testInitGoogleServiceInfoPlist { FlutterMethodCall *initMethodCall = [FlutterMethodCall methodCallWithMethodName:@"init" arguments:@{@"hostedDomain" : @"example.com"}]; XCTestExpectation *initExpectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:initMethodCall result:^(id result) { XCTAssertNil(result); [initExpectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; // Initialization values used in the next sign in request. FlutterMethodCall *signInMethodCall = [FlutterMethodCall methodCallWithMethodName:@"signIn" arguments:nil]; [self.plugin handleMethodCall:signInMethodCall result:^(id r){ }]; OCMVerify([self.mockSignIn signInWithConfiguration:[OCMArg checkWithBlock:^BOOL(GIDConfiguration *configuration) { // Set in example app GoogleService-Info.plist. return [configuration.hostedDomain isEqualToString:@"example.com"] && [configuration.clientID isEqualToString: @"479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com"] && [configuration.serverClientID isEqualToString:@"YOUR_SERVER_CLIENT_ID"]; }] presentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] hint:nil additionalScopes:OCMOCK_ANY callback:OCMOCK_ANY]); } - (void)testInitDynamicClientIdNullDomain { // Init plugin without GoogleService-Info.plist. self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.mockSignIn withGoogleServiceProperties:nil]; FlutterMethodCall *initMethodCall = [FlutterMethodCall methodCallWithMethodName:@"init" arguments:@{@"hostedDomain" : [NSNull null], @"clientId" : @"mockClientId"}]; XCTestExpectation *initExpectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:initMethodCall result:^(id result) { XCTAssertNil(result); [initExpectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; // Initialization values used in the next sign in request. FlutterMethodCall *signInMethodCall = [FlutterMethodCall methodCallWithMethodName:@"signIn" arguments:nil]; [self.plugin handleMethodCall:signInMethodCall result:^(id r){ }]; OCMVerify([self.mockSignIn signInWithConfiguration:[OCMArg checkWithBlock:^BOOL(GIDConfiguration *configuration) { return configuration.hostedDomain == nil && [configuration.clientID isEqualToString:@"mockClientId"]; }] presentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] hint:nil additionalScopes:OCMOCK_ANY callback:OCMOCK_ANY]); } - (void)testInitDynamicServerClientIdNullDomain { FlutterMethodCall *initMethodCall = [FlutterMethodCall methodCallWithMethodName:@"init" arguments:@{ @"hostedDomain" : [NSNull null], @"serverClientId" : @"mockServerClientId" }]; XCTestExpectation *initExpectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:initMethodCall result:^(id result) { XCTAssertNil(result); [initExpectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; // Initialization values used in the next sign in request. FlutterMethodCall *signInMethodCall = [FlutterMethodCall methodCallWithMethodName:@"signIn" arguments:nil]; [self.plugin handleMethodCall:signInMethodCall result:^(id r){ }]; OCMVerify([self.mockSignIn signInWithConfiguration:[OCMArg checkWithBlock:^BOOL(GIDConfiguration *configuration) { return configuration.hostedDomain == nil && [configuration.serverClientID isEqualToString:@"mockServerClientId"]; }] presentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] hint:nil additionalScopes:OCMOCK_ANY callback:OCMOCK_ANY]); } #pragma mark - Is signed in - (void)testIsNotSignedIn { OCMStub([self.mockSignIn hasPreviousSignIn]).andReturn(NO); FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"isSignedIn" arguments:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(NSNumber *result) { XCTAssertFalse(result.boolValue); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testIsSignedIn { OCMStub([self.mockSignIn hasPreviousSignIn]).andReturn(YES); FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"isSignedIn" arguments:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(NSNumber *result) { XCTAssertTrue(result.boolValue); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } #pragma mark - Sign in silently - (void)testSignInSilently { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([mockUser userID]).andReturn(@"mockID"); [[self.mockSignIn stub] restorePreviousSignInWithCallback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signInSilently" arguments:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(NSDictionary *result) { XCTAssertEqualObjects(result[@"displayName"], [NSNull null]); XCTAssertEqualObjects(result[@"email"], [NSNull null]); XCTAssertEqualObjects(result[@"id"], @"mockID"); XCTAssertEqualObjects(result[@"photoUrl"], [NSNull null]); XCTAssertEqualObjects(result[@"serverAuthCode"], [NSNull null]); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testSignInSilentlyWithError { NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeHasNoAuthInKeychain userInfo:nil]; [[self.mockSignIn stub] restorePreviousSignInWithCallback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signInSilently" arguments:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(FlutterError *result) { XCTAssertEqualObjects(result.code, @"sign_in_required"); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } #pragma mark - Sign in - (void)testSignIn { id mockUser = OCMClassMock([GIDGoogleUser class]); id mockUserProfile = OCMClassMock([GIDProfileData class]); OCMStub([mockUserProfile name]).andReturn(@"mockDisplay"); OCMStub([mockUserProfile email]).andReturn(@"mock@example.com"); OCMStub([mockUserProfile hasImage]).andReturn(YES); OCMStub([mockUserProfile imageURLWithDimension:1337]) .andReturn([NSURL URLWithString:@"https://example.com/profile.png"]); OCMStub([mockUser profile]).andReturn(mockUserProfile); OCMStub([mockUser userID]).andReturn(@"mockID"); OCMStub([mockUser serverAuthCode]).andReturn(@"mockAuthCode"); [[self.mockSignIn expect] signInWithConfiguration:[OCMArg checkWithBlock:^BOOL(GIDConfiguration *configuration) { return [configuration.clientID isEqualToString: @"479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com"]; }] presentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]] hint:nil additionalScopes:@[] callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signIn" arguments:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(NSDictionary *result) { XCTAssertEqualObjects(result[@"displayName"], @"mockDisplay"); XCTAssertEqualObjects(result[@"email"], @"mock@example.com"); XCTAssertEqualObjects(result[@"id"], @"mockID"); XCTAssertEqualObjects(result[@"photoUrl"], @"https://example.com/profile.png"); XCTAssertEqualObjects(result[@"serverAuthCode"], @"mockAuthCode"); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; OCMVerifyAll(self.mockSignIn); } - (void)testSignInWithInitializedScopes { FlutterMethodCall *initMethodCall = [FlutterMethodCall methodCallWithMethodName:@"init" arguments:@{@"scopes" : @[ @"initial1", @"initial2" ]}]; XCTestExpectation *initExpectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:initMethodCall result:^(id result) { XCTAssertNil(result); [initExpectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([mockUser userID]).andReturn(@"mockID"); [[self.mockSignIn expect] signInWithConfiguration:OCMOCK_ANY presentingViewController:OCMOCK_ANY hint:nil additionalScopes:[OCMArg checkWithBlock:^BOOL(NSArray *scopes) { return [[NSSet setWithArray:scopes] isEqualToSet:[NSSet setWithObjects:@"initial1", @"initial2", nil]]; }] callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signIn" arguments:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(NSDictionary *result) { XCTAssertEqualObjects(result[@"id"], @"mockID"); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; OCMVerifyAll(self.mockSignIn); } - (void)testSignInAlreadyGranted { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([mockUser userID]).andReturn(@"mockID"); [[self.mockSignIn stub] signInWithConfiguration:OCMOCK_ANY presentingViewController:OCMOCK_ANY hint:nil additionalScopes:OCMOCK_ANY callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeScopesAlreadyGranted userInfo:nil]; [[self.mockSignIn stub] addScopes:OCMOCK_ANY presentingViewController:OCMOCK_ANY callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signIn" arguments:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(NSDictionary *result) { XCTAssertEqualObjects(result[@"id"], @"mockID"); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testSignInError { NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeCanceled userInfo:nil]; [[self.mockSignIn stub] signInWithConfiguration:OCMOCK_ANY presentingViewController:OCMOCK_ANY hint:nil additionalScopes:OCMOCK_ANY callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signIn" arguments:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(FlutterError *result) { XCTAssertEqualObjects(result.code, @"sign_in_canceled"); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testSignInException { FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"signIn" arguments:nil]; OCMExpect([self.mockSignIn signInWithConfiguration:OCMOCK_ANY presentingViewController:OCMOCK_ANY hint:OCMOCK_ANY additionalScopes:OCMOCK_ANY callback:OCMOCK_ANY]) .andThrow([NSException exceptionWithName:@"MockName" reason:@"MockReason" userInfo:nil]); __block FlutterError *error; XCTAssertThrows([self.plugin handleMethodCall:methodCall result:^(FlutterError *result) { error = result; }]); XCTAssertEqualObjects(error.code, @"google_sign_in"); XCTAssertEqualObjects(error.message, @"MockReason"); XCTAssertEqualObjects(error.details, @"MockName"); } #pragma mark - Get tokens - (void)testGetTokens { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); id mockAuthentication = OCMClassMock([GIDAuthentication class]); OCMStub([mockAuthentication idToken]).andReturn(@"mockIdToken"); OCMStub([mockAuthentication accessToken]).andReturn(@"mockAccessToken"); [[mockAuthentication stub] doWithFreshTokens:[OCMArg invokeBlockWithArgs:mockAuthentication, [NSNull null], nil]]; OCMStub([mockUser authentication]).andReturn(mockAuthentication); FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"getTokens" arguments:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(NSDictionary *result) { XCTAssertEqualObjects(result[@"idToken"], @"mockIdToken"); XCTAssertEqualObjects(result[@"accessToken"], @"mockAccessToken"); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testGetTokensNoAuthKeychainError { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); id mockAuthentication = OCMClassMock([GIDAuthentication class]); NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeHasNoAuthInKeychain userInfo:nil]; [[mockAuthentication stub] doWithFreshTokens:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; OCMStub([mockUser authentication]).andReturn(mockAuthentication); FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"getTokens" arguments:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(FlutterError *result) { XCTAssertEqualObjects(result.code, @"sign_in_required"); XCTAssertEqualObjects(result.message, kGIDSignInErrorDomain); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testGetTokensCancelledError { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); id mockAuthentication = OCMClassMock([GIDAuthentication class]); NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeCanceled userInfo:nil]; [[mockAuthentication stub] doWithFreshTokens:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; OCMStub([mockUser authentication]).andReturn(mockAuthentication); FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"getTokens" arguments:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(FlutterError *result) { XCTAssertEqualObjects(result.code, @"sign_in_canceled"); XCTAssertEqualObjects(result.message, kGIDSignInErrorDomain); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testGetTokensURLError { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); id mockAuthentication = OCMClassMock([GIDAuthentication class]); NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:nil]; [[mockAuthentication stub] doWithFreshTokens:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; OCMStub([mockUser authentication]).andReturn(mockAuthentication); FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"getTokens" arguments:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(FlutterError *result) { XCTAssertEqualObjects(result.code, @"network_error"); XCTAssertEqualObjects(result.message, NSURLErrorDomain); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testGetTokensUnknownError { id mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); id mockAuthentication = OCMClassMock([GIDAuthentication class]); NSError *error = [NSError errorWithDomain:@"BogusDomain" code:42 userInfo:nil]; [[mockAuthentication stub] doWithFreshTokens:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; OCMStub([mockUser authentication]).andReturn(mockAuthentication); FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"getTokens" arguments:nil]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(FlutterError *result) { XCTAssertEqualObjects(result.code, @"sign_in_failed"); XCTAssertEqualObjects(result.message, @"BogusDomain"); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } #pragma mark - Request scopes - (void)testRequestScopesResultErrorIfNotSignedIn { NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeNoCurrentUser userInfo:nil]; [[self.mockSignIn stub] addScopes:@[ @"mockScope1" ] presentingViewController:OCMOCK_ANY callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"requestScopes" arguments:@{@"scopes" : @[ @"mockScope1" ]}]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(FlutterError *result) { XCTAssertEqualObjects(result.code, @"sign_in_required"); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testRequestScopesIfNoMissingScope { NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain code:kGIDSignInErrorCodeScopesAlreadyGranted userInfo:nil]; [[self.mockSignIn stub] addScopes:@[ @"mockScope1" ] presentingViewController:OCMOCK_ANY callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"requestScopes" arguments:@{@"scopes" : @[ @"mockScope1" ]}]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(NSNumber *result) { XCTAssertTrue(result.boolValue); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testRequestScopesWithUnknownError { NSError *error = [NSError errorWithDomain:@"BogusDomain" code:42 userInfo:nil]; [[self.mockSignIn stub] addScopes:@[ @"mockScope1" ] presentingViewController:OCMOCK_ANY callback:[OCMArg invokeBlockWithArgs:[NSNull null], error, nil]]; FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"requestScopes" arguments:@{@"scopes" : @[ @"mockScope1" ]}]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(NSNumber *result) { XCTAssertFalse(result.boolValue); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testRequestScopesException { FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"requestScopes" arguments:nil]; OCMExpect([self.mockSignIn addScopes:@[] presentingViewController:OCMOCK_ANY callback:OCMOCK_ANY]) .andThrow([NSException exceptionWithName:@"MockName" reason:@"MockReason" userInfo:nil]); [self.plugin handleMethodCall:methodCall result:^(FlutterError *result) { XCTAssertEqualObjects(result.code, @"request_scopes"); XCTAssertEqualObjects(result.message, @"MockReason"); XCTAssertEqualObjects(result.details, @"MockName"); }]; } - (void)testRequestScopesReturnsFalseIfOnlySubsetGranted { GIDGoogleUser *mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); NSArray *requestedScopes = @[ @"mockScope1", @"mockScope2" ]; // Only grant one of the two requested scopes. OCMStub(mockUser.grantedScopes).andReturn(@[ @"mockScope1" ]); [[self.mockSignIn stub] addScopes:requestedScopes presentingViewController:OCMOCK_ANY callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"requestScopes" arguments:@{@"scopes" : requestedScopes}]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns false"]; [self.plugin handleMethodCall:methodCall result:^(NSNumber *result) { XCTAssertFalse(result.boolValue); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } - (void)testRequestsInitializedScopes { FlutterMethodCall *initMethodCall = [FlutterMethodCall methodCallWithMethodName:@"init" arguments:@{@"scopes" : @[ @"initial1", @"initial2" ]}]; XCTestExpectation *initExpectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:initMethodCall result:^(id result) { XCTAssertNil(result); [initExpectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; // Include one of the initially requested scopes. NSArray *addedScopes = @[ @"initial1", @"addScope1", @"addScope2" ]; FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"requestScopes" arguments:@{@"scopes" : addedScopes}]; [self.plugin handleMethodCall:methodCall result:^(id result){ }]; // All four scopes are requested. [[self.mockSignIn verify] addScopes:[OCMArg checkWithBlock:^BOOL(NSArray *scopes) { return [[NSSet setWithArray:scopes] isEqualToSet:[NSSet setWithObjects:@"initial1", @"initial2", @"addScope1", @"addScope2", nil]]; }] presentingViewController:OCMOCK_ANY callback:OCMOCK_ANY]; } - (void)testRequestScopesReturnsTrueIfGranted { GIDGoogleUser *mockUser = OCMClassMock([GIDGoogleUser class]); OCMStub([self.mockSignIn currentUser]).andReturn(mockUser); NSArray *requestedScopes = @[ @"mockScope1", @"mockScope2" ]; // Grant both of the requested scopes. OCMStub(mockUser.grantedScopes).andReturn(requestedScopes); [[self.mockSignIn stub] addScopes:requestedScopes presentingViewController:OCMOCK_ANY callback:[OCMArg invokeBlockWithArgs:mockUser, [NSNull null], nil]]; FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"requestScopes" arguments:@{@"scopes" : requestedScopes}]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"]; [self.plugin handleMethodCall:methodCall result:^(NSNumber *result) { XCTAssertTrue(result.boolValue); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; } @end ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/Info.plist ================================================ 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 ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/RunnerUITests/GoogleSignInUITests.m ================================================ // Copyright 2013 The Flutter 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 os.log; @import XCTest; @interface GoogleSignInUITests : XCTestCase @property(nonatomic, strong) XCUIApplication *app; @end @implementation GoogleSignInUITests - (void)setUp { self.continueAfterFailure = NO; self.app = [[XCUIApplication alloc] init]; [self.app launch]; } - (void)testSignInPopUp { XCUIApplication *app = self.app; XCUIElement *signInButton = app.buttons[@"SIGN IN"]; if (![signInButton waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find Sign In button"); } [signInButton tap]; [self allowSignInPermissions]; } - (void)allowSignInPermissions { // The "Sign In" system permissions pop up isn't caught by // addUIInterruptionMonitorWithDescription. XCUIApplication *springboard = [[XCUIApplication alloc] initWithBundleIdentifier:@"com.apple.springboard"]; XCUIElement *permissionAlert = springboard.alerts.firstMatch; if ([permissionAlert waitForExistenceWithTimeout:5.0]) { [permissionAlert.buttons[@"Continue"] tap]; } else { os_log(OS_LOG_DEFAULT, "Permission alert not detected, continuing."); } } @end ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/ios/RunnerUITests/Info.plist ================================================ 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 ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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, avoid_print import 'dart:async'; import 'dart:convert' show json; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'package:http/http.dart' as http; void main() { runApp( const MaterialApp( title: 'Google Sign In', home: SignInDemo(), ), ); } class SignInDemo extends StatefulWidget { const SignInDemo({Key? key}) : super(key: key); @override State createState() => SignInDemoState(); } class SignInDemoState extends State { GoogleSignInUserData? _currentUser; String _contactText = ''; // Future that completes when `initWithParams` has completed on the sign in // instance. Future? _initialization; @override void initState() { super.initState(); _signIn(); } Future _ensureInitialized() { return _initialization ??= GoogleSignInPlatform.instance.initWithParams(const SignInInitParameters( scopes: [ 'email', 'https://www.googleapis.com/auth/contacts.readonly', ], )) ..catchError((dynamic _) { _initialization = null; }); } void _setUser(GoogleSignInUserData? user) { setState(() { _currentUser = user; if (user != null) { _handleGetContact(user); } }); } Future _signIn() async { await _ensureInitialized(); final GoogleSignInUserData? newUser = await GoogleSignInPlatform.instance.signInSilently(); _setUser(newUser); } Future> _getAuthHeaders() async { final GoogleSignInUserData? user = _currentUser; if (user == null) { throw StateError('No user signed in'); } final GoogleSignInTokenData response = await GoogleSignInPlatform.instance.getTokens( email: user.email, shouldRecoverAuth: true, ); return { 'Authorization': 'Bearer ${response.accessToken}', // TODO(kevmoo): Use the correct value once it's available. // See https://github.com/flutter/flutter/issues/80905 'X-Goog-AuthUser': '0', }; } Future _handleGetContact(GoogleSignInUserData user) async { setState(() { _contactText = 'Loading contact info...'; }); final http.Response response = await http.get( Uri.parse('https://people.googleapis.com/v1/people/me/connections' '?requestMask.includeField=person.names'), headers: await _getAuthHeaders(), ); if (response.statusCode != 200) { setState(() { _contactText = 'People API gave a ${response.statusCode} ' 'response. Check logs for details.'; }); print('People API ${response.statusCode} response: ${response.body}'); return; } final Map data = json.decode(response.body) as Map; final int contactCount = (data['connections'] as List?)?.length ?? 0; setState(() { _contactText = '$contactCount contacts found'; }); } Future _handleSignIn() async { try { await _ensureInitialized(); _setUser(await GoogleSignInPlatform.instance.signIn()); } catch (error) { final bool canceled = error is PlatformException && error.code == 'sign_in_canceled'; if (!canceled) { print(error); } } } Future _handleSignOut() async { await _ensureInitialized(); await GoogleSignInPlatform.instance.disconnect(); } Widget _buildBody() { final GoogleSignInUserData? user = _currentUser; if (user != null) { return Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ ListTile( title: Text(user.displayName ?? ''), subtitle: Text(user.email), ), const Text('Signed in successfully.'), Text(_contactText), ElevatedButton( onPressed: _handleSignOut, child: const Text('SIGN OUT'), ), ElevatedButton( child: const Text('REFRESH'), onPressed: () => _handleGetContact(user), ), ], ); } else { return Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ const Text('You are not currently signed in.'), ElevatedButton( onPressed: _handleSignIn, child: const Text('SIGN IN'), ), ], ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Google Sign In'), ), body: ConstrainedBox( constraints: const BoxConstraints.expand(), child: _buildBody(), )); } } ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/pubspec.yaml ================================================ name: google_sign_in_example description: Example of Google Sign-In plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter google_sign_in_ios: # When depending on this package from a real application you should use: # google_sign_in_ios: ^x.y.z # See https://dart.dev/tools/pub/dependencies#version-constraints # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ google_sign_in_platform_interface: ^2.2.0 http: ^0.13.0 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/google_sign_in/google_sign_in_ios/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/google_sign_in/google_sign_in_ios/ios/Assets/.gitkeep ================================================ ================================================ FILE: packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.h ================================================ // Copyright 2013 The Flutter 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 FLTGoogleSignInPlugin : NSObject @end ================================================ FILE: packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.m ================================================ // Copyright 2013 The Flutter 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 "FLTGoogleSignInPlugin.h" #import "FLTGoogleSignInPlugin_Test.h" #import // The key within `GoogleService-Info.plist` used to hold the application's // client id. See https://developers.google.com/identity/sign-in/ios/start // for more info. static NSString *const kClientIdKey = @"CLIENT_ID"; static NSString *const kServerClientIdKey = @"SERVER_CLIENT_ID"; static NSDictionary *loadGoogleServiceInfo() { NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"GoogleService-Info" ofType:@"plist"]; if (plistPath) { return [[NSDictionary alloc] initWithContentsOfFile:plistPath]; } return nil; } // These error codes must match with ones declared on Android and Dart sides. static NSString *const kErrorReasonSignInRequired = @"sign_in_required"; static NSString *const kErrorReasonSignInCanceled = @"sign_in_canceled"; static NSString *const kErrorReasonNetworkError = @"network_error"; static NSString *const kErrorReasonSignInFailed = @"sign_in_failed"; static FlutterError *getFlutterError(NSError *error) { NSString *errorCode; if (error.code == kGIDSignInErrorCodeHasNoAuthInKeychain) { errorCode = kErrorReasonSignInRequired; } else if (error.code == kGIDSignInErrorCodeCanceled) { errorCode = kErrorReasonSignInCanceled; } else if ([error.domain isEqualToString:NSURLErrorDomain]) { errorCode = kErrorReasonNetworkError; } else { errorCode = kErrorReasonSignInFailed; } return [FlutterError errorWithCode:errorCode message:error.domain details:error.localizedDescription]; } @interface FLTGoogleSignInPlugin () // Configuration wrapping Google Cloud Console, Google Apps, OpenID, // and other initialization metadata. @property(strong) GIDConfiguration *configuration; // Permissions requested during at sign in "init" method call // unioned with scopes requested later with incremental authorization // "requestScopes" method call. // The "email" and "profile" base scopes are always implicitly requested. @property(copy) NSSet *requestedScopes; // Instance used to manage Google Sign In authentication including // sign in, sign out, and requesting additional scopes. @property(strong, readonly) GIDSignIn *signIn; // The contents of GoogleService-Info.plist, if it exists. @property(strong, nullable) NSDictionary *googleServiceProperties; // Redeclared as not a designated initializer. - (instancetype)init; @end @implementation FLTGoogleSignInPlugin + (void)registerWithRegistrar:(NSObject *)registrar { FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/google_sign_in_ios" binaryMessenger:[registrar messenger]]; FLTGoogleSignInPlugin *instance = [[FLTGoogleSignInPlugin alloc] init]; [registrar addApplicationDelegate:instance]; [registrar addMethodCallDelegate:instance channel:channel]; } - (instancetype)init { return [self initWithSignIn:GIDSignIn.sharedInstance]; } - (instancetype)initWithSignIn:(GIDSignIn *)signIn { return [self initWithSignIn:signIn withGoogleServiceProperties:loadGoogleServiceInfo()]; } - (instancetype)initWithSignIn:(GIDSignIn *)signIn withGoogleServiceProperties:(nullable NSDictionary *)googleServiceProperties { self = [super init]; if (self) { _signIn = signIn; _googleServiceProperties = googleServiceProperties; // On the iOS simulator, we get "Broken pipe" errors after sign-in for some // unknown reason. We can avoid crashing the app by ignoring them. signal(SIGPIPE, SIG_IGN); _requestedScopes = [[NSSet alloc] init]; } return self; } #pragma mark - protocol - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if ([call.method isEqualToString:@"init"]) { GIDConfiguration *configuration = [self configurationWithClientIdArgument:call.arguments[@"clientId"] serverClientIdArgument:call.arguments[@"serverClientId"] hostedDomainArgument:call.arguments[@"hostedDomain"]]; if (configuration != nil) { if ([call.arguments[@"scopes"] isKindOfClass:[NSArray class]]) { self.requestedScopes = [NSSet setWithArray:call.arguments[@"scopes"]]; } self.configuration = configuration; result(nil); } else { result([FlutterError errorWithCode:@"missing-config" message:@"GoogleService-Info.plist file not found and clientId " @"was not provided programmatically." details:nil]); } } else if ([call.method isEqualToString:@"signInSilently"]) { [self.signIn restorePreviousSignInWithCallback:^(GIDGoogleUser *user, NSError *error) { [self didSignInForUser:user result:result withError:error]; }]; } else if ([call.method isEqualToString:@"isSignedIn"]) { result(@([self.signIn hasPreviousSignIn])); } else if ([call.method isEqualToString:@"signIn"]) { @try { GIDConfiguration *configuration = self.configuration ?: [self configurationWithClientIdArgument:nil serverClientIdArgument:nil hostedDomainArgument:nil]; [self.signIn signInWithConfiguration:configuration presentingViewController:[self topViewController] hint:nil additionalScopes:self.requestedScopes.allObjects callback:^(GIDGoogleUser *user, NSError *error) { [self didSignInForUser:user result:result withError:error]; }]; } @catch (NSException *e) { result([FlutterError errorWithCode:@"google_sign_in" message:e.reason details:e.name]); [e raise]; } } else if ([call.method isEqualToString:@"getTokens"]) { GIDGoogleUser *currentUser = self.signIn.currentUser; GIDAuthentication *auth = currentUser.authentication; [auth doWithFreshTokens:^void(GIDAuthentication *authentication, NSError *error) { result(error != nil ? getFlutterError(error) : @{ @"idToken" : authentication.idToken, @"accessToken" : authentication.accessToken, }); }]; } else if ([call.method isEqualToString:@"signOut"]) { [self.signIn signOut]; result(nil); } else if ([call.method isEqualToString:@"disconnect"]) { [self.signIn disconnectWithCallback:^(NSError *error) { [self respondWithAccount:@{} result:result error:nil]; }]; } else if ([call.method isEqualToString:@"requestScopes"]) { id scopeArgument = call.arguments[@"scopes"]; if ([scopeArgument isKindOfClass:[NSArray class]]) { self.requestedScopes = [self.requestedScopes setByAddingObjectsFromArray:scopeArgument]; } NSSet *requestedScopes = self.requestedScopes; @try { [self.signIn addScopes:requestedScopes.allObjects presentingViewController:[self topViewController] callback:^(GIDGoogleUser *addedScopeUser, NSError *addedScopeError) { if ([addedScopeError.domain isEqualToString:kGIDSignInErrorDomain] && addedScopeError.code == kGIDSignInErrorCodeNoCurrentUser) { result([FlutterError errorWithCode:@"sign_in_required" message:@"No account to grant scopes." details:nil]); } else if ([addedScopeError.domain isEqualToString:kGIDSignInErrorDomain] && addedScopeError.code == kGIDSignInErrorCodeScopesAlreadyGranted) { // Scopes already granted, report success. result(@YES); } else if (addedScopeUser == nil) { result(@NO); } else { NSSet *grantedScopes = [NSSet setWithArray:addedScopeUser.grantedScopes]; BOOL granted = [requestedScopes isSubsetOfSet:grantedScopes]; result(@(granted)); } }]; } @catch (NSException *e) { result([FlutterError errorWithCode:@"request_scopes" message:e.reason details:e.name]); } } else { result(FlutterMethodNotImplemented); } } - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { return [self.signIn handleURL:url]; } #pragma mark - protocol - (void)signIn:(GIDSignIn *)signIn presentViewController:(UIViewController *)viewController { UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController; [rootViewController presentViewController:viewController animated:YES completion:nil]; } - (void)signIn:(GIDSignIn *)signIn dismissViewController:(UIViewController *)viewController { [viewController dismissViewControllerAnimated:YES completion:nil]; } #pragma mark - private methods /// @return @c nil if GoogleService-Info.plist not found and clientId is not provided. - (GIDConfiguration *)configurationWithClientIdArgument:(id)clientIDArg serverClientIdArgument:(id)serverClientIDArg hostedDomainArgument:(id)hostedDomainArg { NSString *clientID; BOOL hasDynamicClientId = [clientIDArg isKindOfClass:[NSString class]]; if (hasDynamicClientId) { clientID = clientIDArg; } else if (self.googleServiceProperties) { clientID = self.googleServiceProperties[kClientIdKey]; } else { // We couldn't resolve a clientId, without which we cannot create a GIDConfiguration. return nil; } BOOL hasDynamicServerClientId = [serverClientIDArg isKindOfClass:[NSString class]]; NSString *serverClientID = hasDynamicServerClientId ? serverClientIDArg : self.googleServiceProperties[kServerClientIdKey]; NSString *hostedDomain = nil; if (hostedDomainArg != [NSNull null]) { hostedDomain = hostedDomainArg; } return [[GIDConfiguration alloc] initWithClientID:clientID serverClientID:serverClientID hostedDomain:hostedDomain openIDRealm:nil]; } - (void)didSignInForUser:(GIDGoogleUser *)user result:(FlutterResult)result withError:(NSError *)error { if (error != nil) { // Forward all errors and let Dart side decide how to handle. [self respondWithAccount:nil result:result error:error]; } else { NSURL *photoUrl; if (user.profile.hasImage) { // Placeholder that will be replaced by on the Dart side based on screen size. photoUrl = [user.profile imageURLWithDimension:1337]; } [self respondWithAccount:@{ @"displayName" : user.profile.name ?: [NSNull null], @"email" : user.profile.email ?: [NSNull null], @"id" : user.userID ?: [NSNull null], @"photoUrl" : [photoUrl absoluteString] ?: [NSNull null], @"serverAuthCode" : user.serverAuthCode ?: [NSNull null] } result:result error:nil]; } } - (void)respondWithAccount:(NSDictionary *)account result:(FlutterResult)result error:(NSError *)error { result(error != nil ? getFlutterError(error) : account); } - (UIViewController *)topViewController { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" // TODO(stuartmorgan) Provide a non-deprecated codepath. See // https://github.com/flutter/flutter/issues/104117 return [self topViewControllerFromViewController:[UIApplication sharedApplication] .keyWindow.rootViewController]; #pragma clang diagnostic pop } /** * This method recursively iterate through the view hierarchy * to return the top most view controller. * * It supports the following scenarios: * * - The view controller is presenting another view. * - The view controller is a UINavigationController. * - The view controller is a UITabBarController. * * @return The top most view controller. */ - (UIViewController *)topViewControllerFromViewController:(UIViewController *)viewController { if ([viewController isKindOfClass:[UINavigationController class]]) { UINavigationController *navigationController = (UINavigationController *)viewController; return [self topViewControllerFromViewController:[navigationController.viewControllers lastObject]]; } if ([viewController isKindOfClass:[UITabBarController class]]) { UITabBarController *tabController = (UITabBarController *)viewController; return [self topViewControllerFromViewController:tabController.selectedViewController]; } if (viewController.presentedViewController) { return [self topViewControllerFromViewController:viewController.presentedViewController]; } return viewController; } @end ================================================ FILE: packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.modulemap ================================================ framework module google_sign_in_ios { umbrella header "google_sign_in_ios-umbrella.h" export * module * { export * } explicit module Test { header "FLTGoogleSignInPlugin_Test.h" } } ================================================ FILE: packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin_Test.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This header is available in the Test module. Import via "@import google_sign_in.Test;" #import NS_ASSUME_NONNULL_BEGIN @class GIDSignIn; /// Methods exposed for unit testing. @interface FLTGoogleSignInPlugin () /// Inject @c GIDSignIn for testing. - (instancetype)initWithSignIn:(GIDSignIn *)signIn; /// Inject @c GIDSignIn and @c googleServiceProperties for testing. - (instancetype)initWithSignIn:(GIDSignIn *)signIn withGoogleServiceProperties:(nullable NSDictionary *)googleServiceProperties NS_DESIGNATED_INITIALIZER; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/google_sign_in/google_sign_in_ios/ios/Classes/google_sign_in_ios-umbrella.h ================================================ // Copyright 2013 The Flutter 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 FOUNDATION_EXPORT double google_sign_inVersionNumber; FOUNDATION_EXPORT const unsigned char google_sign_inVersionString[]; ================================================ FILE: packages/google_sign_in/google_sign_in_ios/ios/google_sign_in_ios.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| s.name = 'google_sign_in_ios' s.version = '0.0.1' s.summary = 'Google Sign-In plugin for Flutter' s.description = <<-DESC Enables Google Sign-In in Flutter apps. DESC s.homepage = 'https://github.com/flutter/plugins/tree/main/packages/google_sign_in' 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/google_sign_in/google_sign_in_ios' } s.source_files = 'Classes/**/*.{h,m}' s.public_header_files = 'Classes/**/*.h' s.module_map = 'Classes/FLTGoogleSignInPlugin.modulemap' s.dependency 'Flutter' s.dependency 'GoogleSignIn', '~> 6.2' s.static_framework = true s.platform = :ios, '9.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } end ================================================ FILE: packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart ================================================ // Copyright 2013 The Flutter 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' show visibleForTesting; import 'package:flutter/services.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'src/utils.dart'; /// iOS implementation of [GoogleSignInPlatform]. class GoogleSignInIOS extends GoogleSignInPlatform { /// This is only exposed for test purposes. It shouldn't be used by clients of /// the plugin as it may break or change at any time. @visibleForTesting MethodChannel channel = const MethodChannel('plugins.flutter.io/google_sign_in_ios'); /// Registers this class as the default instance of [GoogleSignInPlatform]. static void registerWith() { GoogleSignInPlatform.instance = GoogleSignInIOS(); } @override Future init({ List scopes = const [], SignInOption signInOption = SignInOption.standard, String? hostedDomain, String? clientId, }) { return initWithParams(SignInInitParameters( signInOption: signInOption, scopes: scopes, hostedDomain: hostedDomain, clientId: clientId, )); } @override Future initWithParams(SignInInitParameters params) { if (params.signInOption == SignInOption.games) { throw PlatformException( code: 'unsupported-options', message: 'Games sign in is not supported on iOS'); } return channel.invokeMethod('init', { 'scopes': params.scopes, 'hostedDomain': params.hostedDomain, 'clientId': params.clientId, 'serverClientId': params.serverClientId, }); } @override Future signInSilently() { return channel .invokeMapMethod('signInSilently') .then(getUserDataFromMap); } @override Future signIn() { return channel .invokeMapMethod('signIn') .then(getUserDataFromMap); } @override Future getTokens( {required String email, bool? shouldRecoverAuth = true}) { return channel .invokeMapMethod('getTokens', { 'email': email, 'shouldRecoverAuth': shouldRecoverAuth, }).then((Map? result) => getTokenDataFromMap(result!)); } @override Future signOut() { return channel.invokeMapMethod('signOut'); } @override Future disconnect() { return channel.invokeMapMethod('disconnect'); } @override Future isSignedIn() async { return (await channel.invokeMethod('isSignedIn'))!; } @override Future clearAuthCache({String? token}) async { // There's nothing to be done here on iOS since the expired/invalid // tokens are refreshed automatically by getTokens. } @override Future requestScopes(List scopes) async { return (await channel.invokeMethod( 'requestScopes', >{'scopes': scopes}, ))!; } } ================================================ FILE: packages/google_sign_in/google_sign_in_ios/lib/src/utils.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; /// Converts user data coming from native code into the proper platform interface type. GoogleSignInUserData? getUserDataFromMap(Map? data) { if (data == null) { return null; } return GoogleSignInUserData( email: data['email']! as String, id: data['id']! as String, displayName: data['displayName'] as String?, photoUrl: data['photoUrl'] as String?, idToken: data['idToken'] as String?, serverAuthCode: data['serverAuthCode'] as String?); } /// Converts token data coming from native code into the proper platform interface type. GoogleSignInTokenData getTokenDataFromMap(Map data) { return GoogleSignInTokenData( idToken: data['idToken'] as String?, accessToken: data['accessToken'] as String?, serverAuthCode: data['serverAuthCode'] as String?, ); } ================================================ FILE: packages/google_sign_in/google_sign_in_ios/pubspec.yaml ================================================ name: google_sign_in_ios description: iOS implementation of the google_sign_in plugin. repository: https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22 version: 5.5.1 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: google_sign_in platforms: ios: dartPluginClass: GoogleSignInIOS pluginClass: FLTGoogleSignInPlugin dependencies: flutter: sdk: flutter google_sign_in_platform_interface: ^2.2.0 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter # The example deliberately includes limited-use secrets. false_secrets: - /example/ios/Runner/GoogleService-Info.plist - /example/ios/RunnerTests/GoogleSignInTests.m - /example/lib/main.dart ================================================ FILE: packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart ================================================ // Copyright 2013 The Flutter 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:google_sign_in_ios/google_sign_in_ios.dart'; import 'package:google_sign_in_ios/src/utils.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; const Map kUserData = { 'email': 'john.doe@gmail.com', 'id': '8162538176523816253123', 'photoUrl': 'https://lh5.googleusercontent.com/photo.jpg', 'displayName': 'John Doe', 'idToken': '123', 'serverAuthCode': '789', }; const Map kTokenData = { 'idToken': '123', 'accessToken': '456', 'serverAuthCode': '789', }; const Map kDefaultResponses = { 'init': null, 'signInSilently': kUserData, 'signIn': kUserData, 'signOut': null, 'disconnect': null, 'isSignedIn': true, 'getTokens': kTokenData, 'requestScopes': true, }; final GoogleSignInUserData? kUser = getUserDataFromMap(kUserData); final GoogleSignInTokenData kToken = getTokenDataFromMap(kTokenData as Map); void main() { TestWidgetsFlutterBinding.ensureInitialized(); final GoogleSignInIOS googleSignIn = GoogleSignInIOS(); final MethodChannel channel = googleSignIn.channel; late List log; late Map responses; // Some tests mutate some kDefaultResponses setUp(() { responses = Map.from(kDefaultResponses); log = []; _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( channel, (MethodCall methodCall) { log.add(methodCall); final dynamic response = responses[methodCall.method]; if (response != null && response is Exception) { return Future.error('$response'); } return Future.value(response); }, ); }); test('registered instance', () { GoogleSignInIOS.registerWith(); expect(GoogleSignInPlatform.instance, isA()); }); test('init throws for SignInOptions.games', () async { expect( () => googleSignIn.init( hostedDomain: 'example.com', signInOption: SignInOption.games, clientId: 'fakeClientId'), throwsA(isInstanceOf().having( (PlatformException e) => e.code, 'code', 'unsupported-options'))); }); test('signInSilently transforms platform data to GoogleSignInUserData', () async { final dynamic response = await googleSignIn.signInSilently(); expect(response, kUser); }); test('signInSilently Exceptions -> throws', () async { responses['signInSilently'] = Exception('Not a user'); expect(googleSignIn.signInSilently(), throwsA(isInstanceOf())); }); test('signIn transforms platform data to GoogleSignInUserData', () async { final dynamic response = await googleSignIn.signIn(); expect(response, kUser); }); test('signIn Exceptions -> throws', () async { responses['signIn'] = Exception('Not a user'); expect(googleSignIn.signIn(), throwsA(isInstanceOf())); }); test('getTokens transforms platform data to GoogleSignInTokenData', () async { final dynamic response = await googleSignIn.getTokens( email: 'example@example.com', shouldRecoverAuth: false); expect(response, kToken); expect( log[0], isMethodCall('getTokens', arguments: { 'email': 'example@example.com', 'shouldRecoverAuth': false, })); }); test('clearAuthCache is a no-op', () async { await googleSignIn.clearAuthCache(token: 'abc'); expect(log.isEmpty, true); }); test('Other functions pass through arguments to the channel', () async { final Map tests = { () { googleSignIn.init( hostedDomain: 'example.com', scopes: ['two', 'scopes'], clientId: 'fakeClientId'); }: isMethodCall('init', arguments: { 'hostedDomain': 'example.com', 'scopes': ['two', 'scopes'], 'clientId': 'fakeClientId', 'serverClientId': null, }), () { googleSignIn.initWithParams(const SignInInitParameters( hostedDomain: 'example.com', scopes: ['two', 'scopes'], clientId: 'fakeClientId', serverClientId: 'fakeServerClientId')); }: isMethodCall('init', arguments: { 'hostedDomain': 'example.com', 'scopes': ['two', 'scopes'], 'clientId': 'fakeClientId', 'serverClientId': 'fakeServerClientId', }), () { googleSignIn.getTokens( email: 'example@example.com', shouldRecoverAuth: false); }: isMethodCall('getTokens', arguments: { 'email': 'example@example.com', 'shouldRecoverAuth': false, }), () { googleSignIn.requestScopes(['newScope', 'anotherScope']); }: isMethodCall('requestScopes', arguments: { 'scopes': ['newScope', 'anotherScope'], }), googleSignIn.signOut: isMethodCall('signOut', arguments: null), googleSignIn.disconnect: isMethodCall('disconnect', arguments: null), googleSignIn.isSignedIn: isMethodCall('isSignedIn', arguments: null), }; for (final void Function() f in tests.keys) { f(); } expect(log, tests.values); }); } /// 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; ================================================ FILE: packages/google_sign_in/google_sign_in_platform_interface/AUTHORS ================================================ # 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> Twin Sun, LLC ================================================ FILE: packages/google_sign_in/google_sign_in_platform_interface/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 2.3.0 * Adopts `plugin_platform_interface`. As a result, `isMock` is deprecated in favor of the now-standard `MockPlatformInterfaceMixin`. ## 2.2.0 * Adds support for the `serverClientId` parameter. ## 2.1.3 * Enables mocking models by changing overridden operator == parameter type from `dynamic` to `Object`. * Removes unnecessary imports. * Adds `SignInInitParameters` class to hold all sign in params, including the new `forceCodeForRefreshToken`. ## 2.1.2 * Internal code cleanup for stricter analysis options. ## 2.1.1 * Removes dependency on `meta`. ## 2.1.0 * Add serverAuthCode attribute to user data ## 2.0.1 * Updates `init` function in `MethodChannelGoogleSignIn` to parametrize `clientId` property. ## 2.0.0 * Migrate to null-safety. ## 1.1.3 * Update Flutter SDK constraint. ## 1.1.2 * Update lower bound of dart dependency to 2.1.0. ## 1.1.1 * Add attribute serverAuthCode. ## 1.1.0 * Add hasRequestedScope method to determine if an Oauth scope has been granted. * Add requestScope Method to request new Oauth scopes be granted by the user. ## 1.0.4 * Make the pedantic dev_dependency explicit. ## 1.0.3 * Remove the deprecated `author:` field from pubspec.yaml * Require Flutter SDK 1.10.0 or greater. ## 1.0.2 * Add missing documentation. ## 1.0.1 * Switch away from quiver_hashcode. ## 1.0.0 * Initial release. ================================================ FILE: packages/google_sign_in/google_sign_in_platform_interface/LICENSE ================================================ 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. ================================================ FILE: packages/google_sign_in/google_sign_in_platform_interface/README.md ================================================ # google_sign_in_platform_interface A common platform interface for the [`google_sign_in`][1] plugin. This interface allows platform-specific implementations of the `google_sign_in` plugin, as well as the plugin itself, to ensure they are supporting the same interface. # Usage To implement a new platform-specific implementation of `google_sign_in`, extend [`GoogleSignInPlatform`][2] with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `GoogleSignInPlatform` by calling `GoogleSignInPlatform.instance = MyPlatformGoogleSignIn()`. # Note on breaking changes Strongly prefer non-breaking changes (such as adding a method to the interface) over breaking changes for this package. See https://flutter.dev/go/platform-interface-breaking-changes for a discussion on why a less-clean interface is preferable to a breaking change. [1]: ../google_sign_in [2]: lib/google_sign_in_platform_interface.dart ================================================ FILE: packages/google_sign_in/google_sign_in_platform_interface/lib/google_sign_in_platform_interface.dart ================================================ // Copyright 2013 The Flutter 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' show visibleForTesting; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'src/method_channel_google_sign_in.dart'; import 'src/types.dart'; export 'src/method_channel_google_sign_in.dart'; export 'src/types.dart'; /// The interface that implementations of google_sign_in must implement. /// /// Platform implementations that live in a separate package should extend this /// class rather than implement it as `google_sign_in` does not consider newly /// added methods to be breaking changes. Extending this class (using `extends`) /// ensures that the subclass will get the default implementation, while /// platform implementations that `implements` this interface will be broken by /// newly added [GoogleSignInPlatform] methods. abstract class GoogleSignInPlatform extends PlatformInterface { /// Constructs a GoogleSignInPlatform. GoogleSignInPlatform() : super(token: _token); static final Object _token = Object(); /// Only mock implementations should set this to `true`. /// /// Mockito mocks implement this class with `implements` which is forbidden /// (see class docs). This property provides a backdoor for mocks to skip the /// verification that the class isn't implemented with `implements`. @visibleForTesting @Deprecated('Use MockPlatformInterfaceMixin instead') bool get isMock => false; /// The default instance of [GoogleSignInPlatform] to use. /// /// Platform-specific plugins should override this with their own /// platform-specific class that extends [GoogleSignInPlatform] when they /// register themselves. /// /// Defaults to [MethodChannelGoogleSignIn]. static GoogleSignInPlatform get instance => _instance; static GoogleSignInPlatform _instance = MethodChannelGoogleSignIn(); // TODO(amirh): Extract common platform interface logic. // https://github.com/flutter/flutter/issues/43368 static set instance(GoogleSignInPlatform instance) { if (!instance.isMock) { PlatformInterface.verify(instance, _token); } _instance = instance; } /// Initializes the plugin. Deprecated: call [initWithParams] instead. /// /// The [hostedDomain] argument specifies a hosted domain restriction. By /// setting this, sign in will be restricted to accounts of the user in the /// specified domain. By default, the list of accounts will not be restricted. /// /// The list of [scopes] are OAuth scope codes to request when signing in. /// These scope codes will determine the level of data access that is granted /// to your application by the user. The full list of available scopes can be /// found here: /// /// The [signInOption] determines the user experience. [SigninOption.games] is /// only supported on Android. /// /// See: /// https://developers.google.com/identity/sign-in/web/reference#gapiauth2initparams Future init({ List scopes = const [], SignInOption signInOption = SignInOption.standard, String? hostedDomain, String? clientId, }) async { throw UnimplementedError('init() has not been implemented.'); } /// Initializes the plugin with specified [params]. You must call this method /// before calling other methods. /// /// See: /// /// * [SignInInitParameters] Future initWithParams(SignInInitParameters params) async { await init( scopes: params.scopes, signInOption: params.signInOption, hostedDomain: params.hostedDomain, clientId: params.clientId, ); } /// Attempts to reuse pre-existing credentials to sign in again, without user interaction. Future signInSilently() async { throw UnimplementedError('signInSilently() has not been implemented.'); } /// Signs in the user with the options specified to [init]. Future signIn() async { throw UnimplementedError('signIn() has not been implemented.'); } /// Returns the Tokens used to authenticate other API calls. Future getTokens( {required String email, bool? shouldRecoverAuth}) async { throw UnimplementedError('getTokens() has not been implemented.'); } /// Signs out the current account from the application. Future signOut() async { throw UnimplementedError('signOut() has not been implemented.'); } /// Revokes all of the scopes that the user granted. Future disconnect() async { throw UnimplementedError('disconnect() has not been implemented.'); } /// Returns whether the current user is currently signed in. Future isSignedIn() async { throw UnimplementedError('isSignedIn() has not been implemented.'); } /// Clears any cached information that the plugin may be holding on to. Future clearAuthCache({required String token}) async { throw UnimplementedError('clearAuthCache() has not been implemented.'); } /// Requests the user grants additional Oauth [scopes]. /// /// Scopes should come from the full list /// [here](https://developers.google.com/identity/protocols/googlescopes). Future requestScopes(List scopes) async { throw UnimplementedError('requestScopes() has not been implmented.'); } } ================================================ FILE: packages/google_sign_in/google_sign_in_platform_interface/lib/src/method_channel_google_sign_in.dart ================================================ // Copyright 2013 The Flutter 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' show visibleForTesting; import 'package:flutter/services.dart'; import '../google_sign_in_platform_interface.dart'; import 'utils.dart'; /// An implementation of [GoogleSignInPlatform] that uses method channels. class MethodChannelGoogleSignIn extends GoogleSignInPlatform { /// This is only exposed for test purposes. It shouldn't be used by clients of /// the plugin as it may break or change at any time. @visibleForTesting MethodChannel channel = const MethodChannel('plugins.flutter.io/google_sign_in'); @override Future init({ List scopes = const [], SignInOption signInOption = SignInOption.standard, String? hostedDomain, String? clientId, }) { return initWithParams(SignInInitParameters( scopes: scopes, signInOption: signInOption, hostedDomain: hostedDomain, clientId: clientId)); } @override Future initWithParams(SignInInitParameters params) { return channel.invokeMethod('init', { 'signInOption': params.signInOption.toString(), 'scopes': params.scopes, 'hostedDomain': params.hostedDomain, 'clientId': params.clientId, 'serverClientId': params.serverClientId, 'forceCodeForRefreshToken': params.forceCodeForRefreshToken, }); } @override Future signInSilently() { return channel .invokeMapMethod('signInSilently') .then(getUserDataFromMap); } @override Future signIn() { return channel .invokeMapMethod('signIn') .then(getUserDataFromMap); } @override Future getTokens( {required String email, bool? shouldRecoverAuth = true}) { return channel .invokeMapMethod('getTokens', { 'email': email, 'shouldRecoverAuth': shouldRecoverAuth, }).then((Map? result) => getTokenDataFromMap(result!)); } @override Future signOut() { return channel.invokeMapMethod('signOut'); } @override Future disconnect() { return channel.invokeMapMethod('disconnect'); } @override Future isSignedIn() async { return (await channel.invokeMethod('isSignedIn'))!; } @override Future clearAuthCache({String? token}) { return channel.invokeMethod( 'clearAuthCache', {'token': token}, ); } @override Future requestScopes(List scopes) async { return (await channel.invokeMethod( 'requestScopes', >{'scopes': scopes}, ))!; } } ================================================ FILE: packages/google_sign_in/google_sign_in_platform_interface/lib/src/types.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/widgets.dart'; import 'package:quiver/core.dart'; /// Default configuration options to use when signing in. /// /// See also https://developers.google.com/android/reference/com/google/android/gms/auth/api/signin/GoogleSignInOptions enum SignInOption { /// Default configuration. Provides stable user ID and basic profile information. /// /// See also https://developers.google.com/android/reference/com/google/android/gms/auth/api/signin/GoogleSignInOptions.html#DEFAULT_SIGN_IN. standard, /// Recommended configuration for Games sign in. /// /// This is currently only supported on Android and will throw an error if used /// on other platforms. /// /// See also https://developers.google.com/android/reference/com/google/android/gms/auth/api/signin/GoogleSignInOptions.html#public-static-final-googlesigninoptions-default_games_sign_in. games } /// The parameters to use when initializing the sign in process. /// /// See: /// https://developers.google.com/identity/sign-in/web/reference#gapiauth2initparams @immutable class SignInInitParameters { /// The parameters to use when initializing the sign in process. const SignInInitParameters({ this.scopes = const [], this.signInOption = SignInOption.standard, this.hostedDomain, this.clientId, this.serverClientId, this.forceCodeForRefreshToken = false, }); /// The list of OAuth scope codes to request when signing in. final List scopes; /// The user experience to use when signing in. [SignInOption.games] is /// only supported on Android. final SignInOption signInOption; /// Restricts sign in to accounts of the user in the specified domain. /// By default, the list of accounts will not be restricted. final String? hostedDomain; /// The OAuth client ID of the app. /// /// The default is null, which means that the client ID will be sourced from a /// configuration file, if required on the current platform. A value specified /// here takes precedence over a value specified in a configuration file. /// See also: /// /// * [Platform Integration](https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in#platform-integration), /// where you can find the details about the configuration files. final String? clientId; /// The OAuth client ID of the backend server. /// /// The default is null, which means that the server client ID will be sourced /// from a configuration file, if available and supported on the current /// platform. A value specified here takes precedence over a value specified /// in a configuration file. /// /// See also: /// /// * [Platform Integration](https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in#platform-integration), /// where you can find the details about the configuration files. final String? serverClientId; /// If true, ensures the authorization code can be exchanged for an access /// token. /// /// This is only used on Android. final bool forceCodeForRefreshToken; } /// Holds information about the signed in user. class GoogleSignInUserData { /// Uses the given data to construct an instance. GoogleSignInUserData({ required this.email, required this.id, this.displayName, this.photoUrl, this.idToken, this.serverAuthCode, }); /// The display name of the signed in user. /// /// Not guaranteed to be present for all users, even when configured. String? displayName; /// The email address of the signed in user. /// /// Applications should not key users by email address since a Google account's /// email address can change. Use [id] as a key instead. /// /// _Important_: Do not use this returned email address to communicate the /// currently signed in user to your backend server. Instead, send an ID token /// which can be securely validated on the server. See [idToken]. String email; /// The unique ID for the Google account. /// /// This is the preferred unique key to use for a user record. /// /// _Important_: Do not use this returned Google ID to communicate the /// currently signed in user to your backend server. Instead, send an ID token /// which can be securely validated on the server. See [idToken]. String id; /// The photo url of the signed in user if the user has a profile picture. /// /// Not guaranteed to be present for all users, even when configured. String? photoUrl; /// A token that can be sent to your own server to verify the authentication /// data. String? idToken; /// Server auth code used to access Google Login String? serverAuthCode; @override // TODO(stuartmorgan): Make this class immutable in the next breaking change. // ignore: avoid_equals_and_hash_code_on_mutable_classes int get hashCode => hashObjects( [displayName, email, id, photoUrl, idToken, serverAuthCode]); @override // TODO(stuartmorgan): Make this class immutable in the next breaking change. // ignore: avoid_equals_and_hash_code_on_mutable_classes bool operator ==(Object other) { if (identical(this, other)) { return true; } if (other is! GoogleSignInUserData) { return false; } final GoogleSignInUserData otherUserData = other; return otherUserData.displayName == displayName && otherUserData.email == email && otherUserData.id == id && otherUserData.photoUrl == photoUrl && otherUserData.idToken == idToken && otherUserData.serverAuthCode == serverAuthCode; } } /// Holds authentication data after sign in. class GoogleSignInTokenData { /// Build `GoogleSignInTokenData`. GoogleSignInTokenData({ this.idToken, this.accessToken, this.serverAuthCode, }); /// An OpenID Connect ID token for the authenticated user. String? idToken; /// The OAuth2 access token used to access Google services. String? accessToken; /// Server auth code used to access Google Login String? serverAuthCode; @override // TODO(stuartmorgan): Make this class immutable in the next breaking change. // ignore: avoid_equals_and_hash_code_on_mutable_classes int get hashCode => hash3(idToken, accessToken, serverAuthCode); @override // TODO(stuartmorgan): Make this class immutable in the next breaking change. // ignore: avoid_equals_and_hash_code_on_mutable_classes bool operator ==(Object other) { if (identical(this, other)) { return true; } if (other is! GoogleSignInTokenData) { return false; } final GoogleSignInTokenData otherTokenData = other; return otherTokenData.idToken == idToken && otherTokenData.accessToken == accessToken && otherTokenData.serverAuthCode == serverAuthCode; } } ================================================ FILE: packages/google_sign_in/google_sign_in_platform_interface/lib/src/utils.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import '../google_sign_in_platform_interface.dart'; /// Converts user data coming from native code into the proper platform interface type. GoogleSignInUserData? getUserDataFromMap(Map? data) { if (data == null) { return null; } return GoogleSignInUserData( email: data['email']! as String, id: data['id']! as String, displayName: data['displayName'] as String?, photoUrl: data['photoUrl'] as String?, idToken: data['idToken'] as String?, serverAuthCode: data['serverAuthCode'] as String?); } /// Converts token data coming from native code into the proper platform interface type. GoogleSignInTokenData getTokenDataFromMap(Map data) { return GoogleSignInTokenData( idToken: data['idToken'] as String?, accessToken: data['accessToken'] as String?, serverAuthCode: data['serverAuthCode'] as String?, ); } ================================================ FILE: packages/google_sign_in/google_sign_in_platform_interface/pubspec.yaml ================================================ name: google_sign_in_platform_interface description: A common platform interface for the google_sign_in plugin. repository: https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in_platform_interface issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%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.0 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.1.0 quiver: ^3.0.0 dev_dependencies: flutter_test: sdk: flutter mockito: ^5.0.0 ================================================ FILE: packages/google_sign_in/google_sign_in_platform_interface/test/google_sign_in_platform_interface_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'package:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; void main() { // Store the initial instance before any tests change it. final GoogleSignInPlatform initialInstance = GoogleSignInPlatform.instance; group('$GoogleSignInPlatform', () { test('$MethodChannelGoogleSignIn is the default instance', () { expect(initialInstance, isA()); }); test('Cannot be implemented with `implements`', () { expect(() { GoogleSignInPlatform.instance = ImplementsGoogleSignInPlatform(); // In versions of `package:plugin_platform_interface` prior to fixing // https://github.com/flutter/flutter/issues/109339, an attempt to // implement a platform interface using `implements` would sometimes // throw a `NoSuchMethodError` and other times throw an // `AssertionError`. After the issue is fixed, an `AssertionError` will // always be thrown. For the purpose of this test, we don't really care // what exception is thrown, so just allow any exception. }, throwsA(anything)); }); test('Can be extended', () { GoogleSignInPlatform.instance = ExtendsGoogleSignInPlatform(); }); test('Can be mocked with `implements`', () { GoogleSignInPlatform.instance = ModernMockImplementation(); }); test('still supports legacy isMock', () { GoogleSignInPlatform.instance = LegacyIsMockImplementation(); }); }); group('GoogleSignInTokenData', () { test('can be compared by == operator', () { final GoogleSignInTokenData firstInstance = GoogleSignInTokenData( accessToken: 'accessToken', idToken: 'idToken', serverAuthCode: 'serverAuthCode', ); final GoogleSignInTokenData secondInstance = GoogleSignInTokenData( accessToken: 'accessToken', idToken: 'idToken', serverAuthCode: 'serverAuthCode', ); expect(firstInstance == secondInstance, isTrue); }); }); group('GoogleSignInUserData', () { test('can be compared by == operator', () { final GoogleSignInUserData firstInstance = GoogleSignInUserData( email: 'email', id: 'id', displayName: 'displayName', photoUrl: 'photoUrl', idToken: 'idToken', serverAuthCode: 'serverAuthCode', ); final GoogleSignInUserData secondInstance = GoogleSignInUserData( email: 'email', id: 'id', displayName: 'displayName', photoUrl: 'photoUrl', idToken: 'idToken', serverAuthCode: 'serverAuthCode', ); expect(firstInstance == secondInstance, isTrue); }); }); } class LegacyIsMockImplementation extends Mock implements GoogleSignInPlatform { @override bool get isMock => true; } class ModernMockImplementation extends Mock with MockPlatformInterfaceMixin implements GoogleSignInPlatform { @override bool get isMock => false; } class ImplementsGoogleSignInPlatform extends Mock implements GoogleSignInPlatform {} class ExtendsGoogleSignInPlatform extends GoogleSignInPlatform {} ================================================ FILE: packages/google_sign_in/google_sign_in_platform_interface/test/method_channel_google_sign_in_test.dart ================================================ // Copyright 2013 The Flutter 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:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'package:google_sign_in_platform_interface/src/utils.dart'; const Map kUserData = { 'email': 'john.doe@gmail.com', 'id': '8162538176523816253123', 'photoUrl': 'https://lh5.googleusercontent.com/photo.jpg', 'displayName': 'John Doe', 'idToken': '123', 'serverAuthCode': '789', }; const Map kTokenData = { 'idToken': '123', 'accessToken': '456', 'serverAuthCode': '789', }; const Map kDefaultResponses = { 'init': null, 'signInSilently': kUserData, 'signIn': kUserData, 'signOut': null, 'disconnect': null, 'isSignedIn': true, 'getTokens': kTokenData, 'requestScopes': true, }; final GoogleSignInUserData? kUser = getUserDataFromMap(kUserData); final GoogleSignInTokenData kToken = getTokenDataFromMap(kTokenData as Map); void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$MethodChannelGoogleSignIn', () { final MethodChannelGoogleSignIn googleSignIn = MethodChannelGoogleSignIn(); final MethodChannel channel = googleSignIn.channel; final List log = []; late Map responses; // Some tests mutate some kDefaultResponses setUp(() { responses = Map.from(kDefaultResponses); _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, (MethodCall methodCall) { log.add(methodCall); final dynamic response = responses[methodCall.method]; if (response != null && response is Exception) { return Future.error('$response'); } return Future.value(response); }); log.clear(); }); test('signInSilently transforms platform data to GoogleSignInUserData', () async { final dynamic response = await googleSignIn.signInSilently(); expect(response, kUser); }); test('signInSilently Exceptions -> throws', () async { responses['signInSilently'] = Exception('Not a user'); expect(googleSignIn.signInSilently(), throwsA(isInstanceOf())); }); test('signIn transforms platform data to GoogleSignInUserData', () async { final dynamic response = await googleSignIn.signIn(); expect(response, kUser); }); test('signIn Exceptions -> throws', () async { responses['signIn'] = Exception('Not a user'); expect(googleSignIn.signIn(), throwsA(isInstanceOf())); }); test('getTokens transforms platform data to GoogleSignInTokenData', () async { final dynamic response = await googleSignIn.getTokens( email: 'example@example.com', shouldRecoverAuth: false); expect(response, kToken); expect( log[0], isMethodCall('getTokens', arguments: { 'email': 'example@example.com', 'shouldRecoverAuth': false, })); }); test('Other functions pass through arguments to the channel', () async { final Map tests = { () { googleSignIn.init( hostedDomain: 'example.com', scopes: ['two', 'scopes'], signInOption: SignInOption.games, clientId: 'fakeClientId'); }: isMethodCall('init', arguments: { 'hostedDomain': 'example.com', 'scopes': ['two', 'scopes'], 'signInOption': 'SignInOption.games', 'clientId': 'fakeClientId', 'serverClientId': null, 'forceCodeForRefreshToken': false, }), () { googleSignIn.getTokens( email: 'example@example.com', shouldRecoverAuth: false); }: isMethodCall('getTokens', arguments: { 'email': 'example@example.com', 'shouldRecoverAuth': false, }), () { googleSignIn.clearAuthCache(token: 'abc'); }: isMethodCall('clearAuthCache', arguments: { 'token': 'abc', }), () { googleSignIn.requestScopes(['newScope', 'anotherScope']); }: isMethodCall('requestScopes', arguments: { 'scopes': ['newScope', 'anotherScope'], }), googleSignIn.signOut: isMethodCall('signOut', arguments: null), googleSignIn.disconnect: isMethodCall('disconnect', arguments: null), googleSignIn.isSignedIn: isMethodCall('isSignedIn', arguments: null), }; for (final void Function() f in tests.keys) { f(); } expect(log, tests.values); }); test('initWithParams passes through arguments to the channel', () async { await googleSignIn.initWithParams(const SignInInitParameters( hostedDomain: 'example.com', scopes: ['two', 'scopes'], signInOption: SignInOption.games, clientId: 'fakeClientId', serverClientId: 'fakeServerClientId', forceCodeForRefreshToken: true)); expect(log, [ isMethodCall('init', arguments: { 'hostedDomain': 'example.com', 'scopes': ['two', 'scopes'], 'signInOption': 'SignInOption.games', 'clientId': 'fakeClientId', 'serverClientId': 'fakeServerClientId', 'forceCodeForRefreshToken': true, }), ]); }); }); } /// 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; ================================================ FILE: packages/google_sign_in/google_sign_in_web/AUTHORS ================================================ # 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> ================================================ FILE: packages/google_sign_in/google_sign_in_web/CHANGELOG.md ================================================ ## 0.11.0 * **Breaking Change:** Migrates JS-interop to `package:google_identity_services_web` * Uses the new Google Identity Authentication and Authorization JS SDKs. [Docs](https://developers.google.com/identity). * Added "Migrating to v0.11" section to the `README.md`. * 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 * Migrates to new platform-interface `initWithParams` method. * Throws when unsupported `serverClientId` option is provided. ## 0.10.1+3 * Updates references to the obsolete master branch. ## 0.10.1+2 * Minor fixes for new analysis options. ## 0.10.1+1 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.10.1 * Updates minimum Flutter version to 2.8. * Passes `plugin_name` to Google Sign-In's `init` method so new applications can continue using this plugin after April 30th 2022. Issue [#88084](https://github.com/flutter/flutter/issues/88084). ## 0.10.0+5 * Internal code cleanup for stricter analysis options. ## 0.10.0+4 * Removes dependency on `meta`. ## 0.10.0+3 * Updated URL to the `google_sign_in` package in README. ## 0.10.0+2 * Add `implements` to pubspec. ## 0.10.0+1 * Updated installation instructions in README. ## 0.10.0 * Migrate to null-safety. ## 0.9.2+1 * Update Flutter SDK constraint. ## 0.9.2 * Throw PlatformExceptions from where the GMaps SDK may throw exceptions: `init()` and `signIn()`. * Add two new JS-interop types to be able to unwrap JS errors in release mode. * Align the fields of the thrown PlatformExceptions with the mobile version. * Migrate tests to run with `flutter drive` ## 0.9.1+2 * Update package:e2e reference to use the local version in the flutter/plugins repository. ## 0.9.1+1 * Remove Android folder from `google_sign_in_web`. ## 0.9.1 * Ensure the web code returns `null` when the user is not signed in, instead of a `null-object` User. Fixes [issue 52338](https://github.com/flutter/flutter/issues/52338). ## 0.9.0 * Add support for methods introduced in `google_sign_in_platform_interface` 1.1.0. ## 0.8.4 * Remove all `fakeConstructor$` from the generated facade. JS interop classes do not support non-external constructors. ## 0.8.3+2 * Make the pedantic dev_dependency explicit. ## 0.8.3+1 * Updated documentation with more instructions about Google Sign In web setup. ## 0.8.3 * Fix initialization error that causes https://github.com/flutter/flutter/issues/48527 * Throw a PlatformException when there's an initialization problem (like wrong server-side config). * Throw a StateError when checking .initialized before calling .init() * Update setup instructions in the README. ## 0.8.2+1 * Add a non-op Android implementation to avoid a flaky Gradle issue. ## 0.8.2 * Require Flutter SDK 1.12.13+hotfix.4 or greater. ## 0.8.1+2 * Remove the deprecated `author:` field from pubspec.yaml * Require Flutter SDK 1.10.0 or greater. ## 0.8.1+1 * Add missing documentation. ## 0.8.1 * Add podspec to enable compilation on iOS. ## 0.8.0 * Flutter for web initial release ================================================ FILE: packages/google_sign_in/google_sign_in_web/LICENSE ================================================ 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. ================================================ FILE: packages/google_sign_in/google_sign_in_web/README.md ================================================ # google\_sign\_in\_web The web implementation of [google_sign_in](https://pub.dev/packages/google_sign_in) ## Migrating to v0.11 (Google Identity Services) The `google_sign_in_web` plugin is backed by the new Google Identity Services (GIS) JS SDK since version 0.11.0. The GIS SDK is used both for [Authentication](https://developers.google.com/identity/gsi/web/guides/overview) and [Authorization](https://developers.google.com/identity/oauth2/web/guides/overview) flows. The GIS SDK, however, doesn't behave exactly like the one being deprecated. Some concepts have experienced pretty drastic changes, and that's why this plugin required a major version update. ### Differences between Google Identity Services SDK and Google Sign-In for Web SDK. The **Google Sign-In JavaScript for Web JS SDK** is set to be deprecated after March 31, 2023. **Google Identity Services (GIS) SDK** is the new solution to quickly and easily sign users into your app suing their Google accounts. * In the GIS SDK, Authentication and Authorization are now two separate concerns. * Authentication (information about the current user) flows will not authorize `scopes` anymore. * Authorization (permissions for the app to access certain user information) flows will not return authentication information. * The GIS SDK no longer has direct access to previously-seen users upon initialization. * `signInSilently` now displays the One Tap UX for web. * The GIS SDK only provides an `idToken` (JWT-encoded info) when the user successfully completes an authentication flow. In the plugin: `signInSilently`. * The plugin `signIn` method uses the Oauth "Implicit Flow" to Authorize the requested `scopes`. * If the user hasn't `signInSilently`, they'll have to sign in as a first step of the Authorization popup flow. * If `signInSilently` was unsuccessful, the plugin will add extra `scopes` to `signIn` and retrieve basic Profile information from the People API via a REST call immediately after a successful authorization. In this case, the `idToken` field of the `GoogleSignInUserData` will always be null. * The GIS SDK no longer handles sign-in state and user sessions, it only provides Authentication credentials for the moment the user did authenticate. * The GIS SDK no longer is able to renew Authorization sessions on the web. Once the token expires, API requests will begin to fail with unauthorized, and user Authorization is required again. See more differences in the following migration guides: * Authentication > [Migrating from Google Sign-In](https://developers.google.com/identity/gsi/web/guides/migration) * Authorization > [Migrate to Google Identity Services](https://developers.google.com/identity/oauth2/web/guides/migration-to-gis) ### New use cases to take into account in your app #### Enable access to the People API for your GCP project Since the GIS SDK is separating Authentication from Authorization, the [Oauth Implicit pop-up flow](https://developers.google.com/identity/oauth2/web/guides/use-token-model) used to Authorize scopes does **not** return any Authentication information anymore (user credential / `idToken`). If the plugin is not able to Authenticate an user from `signInSilently` (the OneTap UX flow), it'll add extra `scopes` to those requested by the programmer so it can perform a [People API request](https://developers.google.com/people/api/rest/v1/people/get) to retrieve basic profile information about the user that is signed-in. The information retrieved from the People API is used to complete data for the [`GoogleSignInAccount`](https://pub.dev/documentation/google_sign_in/latest/google_sign_in/GoogleSignInAccount-class.html) object that is returned after `signIn` completes successfully. #### `signInSilently` always returns `null` Previous versions of this plugin were able to return a `GoogleSignInAccount` object that was fully populated (signed-in and authorized) from `signInSilently` because the former SDK equated "is authenticated" and "is authorized". With the GIS SDK, `signInSilently` only deals with user Authentication, so users retrieved "silently" will only contain an `idToken`, but not an `accessToken`. Only after `signIn` or `requestScopes`, a user will be fully formed. The GIS-backed plugin always returns `null` from `signInSilently`, to force apps that expect the former logic to perform a full `signIn`, which will result in a fully Authenticated and Authorized user, and making this migration easier. #### `idToken` is `null` in the `GoogleSignInAccount` object after `signIn` Since the GIS SDK is separating Authentication and Authorization, when a user fails to Authenticate through `signInSilently` and the plugin performs the fallback request to the People API described above, the returned `GoogleSignInUserData` object will contain basic profile information (name, email, photo, ID), but its `idToken` will be `null`. This is because JWT are cryptographically signed by Google Identity Services, and this plugin won't spoof that signature when it retrieves the information from a simple REST request. #### User Sessions Since the GIS SDK does _not_ manage user sessions anymore, apps that relied on this feature might break. If long-lived sessions are required, consider using some user authentication system that supports Google Sign In as a federated Authentication provider, like [Firebase Auth](https://firebase.google.com/docs/auth/flutter/federated-auth#google), or similar. #### Expired / Invalid Authorization Tokens Since the GIS SDK does _not_ auto-renew authorization tokens anymore, it's now the responsibility of your app to do so. Apps now need to monitor the status code of their REST API requests for response codes different to `200`. For example: * `401`: Missing or invalid access token. * `403`: Expired access token. In either case, your app needs to prompt the end user to `signIn` or `requestScopes`, to interactively renew the token. The GIS SDK limits authorization token duration to one hour (3600 seconds). ## Usage ### Import the package This package is [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin), which means you can simply use `google_sign_in` normally. This package will be automatically included in your app when you do. ### Web integration First, go through the instructions [here](https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid) to create your Google Sign-In OAuth client ID. On your `web/index.html` file, add the following `meta` tag, somewhere in the `head` of the document: ```html ``` For this client to work correctly, the last step is to configure the **Authorized JavaScript origins**, which _identify the domains from which your application can send API requests._ When in local development, this is normally `localhost` and some port. You can do this by: 1. Going to the [Credentials page](https://console.developers.google.com/apis/credentials). 2. Clicking "Edit" in the OAuth 2.0 Web application client that you created above. 3. Adding the URIs you want to the **Authorized JavaScript origins**. For local development, you must add two `localhost` entries: * `http://localhost` and * `http://localhost:7357` (or any port that is free in your machine) #### Starting flutter in http://localhost:7357 Normally `flutter run` starts in a random port. In the case where you need to deal with authentication like the above, that's not the most appropriate behavior. You can tell `flutter run` to listen for requests in a specific host and port with the following: ```sh flutter run -d chrome --web-hostname localhost --web-port 7357 ``` ### Other APIs Read the rest of the instructions if you need to add extra APIs (like Google People API). ### Using the plugin See the [**Usage** instructions of `package:google_sign_in`](https://pub.dev/packages/google_sign_in#usage) Note that the **`serverClientId` parameter of the `GoogleSignIn` constructor is not supported on Web.** ## Example Find the example wiring in the [Google sign-in example application](https://github.com/flutter/plugins/blob/main/packages/google_sign_in/google_sign_in/example/lib/main.dart). ## API details See [google_sign_in.dart](https://github.com/flutter/plugins/blob/main/packages/google_sign_in/google_sign_in/lib/google_sign_in.dart) for more API details. ## Contributions and Testing Tests are crucial for contributions to this package. All new contributions should be reasonably tested. **Check the [`test/README.md` file](https://github.com/flutter/plugins/blob/main/packages/google_sign_in/google_sign_in_web/test/README.md)** for more information on how to run tests on this package. Contributions to this package are welcome. Read the [Contributing to Flutter Plugins](https://github.com/flutter/plugins/blob/main/CONTRIBUTING.md) guide to get started. ## Issues and feedback Please file [issues](https://github.com/flutter/flutter/issues/new) to send feedback or report a bug. **Thank you!** ================================================ FILE: packages/google_sign_in/google_sign_in_web/example/README.md ================================================ # 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. ## Testing This package uses `package:integration_test` to run its tests in a web browser. See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/wiki/Plugin-Tests#web-tests) in the Flutter wiki for instructions to setup and run the tests in this package. Check [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) for more info. ================================================ FILE: packages/google_sign_in/google_sign_in_web/example/build.yaml ================================================ targets: $default: sources: - integration_test/*.dart - lib/$lib$ - $package$ ================================================ FILE: packages/google_sign_in/google_sign_in_web/example/integration_test/google_sign_in_web_test.dart ================================================ // Copyright 2013 The Flutter 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 PlatformException; import 'package:flutter_test/flutter_test.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'package:google_sign_in_web/google_sign_in_web.dart'; import 'package:google_sign_in_web/src/gis_client.dart'; import 'package:google_sign_in_web/src/people.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart' as mockito; import 'google_sign_in_web_test.mocks.dart'; import 'src/dom.dart'; import 'src/person.dart'; // Mock GisSdkClient so we can simulate any response from the JS side. @GenerateMocks([], customMocks: >[ MockSpec(onMissingStub: OnMissingStub.returnDefault), ]) void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('Constructor', () { const String expectedClientId = '3xp3c73d_c113n7_1d'; testWidgets('Loads clientId when set in a meta', (_) async { final GoogleSignInPlugin plugin = GoogleSignInPlugin( debugOverrideLoader: true, ); expect(plugin.autoDetectedClientId, isNull); // Add it to the test page now, and try again final DomHtmlMetaElement meta = document.createElement('meta') as DomHtmlMetaElement ..name = clientIdMetaName ..content = expectedClientId; document.head.appendChild(meta); final GoogleSignInPlugin another = GoogleSignInPlugin( debugOverrideLoader: true, ); expect(another.autoDetectedClientId, expectedClientId); // cleanup meta.remove(); }); }); group('initWithParams', () { late GoogleSignInPlugin plugin; late MockGisSdkClient mockGis; setUp(() { plugin = GoogleSignInPlugin( debugOverrideLoader: true, ); mockGis = MockGisSdkClient(); }); testWidgets('initializes if all is OK', (_) async { await plugin.initWithParams( const SignInInitParameters( clientId: 'some-non-null-client-id', scopes: ['ok1', 'ok2', 'ok3'], ), overrideClient: mockGis, ); expect(plugin.initialized, completes); }); testWidgets('asserts clientId is not null', (_) async { expect(() async { await plugin.initWithParams( const SignInInitParameters(), overrideClient: mockGis, ); }, throwsAssertionError); }); testWidgets('asserts serverClientId must be null', (_) async { expect(() async { await plugin.initWithParams( const SignInInitParameters( clientId: 'some-non-null-client-id', serverClientId: 'unexpected-non-null-client-id', ), overrideClient: mockGis, ); }, throwsAssertionError); }); testWidgets('asserts no scopes have any spaces', (_) async { expect(() async { await plugin.initWithParams( const SignInInitParameters( clientId: 'some-non-null-client-id', scopes: ['ok1', 'ok2', 'not ok', 'ok3'], ), overrideClient: mockGis, ); }, throwsAssertionError); }); testWidgets('must be called for most of the API to work', (_) async { expect(() async { await plugin.signInSilently(); }, throwsStateError); expect(() async { await plugin.signIn(); }, throwsStateError); expect(() async { await plugin.getTokens(email: ''); }, throwsStateError); expect(() async { await plugin.signOut(); }, throwsStateError); expect(() async { await plugin.disconnect(); }, throwsStateError); expect(() async { await plugin.isSignedIn(); }, throwsStateError); expect(() async { await plugin.clearAuthCache(token: ''); }, throwsStateError); expect(() async { await plugin.requestScopes([]); }, throwsStateError); }); }); group('(with mocked GIS)', () { late GoogleSignInPlugin plugin; late MockGisSdkClient mockGis; const SignInInitParameters options = SignInInitParameters( clientId: 'some-non-null-client-id', scopes: ['ok1', 'ok2', 'ok3'], ); setUp(() { plugin = GoogleSignInPlugin( debugOverrideLoader: true, ); mockGis = MockGisSdkClient(); }); group('signInSilently', () { setUp(() { plugin.initWithParams(options, overrideClient: mockGis); }); testWidgets('always returns null, regardless of GIS response', (_) async { final GoogleSignInUserData someUser = extractUserData(person)!; mockito .when(mockGis.signInSilently()) .thenAnswer((_) => Future.value(someUser)); expect(plugin.signInSilently(), completion(isNull)); mockito .when(mockGis.signInSilently()) .thenAnswer((_) => Future.value()); expect(plugin.signInSilently(), completion(isNull)); }); }); group('signIn', () { setUp(() { plugin.initWithParams(options, overrideClient: mockGis); }); testWidgets('returns the signed-in user', (_) async { final GoogleSignInUserData someUser = extractUserData(person)!; mockito .when(mockGis.signIn()) .thenAnswer((_) => Future.value(someUser)); expect(await plugin.signIn(), someUser); }); testWidgets('returns null if no user is signed in', (_) async { mockito .when(mockGis.signIn()) .thenAnswer((_) => Future.value()); expect(await plugin.signIn(), isNull); }); testWidgets('converts inner errors to PlatformException', (_) async { mockito.when(mockGis.signIn()).thenThrow('popup_closed'); try { await plugin.signIn(); fail('signIn should have thrown an exception'); } catch (exception) { expect(exception, isA()); expect((exception as PlatformException).code, 'popup_closed'); } }); }); }); } ================================================ FILE: packages/google_sign_in/google_sign_in_web/example/integration_test/google_sign_in_web_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in google_sign_in_web_integration_tests/integration_test/google_sign_in_web_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i4; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart' as _i2; import 'package:google_sign_in_web/src/gis_client.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class class _FakeGoogleSignInTokenData_0 extends _i1.SmartFake implements _i2.GoogleSignInTokenData { _FakeGoogleSignInTokenData_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [GisSdkClient]. /// /// See the documentation for Mockito's code generation for more information. class MockGisSdkClient extends _i1.Mock implements _i3.GisSdkClient { @override _i4.Future<_i2.GoogleSignInUserData?> signInSilently() => (super.noSuchMethod( Invocation.method( #signInSilently, [], ), returnValue: _i4.Future<_i2.GoogleSignInUserData?>.value(), returnValueForMissingStub: _i4.Future<_i2.GoogleSignInUserData?>.value(), ) as _i4.Future<_i2.GoogleSignInUserData?>); @override _i4.Future<_i2.GoogleSignInUserData?> signIn() => (super.noSuchMethod( Invocation.method( #signIn, [], ), returnValue: _i4.Future<_i2.GoogleSignInUserData?>.value(), returnValueForMissingStub: _i4.Future<_i2.GoogleSignInUserData?>.value(), ) as _i4.Future<_i2.GoogleSignInUserData?>); @override _i2.GoogleSignInTokenData getTokens() => (super.noSuchMethod( Invocation.method( #getTokens, [], ), returnValue: _FakeGoogleSignInTokenData_0( this, Invocation.method( #getTokens, [], ), ), returnValueForMissingStub: _FakeGoogleSignInTokenData_0( this, Invocation.method( #getTokens, [], ), ), ) as _i2.GoogleSignInTokenData); @override _i4.Future signOut() => (super.noSuchMethod( Invocation.method( #signOut, [], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future disconnect() => (super.noSuchMethod( Invocation.method( #disconnect, [], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future isSignedIn() => (super.noSuchMethod( Invocation.method( #isSignedIn, [], ), returnValue: _i4.Future.value(false), returnValueForMissingStub: _i4.Future.value(false), ) as _i4.Future); @override _i4.Future clearAuthCache() => (super.noSuchMethod( Invocation.method( #clearAuthCache, [], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future requestScopes(List? scopes) => (super.noSuchMethod( Invocation.method( #requestScopes, [scopes], ), returnValue: _i4.Future.value(false), returnValueForMissingStub: _i4.Future.value(false), ) as _i4.Future); } ================================================ FILE: packages/google_sign_in/google_sign_in_web/example/integration_test/people_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_identity_services_web/oauth2.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'package:google_sign_in_web/src/people.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart' as http_test; import 'package:integration_test/integration_test.dart'; import 'src/jsify_as.dart'; import 'src/person.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('requestUserData', () { const String expectedAccessToken = '3xp3c73d_4cc355_70k3n'; final TokenResponse fakeToken = jsifyAs({ 'token_type': 'Bearer', 'access_token': expectedAccessToken, }); testWidgets('happy case', (_) async { final Completer accessTokenCompleter = Completer(); final http.Client mockClient = http_test.MockClient( (http.Request request) async { accessTokenCompleter.complete(request.headers['Authorization']); return http.Response( jsonEncode(person), 200, headers: {'content-type': 'application/json'}, ); }, ); final GoogleSignInUserData? user = await requestUserData( fakeToken, overrideClient: mockClient, ); expect(user, isNotNull); expect(user!.email, expectedPersonEmail); expect(user.id, expectedPersonId); expect(user.displayName, expectedPersonName); expect(user.photoUrl, expectedPersonPhoto); expect(user.idToken, isNull); expect( accessTokenCompleter.future, completion('Bearer $expectedAccessToken'), ); }); testWidgets('Unauthorized request - throws exception', (_) async { final http.Client mockClient = http_test.MockClient( (http.Request request) async { return http.Response( 'Unauthorized', 403, ); }, ); expect(() async { await requestUserData( fakeToken, overrideClient: mockClient, ); }, throwsA(isA())); }); }); group('extractUserData', () { testWidgets('happy case', (_) async { final GoogleSignInUserData? user = extractUserData(person); expect(user, isNotNull); expect(user!.email, expectedPersonEmail); expect(user.id, expectedPersonId); expect(user.displayName, expectedPersonName); expect(user.photoUrl, expectedPersonPhoto); expect(user.idToken, isNull); }); testWidgets('no name/photo - keeps going', (_) async { final Map personWithoutSomeData = mapWithoutKeys(person, { 'names', 'photos', }); final GoogleSignInUserData? user = extractUserData(personWithoutSomeData); expect(user, isNotNull); expect(user!.email, expectedPersonEmail); expect(user.id, expectedPersonId); expect(user.displayName, isNull); expect(user.photoUrl, isNull); expect(user.idToken, isNull); }); testWidgets('no userId - throws assertion error', (_) async { final Map personWithoutId = mapWithoutKeys(person, { 'resourceName', }); expect(() { extractUserData(personWithoutId); }, throwsAssertionError); }); testWidgets('no email - throws assertion error', (_) async { final Map personWithoutEmail = mapWithoutKeys(person, { 'emailAddresses', }); expect(() { extractUserData(personWithoutEmail); }, throwsAssertionError); }); }); } ================================================ FILE: packages/google_sign_in/google_sign_in_web/example/integration_test/src/dom.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /* // DOM shim. This file contains everything we need from the DOM API written as // @staticInterop, so we don't need dart:html // https://developer.mozilla.org/en-US/docs/Web/API/ // // (To be replaced by `package:web`) */ import 'package:js/js.dart'; /// Document interface @JS() @staticInterop abstract class DomHtmlDocument {} /// Some methods of document extension DomHtmlDocumentExtension on DomHtmlDocument { /// document.head external DomHtmlElement get head; /// document.createElement external DomHtmlElement createElement(String tagName); } /// An instance of an HTMLElement @JS() @staticInterop abstract class DomHtmlElement {} /// (Some) methods of HtmlElement extension DomHtmlElementExtension on DomHtmlElement { /// Node.appendChild external DomHtmlElement appendChild(DomHtmlElement child); /// Element.remove external void remove(); } /// An instance of an HTMLMetaElement @JS() @staticInterop abstract class DomHtmlMetaElement extends DomHtmlElement {} /// Some methods exclusive of Script elements extension DomHtmlMetaElementExtension on DomHtmlMetaElement { external set name(String name); external set content(String content); } // Getters /// window.document @JS() @staticInterop external DomHtmlDocument get document; ================================================ FILE: packages/google_sign_in/google_sign_in_web/example/integration_test/src/jsify_as.dart ================================================ // Copyright 2013 The Flutter 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:js/js_util.dart' as js_util; /// Converts a [data] object into a JS Object of type `T`. T jsifyAs(Map data) { return js_util.jsify(data) as T; } ================================================ FILE: packages/google_sign_in/google_sign_in_web/example/integration_test/src/jwt_examples.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:google_identity_services_web/id.dart'; import 'jsify_as.dart'; /// A CredentialResponse with null `credential`. final CredentialResponse nullCredential = jsifyAs({ 'credential': null, }); /// A CredentialResponse wrapping a known good JWT Token as its `credential`. final CredentialResponse goodCredential = jsifyAs({ 'credential': goodJwtToken, }); /// A JWT token with predefined values. /// /// 'email': 'adultman@example.com', /// 'sub': '123456', /// 'name': 'Vincent Adultman', /// 'picture': 'https://thispersondoesnotexist.com/image?x=.jpg', /// /// Signed with HS256 and the private key: 'symmetric-encryption-is-weak' const String goodJwtToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.$goodPayload.lqzULA_U3YzEl_-fL7YLU-kFXmdD2ttJLTv-UslaNQ4'; /// The payload of a JWT token that contains predefined values. /// /// 'email': 'adultman@example.com', /// 'sub': '123456', /// 'name': 'Vincent Adultman', /// 'picture': 'https://thispersondoesnotexist.com/image?x=.jpg', const String goodPayload = 'eyJlbWFpbCI6ImFkdWx0bWFuQGV4YW1wbGUuY29tIiwic3ViIjoiMTIzNDU2IiwibmFtZSI6IlZpbmNlbnQgQWR1bHRtYW4iLCJwaWN0dXJlIjoiaHR0cHM6Ly90aGlzcGVyc29uZG9lc25vdGV4aXN0LmNvbS9pbWFnZT94PS5qcGcifQ'; // More encrypted JWT Tokens may be created on https://jwt.io. // // First, decode the `goodJwtToken` above, modify to your heart's // content, and add a new credential here. // // (New tokens can also be created with `package:jose` and `dart:convert`.) ================================================ FILE: packages/google_sign_in/google_sign_in_web/example/integration_test/src/person.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. const String expectedPersonId = '1234567890'; const String expectedPersonName = 'Vincent Adultman'; const String expectedPersonEmail = 'adultman@example.com'; const String expectedPersonPhoto = 'https://thispersondoesnotexist.com/image?x=.jpg'; /// A subset of https://developers.google.com/people/api/rest/v1/people#Person. final Map person = { 'resourceName': 'people/$expectedPersonId', 'emailAddresses': [ { 'metadata': { 'primary': false, }, 'value': 'bad@example.com', }, { 'metadata': {}, 'value': 'nope@example.com', }, { 'metadata': { 'primary': true, }, 'value': expectedPersonEmail, }, ], 'names': [ { 'metadata': { 'primary': true, }, 'displayName': expectedPersonName, }, { 'metadata': { 'primary': false, }, 'displayName': 'Fakey McFakeface', }, ], 'photos': [ { 'metadata': { 'primary': true, }, 'url': expectedPersonPhoto, }, ], }; /// Returns a copy of [map] without the [keysToRemove]. T mapWithoutKeys>( T map, Set keysToRemove, ) { return Map.fromEntries( map.entries.where((MapEntry entry) { return !keysToRemove.contains(entry.key); }), ) as T; } ================================================ FILE: packages/google_sign_in/google_sign_in_web/example/integration_test/utils_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_identity_services_web/id.dart'; import 'package:google_identity_services_web/oauth2.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'package:google_sign_in_web/src/utils.dart'; import 'package:integration_test/integration_test.dart'; import 'src/jsify_as.dart'; import 'src/jwt_examples.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('gisResponsesToTokenData', () { testWidgets('null objects -> no problem', (_) async { final GoogleSignInTokenData tokens = gisResponsesToTokenData(null, null); expect(tokens.accessToken, isNull); expect(tokens.idToken, isNull); expect(tokens.serverAuthCode, isNull); }); testWidgets('non-null objects are correctly used', (_) async { const String expectedIdToken = 'some-value-for-testing'; const String expectedAccessToken = 'another-value-for-testing'; final CredentialResponse credential = jsifyAs({ 'credential': expectedIdToken, }); final TokenResponse token = jsifyAs({ 'access_token': expectedAccessToken, }); final GoogleSignInTokenData tokens = gisResponsesToTokenData(credential, token); expect(tokens.accessToken, expectedAccessToken); expect(tokens.idToken, expectedIdToken); expect(tokens.serverAuthCode, isNull); }); }); group('gisResponsesToUserData', () { testWidgets('happy case', (_) async { final GoogleSignInUserData data = gisResponsesToUserData(goodCredential)!; expect(data.displayName, 'Vincent Adultman'); expect(data.id, '123456'); expect(data.email, 'adultman@example.com'); expect(data.photoUrl, 'https://thispersondoesnotexist.com/image?x=.jpg'); expect(data.idToken, goodJwtToken); }); testWidgets('null response -> null', (_) async { expect(gisResponsesToUserData(null), isNull); }); testWidgets('null response.credential -> null', (_) async { expect(gisResponsesToUserData(nullCredential), isNull); }); testWidgets('invalid payload -> null', (_) async { final CredentialResponse response = jsifyAs({ 'credential': 'some-bogus.thing-that-is-not.valid-jwt', }); expect(gisResponsesToUserData(response), isNull); }); }); group('getJwtTokenPayload', () { testWidgets('happy case -> data', (_) async { final Map? data = getJwtTokenPayload(goodJwtToken); expect(data, isNotNull); expect(data, containsPair('name', 'Vincent Adultman')); expect(data, containsPair('email', 'adultman@example.com')); expect(data, containsPair('sub', '123456')); expect( data, containsPair( 'picture', 'https://thispersondoesnotexist.com/image?x=.jpg', )); }); testWidgets('null Token -> null', (_) async { final Map? data = getJwtTokenPayload(null); expect(data, isNull); }); testWidgets('Token not matching the format -> null', (_) async { final Map? data = getJwtTokenPayload('1234.4321'); expect(data, isNull); }); testWidgets('Bad token that matches the format -> null', (_) async { final Map? data = getJwtTokenPayload('1234.abcd.4321'); expect(data, isNull); }); }); group('decodeJwtPayload', () { testWidgets('Good payload -> data', (_) async { final Map? data = decodeJwtPayload(goodPayload); expect(data, isNotNull); expect(data, containsPair('name', 'Vincent Adultman')); expect(data, containsPair('email', 'adultman@example.com')); expect(data, containsPair('sub', '123456')); expect( data, containsPair( 'picture', 'https://thispersondoesnotexist.com/image?x=.jpg', )); }); testWidgets('Proper JSON payload -> data', (_) async { final String payload = base64.encode(utf8.encode('{"properJson": true}')); final Map? data = decodeJwtPayload(payload); expect(data, isNotNull); expect(data, containsPair('properJson', true)); }); testWidgets('Not-normalized base-64 payload -> data', (_) async { // This is the payload generated by the "Proper JSON payload" test, but // we remove the leading "=" symbols so it's length is not a multiple of 4 // anymore! final String payload = 'eyJwcm9wZXJKc29uIjogdHJ1ZX0='.replaceAll('=', ''); final Map? data = decodeJwtPayload(payload); expect(data, isNotNull); expect(data, containsPair('properJson', true)); }); testWidgets('Invalid JSON payload -> null', (_) async { final String payload = base64.encode(utf8.encode('{properJson: false}')); final Map? data = decodeJwtPayload(payload); expect(data, isNull); }); testWidgets('Non JSON payload -> null', (_) async { final String payload = base64.encode(utf8.encode('not-json')); final Map? data = decodeJwtPayload(payload); expect(data, isNull); }); testWidgets('Non base-64 payload -> null', (_) async { const String payload = 'not-base-64-at-all'; final Map? data = decodeJwtPayload(payload); expect(data, isNull); }); }); } ================================================ FILE: packages/google_sign_in/google_sign_in_web/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } /// App for testing class MyApp extends StatefulWidget { /// Default Constructor const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override Widget build(BuildContext context) { return const Text('Testing... Look at the console output for results!'); } } ================================================ FILE: packages/google_sign_in/google_sign_in_web/example/pubspec.yaml ================================================ name: google_sign_in_web_integration_tests publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter google_sign_in_web: path: ../ dev_dependencies: build_runner: ^2.1.1 flutter_driver: sdk: flutter flutter_test: sdk: flutter google_identity_services_web: ^0.2.0 google_sign_in_platform_interface: ^2.2.0 http: ^0.13.0 integration_test: sdk: flutter js: ^0.6.3 mockito: ^5.3.2 ================================================ FILE: packages/google_sign_in/google_sign_in_web/example/regen_mocks.sh ================================================ #!/usr/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. flutter pub get echo "(Re)generating mocks." flutter pub run build_runner build --delete-conflicting-outputs ================================================ FILE: packages/google_sign_in/google_sign_in_web/example/run_test.sh ================================================ #!/usr/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. if pgrep -lf chromedriver > /dev/null; then echo "chromedriver is running." ./regen_mocks.sh if [ $# -eq 0 ]; then echo "No target specified, running all tests..." find integration_test/ -iname *_test.dart | xargs -n1 -i -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target='{}' else echo "Running test target: $1..." set -x flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target=$1 fi else echo "chromedriver is not running." echo "Please, check the README.md for instructions on how to use run_test.sh" fi ================================================ FILE: packages/google_sign_in/google_sign_in_web/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/google_sign_in/google_sign_in_web/example/web/index.html ================================================ Browser Tests ================================================ FILE: packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart ================================================ // Copyright 2013 The Flutter 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 'dart:html' as html; import 'package:flutter/foundation.dart' show visibleForTesting, kDebugMode; import 'package:flutter/services.dart' show PlatformException; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:google_identity_services_web/loader.dart' as loader; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'src/gis_client.dart'; /// The `name` of the meta-tag to define a ClientID in HTML. const String clientIdMetaName = 'google-signin-client_id'; /// The selector used to find the meta-tag that defines a ClientID in HTML. const String clientIdMetaSelector = 'meta[name=$clientIdMetaName]'; /// The attribute name that stores the Client ID in the meta-tag that defines a Client ID in HTML. const String clientIdAttributeName = 'content'; /// Implementation of the google_sign_in plugin for Web. class GoogleSignInPlugin extends GoogleSignInPlatform { /// Constructs the plugin immediately and begins initializing it in the /// background. /// /// The plugin is completely initialized when [initialized] completed. GoogleSignInPlugin({@visibleForTesting bool debugOverrideLoader = false}) { autoDetectedClientId = html .querySelector(clientIdMetaSelector) ?.getAttribute(clientIdAttributeName); if (debugOverrideLoader) { _jsSdkLoadedFuture = Future.value(true); } else { _jsSdkLoadedFuture = loader.loadWebSdk(); } } late Future _jsSdkLoadedFuture; bool _isInitCalled = false; // The instance of [GisSdkClient] backing the plugin. late GisSdkClient _gisClient; // This method throws if init or initWithParams hasn't been called at some // point in the past. It is used by the [initialized] getter to ensure that // users can't await on a Future that will never resolve. void _assertIsInitCalled() { if (!_isInitCalled) { throw StateError( 'GoogleSignInPlugin::init() or GoogleSignInPlugin::initWithParams() ' 'must be called before any other method in this plugin.', ); } } /// A future that resolves when the SDK has been correctly loaded. @visibleForTesting Future get initialized { _assertIsInitCalled(); return _jsSdkLoadedFuture; } /// Stores the client ID if it was set in a meta-tag of the page. @visibleForTesting late String? autoDetectedClientId; /// Factory method that initializes the plugin with [GoogleSignInPlatform]. static void registerWith(Registrar registrar) { GoogleSignInPlatform.instance = GoogleSignInPlugin(); } @override Future init({ List scopes = const [], SignInOption signInOption = SignInOption.standard, String? hostedDomain, String? clientId, }) { return initWithParams(SignInInitParameters( scopes: scopes, signInOption: signInOption, hostedDomain: hostedDomain, clientId: clientId, )); } @override Future initWithParams( SignInInitParameters params, { @visibleForTesting GisSdkClient? overrideClient, }) async { final String? appClientId = params.clientId ?? autoDetectedClientId; assert( appClientId != null, 'ClientID not set. Either set it on a ' ' tag,' ' or pass clientId when initializing GoogleSignIn'); assert(params.serverClientId == null, 'serverClientId is not supported on Web.'); assert( !params.scopes.any((String scope) => scope.contains(' ')), "OAuth 2.0 Scopes for Google APIs can't contain spaces. " 'Check https://developers.google.com/identity/protocols/googlescopes ' 'for a list of valid OAuth 2.0 scopes.'); await _jsSdkLoadedFuture; _gisClient = overrideClient ?? GisSdkClient( clientId: appClientId!, hostedDomain: params.hostedDomain, initialScopes: List.from(params.scopes), loggingEnabled: kDebugMode, ); _isInitCalled = true; } @override Future signInSilently() async { await initialized; // Since the new GIS SDK does *not* perform authorization at the same time as // authentication (and every one of our users expects that), we need to tell // the plugin that this failed regardless of the actual result. // // However, if this succeeds, we'll save a People API request later. return _gisClient.signInSilently().then((_) => null); } @override Future signIn() async { await initialized; // This method mainly does oauth2 authorization, which happens to also do // authentication if needed. However, the authentication information is not // returned anymore. // // This method will synthesize authentication information from the People API // if needed (or use the last identity seen from signInSilently). try { return _gisClient.signIn(); } catch (reason) { throw PlatformException( code: reason.toString(), message: 'Exception raised from signIn', details: 'https://developers.google.com/identity/oauth2/web/guides/error', ); } } @override Future getTokens({ required String email, bool? shouldRecoverAuth, }) async { await initialized; return _gisClient.getTokens(); } @override Future signOut() async { await initialized; _gisClient.signOut(); } @override Future disconnect() async { await initialized; _gisClient.disconnect(); } @override Future isSignedIn() async { await initialized; return _gisClient.isSignedIn(); } @override Future clearAuthCache({required String token}) async { await initialized; _gisClient.clearAuthCache(); } @override Future requestScopes(List scopes) async { await initialized; return _gisClient.requestScopes(scopes); } } ================================================ FILE: packages/google_sign_in/google_sign_in_web/lib/src/gis_client.dart ================================================ // Copyright 2013 The Flutter 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'; // TODO(dit): Split `id` and `oauth2` "services" for mocking. https://github.com/flutter/flutter/issues/120657 import 'package:google_identity_services_web/id.dart'; import 'package:google_identity_services_web/oauth2.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; // ignore: unnecessary_import import 'package:js/js.dart'; import 'package:js/js_util.dart'; import 'people.dart' as people; import 'utils.dart' as utils; /// A client to hide (most) of the interaction with the GIS SDK from the plugin. /// /// (Overridable for testing) class GisSdkClient { /// Create a GisSdkClient object. GisSdkClient({ required List initialScopes, required String clientId, bool loggingEnabled = false, String? hostedDomain, }) : _initialScopes = initialScopes { if (loggingEnabled) { id.setLogLevel('debug'); } // Configure the Stream objects that are going to be used by the clients. _configureStreams(); // Initialize the SDK clients we need. _initializeIdClient( clientId, onResponse: _onCredentialResponse, ); _tokenClient = _initializeTokenClient( clientId, hostedDomain: hostedDomain, onResponse: _onTokenResponse, onError: _onTokenError, ); } // Configure the credential (authentication) and token (authorization) response streams. void _configureStreams() { _tokenResponses = StreamController.broadcast(); _credentialResponses = StreamController.broadcast(); _tokenResponses.stream.listen((TokenResponse response) { _lastTokenResponse = response; }, onError: (Object error) { _lastTokenResponse = null; }); _credentialResponses.stream.listen((CredentialResponse response) { _lastCredentialResponse = response; }, onError: (Object error) { _lastCredentialResponse = null; }); } // Initializes the `id` SDK for the silent-sign in (authentication) client. void _initializeIdClient( String clientId, { required CallbackFn onResponse, }) { // Initialize `id` for the silent-sign in code. final IdConfiguration idConfig = IdConfiguration( client_id: clientId, callback: allowInterop(onResponse), cancel_on_tap_outside: false, auto_select: true, // Attempt to sign-in silently. ); id.initialize(idConfig); } // Handle a "normal" credential (authentication) response. // // (Normal doesn't mean successful, this might contain `error` information.) void _onCredentialResponse(CredentialResponse response) { if (response.error != null) { _credentialResponses.addError(response.error!); } else { _credentialResponses.add(response); } } // Creates a `oauth2.TokenClient` used for authorization (scope) requests. TokenClient _initializeTokenClient( String clientId, { String? hostedDomain, required TokenClientCallbackFn onResponse, required ErrorCallbackFn onError, }) { // Create a Token Client for authorization calls. final TokenClientConfig tokenConfig = TokenClientConfig( client_id: clientId, hosted_domain: hostedDomain, callback: allowInterop(_onTokenResponse), error_callback: allowInterop(_onTokenError), // `scope` will be modified by the `signIn` method, in case we need to // backfill user Profile info. scope: ' ', ); return oauth2.initTokenClient(tokenConfig); } // Handle a "normal" token (authorization) response. // // (Normal doesn't mean successful, this might contain `error` information.) void _onTokenResponse(TokenResponse response) { if (response.error != null) { _tokenResponses.addError(response.error!); } else { _tokenResponses.add(response); } } // Handle a "not-directly-related-to-authorization" error. // // Token clients have an additional `error_callback` for miscellaneous // errors, like "popup couldn't open" or "popup closed by user". void _onTokenError(Object? error) { // This is handled in a funky (js_interop) way because of: // https://github.com/dart-lang/sdk/issues/50899 _tokenResponses.addError(getProperty(error!, 'type')); } /// Attempts to sign-in the user using the OneTap UX flow. /// /// If the user consents, to OneTap, the [GoogleSignInUserData] will be /// generated from a proper [CredentialResponse], which contains `idToken`. /// Else, it'll be synthesized by a request to the People API later, and the /// `idToken` will be null. Future signInSilently() async { final Completer userDataCompleter = Completer(); // Ask the SDK to render the OneClick sign-in. // // And also handle its "moments". id.prompt(allowInterop((PromptMomentNotification moment) { _onPromptMoment(moment, userDataCompleter); })); return userDataCompleter.future; } // Handles "prompt moments" of the OneClick card UI. // // See: https://developers.google.com/identity/gsi/web/guides/receive-notifications-prompt-ui-status Future _onPromptMoment( PromptMomentNotification moment, Completer completer, ) async { if (completer.isCompleted) { return; // Skip once the moment has been handled. } if (moment.isDismissedMoment() && moment.getDismissedReason() == MomentDismissedReason.credential_returned) { // Kick this part of the handler to the bottom of the JS event queue, so // the _credentialResponses stream has time to propagate its last value, // and we can use _lastCredentialResponse. return Future.delayed(Duration.zero, () { completer .complete(utils.gisResponsesToUserData(_lastCredentialResponse)); }); } // In any other 'failed' moments, return null and add an error to the stream. if (moment.isNotDisplayed() || moment.isSkippedMoment() || moment.isDismissedMoment()) { final String reason = moment.getNotDisplayedReason()?.toString() ?? moment.getSkippedReason()?.toString() ?? moment.getDismissedReason()?.toString() ?? 'unknown_error'; _credentialResponses.addError(reason); completer.complete(null); } } /// Starts an oauth2 "implicit" flow to authorize requests. /// /// The new GIS SDK does not return user authentication from this flow, so: /// * If [_lastCredentialResponse] is **not** null (the user has successfully /// `signInSilently`), we return that after this method completes. /// * If [_lastCredentialResponse] is null, we add [people.scopes] to the /// [_initialScopes], so we can retrieve User Profile information back /// from the People API (without idToken). See [people.requestUserData]. Future signIn() async { // If we already know the user, use their `email` as a `hint`, so they don't // have to pick their user again in the Authorization popup. final GoogleSignInUserData? knownUser = utils.gisResponsesToUserData(_lastCredentialResponse); // This toggles a popup, so `signIn` *must* be called with // user activation. _tokenClient.requestAccessToken(OverridableTokenClientConfig( prompt: knownUser == null ? 'select_account' : '', hint: knownUser?.email, scope: [ ..._initialScopes, // If the user hasn't gone through the auth process, // the plugin will attempt to `requestUserData` after, // so we need extra scopes to retrieve that info. if (_lastCredentialResponse == null) ...people.scopes, ].join(' '), )); await _tokenResponses.stream.first; return _computeUserDataForLastToken(); } // This function returns the currently signed-in [GoogleSignInUserData]. // // It'll do a request to the People API (if needed). Future _computeUserDataForLastToken() async { // If the user hasn't authenticated, request their basic profile info // from the People API. // // This synthetic response will *not* contain an `idToken` field. if (_lastCredentialResponse == null && _requestedUserData == null) { assert(_lastTokenResponse != null); _requestedUserData = await people.requestUserData(_lastTokenResponse!); } // Complete user data either with the _lastCredentialResponse seen, // or the synthetic _requestedUserData from above. return utils.gisResponsesToUserData(_lastCredentialResponse) ?? _requestedUserData; } /// Returns a [GoogleSignInTokenData] from the latest seen responses. GoogleSignInTokenData getTokens() { return utils.gisResponsesToTokenData( _lastCredentialResponse, _lastTokenResponse, ); } /// Revokes the current authentication. Future signOut() async { clearAuthCache(); id.disableAutoSelect(); } /// Revokes the current authorization and authentication. Future disconnect() async { if (_lastTokenResponse != null) { oauth2.revoke(_lastTokenResponse!.access_token); } signOut(); } /// Returns true if the client has recognized this user before. Future isSignedIn() async { return _lastCredentialResponse != null || _requestedUserData != null; } /// Clears all the cached results from authentication and authorization. Future clearAuthCache() async { _lastCredentialResponse = null; _lastTokenResponse = null; _requestedUserData = null; } /// Requests the list of [scopes] passed in to the client. /// /// Keeps the previously granted scopes. Future requestScopes(List scopes) async { _tokenClient.requestAccessToken(OverridableTokenClientConfig( scope: scopes.join(' '), include_granted_scopes: true, )); await _tokenResponses.stream.first; return oauth2.hasGrantedAllScopes(_lastTokenResponse!, scopes); } // The scopes initially requested by the developer. // // We store this because we might need to add more at `signIn`. If the user // doesn't `silentSignIn`, we expand this list to consult the People API to // return some basic Authentication information. final List _initialScopes; // The Google Identity Services client for oauth requests. late TokenClient _tokenClient; // Streams of credential and token responses. late StreamController _credentialResponses; late StreamController _tokenResponses; // The last-seen credential and token responses CredentialResponse? _lastCredentialResponse; TokenResponse? _lastTokenResponse; // If the user *authenticates* (signs in) through oauth2, the SDK doesn't return // identity information anymore, so we synthesize it by calling the PeopleAPI // (if needed) // // (This is a synthetic _lastCredentialResponse) GoogleSignInUserData? _requestedUserData; } ================================================ FILE: packages/google_sign_in/google_sign_in_web/lib/src/people.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:google_identity_services_web/oauth2.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'package:http/http.dart' as http; /// Basic scopes for self-id const List scopes = [ 'https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/userinfo.email', ]; /// People API to return my profile info... const String MY_PROFILE = 'https://content-people.googleapis.com/v1/people/me' '?sources=READ_SOURCE_TYPE_PROFILE' '&personFields=photos%2Cnames%2CemailAddresses'; /// Requests user data from the People API using the given [tokenResponse]. Future requestUserData( TokenResponse tokenResponse, { @visibleForTesting http.Client? overrideClient, }) async { // Request my profile from the People API. final Map person = await _doRequest( MY_PROFILE, tokenResponse, overrideClient: overrideClient, ); // Now transform the Person response into a GoogleSignInUserData. return extractUserData(person); } /// Extracts user data from a Person resource. /// /// See: https://developers.google.com/people/api/rest/v1/people#Person GoogleSignInUserData? extractUserData(Map json) { final String? userId = _extractUserId(json); final String? email = _extractPrimaryField( json['emailAddresses'] as List?, 'value', ); assert(userId != null); assert(email != null); return GoogleSignInUserData( id: userId!, email: email!, displayName: _extractPrimaryField( json['names'] as List?, 'displayName', ), photoUrl: _extractPrimaryField( json['photos'] as List?, 'url', ), // Synthetic user data doesn't contain an idToken! ); } /// Extracts the ID from a Person resource. /// /// The User ID looks like this: /// { /// 'resourceName': 'people/PERSON_ID', /// ... /// } String? _extractUserId(Map profile) { final String? resourceName = profile['resourceName'] as String?; return resourceName?.split('/').last; } /// Extracts the [fieldName] marked as 'primary' from a list of [values]. /// /// Values can be one of: /// * `emailAddresses` /// * `names` /// * `photos` /// /// From a Person object. T? _extractPrimaryField(List? values, String fieldName) { if (values != null) { for (final Object? value in values) { if (value != null && value is Map) { final bool isPrimary = _extractPath( value, path: ['metadata', 'primary'], defaultValue: false, ); if (isPrimary) { return value[fieldName] as T?; } } } } return null; } /// Attempts to get the property in [path] of type `T` from a deeply nested [source]. /// /// Returns [default] if the property is not found. T _extractPath( Map source, { required List path, required T defaultValue, }) { final String valueKey = path.removeLast(); Object? data = source; for (final String key in path) { if (data != null && data is Map) { data = data[key]; } else { break; } } if (data != null && data is Map) { return (data[valueKey] ?? defaultValue) as T; } else { return defaultValue; } } /// Gets from [url] with an authorization header defined by [token]. /// /// Attempts to [jsonDecode] the result. Future> _doRequest( String url, TokenResponse token, { http.Client? overrideClient, }) async { final Uri uri = Uri.parse(url); final http.Client client = overrideClient ?? http.Client(); try { final http.Response response = await client.get(uri, headers: { 'Authorization': '${token.token_type} ${token.access_token}', }); if (response.statusCode != 200) { throw http.ClientException(response.body, uri); } return jsonDecode(response.body) as Map; } finally { client.close(); } } ================================================ FILE: packages/google_sign_in/google_sign_in_web/lib/src/utils.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:convert'; import 'package:google_identity_services_web/id.dart'; import 'package:google_identity_services_web/oauth2.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; /// A codec that can encode/decode JWT payloads. /// /// See https://www.rfc-editor.org/rfc/rfc7519#section-3 final Codec jwtCodec = json.fuse(utf8).fuse(base64); /// A RegExp that can match, and extract parts from a JWT Token. /// /// A JWT token consists of 3 base-64 encoded parts of data separated by periods: /// /// header.payload.signature /// /// More info: https://regexr.com/789qc final RegExp jwtTokenRegexp = RegExp( r'^(?

[^\.\s]+)\.(?[^\.\s]+)\.(?[^\.\s]+)$'); /// Decodes the `claims` of a JWT token and returns them as a Map. /// /// JWT `claims` are stored as a JSON object in the `payload` part of the token. /// /// (This method does not validate the signature of the token.) /// /// See https://www.rfc-editor.org/rfc/rfc7519#section-3 Map? getJwtTokenPayload(String? token) { if (token != null) { final RegExpMatch? match = jwtTokenRegexp.firstMatch(token); if (match != null) { return decodeJwtPayload(match.namedGroup('payload')); } } return null; } /// Decodes a JWT payload using the [jwtCodec]. Map? decodeJwtPayload(String? payload) { try { // Payload must be normalized before passing it to the codec return jwtCodec.decode(base64.normalize(payload!)) as Map?; } catch (_) { // Do nothing, we always return null for any failure. } return null; } /// Converts a [CredentialResponse] into a [GoogleSignInUserData]. /// /// May return `null`, if the `credentialResponse` is null, or its `credential` /// cannot be decoded. GoogleSignInUserData? gisResponsesToUserData( CredentialResponse? credentialResponse) { if (credentialResponse == null || credentialResponse.credential == null) { return null; } final Map? payload = getJwtTokenPayload(credentialResponse.credential); if (payload == null) { return null; } return GoogleSignInUserData( email: payload['email']! as String, id: payload['sub']! as String, displayName: payload['name']! as String, photoUrl: payload['picture']! as String, idToken: credentialResponse.credential, ); } /// Converts responses from the GIS library into TokenData for the plugin. GoogleSignInTokenData gisResponsesToTokenData( CredentialResponse? credentialResponse, TokenResponse? tokenResponse) { return GoogleSignInTokenData( idToken: credentialResponse?.credential, accessToken: tokenResponse?.access_token, ); } ================================================ FILE: packages/google_sign_in/google_sign_in_web/pubspec.yaml ================================================ name: google_sign_in_web 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.11.0 environment: sdk: ">=2.17.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: google_sign_in platforms: web: pluginClass: GoogleSignInPlugin fileName: google_sign_in_web.dart dependencies: flutter: sdk: flutter flutter_web_plugins: sdk: flutter google_identity_services_web: ^0.2.0 google_sign_in_platform_interface: ^2.2.0 http: ^0.13.5 js: ^0.6.3 dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/google_sign_in/google_sign_in_web/test/README.md ================================================ ## test This package uses integration tests for testing. See `example/README.md` for more info. ================================================ FILE: packages/google_sign_in/google_sign_in_web/test/tests_exist_elsewhere_test.dart ================================================ // Copyright 2013 The Flutter 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: avoid_print import 'package:flutter_test/flutter_test.dart'; void main() { test('Tell the user where to find the real tests', () { print('---'); print('This package uses integration_test for its tests.'); print('See `example/README.md` for more info.'); print('---'); }); } ================================================ FILE: packages/image_picker/image_picker/AUTHORS ================================================ # 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> ================================================ FILE: packages/image_picker/image_picker/CHANGELOG.md ================================================ ## 0.8.6+2 * Updates `NSPhotoLibraryUsageDescription` description in README. * 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. * Fixes avoid_redundant_argument_values lint warnings and minor typos. * Adds `requestFullMetadata` option to `pickImage`, so images on iOS can be picked without `Photo Library Usage` permission. ## 0.8.5+3 * Adds argument error assertions to the app-facing package, to ensure consistency across platform implementations. * Updates tests to use a mock platform instead of relying on default method channel implementation internals. ## 0.8.5+2 * Minor fixes for new analysis options. ## 0.8.5+1 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.8.5 * Moves Android and iOS implementations to federated packages. * Adds OS version support information to README. ## 0.8.4+11 * Fixes Activity leak. ## 0.8.4+10 * iOS: allows picking images with WebP format. ## 0.8.4+9 * Internal code cleanup for stricter analysis options. ## 0.8.4+8 * Configures the `UIImagePicker` to default to gallery instead of camera when picking multiple images on pre-iOS 14 devices. ## 0.8.4+7 * Refactors unit test to expose private interface via a separate test header instead of the inline declaration. ## 0.8.4+6 * Fixes minor type issues in iOS implementation. ## 0.8.4+5 * Improves the documentation on handling MainActivity being killed by the Android OS. * Updates Android compileSdkVersion to 31. * Fix iOS RunnerUITests search paths. ## 0.8.4+4 * Fix typos in README.md. ## 0.8.4+3 * Suppress a unchecked cast build warning. ## 0.8.4+2 * Update minimum Flutter SDK to 2.5 and iOS deployment target to 9.0. ## 0.8.4+1 * Fix README Example for `ImagePickerCache` to cache multiple files. ## 0.8.4 * Update `ImagePickerCache` to cache multiple files. ## 0.8.3+3 * Fix pickImage not returning a value on iOS when dismissing PHPicker sheet by swiping. * Updated Android lint settings. ## 0.8.3+2 * Fix using Camera as image source on Android 11+ ## 0.8.3+1 * Fixed README Example. ## 0.8.3 * Move `ImagePickerFromLimitedGalleryUITests` to `RunnerUITests` target. * Improved handling of bad image data when applying metadata changes on iOS. ## 0.8.2 * Added new methods that return `package:cross_file` `XFile` instances. [Docs](https://pub.dev/documentation/cross_file/latest/index.html). * Deprecate methods that return `PickedFile` instances: * `getImage`: use **`pickImage`** instead. * `getVideo`: use **`pickVideo`** instead. * `getMultiImage`: use **`pickMultiImage`** instead. * `getLostData`: use **`retrieveLostData`** instead. ## 0.8.1+4 * Fixes an issue where `preferredCameraDevice` option is not working for `getVideo` method. * Refactor unit tests that were device-only before. ## 0.8.1+3 * Fix image picker causing a crash when the cache directory is deleted. ## 0.8.1+2 * Update the example app to support the multi-image feature. ## 0.8.1+1 * Expose errors thrown in `pickImage` and `pickVideo` docs. ## 0.8.1 * Add a new method `getMultiImage` to allow picking multiple images on iOS 14 or higher and Android 4.3 or higher. Returns only 1 image for lower versions of iOS and Android. * Known issue: On Android, `getLostData` will only get the last picked image when picking multiple images, see: [#84634](https://github.com/flutter/flutter/issues/84634). ## 0.8.0+4 * Cleaned up the README example ## 0.8.0+3 * Readded request for camera permissions. ## 0.8.0+2 * Fix a rotation problem where when camera is chosen as a source and additional parameters are added. ## 0.8.0+1 * Removed redundant request for camera permissions. ## 0.8.0 * BREAKING CHANGE: Changed storage location for captured images and videos to internal cache on Android, to comply with new Google Play storage requirements. This means developers are responsible for moving the image or video to a different location in case more permanent storage is required. Other applications will no longer be able to access images or videos captured unless they are moved to a publicly accessible location. * Updated Mockito to fix Android tests. ## 0.7.5+4 * Migrate maven repo from jcenter to mavenCentral. ## 0.7.5+3 * Localize `UIAlertController` strings. ## 0.7.5+2 * Implement `UIAlertController` with a preferredStyle of `UIAlertControllerStyleAlert` since `UIAlertView` is deprecated. ## 0.7.5+1 * Fixes a rotation problem where Select Photos limited access is chosen but the image that is picked is not included selected photos and image is scaled. ## 0.7.5 * Fixes an issue where image rotation is wrong when Select Photos chose and image is scaled. * Migrate to PHPicker for iOS 14 and higher versions to pick image from the photo library. * Implement the limited permission to pick photo from the photo library when Select Photo is chosen. ## 0.7.4 * Update flutter_plugin_android_lifecycle dependency to 2.0.1 to fix an R8 issue on some versions. ## 0.7.3 * Endorse image_picker_for_web. ## 0.7.2+1 * Android: fixes an issue where videos could be wrongly picked with `.jpg` extension. ## 0.7.2 * Run CocoaPods iOS tests in RunnerUITests target. ## 0.7.1 * Update platform_plugin_interface version requirement. ## 0.7.0 * Migrate to nullsafety * Breaking Changes: * Removed the deprecated methods: `ImagePicker.pickImage`, `ImagePicker.pickVideo`, `ImagePicker.retrieveLostData` ## 0.6.7+22 * iOS: update XCUITests to separate each test session. ## 0.6.7+21 * Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets. ## 0.6.7+20 * Updated README.md to show the new Android API requirements. ## 0.6.7+19 * Do not copy static field to another static field. ## 0.6.7+18 * Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) ## 0.6.7+17 * iOS: fix `User-facing text should use localized string macro` warning. ## 0.6.7+16 * Update Flutter SDK constraint. ## 0.6.7+15 * Fix element type in XCUITests to look for staticText type when searching for texts. * See https://github.com/flutter/flutter/issues/71927 * Minor update in XCUITests to search for different elements on iOS 14 and above. ## 0.6.7+14 * Set up XCUITests. ## 0.6.7+13 * Update documentation of `getImage()` about HEIC images. ## 0.6.7+12 * Update android compileSdkVersion to 29. ## 0.6.7+11 * Keep handling deprecated Android v1 classes for backward compatibility. ## 0.6.7+10 * Updated documentation with code that does not throw an error when image is not picked. ## 0.6.7+9 * Updated the ExifInterface to the AndroidX version to support more file formats; * Update documentation of `getImage()` regarding compression support for specific image types. ## 0.6.7+8 * Update documentation of getImage() about Android's disability to preference front/rear camera. ## 0.6.7+7 * Updating documentation to use isEmpty check. ## 0.6.7+6 * Update package:e2e -> package:integration_test ## 0.6.7+5 * Update package:e2e reference to use the local version in the flutter/plugins repository. ## 0.6.7+4 * Support iOS simulator x86_64 architecture. ## 0.6.7+3 * Fixes to the example app: * Make videos in web start muted. This allows auto-play across browsers. * Prevent the app from disposing of video controllers too early. ## 0.6.7+2 * iOS: Fixes unpresentable album/image picker if window's root view controller is already presenting other view controller. ## 0.6.7+1 * Add web support to the example app. ## 0.6.7 * Utilize the new platform_interface package. * **This change marks old methods as `deprecated`. Please check the README for migration instructions to the new API.** ## 0.6.6+5 * Pin the version of the platform interface to 1.0.0 until the plugin refactor is ready to go. ## 0.6.6+4 * Fix bug, sometimes double click cancel button will crash. ## 0.6.6+3 * Update README ## 0.6.6+2 * Update lower bound of dart dependency to 2.1.0. ## 0.6.6+1 * Android: always use URI to get image/video data. ## 0.6.6 * Use the new platform_interface package. ## 0.6.5+3 * Move core plugin to a subdirectory to allow for federation. ## 0.6.5+2 * iOS: Fixes crash when an image in the gallery is tapped more than once. ## 0.6.5+1 * Fix CocoaPods podspec lint warnings. ## 0.6.5 * Set maximum duration for video recording. * Fix some existing XCTests. ## 0.6.4 * Add a new parameter to select preferred camera device. ## 0.6.3+4 * Make the pedantic dev_dependency explicit. ## 0.6.3+3 * Android: Fix a crash when `externalFilesDirectory` does not exist. ## 0.6.3+2 * Bump RoboElectric dependency to 4.3.1 and update resource usage. ## 0.6.3+1 * Fix an issue that the example app won't launch the image picker after Android V2 embedding migration. ## 0.6.3 * Support Android V2 embedding. * Migrate to using the new e2e test binding. ## 0.6.2+3 * Remove the deprecated `author:` field from pubspec.yaml * Migrate the plugin to the pubspec platforms manifest. * Require Flutter SDK 1.10.0 or greater. ## 0.6.2+2 * Android: Revert the image file return logic when the image doesn't have to be scaled. Fix a rotation regression caused by 0.6.2+1 * Example App: Add a dialog to enter `maxWidth`, `maxHeight` or `quality` when picking image. ## 0.6.2+1 * Android: Fix a crash when a non-image file is picked. * Android: Fix unwanted bitmap scaling. ## 0.6.2 * iOS: Fixes an issue where picking content from Gallery would result in a crash on iOS 13. ## 0.6.1+11 * Stability and Maintainability: update documentations, add unit tests. ## 0.6.1+10 * iOS: Fix image orientation problems when scaling images. ## 0.6.1+9 * Remove AndroidX warning. ## 0.6.1+8 * Fix iOS build and analyzer warnings. ## 0.6.1+7 * Android: Fix ImagePickerPlugin#onCreate casting context which causes exception. ## 0.6.1+6 * Define clang module for iOS ## 0.6.1+5 * Update and migrate iOS example project. ## 0.6.1+4 * Android: Fix a regression where the `retrieveLostImage` does not work anymore. * Set up Android unit test to test `ImagePickerCache` and added image quality caching tests. ## 0.6.1+3 * Bugfix iOS: Fix orientation of the picked image after scaling. * Remove unnecessary code that tried to normalize the orientation. * Trivial XCTest code fix. ## 0.6.1+2 * Replace dependency on `androidx.legacy:legacy-support-v4:1.0.0` with `androidx.core:core:1.0.2` ## 0.6.1+1 * Add dependency on `androidx.annotation:annotation:1.0.0`. ## 0.6.1 * New feature : Get images with custom quality. While picking images, user can pass `imageQuality` parameter to compress image. ## 0.6.0+20 * Android: Migrated information cache methods to use instance methods. ## 0.6.0+19 * Android: Fix memory leak due not unregistering ActivityLifecycleCallbacks. ## 0.6.0+18 * Fix video play in example and update video_player plugin dependency. ## 0.6.0+17 * iOS: Fix a crash when user captures image from the camera with devices under iOS 11. ## 0.6.0+16 * iOS Simulator: fix hang after trying to take an image from the non-existent camera. ## 0.6.0+15 * Android: throws an exception when permissions denied instead of ignoring. ## 0.6.0+14 * Fix typo in README. ## 0.6.0+13 * Bugfix Android: Fix a crash occurs in some scenarios when user picks up image from gallery. ## 0.6.0+12 * Use class instead of struct for `GIFInfo` in iOS implementation. ## 0.6.0+11 * Don't use module imports. ## 0.6.0+10 * iOS: support picking GIF from gallery. ## 0.6.0+9 * Add missing template type parameter to `invokeMethod` calls. * Bump minimum Flutter version to 1.5.0. * Replace invokeMethod with invokeMapMethod wherever necessary. ## 0.6.0+8 * Bugfix: Add missed return statement into the image_picker example. ## 0.6.0+7 * iOS: Rename objects to follow Objective-C naming convention to avoid conflicts with other iOS library/frameworks. ## 0.6.0+6 * iOS: Picked image now has all the correct meta data from the original image, includes GPS, orientation and etc. ## 0.6.0+5 * iOS: Add missing import. ## 0.6.0+4 * iOS: Using first byte to determine original image type. * iOS: Added XCTest target. * iOS: The picked image now has the correct EXIF data copied from the original image. ## 0.6.0+3 * Android: fixed assertion failures due to reply messages that were sent on the wrong thread. ## 0.6.0+2 * Android: images are saved with their real extension instead of always using `.jpg`. ## 0.6.0+1 * Android: Using correct suffix syntax when picking image from remote url. ## 0.6.0 * Breaking change iOS: Returned `File` objects when picking videos now always holds the correct path. Before this change, the path returned could have `file://` prepended to it. ## 0.5.4+3 * Fix the example app failing to load picked video. ## 0.5.4+2 * Request Camera permission if it present in Manifest on Android >= M. ## 0.5.4+1 * Bugfix iOS: Cancel button not visible in gallery, if camera was accessed first. ## 0.5.4 * Add `retrieveLostData` to retrieve lost data after MainActivity is killed. ## 0.5.3+2 * Android: fix a crash when the MainActivity is destroyed after selecting the image/video. ## 0.5.3+1 * Update minimum deploy iOS version to 8.0. ## 0.5.3 * Fixed incorrect path being returned from Google Photos on Android. ## 0.5.2 * Check iOS camera authorizationStatus and return an error, if the access was denied. ## 0.5.1 * Android: Do not delete original image after scaling if the image is from gallery. ## 0.5.0+9 * Remove unnecessary temp video file path. ## 0.5.0+8 * Fixed wrong GooglePhotos authority of image Uri. ## 0.5.0+7 * Fix a crash when selecting images from yandex.disk and dropbox. ## 0.5.0+6 * Delete the original image if it was scaled. ## 0.5.0+5 * Remove unnecessary camera permission. ## 0.5.0+4 * Preserve transparency when saving images. ## 0.5.0+3 * Fixed an Android crash when Image Picker is registered without an activity. ## 0.5.0+2 * Log a more detailed warning at build time about the previous AndroidX migration. ## 0.5.0+1 * Fix a crash when user calls the plugin in quick succession on Android. ## 0.5.0 * **Breaking change**. Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to [also migrate](https://developer.android.com/jetpack/androidx/migrate) if they're using the original support library. ## 0.4.12+1 * Fix a crash when selecting downloaded images from image picker on certain devices. ## 0.4.12 * Fix a crash when user tap the image mutiple times. ## 0.4.11 * Use `api` to define `support-v4` dependency to allow automatic version resolution. ## 0.4.10 * Depend on full `support-v4` library for ease of use (fixes conflicts with Firebase and libraries) ## 0.4.9 * Bugfix: on iOS prevent to appear one pixel white line on resized image. ## 0.4.8 * Replace the full `com.android.support:appcompat-v7` dependency with `com.android.support:support-core-utils`, which results in smaller APK sizes. * Upgrade support library to 27.1.1 ## 0.4.7 * Added missing video_player package dev dependency. ## 0.4.6 * Added support for picking remote images. ## 0.4.5 * Bugfixes, code cleanup, more test coverage. ## 0.4.4 * Updated Gradle tooling to match Android Studio 3.1.2. ## 0.4.3 * Bugfix: on iOS the `pickVideo` method will now return null when the user cancels picking a video. ## 0.4.2 * Added support for picking videos. * Updated example app to show video preview. ## 0.4.1 * Bugfix: the `pickImage` method will now return null when the user cancels picking the image, instead of hanging indefinitely. * Removed the third party library dependency for taking pictures with the camera. ## 0.4.0 * **Breaking change**. The `source` parameter for the `pickImage` is now required. Also, the `ImageSource.any` option doesn't exist anymore. * Use the native Android image gallery for picking images instead of a custom UI. ## 0.3.1 * Bugfix: Android version correctly asks for runtime camera permission when using `ImageSource.camera`. ## 0.3.0 * **Breaking change**. Set SDK constraints to match the Flutter beta release. ## 0.2.1 * Simplified and upgraded Android project template to Android SDK 27. * Updated package description. ## 0.2.0 * **Breaking change**. Upgraded to Gradle 4.1 and Android Studio Gradle plugin 3.0.1. Older Flutter projects need to upgrade their Gradle setup as well in order to use this version of the plugin. Instructions can be found [here](https://github.com/flutter/flutter/wiki/Updating-Flutter-projects-to-Gradle-4.1-and-Android-Studio-Gradle-plugin-3.0.1). ## 0.1.5 * Added FLT prefix to iOS types ## 0.1.4 * Bugfix: canceling image picking threw exception. * Bugfix: errors in plugin state management. ## 0.1.3 * Added optional source argument to pickImage for controlling where the image comes from. ## 0.1.2 * Added optional maxWidth and maxHeight arguments to pickImage. ## 0.1.1 * Updated Gradle repositories declaration to avoid the need for manual configuration in the consuming app. ## 0.1.0+1 * Updated readme and description in pubspec.yaml ## 0.1.0 * Updated dependencies * **Breaking Change**: You need to add a maven section with the "https://maven.google.com" endpoint to the repository section of your `android/build.gradle`. For example: ```gradle allprojects { repositories { jcenter() maven { // NEW url "https://maven.google.com" // NEW } // NEW } } ``` ## 0.0.3 * Fix for crash on iPad when showing the Camera/Gallery selection dialog ## 0.0.2+2 * Updated README ## 0.0.2+1 * Updated README ## 0.0.2 * Fix crash when trying to access camera on a device without camera (e.g. the Simulator) ## 0.0.1 * Initial Release ================================================ FILE: packages/image_picker/image_picker/LICENSE ================================================ image_picker 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. -------------------------------------------------------------------------------- aFileChooser Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2011 - 2013 Paul Burke Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: packages/image_picker/image_picker/README.md ================================================ # Image Picker plugin for Flutter [![pub package](https://img.shields.io/pub/v/image_picker.svg)](https://pub.dev/packages/image_picker) A Flutter plugin for iOS and Android for picking images from the image library, and taking new pictures with the camera. | | Android | iOS | Web | |-------------|---------|--------|----------------------------------| | **Support** | SDK 21+ | iOS 9+ | [See `image_picker_for_web `][1] | ## Installation First, add `image_picker` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels). ### iOS This plugin requires iOS 9.0 or higher. Starting with version **0.8.1** the iOS implementation uses PHPicker to pick (multiple) images on iOS 14 or higher. As a result of implementing PHPicker it becomes impossible to pick HEIC images on the iOS simulator in iOS 14+. This is a known issue. Please test this on a real device, or test with non-HEIC images until Apple solves this issue. [63426347 - Apple known issue](https://www.google.com/search?q=63426347+apple&sxsrf=ALeKk01YnTMid5S0PYvhL8GbgXJ40ZS[…]t=gws-wiz&ved=0ahUKEwjKh8XH_5HwAhWL_rsIHUmHDN8Q4dUDCA8&uact=5) Add the following keys to your _Info.plist_ file, located in `/ios/Runner/Info.plist`: * `NSPhotoLibraryUsageDescription` - describe why your app needs permission for the photo library. This is called _Privacy - Photo Library Usage Description_ in the visual editor. * This permission will not be requested if you always pass `false` for `requestFullMetadata`, but App Store policy requires including the plist entry. * `NSCameraUsageDescription` - describe why your app needs access to the camera. This is called _Privacy - Camera Usage Description_ in the visual editor. * `NSMicrophoneUsageDescription` - describe why your app needs access to the microphone, if you intend to record videos. This is called _Privacy - Microphone Usage Description_ in the visual editor. ### Android Starting with version **0.8.1** the Android implementation support to pick (multiple) images on Android 4.3 or higher. No configuration required - the plugin should work out of the box. It is however highly recommended to prepare for Android killing the application when low on memory. How to prepare for this is discussed in the [Handling MainActivity destruction on Android](#handling-mainactivity-destruction-on-android) section. It is no longer required to add `android:requestLegacyExternalStorage="true"` as an attribute to the `` tag in AndroidManifest.xml, as `image_picker` has been updated to make use of scoped storage. **Note:** Images and videos picked using the camera are saved to your application's local cache, and should therefore be expected to only be around temporarily. If you require your picked image to be stored permanently, it is your responsibility to move it to a more permanent location. ### Example ``` dart import 'package:image_picker/image_picker.dart'; ... final ImagePicker _picker = ImagePicker(); // Pick an image final XFile? image = await _picker.pickImage(source: ImageSource.gallery); // Capture a photo final XFile? photo = await _picker.pickImage(source: ImageSource.camera); // Pick a video final XFile? image = await _picker.pickVideo(source: ImageSource.gallery); // Capture a video final XFile? video = await _picker.pickVideo(source: ImageSource.camera); // Pick multiple images final List? images = await _picker.pickMultiImage(); ... ``` ### Handling MainActivity destruction on Android When under high memory pressure the Android system may kill the MainActivity of the application using the image_picker. On Android the image_picker makes use of the default `Intent.ACTION_GET_CONTENT` or `MediaStore.ACTION_IMAGE_CAPTURE` intents. This means that while the intent is executing the source application is moved to the background and becomes eligable for cleanup when the system is low on memory. When the intent finishes executing, Android will restart the application. Since the data is never returned to the original call use the `ImagePicker.retrieveLostData()` method to retrieve the lost data. For example: ```dart Future getLostData() async { final LostDataResponse response = await picker.retrieveLostData(); if (response.isEmpty) { return; } if (response.files != null) { for (final XFile file in response.files) { _handleFile(file); } } else { _handleError(response.exception); } } ``` This check should always be run at startup in order to detect and handle this case. Please refer to the [example app](https://pub.dev/packages/image_picker/example) for a more complete example of handling this flow. ## Migrating to 0.8.2+ Starting with version **0.8.2** of the image_picker plugin, new methods have been added for picking files that return `XFile` instances (from the [cross_file](https://pub.dev/packages/cross_file) package) rather than the plugin's own `PickedFile` instances. While the previous methods still exist, it is already recommended to start migrating over to their new equivalents. Eventually, `PickedFile` and the methods that return instances of it will be deprecated and removed. #### Call the new methods | Old API | New API | |---------|---------| | `PickedFile image = await _picker.getImage(...)` | `XFile image = await _picker.pickImage(...)` | | `List images = await _picker.getMultiImage(...)` | `List images = await _picker.pickMultiImage(...)` | | `PickedFile video = await _picker.getVideo(...)` | `XFile video = await _picker.pickVideo(...)` | | `LostData response = await _picker.getLostData()` | `LostDataResponse response = await _picker.retrieveLostData()` | [1]: https://pub.dev/packages/image_picker_for_web#limitations-on-the-web-platform ================================================ FILE: packages/image_picker/image_picker/example/README.md ================================================ # image_picker_example Demonstrates how to use the image_picker plugin. ================================================ FILE: packages/image_picker/image_picker/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 testOptions.unitTests.includeAndroidResources = true lintOptions { disable 'InvalidPackage' } defaultConfig { applicationId "io.flutter.plugins.imagepicker.example" minSdkVersion 16 targetSdkVersion 28 multiDexEnabled true versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } testOptions { unitTests.returnDefaultValues = true } } flutter { source '../..' } dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' api 'androidx.test:core:1.2.0' } ================================================ FILE: packages/image_picker/image_picker/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/image_picker/image_picker/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/image_picker/image_picker/example/android/app/src/androidTest/java/io/flutter/plugins/imagepickerexample/FlutterActivityTest.java ================================================ // Copyright 2013 The Flutter 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.imagepickerexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class FlutterActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/image_picker/image_picker/example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: packages/image_picker/image_picker/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/image_picker/image_picker/example/android/app/src/main/java/io/flutter/plugins/.gitignore ================================================ GeneratedPluginRegistrant.java ================================================ FILE: packages/image_picker/image_picker/example/android/app/src/main/java/io/flutter/plugins/imagepickerexample/ImagePickerTestActivity.java ================================================ // Copyright 2013 The Flutter 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.imagepickerexample; import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; // Makes the FlutterEngine accessible for testing. public class ImagePickerTestActivity extends FlutterActivity { public FlutterEngine engine; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); engine = flutterEngine; } } ================================================ FILE: packages/image_picker/image_picker/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/image_picker/image_picker/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/image_picker/image_picker/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true ================================================ FILE: packages/image_picker/image_picker/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withInputStream { stream -> plugins.load(stream) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/image_picker/image_picker/example/integration_test/image_picker_test.dart ================================================ // Copyright 2013 The Flutter 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'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('placeholder test', (WidgetTester tester) async {}); } ================================================ FILE: packages/image_picker/image_picker/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 UIRequiredDeviceCapabilities arm64 MinimumOSVersion 9.0 ================================================ FILE: packages/image_picker/image_picker/example/ios/Flutter/Debug.xcconfig ================================================ #include "Generated.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" ================================================ FILE: packages/image_picker/image_picker/example/ios/Flutter/Release.xcconfig ================================================ #include "Generated.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" ================================================ FILE: packages/image_picker/image_picker/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/image_picker/image_picker/example/ios/Runner/.gitignore ================================================ GeneratedPluginRegistrant.h GeneratedPluginRegistrant.m ================================================ FILE: packages/image_picker/image_picker/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/image_picker/image_picker/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/image_picker/image_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "idiom" : "ios-marketing", "size" : "1024x1024", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/image_picker/image_picker/example/ios/Runner/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/image_picker/image_picker/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/image_picker/image_picker/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/image_picker/image_picker/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName image_picker_example CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS NSCameraUsageDescription Used to demonstrate image picker plugin NSMicrophoneUsageDescription Used to capture audio for image picker plugin NSPhotoLibraryUsageDescription Used to demonstrate image picker plugin UIBackgroundModes remote-notification UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: packages/image_picker/image_picker/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/image_picker/image_picker/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 5C9513011EC38BD300040975 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C9513001EC38BD300040975 /* GeneratedPluginRegistrant.m */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 9FC8F0E9229FA49E00C8D58F /* gifImage.gif in Resources */ = {isa = PBXBuildFile; fileRef = 9FC8F0E8229FA49E00C8D58F /* gifImage.gif */; }; F4F7A436CCA4BF276270A3AE /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EC32F6993F4529982D9519F1 /* libPods-Runner.a */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0C7B151765FD4249454C49AD /* 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 = ""; }; 35AE65F25E0B8C8214D8372B /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 5A9D31B91557877A0E8EF3E7 /* 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 = ""; }; 5C9512FF1EC38BD300040975 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 5C9513001EC38BD300040975 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 680049352280F2B8006DD6AB /* pngImage.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = pngImage.png; sourceTree = ""; }; 680049362280F2B8006DD6AB /* jpgImage.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = jpgImage.jpg; sourceTree = ""; }; 6801632E632668F4349764C9 /* 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 = ""; }; 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 = ""; }; 86E9A88F272747B90017E6E0 /* webpImage.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = webpImage.webp; 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 = ""; }; 9FC8F0E8229FA49E00C8D58F /* gifImage.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = gifImage.gif; sourceTree = ""; }; DC6FCAAD4E7580C9B3C2E21D /* 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 = ""; }; EC32F6993F4529982D9519F1 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F4F7A436CCA4BF276270A3AE /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 680049282280E33D006DD6AB /* TestImages */ = { isa = PBXGroup; children = ( 86E9A88F272747B90017E6E0 /* webpImage.webp */, 9FC8F0E8229FA49E00C8D58F /* gifImage.gif */, 680049362280F2B8006DD6AB /* jpgImage.jpg */, 680049352280F2B8006DD6AB /* pngImage.png */, ); path = TestImages; sourceTree = ""; }; 840012C8B5EDBCF56B0E4AC1 /* Pods */ = { isa = PBXGroup; children = ( 6801632E632668F4349764C9 /* Pods-Runner.debug.xcconfig */, 5A9D31B91557877A0E8EF3E7 /* Pods-Runner.release.xcconfig */, DC6FCAAD4E7580C9B3C2E21D /* Pods-RunnerTests.debug.xcconfig */, 0C7B151765FD4249454C49AD /* Pods-RunnerTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 680049282280E33D006DD6AB /* TestImages */, 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 840012C8B5EDBCF56B0E4AC1 /* Pods */, CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 5C9512FF1EC38BD300040975 /* GeneratedPluginRegistrant.h */, 5C9513001EC38BD300040975 /* GeneratedPluginRegistrant.m */, 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = { isa = PBXGroup; children = ( EC32F6993F4529982D9519F1 /* libPods-Runner.a */, 35AE65F25E0B8C8214D8372B /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { DefaultBuildSystemTypeForWorkspace = Original; LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; SystemCapabilities = { com.apple.BackgroundModes = { enabled = 1; }; }; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 9FC8F0E9229FA49E00C8D58F /* gifImage.gif in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; 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"; }; AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 5C9513011EC38BD300040975 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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 = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.imagePickerExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.imagePickerExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/image_picker/image_picker/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/image_picker/image_picker/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/image_picker/image_picker/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/RunnerUITests.xcscheme ================================================ ================================================ FILE: packages/image_picker/image_picker/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/image_picker/image_picker/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/image_picker/image_picker/example/ios/image_picker_exampleTests/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: packages/image_picker/image_picker/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:video_player/video_player.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: 'Image Picker Demo', home: MyHomePage(title: 'Image Picker Example'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, this.title}) : super(key: key); final String? title; @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { List? _imageFileList; void _setImageFileListFromFile(XFile? value) { _imageFileList = value == null ? null : [value]; } dynamic _pickImageError; bool isVideo = false; VideoPlayerController? _controller; VideoPlayerController? _toBeDisposed; String? _retrieveDataError; final ImagePicker _picker = ImagePicker(); final TextEditingController maxWidthController = TextEditingController(); final TextEditingController maxHeightController = TextEditingController(); final TextEditingController qualityController = TextEditingController(); Future _playVideo(XFile? file) async { if (file != null && mounted) { await _disposeVideoController(); late VideoPlayerController controller; if (kIsWeb) { controller = VideoPlayerController.network(file.path); } else { controller = VideoPlayerController.file(File(file.path)); } _controller = controller; // In web, most browsers won't honor a programmatic call to .play // if the video has a sound track (and is not muted). // Mute the video so it auto-plays in web! // This is not needed if the call to .play is the result of user // interaction (clicking on a "play" button, for example). const double volume = kIsWeb ? 0.0 : 1.0; await controller.setVolume(volume); await controller.initialize(); await controller.setLooping(true); await controller.play(); setState(() {}); } } Future _onImageButtonPressed(ImageSource source, {BuildContext? context, bool isMultiImage = false}) async { if (_controller != null) { await _controller!.setVolume(0.0); } if (isVideo) { final XFile? file = await _picker.pickVideo( source: source, maxDuration: const Duration(seconds: 10)); await _playVideo(file); } else if (isMultiImage) { await _displayPickImageDialog(context!, (double? maxWidth, double? maxHeight, int? quality) async { try { final List pickedFileList = await _picker.pickMultiImage( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: quality, ); setState(() { _imageFileList = pickedFileList; }); } catch (e) { setState(() { _pickImageError = e; }); } }); } else { await _displayPickImageDialog(context!, (double? maxWidth, double? maxHeight, int? quality) async { try { final XFile? pickedFile = await _picker.pickImage( source: source, maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: quality, ); setState(() { _setImageFileListFromFile(pickedFile); }); } catch (e) { setState(() { _pickImageError = e; }); } }); } } @override void deactivate() { if (_controller != null) { _controller!.setVolume(0.0); _controller!.pause(); } super.deactivate(); } @override void dispose() { _disposeVideoController(); maxWidthController.dispose(); maxHeightController.dispose(); qualityController.dispose(); super.dispose(); } Future _disposeVideoController() async { if (_toBeDisposed != null) { await _toBeDisposed!.dispose(); } _toBeDisposed = _controller; _controller = null; } Widget _previewVideo() { final Text? retrieveError = _getRetrieveErrorWidget(); if (retrieveError != null) { return retrieveError; } if (_controller == null) { return const Text( 'You have not yet picked a video', textAlign: TextAlign.center, ); } return Padding( padding: const EdgeInsets.all(10.0), child: AspectRatioVideo(_controller), ); } Widget _previewImages() { final Text? retrieveError = _getRetrieveErrorWidget(); if (retrieveError != null) { return retrieveError; } if (_imageFileList != null) { return Semantics( label: 'image_picker_example_picked_images', child: ListView.builder( key: UniqueKey(), itemBuilder: (BuildContext context, int index) { // Why network for web? // See https://pub.dev/packages/image_picker#getting-ready-for-the-web-platform return Semantics( label: 'image_picker_example_picked_image', child: kIsWeb ? Image.network(_imageFileList![index].path) : Image.file(File(_imageFileList![index].path)), ); }, itemCount: _imageFileList!.length, ), ); } else if (_pickImageError != null) { return Text( 'Pick image error: $_pickImageError', textAlign: TextAlign.center, ); } else { return const Text( 'You have not yet picked an image.', textAlign: TextAlign.center, ); } } Widget _handlePreview() { if (isVideo) { return _previewVideo(); } else { return _previewImages(); } } Future retrieveLostData() async { final LostDataResponse response = await _picker.retrieveLostData(); if (response.isEmpty) { return; } if (response.file != null) { if (response.type == RetrieveType.video) { isVideo = true; await _playVideo(response.file); } else { isVideo = false; setState(() { if (response.files == null) { _setImageFileListFromFile(response.file); } else { _imageFileList = response.files; } }); } } else { _retrieveDataError = response.exception!.code; } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title!), ), body: Center( child: !kIsWeb && defaultTargetPlatform == TargetPlatform.android ? FutureBuilder( future: retrieveLostData(), builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { case ConnectionState.none: case ConnectionState.waiting: return const Text( 'You have not yet picked an image.', textAlign: TextAlign.center, ); case ConnectionState.done: return _handlePreview(); case ConnectionState.active: if (snapshot.hasError) { return Text( 'Pick image/video error: ${snapshot.error}}', textAlign: TextAlign.center, ); } else { return const Text( 'You have not yet picked an image.', textAlign: TextAlign.center, ); } } }, ) : _handlePreview(), ), floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ Semantics( label: 'image_picker_example_from_gallery', child: FloatingActionButton( onPressed: () { isVideo = false; _onImageButtonPressed(ImageSource.gallery, context: context); }, heroTag: 'image0', tooltip: 'Pick Image from gallery', child: const Icon(Icons.photo), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( onPressed: () { isVideo = false; _onImageButtonPressed( ImageSource.gallery, context: context, isMultiImage: true, ); }, heroTag: 'image1', tooltip: 'Pick Multiple Image from gallery', child: const Icon(Icons.photo_library), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( onPressed: () { isVideo = false; _onImageButtonPressed(ImageSource.camera, context: context); }, heroTag: 'image2', tooltip: 'Take a Photo', child: const Icon(Icons.camera_alt), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( backgroundColor: Colors.red, onPressed: () { isVideo = true; _onImageButtonPressed(ImageSource.gallery); }, heroTag: 'video0', tooltip: 'Pick Video from gallery', child: const Icon(Icons.video_library), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( backgroundColor: Colors.red, onPressed: () { isVideo = true; _onImageButtonPressed(ImageSource.camera); }, heroTag: 'video1', tooltip: 'Take a Video', child: const Icon(Icons.videocam), ), ), ], ), ); } Text? _getRetrieveErrorWidget() { if (_retrieveDataError != null) { final Text result = Text(_retrieveDataError!); _retrieveDataError = null; return result; } return null; } Future _displayPickImageDialog( BuildContext context, OnPickImageCallback onPick) async { return showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Add optional parameters'), content: Column( children: [ TextField( controller: maxWidthController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration( hintText: 'Enter maxWidth if desired'), ), TextField( controller: maxHeightController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration( hintText: 'Enter maxHeight if desired'), ), TextField( controller: qualityController, keyboardType: TextInputType.number, decoration: const InputDecoration( hintText: 'Enter quality if desired'), ), ], ), actions: [ TextButton( child: const Text('CANCEL'), onPressed: () { Navigator.of(context).pop(); }, ), TextButton( child: const Text('PICK'), onPressed: () { final double? width = maxWidthController.text.isNotEmpty ? double.parse(maxWidthController.text) : null; final double? height = maxHeightController.text.isNotEmpty ? double.parse(maxHeightController.text) : null; final int? quality = qualityController.text.isNotEmpty ? int.parse(qualityController.text) : null; onPick(width, height, quality); Navigator.of(context).pop(); }), ], ); }); } } typedef OnPickImageCallback = void Function( double? maxWidth, double? maxHeight, int? quality); class AspectRatioVideo extends StatefulWidget { const AspectRatioVideo(this.controller, {Key? key}) : super(key: key); final VideoPlayerController? controller; @override AspectRatioVideoState createState() => AspectRatioVideoState(); } class AspectRatioVideoState extends State { VideoPlayerController? get controller => widget.controller; bool initialized = false; void _onVideoControllerUpdate() { if (!mounted) { return; } if (initialized != controller!.value.isInitialized) { initialized = controller!.value.isInitialized; setState(() {}); } } @override void initState() { super.initState(); controller!.addListener(_onVideoControllerUpdate); } @override void dispose() { controller!.removeListener(_onVideoControllerUpdate); super.dispose(); } @override Widget build(BuildContext context) { if (initialized) { return Center( child: AspectRatio( aspectRatio: controller!.value.aspectRatio, child: VideoPlayer(controller!), ), ); } else { return Container(); } } } ================================================ FILE: packages/image_picker/image_picker/example/pubspec.yaml ================================================ name: image_picker_example description: Demonstrates how to use the image_picker plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.1 image_picker: # When depending on this package from a real application you should use: # image_picker: ^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: ../ video_player: ^2.1.4 dev_dependencies: espresso: ^0.2.0 flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/image_picker/image_picker/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/image_picker/image_picker/example/web/index.html ================================================ url_launcher web example ================================================ FILE: packages/image_picker/image_picker/example/web/manifest.json ================================================ { "name": "image_picker example", "short_name": "image_picker", "start_url": ".", "display": "minimal-ui", "background_color": "#0175C2", "theme_color": "#0175C2", "description": "An example of the image_picker on the web.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [ { "src": "icons/Icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" } ] } ================================================ FILE: packages/image_picker/image_picker/lib/image_picker.dart ================================================ // Copyright 2013 The Flutter 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:image_picker_platform_interface/image_picker_platform_interface.dart'; export 'package:image_picker_platform_interface/image_picker_platform_interface.dart' show kTypeImage, kTypeVideo, ImageSource, CameraDevice, LostData, LostDataResponse, PickedFile, XFile, RetrieveType; /// Provides an easy way to pick an image/video from the image library, /// or to take a picture/video with the camera. class ImagePicker { /// The platform interface that drives this plugin @visibleForTesting static ImagePickerPlatform get platform => ImagePickerPlatform.instance; /// Returns a [PickedFile] object wrapping the image that was picked. /// /// The returned [PickedFile] is intended to be used within a single APP session. Do not save the file path and use it across sessions. /// /// The `source` argument controls where the image comes from. This can /// be either [ImageSource.camera] or [ImageSource.gallery]. /// /// Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and above only support HEIC images if used /// in addition to a size modification, of which the usage is explained below. /// /// If specified, the image will be at most `maxWidth` wide and /// `maxHeight` tall. Otherwise the image will be returned at it's /// original width and height. /// The `imageQuality` argument modifies the quality of the image, ranging from 0-100 /// where 100 is the original/max quality. If `imageQuality` is null, the image with /// the original quality will be returned. Compression is only supported for certain /// image types such as JPEG and on Android PNG and WebP, too. If compression is not supported for the image that is picked, /// a warning message will be logged. /// /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera]. /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device. /// Defaults to [CameraDevice.rear]. Note that Android has no documented parameter for an intent to specify if /// the front or rear camera should be opened, this function is not guaranteed /// to work on an Android device. /// /// In Android, the MainActivity can be destroyed for various reasons. If that happens, the result will be lost /// in this call. You can then call [getLostData] when your app relaunches to retrieve the lost data. /// /// See also [getMultiImage] to allow users to select multiple images at once. /// /// The method could throw [PlatformException] if the app does not have permission to access /// the camera or photos gallery, no camera is available, plugin is already in use, /// temporary file could not be created (iOS only), plugin activity could not /// be allocated (Android only) or due to an unknown error. @Deprecated('Switch to using pickImage instead') Future getImage({ required ImageSource source, double? maxWidth, double? maxHeight, int? imageQuality, CameraDevice preferredCameraDevice = CameraDevice.rear, }) { return platform.pickImage( source: source, maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, preferredCameraDevice: preferredCameraDevice, ); } /// Returns a [List] object wrapping the images that were picked. /// /// The returned [List] is intended to be used within a single APP session. Do not save the file path and use it across sessions. /// /// Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and above only support HEIC images if used /// in addition to a size modification, of which the usage is explained below. /// /// This method is not supported in iOS versions lower than 14. /// /// If specified, the images will be at most `maxWidth` wide and /// `maxHeight` tall. Otherwise the images will be returned at it's /// original width and height. /// The `imageQuality` argument modifies the quality of the images, ranging from 0-100 /// where 100 is the original/max quality. If `imageQuality` is null, the images with /// the original quality will be returned. Compression is only supported for certain /// image types such as JPEG and on Android PNG and WebP, too. If compression is not supported for the image that is picked, /// a warning message will be logged. /// /// The method could throw [PlatformException] if the app does not have permission to access /// the camera or photos gallery, no camera is available, plugin is already in use, /// temporary file could not be created (iOS only), plugin activity could not /// be allocated (Android only) or due to an unknown error. /// /// See also [getImage] to allow users to only pick a single image. @Deprecated('Switch to using pickMultiImage instead') Future?> getMultiImage({ double? maxWidth, double? maxHeight, int? imageQuality, }) { return platform.pickMultiImage( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, ); } /// Returns a [PickedFile] object wrapping the video that was picked. /// /// The returned [PickedFile] is intended to be used within a single APP session. Do not save the file path and use it across sessions. /// /// The [source] argument controls where the video comes from. This can /// be either [ImageSource.camera] or [ImageSource.gallery]. /// /// The [maxDuration] argument specifies the maximum duration of the captured video. If no [maxDuration] is specified, /// the maximum duration will be infinite. /// /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera]. /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device. /// Defaults to [CameraDevice.rear]. /// /// In Android, the MainActivity can be destroyed for various fo reasons. If that happens, the result will be lost /// in this call. You can then call [getLostData] when your app relaunches to retrieve the lost data. /// /// The method could throw [PlatformException] if the app does not have permission to access /// the camera or photos gallery, no camera is available, plugin is already in use, /// temporary file could not be created and video could not be cached (iOS only), /// plugin activity could not be allocated (Android only) or due to an unknown error. /// @Deprecated('Switch to using pickVideo instead') Future getVideo({ required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, Duration? maxDuration, }) { return platform.pickVideo( source: source, preferredCameraDevice: preferredCameraDevice, maxDuration: maxDuration, ); } /// Retrieve the lost [PickedFile] when [selectImage] or [selectVideo] failed because the MainActivity is destroyed. (Android only) /// /// Image or video can be lost if the MainActivity is destroyed. And there is no guarantee that the MainActivity is always alive. /// Call this method to retrieve the lost data and process the data according to your APP's business logic. /// /// Returns a [LostData] object if successfully retrieved the lost data. The [LostData] object can represent either a /// successful image/video selection, or a failure. /// /// Calling this on a non-Android platform will throw [UnimplementedError] exception. /// /// See also: /// * [LostData], for what's included in the response. /// * [Android Activity Lifecycle](https://developer.android.com/reference/android/app/Activity.html), for more information on MainActivity destruction. @Deprecated('Switch to using retrieveLostData instead') Future getLostData() { return platform.retrieveLostData(); } /// Returns an [XFile] object wrapping the image that was picked. /// /// The returned [XFile] is intended to be used within a single APP session. Do not save the file path and use it across sessions. /// /// The `source` argument controls where the image comes from. This can /// be either [ImageSource.camera] or [ImageSource.gallery]. /// /// Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and /// above only support HEIC images if used in addition to a size modification, /// of which the usage is explained below. /// /// If specified, the image will be at most `maxWidth` wide and /// `maxHeight` tall. Otherwise the image will be returned at it's /// original width and height. /// The `imageQuality` argument modifies the quality of the image, ranging from 0-100 /// where 100 is the original/max quality. If `imageQuality` is null, the image with /// the original quality will be returned. Compression is only supported for certain /// image types such as JPEG and on Android PNG and WebP, too. If compression is not /// supported for the image that is picked, a warning message will be logged. /// /// Use `preferredCameraDevice` to specify the camera to use when the `source` is /// [ImageSource.camera]. /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. /// It is also ignored if the chosen camera is not supported on the device. /// Defaults to [CameraDevice.rear]. Note that Android has no documented parameter /// for an intent to specify if the front or rear camera should be opened, this /// function is not guaranteed to work on an Android device. /// /// Use `requestFullMetadata` (defaults to `true`) to control how much additional /// information the plugin tries to get. /// If `requestFullMetadata` is set to `true`, the plugin tries to get the full /// image metadata which may require extra permission requests on some platforms, /// such as `Photo Library Usage` permission on iOS. /// /// In Android, the MainActivity can be destroyed for various reasons. If that happens, the result will be lost /// in this call. You can then call [retrieveLostData] when your app relaunches to retrieve the lost data. /// /// See also [pickMultiImage] to allow users to select multiple images at once. /// /// The method could throw [PlatformException] if the app does not have permission to access /// the camera or photos gallery, no camera is available, plugin is already in use, /// temporary file could not be created (iOS only), plugin activity could not /// be allocated (Android only) or due to an unknown error. Future pickImage({ required ImageSource source, double? maxWidth, double? maxHeight, int? imageQuality, CameraDevice preferredCameraDevice = CameraDevice.rear, bool requestFullMetadata = true, }) { if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) { throw ArgumentError.value( imageQuality, 'imageQuality', 'must be between 0 and 100'); } if (maxWidth != null && maxWidth < 0) { throw ArgumentError.value(maxWidth, 'maxWidth', 'cannot be negative'); } if (maxHeight != null && maxHeight < 0) { throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative'); } return platform.getImageFromSource( source: source, options: ImagePickerOptions( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, preferredCameraDevice: preferredCameraDevice, requestFullMetadata: requestFullMetadata, ), ); } /// Returns a [List] object wrapping the images that were picked. /// /// The returned [List] is intended to be used within a single APP session. Do not save the file path and use it across sessions. /// /// Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and above only support HEIC images if used /// in addition to a size modification, of which the usage is explained below. /// /// This method is not supported in iOS versions lower than 14. /// /// If specified, the images will be at most `maxWidth` wide and /// `maxHeight` tall. Otherwise the images will be returned at it's /// original width and height. /// /// The `imageQuality` argument modifies the quality of the images, ranging from 0-100 /// where 100 is the original/max quality. If `imageQuality` is null, the images with /// the original quality will be returned. Compression is only supported for certain /// image types such as JPEG and on Android PNG and WebP, too. If compression is not /// supported for the image that is picked, a warning message will be logged. /// /// Use `requestFullMetadata` (defaults to `true`) to control how much additional /// information the plugin tries to get. /// If `requestFullMetadata` is set to `true`, the plugin tries to get the full /// image metadata which may require extra permission requests on some platforms, /// such as `Photo Library Usage` permission on iOS. /// /// The method could throw [PlatformException] if the app does not have permission to access /// the camera or photos gallery, no camera is available, plugin is already in use, /// temporary file could not be created (iOS only), plugin activity could not /// be allocated (Android only) or due to an unknown error. /// /// See also [pickImage] to allow users to only pick a single image. Future> pickMultiImage({ double? maxWidth, double? maxHeight, int? imageQuality, bool requestFullMetadata = true, }) { if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) { throw ArgumentError.value( imageQuality, 'imageQuality', 'must be between 0 and 100'); } if (maxWidth != null && maxWidth < 0) { throw ArgumentError.value(maxWidth, 'maxWidth', 'cannot be negative'); } if (maxHeight != null && maxHeight < 0) { throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative'); } return platform.getMultiImageWithOptions( options: MultiImagePickerOptions( imageOptions: ImageOptions( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, requestFullMetadata: requestFullMetadata, ), ), ); } /// Returns an [XFile] object wrapping the video that was picked. /// /// The returned [XFile] is intended to be used within a single APP session. Do not save the file path and use it across sessions. /// /// The [source] argument controls where the video comes from. This can /// be either [ImageSource.camera] or [ImageSource.gallery]. /// /// The [maxDuration] argument specifies the maximum duration of the captured video. If no [maxDuration] is specified, /// the maximum duration will be infinite. /// /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera]. /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device. /// Defaults to [CameraDevice.rear]. /// /// In Android, the MainActivity can be destroyed for various fo reasons. If that happens, the result will be lost /// in this call. You can then call [retrieveLostData] when your app relaunches to retrieve the lost data. /// /// The method could throw [PlatformException] if the app does not have permission to access /// the camera or photos gallery, no camera is available, plugin is already in use, /// temporary file could not be created and video could not be cached (iOS only), /// plugin activity could not be allocated (Android only) or due to an unknown error. /// Future pickVideo({ required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, Duration? maxDuration, }) { return platform.getVideo( source: source, preferredCameraDevice: preferredCameraDevice, maxDuration: maxDuration, ); } /// Retrieve the lost [XFile] when [pickImage], [pickMultiImage] or [pickVideo] failed because the MainActivity /// is destroyed. (Android only) /// /// Image or video can be lost if the MainActivity is destroyed. And there is no guarantee that the MainActivity is always alive. /// Call this method to retrieve the lost data and process the data according to your APP's business logic. /// /// Returns a [LostDataResponse] object if successfully retrieved the lost data. The [LostDataResponse] object can \ /// represent either a successful image/video selection, or a failure. /// /// Calling this on a non-Android platform will throw [UnimplementedError] exception. /// /// See also: /// * [LostDataResponse], for what's included in the response. /// * [Android Activity Lifecycle](https://developer.android.com/reference/android/app/Activity.html), for more information on MainActivity destruction. Future retrieveLostData() { return platform.getLostData(); } } ================================================ FILE: packages/image_picker/image_picker/pubspec.yaml ================================================ name: image_picker 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+2 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: platforms: android: default_package: image_picker_android ios: default_package: image_picker_ios web: default_package: image_picker_for_web dependencies: flutter: sdk: flutter image_picker_android: ^0.8.4+11 image_picker_for_web: ^2.1.0 image_picker_ios: ^0.8.6+1 image_picker_platform_interface: ^2.6.1 dev_dependencies: build_runner: ^2.1.10 cross_file: ^0.3.1+1 # Mockito generates a direct include. flutter_test: sdk: flutter mockito: ^5.0.0 plugin_platform_interface: ^2.0.0 ================================================ FILE: packages/image_picker/image_picker/test/image_picker_deprecated_test.dart ================================================ // Copyright 2013 The Flutter 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: deprecated_member_use_from_same_package // This file preserves the tests for the deprecated methods as they were before // the migration. See image_picker_test.dart for the current tests. import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:image_picker/image_picker.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'package:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'image_picker_test.mocks.dart' as base_mock; // Add the mixin to make the platform interface accept the mock. class MockImagePickerPlatform extends base_mock.MockImagePickerPlatform with MockPlatformInterfaceMixin {} void main() { group('ImagePicker', () { late MockImagePickerPlatform mockPlatform; setUp(() { mockPlatform = MockImagePickerPlatform(); ImagePickerPlatform.instance = mockPlatform; }); group('#Single image/video', () { setUp(() { when(mockPlatform.pickImage( source: anyNamed('source'), maxWidth: anyNamed('maxWidth'), maxHeight: anyNamed('maxHeight'), imageQuality: anyNamed('imageQuality'), preferredCameraDevice: anyNamed('preferredCameraDevice'))) .thenAnswer((Invocation _) async => null); }); group('#pickImage', () { test('passes the image source argument correctly', () async { final ImagePicker picker = ImagePicker(); await picker.getImage(source: ImageSource.camera); await picker.getImage(source: ImageSource.gallery); verifyInOrder([ mockPlatform.pickImage(source: ImageSource.camera), mockPlatform.pickImage(source: ImageSource.gallery), ]); }); test('passes the width and height arguments correctly', () async { final ImagePicker picker = ImagePicker(); await picker.getImage(source: ImageSource.camera); await picker.getImage( source: ImageSource.camera, maxWidth: 10.0, ); await picker.getImage( source: ImageSource.camera, maxHeight: 10.0, ); await picker.getImage( source: ImageSource.camera, maxWidth: 10.0, maxHeight: 20.0, ); await picker.getImage( source: ImageSource.camera, maxWidth: 10.0, imageQuality: 70); await picker.getImage( source: ImageSource.camera, maxHeight: 10.0, imageQuality: 70); await picker.getImage( source: ImageSource.camera, maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70); verifyInOrder([ mockPlatform.pickImage(source: ImageSource.camera), mockPlatform.pickImage(source: ImageSource.camera, maxWidth: 10.0), mockPlatform.pickImage(source: ImageSource.camera, maxHeight: 10.0), mockPlatform.pickImage( source: ImageSource.camera, maxWidth: 10.0, maxHeight: 20.0, ), mockPlatform.pickImage( source: ImageSource.camera, maxWidth: 10.0, imageQuality: 70, ), mockPlatform.pickImage( source: ImageSource.camera, maxHeight: 10.0, imageQuality: 70, ), mockPlatform.pickImage( source: ImageSource.camera, maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ), ]); }); test('handles a null image file response gracefully', () async { final ImagePicker picker = ImagePicker(); expect(await picker.getImage(source: ImageSource.gallery), isNull); expect(await picker.getImage(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { final ImagePicker picker = ImagePicker(); await picker.getImage(source: ImageSource.camera); verify(mockPlatform.pickImage(source: ImageSource.camera)); }); test('camera position can set to front', () async { final ImagePicker picker = ImagePicker(); await picker.getImage( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front); verify(mockPlatform.pickImage( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front)); }); }); group('#pickVideo', () { setUp(() { when(mockPlatform.pickVideo( source: anyNamed('source'), preferredCameraDevice: anyNamed('preferredCameraDevice'), maxDuration: anyNamed('maxDuration'))) .thenAnswer((Invocation _) async => null); }); test('passes the image source argument correctly', () async { final ImagePicker picker = ImagePicker(); await picker.getVideo(source: ImageSource.camera); await picker.getVideo(source: ImageSource.gallery); verifyInOrder([ mockPlatform.pickVideo(source: ImageSource.camera), mockPlatform.pickVideo(source: ImageSource.gallery), ]); }); test('passes the duration argument correctly', () async { final ImagePicker picker = ImagePicker(); await picker.getVideo(source: ImageSource.camera); await picker.getVideo( source: ImageSource.camera, maxDuration: const Duration(seconds: 10)); verifyInOrder([ mockPlatform.pickVideo(source: ImageSource.camera), mockPlatform.pickVideo( source: ImageSource.camera, maxDuration: const Duration(seconds: 10), ), ]); }); test('handles a null video file response gracefully', () async { final ImagePicker picker = ImagePicker(); expect(await picker.getVideo(source: ImageSource.gallery), isNull); expect(await picker.getVideo(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { final ImagePicker picker = ImagePicker(); await picker.getVideo(source: ImageSource.camera); verify(mockPlatform.pickVideo(source: ImageSource.camera)); }); test('camera position can set to front', () async { final ImagePicker picker = ImagePicker(); await picker.getVideo( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front); verify(mockPlatform.pickVideo( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front)); }); }); group('#retrieveLostData', () { test('retrieveLostData get success response', () async { final ImagePicker picker = ImagePicker(); when(mockPlatform.retrieveLostData()).thenAnswer( (Invocation _) async => LostData( file: PickedFile('/example/path'), type: RetrieveType.image)); final LostData response = await picker.getLostData(); expect(response.type, RetrieveType.image); expect(response.file!.path, '/example/path'); }); test('retrieveLostData get error response', () async { final ImagePicker picker = ImagePicker(); when(mockPlatform.retrieveLostData()).thenAnswer( (Invocation _) async => LostData( exception: PlatformException( code: 'test_error_code', message: 'test_error_message'), type: RetrieveType.video)); final LostData response = await picker.getLostData(); expect(response.type, RetrieveType.video); expect(response.exception!.code, 'test_error_code'); expect(response.exception!.message, 'test_error_message'); }); }); }); group('Multi images', () { setUp(() { when(mockPlatform.pickMultiImage( maxWidth: anyNamed('maxWidth'), maxHeight: anyNamed('maxHeight'), imageQuality: anyNamed('imageQuality'))) .thenAnswer((Invocation _) async => null); }); group('#pickMultiImage', () { test('passes the width and height arguments correctly', () async { final ImagePicker picker = ImagePicker(); await picker.getMultiImage(); await picker.getMultiImage( maxWidth: 10.0, ); await picker.getMultiImage( maxHeight: 10.0, ); await picker.getMultiImage( maxWidth: 10.0, maxHeight: 20.0, ); await picker.getMultiImage( maxWidth: 10.0, imageQuality: 70, ); await picker.getMultiImage( maxHeight: 10.0, imageQuality: 70, ); await picker.getMultiImage( maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70); verifyInOrder([ mockPlatform.pickMultiImage(), mockPlatform.pickMultiImage(maxWidth: 10.0), mockPlatform.pickMultiImage(maxHeight: 10.0), mockPlatform.pickMultiImage(maxWidth: 10.0, maxHeight: 20.0), mockPlatform.pickMultiImage(maxWidth: 10.0, imageQuality: 70), mockPlatform.pickMultiImage(maxHeight: 10.0, imageQuality: 70), mockPlatform.pickMultiImage( maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ), ]); }); test('handles a null image file response gracefully', () async { final ImagePicker picker = ImagePicker(); expect(await picker.getMultiImage(), isNull); expect(await picker.getMultiImage(), isNull); }); }); }); }); } ================================================ FILE: packages/image_picker/image_picker/test/image_picker_test.dart ================================================ // Copyright 2013 The Flutter 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:image_picker/image_picker.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'image_picker_test.mocks.dart' as base_mock; // Add the mixin to make the platform interface accept the mock. class MockImagePickerPlatform extends base_mock.MockImagePickerPlatform with MockPlatformInterfaceMixin {} @GenerateMocks([ImagePickerPlatform]) void main() { group('ImagePicker', () { late MockImagePickerPlatform mockPlatform; setUp(() { mockPlatform = MockImagePickerPlatform(); ImagePickerPlatform.instance = mockPlatform; }); group('#Single image/video', () { group('#pickImage', () { setUp(() { when(mockPlatform.getImageFromSource( source: anyNamed('source'), options: anyNamed('options'))) .thenAnswer((Invocation _) async => null); }); test('passes the image source argument correctly', () async { final ImagePicker picker = ImagePicker(); await picker.pickImage(source: ImageSource.camera); await picker.pickImage(source: ImageSource.gallery); verifyInOrder([ mockPlatform.getImageFromSource( source: ImageSource.camera, options: argThat( isInstanceOf(), named: 'options', ), ), mockPlatform.getImageFromSource( source: ImageSource.gallery, options: argThat( isInstanceOf(), named: 'options', ), ), ]); }); test('passes the width and height arguments correctly', () async { final ImagePicker picker = ImagePicker(); await picker.pickImage(source: ImageSource.camera); await picker.pickImage( source: ImageSource.camera, maxWidth: 10.0, ); await picker.pickImage( source: ImageSource.camera, maxHeight: 10.0, ); await picker.pickImage( source: ImageSource.camera, maxWidth: 10.0, maxHeight: 20.0, ); await picker.pickImage( source: ImageSource.camera, maxWidth: 10.0, imageQuality: 70); await picker.pickImage( source: ImageSource.camera, maxHeight: 10.0, imageQuality: 70); await picker.pickImage( source: ImageSource.camera, maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70); verifyInOrder([ mockPlatform.getImageFromSource( source: ImageSource.camera, options: argThat( isInstanceOf() .having((ImagePickerOptions options) => options.maxWidth, 'maxWidth', isNull) .having((ImagePickerOptions options) => options.maxHeight, 'maxHeight', isNull) .having( (ImagePickerOptions options) => options.imageQuality, 'imageQuality', isNull), named: 'options', ), ), mockPlatform.getImageFromSource( source: ImageSource.camera, options: argThat( isInstanceOf() .having((ImagePickerOptions options) => options.maxWidth, 'maxWidth', equals(10.0)) .having((ImagePickerOptions options) => options.maxHeight, 'maxHeight', isNull) .having( (ImagePickerOptions options) => options.imageQuality, 'imageQuality', isNull), named: 'options', ), ), mockPlatform.getImageFromSource( source: ImageSource.camera, options: argThat( isInstanceOf() .having((ImagePickerOptions options) => options.maxWidth, 'maxWidth', isNull) .having((ImagePickerOptions options) => options.maxHeight, 'maxHeight', equals(10.0)) .having( (ImagePickerOptions options) => options.imageQuality, 'imageQuality', isNull), named: 'options', ), ), mockPlatform.getImageFromSource( source: ImageSource.camera, options: argThat( isInstanceOf() .having((ImagePickerOptions options) => options.maxWidth, 'maxWidth', equals(10.0)) .having((ImagePickerOptions options) => options.maxHeight, 'maxHeight', equals(20.0)) .having( (ImagePickerOptions options) => options.imageQuality, 'imageQuality', isNull), named: 'options', ), ), mockPlatform.getImageFromSource( source: ImageSource.camera, options: argThat( isInstanceOf() .having((ImagePickerOptions options) => options.maxWidth, 'maxWidth', equals(10.0)) .having((ImagePickerOptions options) => options.maxHeight, 'maxHeight', isNull) .having( (ImagePickerOptions options) => options.imageQuality, 'imageQuality', equals(70)), named: 'options', ), ), mockPlatform.getImageFromSource( source: ImageSource.camera, options: argThat( isInstanceOf() .having((ImagePickerOptions options) => options.maxWidth, 'maxWidth', isNull) .having((ImagePickerOptions options) => options.maxHeight, 'maxHeight', equals(10.0)) .having( (ImagePickerOptions options) => options.imageQuality, 'imageQuality', equals(70)), named: 'options', ), ), mockPlatform.getImageFromSource( source: ImageSource.camera, options: argThat( isInstanceOf() .having((ImagePickerOptions options) => options.maxWidth, 'maxWidth', equals(10.0)) .having((ImagePickerOptions options) => options.maxHeight, 'maxHeight', equals(20.0)) .having( (ImagePickerOptions options) => options.imageQuality, 'imageQuality', equals(70)), named: 'options', ), ), ]); }); test('does not accept a negative width or height argument', () { final ImagePicker picker = ImagePicker(); expect( () => picker.pickImage(source: ImageSource.camera, maxWidth: -1.0), throwsArgumentError, ); expect( () => picker.pickImage(source: ImageSource.camera, maxHeight: -1.0), throwsArgumentError, ); }); test('handles a null image file response gracefully', () async { final ImagePicker picker = ImagePicker(); expect(await picker.pickImage(source: ImageSource.gallery), isNull); expect(await picker.pickImage(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { final ImagePicker picker = ImagePicker(); await picker.pickImage(source: ImageSource.camera); verify(mockPlatform.getImageFromSource( source: ImageSource.camera, options: argThat( isInstanceOf().having( (ImagePickerOptions options) => options.preferredCameraDevice, 'preferredCameraDevice', equals(CameraDevice.rear)), named: 'options', ), )); }); test('camera position can set to front', () async { final ImagePicker picker = ImagePicker(); await picker.pickImage( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front); verify(mockPlatform.getImageFromSource( source: ImageSource.camera, options: argThat( isInstanceOf().having( (ImagePickerOptions options) => options.preferredCameraDevice, 'preferredCameraDevice', equals(CameraDevice.front)), named: 'options', ), )); }); test('full metadata argument defaults to true', () async { final ImagePicker picker = ImagePicker(); await picker.pickImage(source: ImageSource.gallery); verify(mockPlatform.getImageFromSource( source: ImageSource.gallery, options: argThat( isInstanceOf().having( (ImagePickerOptions options) => options.requestFullMetadata, 'requestFullMetadata', isTrue), named: 'options', ), )); }); test('passes the full metadata argument correctly', () async { final ImagePicker picker = ImagePicker(); await picker.pickImage( source: ImageSource.gallery, requestFullMetadata: false, ); verify(mockPlatform.getImageFromSource( source: ImageSource.gallery, options: argThat( isInstanceOf().having( (ImagePickerOptions options) => options.requestFullMetadata, 'requestFullMetadata', isFalse), named: 'options', ), )); }); }); group('#pickVideo', () { setUp(() { when(mockPlatform.getVideo( source: anyNamed('source'), preferredCameraDevice: anyNamed('preferredCameraDevice'), maxDuration: anyNamed('maxDuration'))) .thenAnswer((Invocation _) async => null); }); test('passes the image source argument correctly', () async { final ImagePicker picker = ImagePicker(); await picker.pickVideo(source: ImageSource.camera); await picker.pickVideo(source: ImageSource.gallery); verifyInOrder([ mockPlatform.getVideo(source: ImageSource.camera), mockPlatform.getVideo(source: ImageSource.gallery), ]); }); test('passes the duration argument correctly', () async { final ImagePicker picker = ImagePicker(); await picker.pickVideo(source: ImageSource.camera); await picker.pickVideo( source: ImageSource.camera, maxDuration: const Duration(seconds: 10)); verifyInOrder([ mockPlatform.getVideo(source: ImageSource.camera), mockPlatform.getVideo( source: ImageSource.camera, maxDuration: const Duration(seconds: 10)), ]); }); test('handles a null video file response gracefully', () async { final ImagePicker picker = ImagePicker(); expect(await picker.pickVideo(source: ImageSource.gallery), isNull); expect(await picker.pickVideo(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { final ImagePicker picker = ImagePicker(); await picker.pickVideo(source: ImageSource.camera); verify(mockPlatform.getVideo(source: ImageSource.camera)); }); test('camera position can set to front', () async { final ImagePicker picker = ImagePicker(); await picker.pickVideo( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front); verify(mockPlatform.getVideo( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front)); }); }); group('#retrieveLostData', () { test('retrieveLostData get success response', () async { final ImagePicker picker = ImagePicker(); final XFile lostFile = XFile('/example/path'); when(mockPlatform.getLostData()).thenAnswer((Invocation _) async => LostDataResponse( file: lostFile, files: [lostFile], type: RetrieveType.image)); final LostDataResponse response = await picker.retrieveLostData(); expect(response.type, RetrieveType.image); expect(response.file!.path, '/example/path'); }); test('retrieveLostData should successfully retrieve multiple files', () async { final ImagePicker picker = ImagePicker(); final List lostFiles = [ XFile('/example/path0'), XFile('/example/path1'), ]; when(mockPlatform.getLostData()).thenAnswer((Invocation _) async => LostDataResponse( file: lostFiles.last, files: lostFiles, type: RetrieveType.image)); final LostDataResponse response = await picker.retrieveLostData(); expect(response.type, RetrieveType.image); expect(response.file, isNotNull); expect(response.file!.path, '/example/path1'); expect(response.files!.first.path, '/example/path0'); expect(response.files!.length, 2); }); test('retrieveLostData get error response', () async { final ImagePicker picker = ImagePicker(); when(mockPlatform.getLostData()).thenAnswer((Invocation _) async => LostDataResponse( exception: PlatformException( code: 'test_error_code', message: 'test_error_message'), type: RetrieveType.video)); final LostDataResponse response = await picker.retrieveLostData(); expect(response.type, RetrieveType.video); expect(response.exception!.code, 'test_error_code'); expect(response.exception!.message, 'test_error_message'); }); }); }); group('#Multi images', () { setUp(() { when( mockPlatform.getMultiImageWithOptions( options: anyNamed('options'), ), ).thenAnswer((Invocation _) async => []); }); group('#pickMultiImage', () { test('passes the width and height arguments correctly', () async { final ImagePicker picker = ImagePicker(); await picker.pickMultiImage(); await picker.pickMultiImage( maxWidth: 10.0, ); await picker.pickMultiImage( maxHeight: 10.0, ); await picker.pickMultiImage( maxWidth: 10.0, maxHeight: 20.0, ); await picker.pickMultiImage( maxWidth: 10.0, imageQuality: 70, ); await picker.pickMultiImage( maxHeight: 10.0, imageQuality: 70, ); await picker.pickMultiImage( maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70); verifyInOrder([ mockPlatform.getMultiImageWithOptions( options: argThat( isInstanceOf(), named: 'options', ), ), mockPlatform.getMultiImageWithOptions( options: argThat( isInstanceOf().having( (MultiImagePickerOptions options) => options.imageOptions.maxWidth, 'maxWidth', equals(10.0)), named: 'options', ), ), mockPlatform.getMultiImageWithOptions( options: argThat( isInstanceOf().having( (MultiImagePickerOptions options) => options.imageOptions.maxHeight, 'maxHeight', equals(10.0)), named: 'options', ), ), mockPlatform.getMultiImageWithOptions( options: argThat( isInstanceOf() .having( (MultiImagePickerOptions options) => options.imageOptions.maxWidth, 'maxWidth', equals(10.0)) .having( (MultiImagePickerOptions options) => options.imageOptions.maxHeight, 'maxHeight', equals(20.0)), named: 'options', ), ), mockPlatform.getMultiImageWithOptions( options: argThat( isInstanceOf() .having( (MultiImagePickerOptions options) => options.imageOptions.maxWidth, 'maxWidth', equals(10.0)) .having( (MultiImagePickerOptions options) => options.imageOptions.imageQuality, 'imageQuality', equals(70)), named: 'options', ), ), mockPlatform.getMultiImageWithOptions( options: argThat( isInstanceOf() .having( (MultiImagePickerOptions options) => options.imageOptions.maxHeight, 'maxHeight', equals(10.0)) .having( (MultiImagePickerOptions options) => options.imageOptions.imageQuality, 'imageQuality', equals(70)), named: 'options', ), ), mockPlatform.getMultiImageWithOptions( options: argThat( isInstanceOf() .having( (MultiImagePickerOptions options) => options.imageOptions.maxWidth, 'maxWidth', equals(10.0)) .having( (MultiImagePickerOptions options) => options.imageOptions.maxWidth, 'maxHeight', equals(10.0)) .having( (MultiImagePickerOptions options) => options.imageOptions.imageQuality, 'imageQuality', equals(70)), named: 'options', ), ), ]); }); test('does not accept a negative width or height argument', () { final ImagePicker picker = ImagePicker(); expect( () => picker.pickMultiImage(maxWidth: -1.0), throwsArgumentError, ); expect( () => picker.pickMultiImage(maxHeight: -1.0), throwsArgumentError, ); }); test('handles an empty image file response gracefully', () async { final ImagePicker picker = ImagePicker(); expect(await picker.pickMultiImage(), isEmpty); expect(await picker.pickMultiImage(), isEmpty); }); test('full metadata argument defaults to true', () async { final ImagePicker picker = ImagePicker(); await picker.pickMultiImage(); verify(mockPlatform.getMultiImageWithOptions( options: argThat( isInstanceOf().having( (MultiImagePickerOptions options) => options.imageOptions.requestFullMetadata, 'requestFullMetadata', isTrue), named: 'options', ), )); }); test('passes the full metadata argument correctly', () async { final ImagePicker picker = ImagePicker(); await picker.pickMultiImage( requestFullMetadata: false, ); verify(mockPlatform.getMultiImageWithOptions( options: argThat( isInstanceOf().having( (MultiImagePickerOptions options) => options.imageOptions.requestFullMetadata, 'requestFullMetadata', isFalse), named: 'options', ), )); }); }); }); }); } ================================================ FILE: packages/image_picker/image_picker/test/image_picker_test.mocks.dart ================================================ // Mocks generated by Mockito 5.1.0 from annotations // in image_picker/test/image_picker_test.dart. // Do not manually edit this file. import 'dart:async' as _i4; import 'package:cross_file/cross_file.dart' as _i5; import 'package:image_picker_platform_interface/src/platform_interface/image_picker_platform.dart' as _i3; import 'package:image_picker_platform_interface/src/types/types.dart' as _i2; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: 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 class _FakeLostData_0 extends _i1.Fake implements _i2.LostData {} class _FakeLostDataResponse_1 extends _i1.Fake implements _i2.LostDataResponse {} /// A class which mocks [ImagePickerPlatform]. /// /// See the documentation for Mockito's code generation for more information. class MockImagePickerPlatform extends _i1.Mock implements _i3.ImagePickerPlatform { MockImagePickerPlatform() { _i1.throwOnMissingStub(this); } @override _i4.Future<_i2.PickedFile?> pickImage( {_i2.ImageSource? source, double? maxWidth, double? maxHeight, int? imageQuality, _i2.CameraDevice? preferredCameraDevice = _i2.CameraDevice.rear}) => (super.noSuchMethod( Invocation.method(#pickImage, [], { #source: source, #maxWidth: maxWidth, #maxHeight: maxHeight, #imageQuality: imageQuality, #preferredCameraDevice: preferredCameraDevice }), returnValue: Future<_i2.PickedFile?>.value()) as _i4.Future<_i2.PickedFile?>); @override _i4.Future?> pickMultiImage( {double? maxWidth, double? maxHeight, int? imageQuality}) => (super.noSuchMethod( Invocation.method(#pickMultiImage, [], { #maxWidth: maxWidth, #maxHeight: maxHeight, #imageQuality: imageQuality }), returnValue: Future?>.value()) as _i4.Future?>); @override _i4.Future<_i2.PickedFile?> pickVideo( {_i2.ImageSource? source, _i2.CameraDevice? preferredCameraDevice = _i2.CameraDevice.rear, Duration? maxDuration}) => (super.noSuchMethod( Invocation.method(#pickVideo, [], { #source: source, #preferredCameraDevice: preferredCameraDevice, #maxDuration: maxDuration }), returnValue: Future<_i2.PickedFile?>.value()) as _i4.Future<_i2.PickedFile?>); @override _i4.Future<_i2.LostData> retrieveLostData() => (super.noSuchMethod(Invocation.method(#retrieveLostData, []), returnValue: Future<_i2.LostData>.value(_FakeLostData_0())) as _i4.Future<_i2.LostData>); @override _i4.Future<_i5.XFile?> getImage( {_i2.ImageSource? source, double? maxWidth, double? maxHeight, int? imageQuality, _i2.CameraDevice? preferredCameraDevice = _i2.CameraDevice.rear}) => (super.noSuchMethod( Invocation.method(#getImage, [], { #source: source, #maxWidth: maxWidth, #maxHeight: maxHeight, #imageQuality: imageQuality, #preferredCameraDevice: preferredCameraDevice }), returnValue: Future<_i5.XFile?>.value()) as _i4.Future<_i5.XFile?>); @override _i4.Future?> getMultiImage( {double? maxWidth, double? maxHeight, int? imageQuality}) => (super.noSuchMethod( Invocation.method(#getMultiImage, [], { #maxWidth: maxWidth, #maxHeight: maxHeight, #imageQuality: imageQuality }), returnValue: Future?>.value()) as _i4.Future?>); @override _i4.Future<_i5.XFile?> getVideo( {_i2.ImageSource? source, _i2.CameraDevice? preferredCameraDevice = _i2.CameraDevice.rear, Duration? maxDuration}) => (super.noSuchMethod( Invocation.method(#getVideo, [], { #source: source, #preferredCameraDevice: preferredCameraDevice, #maxDuration: maxDuration }), returnValue: Future<_i5.XFile?>.value()) as _i4.Future<_i5.XFile?>); @override _i4.Future<_i2.LostDataResponse> getLostData() => (super.noSuchMethod(Invocation.method(#getLostData, []), returnValue: Future<_i2.LostDataResponse>.value(_FakeLostDataResponse_1())) as _i4.Future<_i2.LostDataResponse>); @override _i4.Future<_i5.XFile?> getImageFromSource( {_i2.ImageSource? source, _i2.ImagePickerOptions? options = const _i2.ImagePickerOptions()}) => (super.noSuchMethod( Invocation.method( #getImageFromSource, [], {#source: source, #options: options}), returnValue: Future<_i5.XFile?>.value()) as _i4.Future<_i5.XFile?>); @override _i4.Future> getMultiImageWithOptions( {_i2.MultiImagePickerOptions? options = const _i2.MultiImagePickerOptions()}) => (super.noSuchMethod( Invocation.method(#getMultiImageWithOptions, [], {#options: options}), returnValue: Future>.value(<_i5.XFile>[])) as _i4 .Future>); } ================================================ FILE: packages/image_picker/image_picker_android/AUTHORS ================================================ # 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> ================================================ FILE: packages/image_picker/image_picker_android/CHANGELOG.md ================================================ ## 0.8.5+6 * Updates minimum Flutter version to 3.0. * Fixes names of picked files to match original filenames where possible. ## 0.8.5+5 * Updates code for stricter lint checks. ## 0.8.5+4 * Fixes null cast exception when restoring a cancelled selection. ## 0.8.5+3 * Updates minimum Flutter version to 2.10. * Bumps gradle from 7.1.2 to 7.2.1. ## 0.8.5+2 * Updates `image_picker_platform_interface` constraint to the correct minimum version. ## 0.8.5+1 * Switches to an internal method channel implementation. ## 0.8.5 * Updates gradle to 7.1.2. ## 0.8.4+13 * Minor fixes for new analysis options. ## 0.8.4+12 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.8.4+11 * Splits from `image_picker` as a federated implementation. ================================================ FILE: packages/image_picker/image_picker_android/LICENSE ================================================ image_picker 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. -------------------------------------------------------------------------------- aFileChooser Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2011 - 2013 Paul Burke Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: packages/image_picker/image_picker_android/README.md ================================================ # image\_picker\_android The Android implementation of [`image_picker`][1]. ## Usage This package is [endorsed][2], which means you can simply use `image_picker` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/image_picker [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/image_picker/image_picker_android/android/build.gradle ================================================ group 'io.flutter.plugins.imagepicker' version '1.0-SNAPSHOT' buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.2.1' } } rootProject.allprojects { repositories { google() mavenCentral() } } apply plugin: 'com.android.library' android { compileSdkVersion 31 defaultConfig { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } dependencies { implementation 'androidx.core:core:1.8.0' implementation 'androidx.annotation:annotation:1.3.0' implementation 'androidx.exifinterface:exifinterface:1.3.3' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:5.1.1' testImplementation 'androidx.test:core:1.4.0' testImplementation "org.robolectric:robolectric:4.8.1" } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } testOptions { unitTests.includeAndroidResources = true unitTests.returnDefaultValues = true unitTests.all { testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" outputs.upToDateWhen {false} showStandardStreams = true } } } } ================================================ FILE: packages/image_picker/image_picker_android/android/settings.gradle ================================================ rootProject.name = 'image_picker_android' ================================================ FILE: packages/image_picker/image_picker_android/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java ================================================ // Copyright 2013 The Flutter 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.imagepicker; import android.util.Log; import androidx.exifinterface.media.ExifInterface; import java.util.Arrays; import java.util.List; class ExifDataCopier { void copyExif(String filePathOri, String filePathDest) { try { ExifInterface oldExif = new ExifInterface(filePathOri); ExifInterface newExif = new ExifInterface(filePathDest); List attributes = Arrays.asList( "FNumber", "ExposureTime", "ISOSpeedRatings", "GPSAltitude", "GPSAltitudeRef", "FocalLength", "GPSDateStamp", "WhiteBalance", "GPSProcessingMethod", "GPSTimeStamp", "DateTime", "Flash", "GPSLatitude", "GPSLatitudeRef", "GPSLongitude", "GPSLongitudeRef", "Make", "Model", "Orientation"); for (String attribute : attributes) { setIfNotNull(oldExif, newExif, attribute); } newExif.saveAttributes(); } catch (Exception ex) { Log.e("ExifDataCopier", "Error preserving Exif data on selected image: " + ex); } } private static void setIfNotNull(ExifInterface oldExif, ExifInterface newExif, String property) { if (oldExif.getAttribute(property) != null) { newExif.setAttribute(property, oldExif.getAttribute(property)); } } } ================================================ FILE: packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /* * Copyright (C) 2007-2008 OpenIntents.org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * This file was modified by the Flutter authors from the following original file: * https://raw.githubusercontent.com/iPaulPro/aFileChooser/master/aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java */ package io.flutter.plugins.imagepicker; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.provider.MediaStore; import android.webkit.MimeTypeMap; import io.flutter.Log; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.UUID; class FileUtils { /** * Copies the file from the given content URI to a temporary directory, retaining the original * file name if possible. * *

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

If the original file name is unknown, a predefined "image_picker" filename is used and the * file extension is deduced from the mime type (with fallback to ".jpg" in case of failure). */ String getPathFromUri(final Context context, final Uri uri) { try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) { String uuid = UUID.randomUUID().toString(); File targetDirectory = new File(context.getCacheDir(), uuid); targetDirectory.mkdir(); // TODO(SynSzakala) according to the docs, `deleteOnExit` does not work reliably on Android; we should preferably // just clear the picked files after the app startup. targetDirectory.deleteOnExit(); String fileName = getImageName(context, uri); if (fileName == null) { Log.w("FileUtils", "Cannot get file name for " + uri); fileName = "image_picker" + getImageExtension(context, uri); } File file = new File(targetDirectory, fileName); try (OutputStream outputStream = new FileOutputStream(file)) { copy(inputStream, outputStream); return file.getPath(); } } catch (IOException e) { // If closing the output stream fails, we cannot be sure that the // target file was written in full. Flushing the stream merely moves // the bytes into the OS, not necessarily to the file. return null; } } /** @return extension of image with dot, or default .jpg if it none. */ private static String getImageExtension(Context context, Uri uriImage) { String extension; try { if (uriImage.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { final MimeTypeMap mime = MimeTypeMap.getSingleton(); extension = mime.getExtensionFromMimeType(context.getContentResolver().getType(uriImage)); } else { extension = MimeTypeMap.getFileExtensionFromUrl( Uri.fromFile(new File(uriImage.getPath())).toString()); } } catch (Exception e) { extension = null; } if (extension == null || extension.isEmpty()) { //default extension for matches the previous behavior of the plugin extension = "jpg"; } return "." + extension; } /** @return name of the image provided by ContentResolver; this may be null. */ private static String getImageName(Context context, Uri uriImage) { try (Cursor cursor = queryImageName(context, uriImage)) { if (cursor == null || !cursor.moveToFirst() || cursor.getColumnCount() < 1) return null; return cursor.getString(0); } } private static Cursor queryImageName(Context context, Uri uriImage) { return context .getContentResolver() .query(uriImage, new String[] {MediaStore.MediaColumns.DISPLAY_NAME}, null, null, null); } private static void copy(InputStream in, OutputStream out) throws IOException { final byte[] buffer = new byte[4 * 1024]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } out.flush(); } } ================================================ FILE: packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerCache.java ================================================ // Copyright 2013 The Flutter 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.imagepicker; import android.content.Context; import android.content.SharedPreferences; import android.net.Uri; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import io.flutter.plugin.common.MethodCall; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; class ImagePickerCache { static final String MAP_KEY_PATH = "path"; static final String MAP_KEY_PATH_LIST = "pathList"; static final String MAP_KEY_MAX_WIDTH = "maxWidth"; static final String MAP_KEY_MAX_HEIGHT = "maxHeight"; static final String MAP_KEY_IMAGE_QUALITY = "imageQuality"; private static final String MAP_KEY_TYPE = "type"; private static final String MAP_KEY_ERROR_CODE = "errorCode"; private static final String MAP_KEY_ERROR_MESSAGE = "errorMessage"; private static final String FLUTTER_IMAGE_PICKER_IMAGE_PATH_KEY = "flutter_image_picker_image_path"; private static final String SHARED_PREFERENCE_ERROR_CODE_KEY = "flutter_image_picker_error_code"; private static final String SHARED_PREFERENCE_ERROR_MESSAGE_KEY = "flutter_image_picker_error_message"; private static final String SHARED_PREFERENCE_MAX_WIDTH_KEY = "flutter_image_picker_max_width"; private static final String SHARED_PREFERENCE_MAX_HEIGHT_KEY = "flutter_image_picker_max_height"; private static final String SHARED_PREFERENCE_IMAGE_QUALITY_KEY = "flutter_image_picker_image_quality"; private static final String SHARED_PREFERENCE_TYPE_KEY = "flutter_image_picker_type"; private static final String SHARED_PREFERENCE_PENDING_IMAGE_URI_PATH_KEY = "flutter_image_picker_pending_image_uri"; @VisibleForTesting static final String SHARED_PREFERENCES_NAME = "flutter_image_picker_shared_preference"; private SharedPreferences prefs; ImagePickerCache(Context context) { prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); } void saveTypeWithMethodCallName(String methodCallName) { if (methodCallName.equals(ImagePickerPlugin.METHOD_CALL_IMAGE) | methodCallName.equals(ImagePickerPlugin.METHOD_CALL_MULTI_IMAGE)) { setType("image"); } else if (methodCallName.equals(ImagePickerPlugin.METHOD_CALL_VIDEO)) { setType("video"); } } private void setType(String type) { prefs.edit().putString(SHARED_PREFERENCE_TYPE_KEY, type).apply(); } void saveDimensionWithMethodCall(MethodCall methodCall) { Double maxWidth = methodCall.argument(MAP_KEY_MAX_WIDTH); Double maxHeight = methodCall.argument(MAP_KEY_MAX_HEIGHT); int imageQuality = methodCall.argument(MAP_KEY_IMAGE_QUALITY) == null ? 100 : (int) methodCall.argument(MAP_KEY_IMAGE_QUALITY); setMaxDimension(maxWidth, maxHeight, imageQuality); } private void setMaxDimension(Double maxWidth, Double maxHeight, int imageQuality) { SharedPreferences.Editor editor = prefs.edit(); if (maxWidth != null) { editor.putLong(SHARED_PREFERENCE_MAX_WIDTH_KEY, Double.doubleToRawLongBits(maxWidth)); } if (maxHeight != null) { editor.putLong(SHARED_PREFERENCE_MAX_HEIGHT_KEY, Double.doubleToRawLongBits(maxHeight)); } if (imageQuality > -1 && imageQuality < 101) { editor.putInt(SHARED_PREFERENCE_IMAGE_QUALITY_KEY, imageQuality); } else { editor.putInt(SHARED_PREFERENCE_IMAGE_QUALITY_KEY, 100); } editor.apply(); } void savePendingCameraMediaUriPath(Uri uri) { prefs.edit().putString(SHARED_PREFERENCE_PENDING_IMAGE_URI_PATH_KEY, uri.getPath()).apply(); } String retrievePendingCameraMediaUriPath() { return prefs.getString(SHARED_PREFERENCE_PENDING_IMAGE_URI_PATH_KEY, ""); } void saveResult( @Nullable ArrayList path, @Nullable String errorCode, @Nullable String errorMessage) { Set imageSet = new HashSet<>(); imageSet.addAll(path); SharedPreferences.Editor editor = prefs.edit(); if (path != null) { editor.putStringSet(FLUTTER_IMAGE_PICKER_IMAGE_PATH_KEY, imageSet); } if (errorCode != null) { editor.putString(SHARED_PREFERENCE_ERROR_CODE_KEY, errorCode); } if (errorMessage != null) { editor.putString(SHARED_PREFERENCE_ERROR_MESSAGE_KEY, errorMessage); } editor.apply(); } void clear() { prefs.edit().clear().apply(); } Map getCacheMap() { Map resultMap = new HashMap<>(); ArrayList pathList = new ArrayList<>(); boolean hasData = false; if (prefs.contains(FLUTTER_IMAGE_PICKER_IMAGE_PATH_KEY)) { final Set imagePathList = prefs.getStringSet(FLUTTER_IMAGE_PICKER_IMAGE_PATH_KEY, null); if (imagePathList != null) { pathList.addAll(imagePathList); resultMap.put(MAP_KEY_PATH_LIST, pathList); hasData = true; } } if (prefs.contains(SHARED_PREFERENCE_ERROR_CODE_KEY)) { final String errorCodeValue = prefs.getString(SHARED_PREFERENCE_ERROR_CODE_KEY, ""); resultMap.put(MAP_KEY_ERROR_CODE, errorCodeValue); hasData = true; if (prefs.contains(SHARED_PREFERENCE_ERROR_MESSAGE_KEY)) { final String errorMessageValue = prefs.getString(SHARED_PREFERENCE_ERROR_MESSAGE_KEY, ""); resultMap.put(MAP_KEY_ERROR_MESSAGE, errorMessageValue); } } if (hasData) { if (prefs.contains(SHARED_PREFERENCE_TYPE_KEY)) { final String typeValue = prefs.getString(SHARED_PREFERENCE_TYPE_KEY, ""); resultMap.put(MAP_KEY_TYPE, typeValue); } if (prefs.contains(SHARED_PREFERENCE_MAX_WIDTH_KEY)) { final long maxWidthValue = prefs.getLong(SHARED_PREFERENCE_MAX_WIDTH_KEY, 0); resultMap.put(MAP_KEY_MAX_WIDTH, Double.longBitsToDouble(maxWidthValue)); } if (prefs.contains(SHARED_PREFERENCE_MAX_HEIGHT_KEY)) { final long maxHeightValue = prefs.getLong(SHARED_PREFERENCE_MAX_HEIGHT_KEY, 0); resultMap.put(MAP_KEY_MAX_HEIGHT, Double.longBitsToDouble(maxHeightValue)); } if (prefs.contains(SHARED_PREFERENCE_IMAGE_QUALITY_KEY)) { final int imageQuality = prefs.getInt(SHARED_PREFERENCE_IMAGE_QUALITY_KEY, 100); resultMap.put(MAP_KEY_IMAGE_QUALITY, imageQuality); } else { resultMap.put(MAP_KEY_IMAGE_QUALITY, 100); } } return resultMap; } } ================================================ FILE: packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java ================================================ // Copyright 2013 The Flutter 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.imagepicker; import android.Manifest; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.hardware.camera2.CameraCharacteristics; import android.media.MediaScannerConnection; import android.net.Uri; import android.os.Build; import android.provider.MediaStore; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.core.app.ActivityCompat; import androidx.core.content.FileProvider; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.PluginRegistry; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.UUID; enum CameraDevice { REAR, FRONT } /** * A delegate class doing the heavy lifting for the plugin. * *

When invoked, both the {@link #chooseImageFromGallery} and {@link #takeImageWithCamera} * methods go through the same steps: * *

1. Check for an existing {@link #pendingResult}. If a previous pendingResult exists, this * means that the chooseImageFromGallery() or takeImageWithCamera() method was called at least * twice. In this case, stop executing and finish with an error. * *

2. Check that a required runtime permission has been granted. The takeImageWithCamera() method * checks that {@link Manifest.permission#CAMERA} has been granted. * *

The permission check can end up in two different outcomes: * *

A) If the permission has already been granted, continue with picking the image from gallery or * camera. * *

B) If the permission hasn't already been granted, ask for the permission from the user. If the * user grants the permission, proceed with step #3. If the user denies the permission, stop doing * anything else and finish with a null result. * *

3. Launch the gallery or camera for picking the image, depending on whether * chooseImageFromGallery() or takeImageWithCamera() was called. * *

This can end up in three different outcomes: * *

A) User picks an image. No maxWidth or maxHeight was specified when calling {@code * pickImage()} method in the Dart side of this plugin. Finish with full path for the picked image * as the result. * *

B) User picks an image. A maxWidth and/or maxHeight was provided when calling {@code * pickImage()} method in the Dart side of this plugin. A scaled copy of the image is created. * Finish with full path for the scaled image as the result. * *

C) User cancels picking an image. Finish with null result. */ public class ImagePickerDelegate implements PluginRegistry.ActivityResultListener, PluginRegistry.RequestPermissionsResultListener { @VisibleForTesting static final int REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY = 2342; @VisibleForTesting static final int REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA = 2343; @VisibleForTesting static final int REQUEST_CAMERA_IMAGE_PERMISSION = 2345; @VisibleForTesting static final int REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY = 2346; @VisibleForTesting static final int REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY = 2352; @VisibleForTesting static final int REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA = 2353; @VisibleForTesting static final int REQUEST_CAMERA_VIDEO_PERMISSION = 2355; @VisibleForTesting final String fileProviderName; private final Activity activity; @VisibleForTesting final File externalFilesDirectory; private final ImageResizer imageResizer; private final ImagePickerCache cache; private final PermissionManager permissionManager; private final FileUriResolver fileUriResolver; private final FileUtils fileUtils; private CameraDevice cameraDevice; interface PermissionManager { boolean isPermissionGranted(String permissionName); void askForPermission(String permissionName, int requestCode); boolean needRequestCameraPermission(); } interface FileUriResolver { Uri resolveFileProviderUriForFile(String fileProviderName, File imageFile); void getFullImagePath(Uri imageUri, OnPathReadyListener listener); } interface OnPathReadyListener { void onPathReady(String path); } private Uri pendingCameraMediaUri; private MethodChannel.Result pendingResult; private MethodCall methodCall; public ImagePickerDelegate( final Activity activity, final File externalFilesDirectory, final ImageResizer imageResizer, final ImagePickerCache cache) { this( activity, externalFilesDirectory, imageResizer, null, null, cache, new PermissionManager() { @Override public boolean isPermissionGranted(String permissionName) { return ActivityCompat.checkSelfPermission(activity, permissionName) == PackageManager.PERMISSION_GRANTED; } @Override public void askForPermission(String permissionName, int requestCode) { ActivityCompat.requestPermissions(activity, new String[] {permissionName}, requestCode); } @Override public boolean needRequestCameraPermission() { return ImagePickerUtils.needRequestCameraPermission(activity); } }, new FileUriResolver() { @Override public Uri resolveFileProviderUriForFile(String fileProviderName, File file) { return FileProvider.getUriForFile(activity, fileProviderName, file); } @Override public void getFullImagePath(final Uri imageUri, final OnPathReadyListener listener) { MediaScannerConnection.scanFile( activity, new String[] {(imageUri != null) ? imageUri.getPath() : ""}, null, new MediaScannerConnection.OnScanCompletedListener() { @Override public void onScanCompleted(String path, Uri uri) { listener.onPathReady(path); } }); } }, new FileUtils()); } /** * This constructor is used exclusively for testing; it can be used to provide mocks to final * fields of this class. Otherwise those fields would have to be mutable and visible. */ @VisibleForTesting ImagePickerDelegate( final Activity activity, final File externalFilesDirectory, final ImageResizer imageResizer, final MethodChannel.Result result, final MethodCall methodCall, final ImagePickerCache cache, final PermissionManager permissionManager, final FileUriResolver fileUriResolver, final FileUtils fileUtils) { this.activity = activity; this.externalFilesDirectory = externalFilesDirectory; this.imageResizer = imageResizer; this.fileProviderName = activity.getPackageName() + ".flutter.image_provider"; this.pendingResult = result; this.methodCall = methodCall; this.permissionManager = permissionManager; this.fileUriResolver = fileUriResolver; this.fileUtils = fileUtils; this.cache = cache; } void setCameraDevice(CameraDevice device) { cameraDevice = device; } CameraDevice getCameraDevice() { return cameraDevice; } // Save the state of the image picker so it can be retrieved with `retrieveLostImage`. void saveStateBeforeResult() { if (methodCall == null) { return; } cache.saveTypeWithMethodCallName(methodCall.method); cache.saveDimensionWithMethodCall(methodCall); if (pendingCameraMediaUri != null) { cache.savePendingCameraMediaUriPath(pendingCameraMediaUri); } } void retrieveLostImage(MethodChannel.Result result) { Map resultMap = cache.getCacheMap(); @SuppressWarnings("unchecked") ArrayList pathList = (ArrayList) resultMap.get(cache.MAP_KEY_PATH_LIST); ArrayList newPathList = new ArrayList<>(); if (pathList != null) { for (String path : pathList) { Double maxWidth = (Double) resultMap.get(cache.MAP_KEY_MAX_WIDTH); Double maxHeight = (Double) resultMap.get(cache.MAP_KEY_MAX_HEIGHT); int imageQuality = resultMap.get(cache.MAP_KEY_IMAGE_QUALITY) == null ? 100 : (int) resultMap.get(cache.MAP_KEY_IMAGE_QUALITY); newPathList.add(imageResizer.resizeImageIfNeeded(path, maxWidth, maxHeight, imageQuality)); } resultMap.put(cache.MAP_KEY_PATH_LIST, newPathList); resultMap.put(cache.MAP_KEY_PATH, newPathList.get(newPathList.size() - 1)); } if (resultMap.isEmpty()) { result.success(null); } else { result.success(resultMap); } cache.clear(); } public void chooseVideoFromGallery(MethodCall methodCall, MethodChannel.Result result) { if (!setPendingMethodCallAndResult(methodCall, result)) { finishWithAlreadyActiveError(result); return; } launchPickVideoFromGalleryIntent(); } private void launchPickVideoFromGalleryIntent() { Intent pickVideoIntent = new Intent(Intent.ACTION_GET_CONTENT); pickVideoIntent.setType("video/*"); activity.startActivityForResult(pickVideoIntent, REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY); } public void takeVideoWithCamera(MethodCall methodCall, MethodChannel.Result result) { if (!setPendingMethodCallAndResult(methodCall, result)) { finishWithAlreadyActiveError(result); return; } if (needRequestCameraPermission() && !permissionManager.isPermissionGranted(Manifest.permission.CAMERA)) { permissionManager.askForPermission( Manifest.permission.CAMERA, REQUEST_CAMERA_VIDEO_PERMISSION); return; } launchTakeVideoWithCameraIntent(); } private void launchTakeVideoWithCameraIntent() { Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); if (this.methodCall != null && this.methodCall.argument("maxDuration") != null) { int maxSeconds = this.methodCall.argument("maxDuration"); intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, maxSeconds); } if (cameraDevice == CameraDevice.FRONT) { useFrontCamera(intent); } File videoFile = createTemporaryWritableVideoFile(); pendingCameraMediaUri = Uri.parse("file:" + videoFile.getAbsolutePath()); Uri videoUri = fileUriResolver.resolveFileProviderUriForFile(fileProviderName, videoFile); intent.putExtra(MediaStore.EXTRA_OUTPUT, videoUri); grantUriPermissions(intent, videoUri); try { activity.startActivityForResult(intent, REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA); } catch (ActivityNotFoundException e) { try { // If we can't delete the file again here, there's not really anything we can do about it. //noinspection ResultOfMethodCallIgnored videoFile.delete(); } catch (SecurityException exception) { exception.printStackTrace(); } finishWithError("no_available_camera", "No cameras available for taking pictures."); } } public void chooseImageFromGallery(MethodCall methodCall, MethodChannel.Result result) { if (!setPendingMethodCallAndResult(methodCall, result)) { finishWithAlreadyActiveError(result); return; } launchPickImageFromGalleryIntent(); } public void chooseMultiImageFromGallery(MethodCall methodCall, MethodChannel.Result result) { if (!setPendingMethodCallAndResult(methodCall, result)) { finishWithAlreadyActiveError(result); return; } launchMultiPickImageFromGalleryIntent(); } private void launchPickImageFromGalleryIntent() { Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT); pickImageIntent.setType("image/*"); activity.startActivityForResult(pickImageIntent, REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY); } private void launchMultiPickImageFromGalleryIntent() { Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { pickImageIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); } pickImageIntent.setType("image/*"); activity.startActivityForResult(pickImageIntent, REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY); } public void takeImageWithCamera(MethodCall methodCall, MethodChannel.Result result) { if (!setPendingMethodCallAndResult(methodCall, result)) { finishWithAlreadyActiveError(result); return; } if (needRequestCameraPermission() && !permissionManager.isPermissionGranted(Manifest.permission.CAMERA)) { permissionManager.askForPermission( Manifest.permission.CAMERA, REQUEST_CAMERA_IMAGE_PERMISSION); return; } launchTakeImageWithCameraIntent(); } private boolean needRequestCameraPermission() { if (permissionManager == null) { return false; } return permissionManager.needRequestCameraPermission(); } private void launchTakeImageWithCameraIntent() { Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (cameraDevice == CameraDevice.FRONT) { useFrontCamera(intent); } File imageFile = createTemporaryWritableImageFile(); pendingCameraMediaUri = Uri.parse("file:" + imageFile.getAbsolutePath()); Uri imageUri = fileUriResolver.resolveFileProviderUriForFile(fileProviderName, imageFile); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); grantUriPermissions(intent, imageUri); try { activity.startActivityForResult(intent, REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA); } catch (ActivityNotFoundException e) { try { // If we can't delete the file again here, there's not really anything we can do about it. //noinspection ResultOfMethodCallIgnored imageFile.delete(); } catch (SecurityException exception) { exception.printStackTrace(); } finishWithError("no_available_camera", "No cameras available for taking pictures."); } } private File createTemporaryWritableImageFile() { return createTemporaryWritableFile(".jpg"); } private File createTemporaryWritableVideoFile() { return createTemporaryWritableFile(".mp4"); } private File createTemporaryWritableFile(String suffix) { String filename = UUID.randomUUID().toString(); File image; try { externalFilesDirectory.mkdirs(); image = File.createTempFile(filename, suffix, externalFilesDirectory); } catch (IOException e) { throw new RuntimeException(e); } return image; } private void grantUriPermissions(Intent intent, Uri imageUri) { PackageManager packageManager = activity.getPackageManager(); List compatibleActivities = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo info : compatibleActivities) { activity.grantUriPermission( info.activityInfo.packageName, imageUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } } @Override public boolean onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults) { boolean permissionGranted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED; switch (requestCode) { case REQUEST_CAMERA_IMAGE_PERMISSION: if (permissionGranted) { launchTakeImageWithCameraIntent(); } break; case REQUEST_CAMERA_VIDEO_PERMISSION: if (permissionGranted) { launchTakeVideoWithCameraIntent(); } break; default: return false; } if (!permissionGranted) { switch (requestCode) { case REQUEST_CAMERA_IMAGE_PERMISSION: case REQUEST_CAMERA_VIDEO_PERMISSION: finishWithError("camera_access_denied", "The user did not allow camera access."); break; } } return true; } @Override public boolean onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY: handleChooseImageResult(resultCode, data); break; case REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY: handleChooseMultiImageResult(resultCode, data); break; case REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA: handleCaptureImageResult(resultCode); break; case REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY: handleChooseVideoResult(resultCode, data); break; case REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA: handleCaptureVideoResult(resultCode); break; default: return false; } return true; } private void handleChooseImageResult(int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK && data != null) { String path = fileUtils.getPathFromUri(activity, data.getData()); handleImageResult(path, false); return; } // User cancelled choosing a picture. finishWithSuccess(null); } private void handleChooseMultiImageResult(int resultCode, Intent intent) { if (resultCode == Activity.RESULT_OK && intent != null) { ArrayList paths = new ArrayList<>(); if (intent.getClipData() != null) { for (int i = 0; i < intent.getClipData().getItemCount(); i++) { paths.add(fileUtils.getPathFromUri(activity, intent.getClipData().getItemAt(i).getUri())); } } else { paths.add(fileUtils.getPathFromUri(activity, intent.getData())); } handleMultiImageResult(paths, false); return; } // User cancelled choosing a picture. finishWithSuccess(null); } private void handleChooseVideoResult(int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK && data != null) { String path = fileUtils.getPathFromUri(activity, data.getData()); handleVideoResult(path); return; } // User cancelled choosing a picture. finishWithSuccess(null); } private void handleCaptureImageResult(int resultCode) { if (resultCode == Activity.RESULT_OK) { fileUriResolver.getFullImagePath( pendingCameraMediaUri != null ? pendingCameraMediaUri : Uri.parse(cache.retrievePendingCameraMediaUriPath()), new OnPathReadyListener() { @Override public void onPathReady(String path) { handleImageResult(path, true); } }); return; } // User cancelled taking a picture. finishWithSuccess(null); } private void handleCaptureVideoResult(int resultCode) { if (resultCode == Activity.RESULT_OK) { fileUriResolver.getFullImagePath( pendingCameraMediaUri != null ? pendingCameraMediaUri : Uri.parse(cache.retrievePendingCameraMediaUriPath()), new OnPathReadyListener() { @Override public void onPathReady(String path) { handleVideoResult(path); } }); return; } // User cancelled taking a picture. finishWithSuccess(null); } private void handleMultiImageResult( ArrayList paths, boolean shouldDeleteOriginalIfScaled) { if (methodCall != null) { ArrayList finalPath = new ArrayList<>(); for (int i = 0; i < paths.size(); i++) { String finalImagePath = getResizedImagePath(paths.get(i)); //delete original file if scaled if (finalImagePath != null && !finalImagePath.equals(paths.get(i)) && shouldDeleteOriginalIfScaled) { new File(paths.get(i)).delete(); } finalPath.add(i, finalImagePath); } finishWithListSuccess(finalPath); } else { finishWithListSuccess(paths); } } private void handleImageResult(String path, boolean shouldDeleteOriginalIfScaled) { if (methodCall != null) { String finalImagePath = getResizedImagePath(path); //delete original file if scaled if (finalImagePath != null && !finalImagePath.equals(path) && shouldDeleteOriginalIfScaled) { new File(path).delete(); } finishWithSuccess(finalImagePath); } else { finishWithSuccess(path); } } private String getResizedImagePath(String path) { Double maxWidth = methodCall.argument("maxWidth"); Double maxHeight = methodCall.argument("maxHeight"); Integer imageQuality = methodCall.argument("imageQuality"); return imageResizer.resizeImageIfNeeded(path, maxWidth, maxHeight, imageQuality); } private void handleVideoResult(String path) { finishWithSuccess(path); } private boolean setPendingMethodCallAndResult( MethodCall methodCall, MethodChannel.Result result) { if (pendingResult != null) { return false; } this.methodCall = methodCall; pendingResult = result; // Clean up cache if a new image picker is launched. cache.clear(); return true; } // Handles completion of selection with a single result. // // A null imagePath indicates that the image picker was cancelled without // selection. private void finishWithSuccess(@Nullable String imagePath) { if (pendingResult == null) { // Only save data for later retrieval if something was actually selected. if (imagePath != null) { ArrayList pathList = new ArrayList<>(); pathList.add(imagePath); cache.saveResult(pathList, null, null); } return; } pendingResult.success(imagePath); clearMethodCallAndResult(); } private void finishWithListSuccess(ArrayList imagePaths) { if (pendingResult == null) { cache.saveResult(imagePaths, null, null); return; } pendingResult.success(imagePaths); clearMethodCallAndResult(); } private void finishWithAlreadyActiveError(MethodChannel.Result result) { result.error("already_active", "Image picker is already active", null); } private void finishWithError(String errorCode, String errorMessage) { if (pendingResult == null) { cache.saveResult(null, errorCode, errorMessage); return; } pendingResult.error(errorCode, errorMessage, null); clearMethodCallAndResult(); } private void clearMethodCallAndResult() { methodCall = null; pendingResult = null; } private void useFrontCamera(Intent intent) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { intent.putExtra( "android.intent.extras.CAMERA_FACING", CameraCharacteristics.LENS_FACING_FRONT); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { intent.putExtra("android.intent.extra.USE_FRONT_CAMERA", true); } } else { intent.putExtra("android.intent.extras.CAMERA_FACING", 1); } } } ================================================ FILE: packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerFileProvider.java ================================================ // Copyright 2013 The Flutter 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.imagepicker; import androidx.core.content.FileProvider; /** * Providing a custom {@code FileProvider} prevents manifest {@code } name collisions. * *

See https://developer.android.com/guide/topics/manifest/provider-element.html for details. */ public class ImagePickerFileProvider extends FileProvider {} ================================================ FILE: packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java ================================================ // Copyright 2013 The Flutter 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.imagepicker; import android.app.Activity; import android.app.Application; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.Lifecycle; 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.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.PluginRegistry; import java.io.File; @SuppressWarnings("deprecation") public class ImagePickerPlugin implements MethodChannel.MethodCallHandler, FlutterPlugin, ActivityAware { private class LifeCycleObserver implements Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver { private final Activity thisActivity; LifeCycleObserver(Activity activity) { this.thisActivity = activity; } @Override public void onCreate(@NonNull LifecycleOwner owner) {} @Override public void onStart(@NonNull LifecycleOwner owner) {} @Override public void onResume(@NonNull LifecycleOwner owner) {} @Override public void onPause(@NonNull LifecycleOwner owner) {} @Override public void onStop(@NonNull LifecycleOwner owner) { onActivityStopped(thisActivity); } @Override public void onDestroy(@NonNull LifecycleOwner owner) { onActivityDestroyed(thisActivity); } @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {} @Override public void onActivityStarted(Activity activity) {} @Override public void onActivityResumed(Activity activity) {} @Override public void onActivityPaused(Activity activity) {} @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} @Override public void onActivityDestroyed(Activity activity) { if (thisActivity == activity && activity.getApplicationContext() != null) { ((Application) activity.getApplicationContext()) .unregisterActivityLifecycleCallbacks( this); // Use getApplicationContext() to avoid casting failures } } @Override public void onActivityStopped(Activity activity) { if (thisActivity == activity) { activityState.getDelegate().saveStateBeforeResult(); } } } /** * Move all activity-lifetime-bound states into this helper object, so that {@code setup} and * {@code tearDown} would just become constructor and finalize calls of the helper object. */ private class ActivityState { private Application application; private Activity activity; private ImagePickerDelegate delegate; private MethodChannel channel; private LifeCycleObserver observer; private ActivityPluginBinding activityBinding; // This is null when not using v2 embedding; private Lifecycle lifecycle; // Default constructor ActivityState( final Application application, final Activity activity, final BinaryMessenger messenger, final MethodChannel.MethodCallHandler handler, final PluginRegistry.Registrar registrar, final ActivityPluginBinding activityBinding) { this.application = application; this.activity = activity; this.activityBinding = activityBinding; delegate = constructDelegate(activity); channel = new MethodChannel(messenger, CHANNEL); channel.setMethodCallHandler(handler); observer = new LifeCycleObserver(activity); if (registrar != null) { // V1 embedding setup for activity listeners. application.registerActivityLifecycleCallbacks(observer); registrar.addActivityResultListener(delegate); registrar.addRequestPermissionsResultListener(delegate); } else { // V2 embedding setup for activity listeners. activityBinding.addActivityResultListener(delegate); activityBinding.addRequestPermissionsResultListener(delegate); lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(activityBinding); lifecycle.addObserver(observer); } } // Only invoked by {@link #ImagePickerPlugin(ImagePickerDelegate, Activity)} for testing. ActivityState(final ImagePickerDelegate delegate, final Activity activity) { this.activity = activity; this.delegate = delegate; } void release() { if (activityBinding != null) { activityBinding.removeActivityResultListener(delegate); activityBinding.removeRequestPermissionsResultListener(delegate); activityBinding = null; } if (lifecycle != null) { lifecycle.removeObserver(observer); lifecycle = null; } if (channel != null) { channel.setMethodCallHandler(null); channel = null; } if (application != null) { application.unregisterActivityLifecycleCallbacks(observer); application = null; } activity = null; observer = null; delegate = null; } Activity getActivity() { return activity; } ImagePickerDelegate getDelegate() { return delegate; } } static final String METHOD_CALL_IMAGE = "pickImage"; static final String METHOD_CALL_MULTI_IMAGE = "pickMultiImage"; static final String METHOD_CALL_VIDEO = "pickVideo"; private static final String METHOD_CALL_RETRIEVE = "retrieve"; private static final int CAMERA_DEVICE_FRONT = 1; private static final int CAMERA_DEVICE_REAR = 0; private static final String CHANNEL = "plugins.flutter.io/image_picker_android"; private static final int SOURCE_CAMERA = 0; private static final int SOURCE_GALLERY = 1; private FlutterPluginBinding pluginBinding; private ActivityState activityState; @SuppressWarnings("deprecation") public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { if (registrar.activity() == null) { // If a background flutter view tries to register the plugin, there will be no activity from the registrar, // we stop the registering process immediately because the ImagePicker requires an activity. return; } Activity activity = registrar.activity(); Application application = null; if (registrar.context() != null) { application = (Application) (registrar.context().getApplicationContext()); } ImagePickerPlugin plugin = new ImagePickerPlugin(); plugin.setup(registrar.messenger(), application, activity, registrar, null); } /** * Default constructor for the plugin. * *

Use this constructor for production code. */ // See also: * {@link #ImagePickerPlugin(ImagePickerDelegate, Activity)} for testing. public ImagePickerPlugin() {} @VisibleForTesting ImagePickerPlugin(final ImagePickerDelegate delegate, final Activity activity) { activityState = new ActivityState(delegate, activity); } @VisibleForTesting final ActivityState getActivityState() { return activityState; } @Override public void onAttachedToEngine(FlutterPluginBinding binding) { pluginBinding = binding; } @Override public void onDetachedFromEngine(FlutterPluginBinding binding) { pluginBinding = null; } @Override public void onAttachedToActivity(ActivityPluginBinding binding) { setup( pluginBinding.getBinaryMessenger(), (Application) pluginBinding.getApplicationContext(), binding.getActivity(), null, binding); } @Override public void onDetachedFromActivity() { tearDown(); } @Override public void onDetachedFromActivityForConfigChanges() { onDetachedFromActivity(); } @Override public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { onAttachedToActivity(binding); } private void setup( final BinaryMessenger messenger, final Application application, final Activity activity, final PluginRegistry.Registrar registrar, final ActivityPluginBinding activityBinding) { activityState = new ActivityState(application, activity, messenger, this, registrar, activityBinding); } private void tearDown() { if (activityState != null) { activityState.release(); activityState = null; } } @VisibleForTesting final ImagePickerDelegate constructDelegate(final Activity setupActivity) { final ImagePickerCache cache = new ImagePickerCache(setupActivity); final File externalFilesDirectory = setupActivity.getCacheDir(); final ExifDataCopier exifDataCopier = new ExifDataCopier(); final ImageResizer imageResizer = new ImageResizer(externalFilesDirectory, exifDataCopier); return new ImagePickerDelegate(setupActivity, externalFilesDirectory, imageResizer, cache); } // MethodChannel.Result wrapper that responds on the platform thread. private static class MethodResultWrapper implements MethodChannel.Result { private MethodChannel.Result methodResult; private Handler handler; MethodResultWrapper(MethodChannel.Result result) { methodResult = result; handler = new Handler(Looper.getMainLooper()); } @Override public void success(final Object result) { handler.post( new Runnable() { @Override public void run() { methodResult.success(result); } }); } @Override public void error( final String errorCode, final String errorMessage, final Object errorDetails) { handler.post( new Runnable() { @Override public void run() { methodResult.error(errorCode, errorMessage, errorDetails); } }); } @Override public void notImplemented() { handler.post( new Runnable() { @Override public void run() { methodResult.notImplemented(); } }); } } @Override public void onMethodCall(MethodCall call, MethodChannel.Result rawResult) { if (activityState == null || activityState.getActivity() == null) { rawResult.error("no_activity", "image_picker plugin requires a foreground activity.", null); return; } MethodChannel.Result result = new MethodResultWrapper(rawResult); int imageSource; ImagePickerDelegate delegate = activityState.getDelegate(); if (call.argument("cameraDevice") != null) { CameraDevice device; int deviceIntValue = call.argument("cameraDevice"); if (deviceIntValue == CAMERA_DEVICE_FRONT) { device = CameraDevice.FRONT; } else { device = CameraDevice.REAR; } delegate.setCameraDevice(device); } switch (call.method) { case METHOD_CALL_IMAGE: imageSource = call.argument("source"); switch (imageSource) { case SOURCE_GALLERY: delegate.chooseImageFromGallery(call, result); break; case SOURCE_CAMERA: delegate.takeImageWithCamera(call, result); break; default: throw new IllegalArgumentException("Invalid image source: " + imageSource); } break; case METHOD_CALL_MULTI_IMAGE: delegate.chooseMultiImageFromGallery(call, result); break; case METHOD_CALL_VIDEO: imageSource = call.argument("source"); switch (imageSource) { case SOURCE_GALLERY: delegate.chooseVideoFromGallery(call, result); break; case SOURCE_CAMERA: delegate.takeVideoWithCamera(call, result); break; default: throw new IllegalArgumentException("Invalid video source: " + imageSource); } break; case METHOD_CALL_RETRIEVE: delegate.retrieveLostImage(result); break; default: throw new IllegalArgumentException("Unknown method " + call.method); } } } ================================================ FILE: packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerUtils.java ================================================ // Copyright 2013 The Flutter 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.imagepicker; import android.Manifest; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; import java.util.Arrays; final class ImagePickerUtils { /** returns true, if permission present in manifest, otherwise false */ private static boolean isPermissionPresentInManifest(Context context, String permissionName) { try { PackageManager packageManager = context.getPackageManager(); PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS); String[] requestedPermissions = packageInfo.requestedPermissions; return Arrays.asList(requestedPermissions).contains(permissionName); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); return false; } } /** * Camera permission need request if it present in manifest, because for M or great for take Photo * ar Video by intent need it permission, even if the camera permission is not used. * *

Camera permission may be used in another package, as example flutter_barcode_reader. * https://github.com/flutter/flutter/issues/29837 * * @return returns true, if need request camera permission, otherwise false */ static boolean needRequestCameraPermission(Context context) { boolean greatOrEqualM = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; return greatOrEqualM && isPermissionPresentInManifest(context, Manifest.permission.CAMERA); } } ================================================ FILE: packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java ================================================ // Copyright 2013 The Flutter 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.imagepicker; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.Log; import androidx.annotation.Nullable; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; class ImageResizer { private final File externalFilesDirectory; private final ExifDataCopier exifDataCopier; ImageResizer(File externalFilesDirectory, ExifDataCopier exifDataCopier) { this.externalFilesDirectory = externalFilesDirectory; this.exifDataCopier = exifDataCopier; } /** * If necessary, resizes the image located in imagePath and then returns the path for the scaled * image. * *

If no resizing is needed, returns the path for the original image. */ String resizeImageIfNeeded( String imagePath, @Nullable Double maxWidth, @Nullable Double maxHeight, @Nullable Integer imageQuality) { Bitmap bmp = decodeFile(imagePath); if (bmp == null) { return null; } boolean shouldScale = maxWidth != null || maxHeight != null || isImageQualityValid(imageQuality); if (!shouldScale) { return imagePath; } try { String[] pathParts = imagePath.split("/"); String imageName = pathParts[pathParts.length - 1]; File file = resizedImage(bmp, maxWidth, maxHeight, imageQuality, imageName); copyExif(imagePath, file.getPath()); return file.getPath(); } catch (IOException e) { throw new RuntimeException(e); } } private File resizedImage( Bitmap bmp, Double maxWidth, Double maxHeight, Integer imageQuality, String outputImageName) throws IOException { double originalWidth = bmp.getWidth() * 1.0; double originalHeight = bmp.getHeight() * 1.0; if (!isImageQualityValid(imageQuality)) { imageQuality = 100; } boolean hasMaxWidth = maxWidth != null; boolean hasMaxHeight = maxHeight != null; Double width = hasMaxWidth ? Math.min(originalWidth, maxWidth) : originalWidth; Double height = hasMaxHeight ? Math.min(originalHeight, maxHeight) : originalHeight; boolean shouldDownscaleWidth = hasMaxWidth && maxWidth < originalWidth; boolean shouldDownscaleHeight = hasMaxHeight && maxHeight < originalHeight; boolean shouldDownscale = shouldDownscaleWidth || shouldDownscaleHeight; if (shouldDownscale) { double downscaledWidth = (height / originalHeight) * originalWidth; double downscaledHeight = (width / originalWidth) * originalHeight; if (width < height) { if (!hasMaxWidth) { width = downscaledWidth; } else { height = downscaledHeight; } } else if (height < width) { if (!hasMaxHeight) { height = downscaledHeight; } else { width = downscaledWidth; } } else { if (originalWidth < originalHeight) { width = downscaledWidth; } else if (originalHeight < originalWidth) { height = downscaledHeight; } } } Bitmap scaledBmp = createScaledBitmap(bmp, width.intValue(), height.intValue(), false); File file = createImageOnExternalDirectory("/scaled_" + outputImageName, scaledBmp, imageQuality); return file; } private File createFile(File externalFilesDirectory, String child) { File image = new File(externalFilesDirectory, child); if (!image.getParentFile().exists()) { image.getParentFile().mkdirs(); } return image; } private FileOutputStream createOutputStream(File imageFile) throws IOException { return new FileOutputStream(imageFile); } private void copyExif(String filePathOri, String filePathDest) { exifDataCopier.copyExif(filePathOri, filePathDest); } private Bitmap decodeFile(String path) { return BitmapFactory.decodeFile(path); } private Bitmap createScaledBitmap(Bitmap bmp, int width, int height, boolean filter) { return Bitmap.createScaledBitmap(bmp, width, height, filter); } private boolean isImageQualityValid(Integer imageQuality) { return imageQuality != null && imageQuality > 0 && imageQuality < 100; } private File createImageOnExternalDirectory(String name, Bitmap bitmap, int imageQuality) throws IOException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); boolean saveAsPNG = bitmap.hasAlpha(); if (saveAsPNG) { Log.d( "ImageResizer", "image_picker: compressing is not supported for type PNG. Returning the image with original quality"); } bitmap.compress( saveAsPNG ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG, imageQuality, outputStream); File imageFile = createFile(externalFilesDirectory, name); FileOutputStream fileOutput = createOutputStream(imageFile); fileOutput.write(outputStream.toByteArray()); fileOutput.close(); return imageFile; } } ================================================ FILE: packages/image_picker/image_picker_android/android/src/main/res/xml/flutter_image_picker_file_paths.xml ================================================ ================================================ FILE: packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java ================================================ // Copyright 2013 The Flutter 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.imagepicker; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertTrue; import static org.robolectric.Shadows.shadowOf; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.provider.MediaStore; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.shadows.ShadowContentResolver; @RunWith(RobolectricTestRunner.class) public class FileUtilTest { private Context context; private FileUtils fileUtils; ShadowContentResolver shadowContentResolver; @Before public void before() { context = ApplicationProvider.getApplicationContext(); shadowContentResolver = shadowOf(context.getContentResolver()); fileUtils = new FileUtils(); } @Test public void FileUtil_GetPathFromUri() throws IOException { Uri uri = Uri.parse("content://dummy/dummy.png"); shadowContentResolver.registerInputStream( uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8))); String path = fileUtils.getPathFromUri(context, uri); File file = new File(path); int size = (int) file.length(); byte[] bytes = new byte[size]; BufferedInputStream buf = new BufferedInputStream(new FileInputStream(file)); buf.read(bytes, 0, bytes.length); buf.close(); assertTrue(bytes.length > 0); String imageStream = new String(bytes, UTF_8); assertTrue(imageStream.equals("imageStream")); } @Test public void FileUtil_getImageExtension() throws IOException { Uri uri = Uri.parse("content://dummy/dummy.png"); shadowContentResolver.registerInputStream( uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8))); String path = fileUtils.getPathFromUri(context, uri); assertTrue(path.endsWith(".jpg")); } @Test public void FileUtil_getImageName() throws IOException { Uri uri = Uri.parse("content://dummy/dummy.png"); Robolectric.buildContentProvider(MockContentProvider.class).create("dummy"); shadowContentResolver.registerInputStream( uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8))); String path = fileUtils.getPathFromUri(context, uri); assertTrue(path.endsWith("dummy.png")); } private static class MockContentProvider extends ContentProvider { @Override public boolean onCreate() { return true; } @Nullable @Override public Cursor query( @NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { MatrixCursor cursor = new MatrixCursor(new String[] {MediaStore.MediaColumns.DISPLAY_NAME}); cursor.addRow(new Object[] {"dummy.png"}); return cursor; } @Nullable @Override public String getType(@NonNull Uri uri) { return "image/png"; } @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { return null; } @Override public int delete( @NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { return 0; } @Override public int update( @NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { return 0; } } } ================================================ FILE: packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerCacheTest.java ================================================ // Copyright 2013 The Flutter 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.imagepicker; import static io.flutter.plugins.imagepicker.ImagePickerCache.MAP_KEY_IMAGE_QUALITY; import static io.flutter.plugins.imagepicker.ImagePickerCache.SHARED_PREFERENCES_NAME; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageManager; import io.flutter.plugin.common.MethodCall; import java.util.HashMap; import java.util.Map; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; public class ImagePickerCacheTest { private static final int IMAGE_QUALITY = 90; @Mock Activity mockActivity; @Mock SharedPreferences mockPreference; @Mock SharedPreferences.Editor mockEditor; @Mock MethodCall mockMethodCall; static Map preferenceStorage; @Before public void setUp() { MockitoAnnotations.initMocks(this); preferenceStorage = new HashMap(); when(mockActivity.getPackageName()).thenReturn("com.example.test"); when(mockActivity.getPackageManager()).thenReturn(mock(PackageManager.class)); when(mockActivity.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)) .thenReturn(mockPreference); when(mockPreference.edit()).thenReturn(mockEditor); when(mockEditor.putInt(any(String.class), any(int.class))) .then( i -> { preferenceStorage.put(i.getArgument(0), i.getArgument(1)); return mockEditor; }); when(mockEditor.putLong(any(String.class), any(long.class))) .then( i -> { preferenceStorage.put(i.getArgument(0), i.getArgument(1)); return mockEditor; }); when(mockEditor.putString(any(String.class), any(String.class))) .then( i -> { preferenceStorage.put(i.getArgument(0), i.getArgument(1)); return mockEditor; }); when(mockPreference.getInt(any(String.class), any(int.class))) .then( i -> { int result = (int) ((preferenceStorage.get(i.getArgument(0)) != null) ? preferenceStorage.get(i.getArgument(0)) : i.getArgument(1)); return result; }); when(mockPreference.getLong(any(String.class), any(long.class))) .then( i -> { long result = (long) ((preferenceStorage.get(i.getArgument(0)) != null) ? preferenceStorage.get(i.getArgument(0)) : i.getArgument(1)); return result; }); when(mockPreference.getString(any(String.class), any(String.class))) .then( i -> { String result = (String) ((preferenceStorage.get(i.getArgument(0)) != null) ? preferenceStorage.get(i.getArgument(0)) : i.getArgument(1)); return result; }); when(mockPreference.contains(any(String.class))).thenReturn(true); } @Test public void ImageCache_ShouldBeAbleToSetAndGetQuality() { when(mockMethodCall.argument(MAP_KEY_IMAGE_QUALITY)).thenReturn(IMAGE_QUALITY); ImagePickerCache cache = new ImagePickerCache(mockActivity); cache.saveDimensionWithMethodCall(mockMethodCall); Map resultMap = cache.getCacheMap(); int imageQuality = (int) resultMap.get(cache.MAP_KEY_IMAGE_QUALITY); assertThat(imageQuality, equalTo(IMAGE_QUALITY)); when(mockMethodCall.argument(MAP_KEY_IMAGE_QUALITY)).thenReturn(null); cache.saveDimensionWithMethodCall(mockMethodCall); Map resultMapWithDefaultQuality = cache.getCacheMap(); int defaultImageQuality = (int) resultMapWithDefaultQuality.get(cache.MAP_KEY_IMAGE_QUALITY); assertThat(defaultImageQuality, equalTo(100)); } } ================================================ FILE: packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java ================================================ // Copyright 2013 The Flutter 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.imagepicker; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.Manifest; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class ImagePickerDelegateTest { private static final Double WIDTH = 10.0; private static final Double HEIGHT = 10.0; private static final Double MAX_DURATION = 10.0; private static final Integer IMAGE_QUALITY = 90; @Mock Activity mockActivity; @Mock ImageResizer mockImageResizer; @Mock MethodCall mockMethodCall; @Mock MethodChannel.Result mockResult; @Mock ImagePickerDelegate.PermissionManager mockPermissionManager; @Mock FileUtils mockFileUtils; @Mock Intent mockIntent; @Mock ImagePickerCache cache; ImagePickerDelegate.FileUriResolver mockFileUriResolver; MockedStatic mockStaticFile; private static class MockFileUriResolver implements ImagePickerDelegate.FileUriResolver { @Override public Uri resolveFileProviderUriForFile(String fileProviderName, File imageFile) { return null; } @Override public void getFullImagePath(Uri imageUri, ImagePickerDelegate.OnPathReadyListener listener) { listener.onPathReady("pathFromUri"); } } @Before public void setUp() { MockitoAnnotations.initMocks(this); mockStaticFile = Mockito.mockStatic(File.class); mockStaticFile .when(() -> File.createTempFile(any(), any(), any())) .thenReturn(new File("/tmpfile")); when(mockActivity.getPackageName()).thenReturn("com.example.test"); when(mockActivity.getPackageManager()).thenReturn(mock(PackageManager.class)); when(mockFileUtils.getPathFromUri(any(Context.class), any(Uri.class))) .thenReturn("pathFromUri"); when(mockImageResizer.resizeImageIfNeeded("pathFromUri", null, null, null)) .thenReturn("originalPath"); when(mockImageResizer.resizeImageIfNeeded("pathFromUri", null, null, IMAGE_QUALITY)) .thenReturn("originalPath"); when(mockImageResizer.resizeImageIfNeeded("pathFromUri", WIDTH, HEIGHT, null)) .thenReturn("scaledPath"); when(mockImageResizer.resizeImageIfNeeded("pathFromUri", WIDTH, null, null)) .thenReturn("scaledPath"); when(mockImageResizer.resizeImageIfNeeded("pathFromUri", null, HEIGHT, null)) .thenReturn("scaledPath"); mockFileUriResolver = new MockFileUriResolver(); Uri mockUri = mock(Uri.class); when(mockIntent.getData()).thenReturn(mockUri); } @After public void tearDown() { mockStaticFile.close(); } @Test public void whenConstructed_setsCorrectFileProviderName() { ImagePickerDelegate delegate = createDelegate(); assertThat(delegate.fileProviderName, equalTo("com.example.test.flutter.image_provider")); } @Test public void chooseImageFromGallery_WhenPendingResultExists_FinishesWithAlreadyActiveError() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.chooseImageFromGallery(mockMethodCall, mockResult); verifyFinishedWithAlreadyActiveError(); verifyNoMoreInteractions(mockResult); } @Test public void chooseMultiImageFromGallery_WhenPendingResultExists_FinishesWithAlreadyActiveError() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.chooseMultiImageFromGallery(mockMethodCall, mockResult); verifyFinishedWithAlreadyActiveError(); verifyNoMoreInteractions(mockResult); } public void chooseImageFromGallery_WhenHasExternalStoragePermission_LaunchesChooseFromGalleryIntent() { when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) .thenReturn(true); ImagePickerDelegate delegate = createDelegate(); delegate.chooseImageFromGallery(mockMethodCall, mockResult); verify(mockActivity) .startActivityForResult( any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY)); } @Test public void takeImageWithCamera_WhenPendingResultExists_FinishesWithAlreadyActiveError() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.takeImageWithCamera(mockMethodCall, mockResult); verifyFinishedWithAlreadyActiveError(); verifyNoMoreInteractions(mockResult); } @Test public void takeImageWithCamera_WhenHasNoCameraPermission_RequestsForPermission() { when(mockPermissionManager.isPermissionGranted(Manifest.permission.CAMERA)).thenReturn(false); when(mockPermissionManager.needRequestCameraPermission()).thenReturn(true); ImagePickerDelegate delegate = createDelegate(); delegate.takeImageWithCamera(mockMethodCall, mockResult); verify(mockPermissionManager) .askForPermission( Manifest.permission.CAMERA, ImagePickerDelegate.REQUEST_CAMERA_IMAGE_PERMISSION); } @Test public void takeImageWithCamera_WhenCameraPermissionNotPresent_RequestsForPermission() { when(mockPermissionManager.needRequestCameraPermission()).thenReturn(false); ImagePickerDelegate delegate = createDelegate(); delegate.takeImageWithCamera(mockMethodCall, mockResult); verify(mockActivity) .startActivityForResult( any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA)); } @Test public void takeImageWithCamera_WhenHasCameraPermission_AndAnActivityCanHandleCameraIntent_LaunchesTakeWithCameraIntent() { when(mockPermissionManager.isPermissionGranted(Manifest.permission.CAMERA)).thenReturn(true); ImagePickerDelegate delegate = createDelegate(); delegate.takeImageWithCamera(mockMethodCall, mockResult); verify(mockActivity) .startActivityForResult( any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA)); } @Test public void takeImageWithCamera_WhenHasCameraPermission_AndNoActivityToHandleCameraIntent_FinishesWithNoCamerasAvailableError() { when(mockPermissionManager.isPermissionGranted(Manifest.permission.CAMERA)).thenReturn(true); doThrow(ActivityNotFoundException.class) .when(mockActivity) .startActivityForResult(any(Intent.class), anyInt()); ImagePickerDelegate delegate = createDelegate(); delegate.takeImageWithCamera(mockMethodCall, mockResult); verify(mockResult) .error("no_available_camera", "No cameras available for taking pictures.", null); verifyNoMoreInteractions(mockResult); } @Test public void takeImageWithCamera_WritesImageToCacheDirectory() { when(mockPermissionManager.isPermissionGranted(Manifest.permission.CAMERA)).thenReturn(true); ImagePickerDelegate delegate = createDelegate(); delegate.takeImageWithCamera(mockMethodCall, mockResult); mockStaticFile.verify( () -> File.createTempFile(any(), eq(".jpg"), eq(new File("/image_picker_cache"))), times(1)); } @Test public void onRequestPermissionsResult_WhenCameraPermissionDenied_FinishesWithError() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.onRequestPermissionsResult( ImagePickerDelegate.REQUEST_CAMERA_IMAGE_PERMISSION, new String[] {Manifest.permission.CAMERA}, new int[] {PackageManager.PERMISSION_DENIED}); verify(mockResult).error("camera_access_denied", "The user did not allow camera access.", null); verifyNoMoreInteractions(mockResult); } @Test public void onRequestTakeVideoPermissionsResult_WhenCameraPermissionGranted_LaunchesTakeVideoWithCameraIntent() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.onRequestPermissionsResult( ImagePickerDelegate.REQUEST_CAMERA_VIDEO_PERMISSION, new String[] {Manifest.permission.CAMERA}, new int[] {PackageManager.PERMISSION_GRANTED}); verify(mockActivity) .startActivityForResult( any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA)); } @Test public void onRequestTakeImagePermissionsResult_WhenCameraPermissionGranted_LaunchesTakeWithCameraIntent() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.onRequestPermissionsResult( ImagePickerDelegate.REQUEST_CAMERA_IMAGE_PERMISSION, new String[] {Manifest.permission.CAMERA}, new int[] {PackageManager.PERMISSION_GRANTED}); verify(mockActivity) .startActivityForResult( any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA)); } @Test public void onActivityResult_WhenPickFromGalleryCanceled_FinishesWithNull() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_CANCELED, null); verify(mockResult).success(null); verifyNoMoreInteractions(mockResult); } @Test public void onActivityResult_WhenPickFromGalleryCanceled_StoresNothingInCache() { ImagePickerDelegate delegate = createDelegate(); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_CANCELED, null); verify(cache, never()).saveResult(any(), any(), any()); } @Test public void onActivityResult_WhenImagePickedFromGallery_AndNoResizeNeeded_FinishesWithImagePath() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); verify(mockResult).success("originalPath"); verifyNoMoreInteractions(mockResult); } @Test public void onActivityResult_WhenImagePickedFromGallery_AndNoResizeNeeded_StoresImageInCache() { ImagePickerDelegate delegate = createDelegate(); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); ArgumentCaptor> pathListCapture = ArgumentCaptor.forClass(ArrayList.class); verify(cache, times(1)).saveResult(pathListCapture.capture(), any(), any()); assertEquals("pathFromUri", pathListCapture.getValue().get(0)); } @Test public void onActivityResult_WhenImagePickedFromGallery_AndResizeNeeded_FinishesWithScaledImagePath() { when(mockMethodCall.argument("maxWidth")).thenReturn(WIDTH); ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY, Activity.RESULT_OK, mockIntent); verify(mockResult).success("scaledPath"); verifyNoMoreInteractions(mockResult); } @Test public void onActivityResult_WhenVideoPickedFromGallery_AndResizeParametersSupplied_FinishesWithFilePath() { when(mockMethodCall.argument("maxWidth")).thenReturn(WIDTH); ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY, Activity.RESULT_OK, mockIntent); verify(mockResult).success("pathFromUri"); verifyNoMoreInteractions(mockResult); } @Test public void onActivityResult_WhenTakeImageWithCameraCanceled_FinishesWithNull() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_CANCELED, null); verify(mockResult).success(null); verifyNoMoreInteractions(mockResult); } @Test public void onActivityResult_WhenImageTakenWithCamera_AndNoResizeNeeded_FinishesWithImagePath() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_OK, mockIntent); verify(mockResult).success("originalPath"); verifyNoMoreInteractions(mockResult); } @Test public void onActivityResult_WhenImageTakenWithCamera_AndResizeNeeded_FinishesWithScaledImagePath() { when(mockMethodCall.argument("maxWidth")).thenReturn(WIDTH); ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA, Activity.RESULT_OK, mockIntent); verify(mockResult).success("scaledPath"); verifyNoMoreInteractions(mockResult); } @Test public void onActivityResult_WhenVideoTakenWithCamera_AndResizeParametersSupplied_FinishesWithFilePath() { when(mockMethodCall.argument("maxWidth")).thenReturn(WIDTH); ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA, Activity.RESULT_OK, mockIntent); verify(mockResult).success("pathFromUri"); verifyNoMoreInteractions(mockResult); } @Test public void onActivityResult_WhenVideoTakenWithCamera_AndMaxDurationParametersSupplied_FinishesWithFilePath() { when(mockMethodCall.argument("maxDuration")).thenReturn(MAX_DURATION); ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.onActivityResult( ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA, Activity.RESULT_OK, mockIntent); verify(mockResult).success("pathFromUri"); verifyNoMoreInteractions(mockResult); } @Test public void retrieveLostImage_ShouldBeAbleToReturnLastItemFromResultMapWhenSingleFileIsRecovered() { Map resultMap = new HashMap<>(); ArrayList pathList = new ArrayList<>(); pathList.add("/example/first_item"); pathList.add("/example/last_item"); resultMap.put("pathList", pathList); when(mockImageResizer.resizeImageIfNeeded(pathList.get(0), null, null, 100)) .thenReturn(pathList.get(0)); when(mockImageResizer.resizeImageIfNeeded(pathList.get(1), null, null, 100)) .thenReturn(pathList.get(1)); when(cache.getCacheMap()).thenReturn(resultMap); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); ImagePickerDelegate mockDelegate = createDelegate(); ArgumentCaptor> valueCapture = ArgumentCaptor.forClass(Map.class); doNothing().when(mockResult).success(valueCapture.capture()); mockDelegate.retrieveLostImage(mockResult); assertEquals("/example/last_item", valueCapture.getValue().get("path")); } private ImagePickerDelegate createDelegate() { return new ImagePickerDelegate( mockActivity, new File("/image_picker_cache"), mockImageResizer, null, null, cache, mockPermissionManager, mockFileUriResolver, mockFileUtils); } private ImagePickerDelegate createDelegateWithPendingResultAndMethodCall() { return new ImagePickerDelegate( mockActivity, new File("/image_picker_cache"), mockImageResizer, mockResult, mockMethodCall, cache, mockPermissionManager, mockFileUriResolver, mockFileUtils); } private void verifyFinishedWithAlreadyActiveError() { verify(mockResult).error("already_active", "Image picker is already active", null); } } ================================================ FILE: packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java ================================================ // Copyright 2013 The Flutter 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.imagepicker; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import android.app.Activity; import android.app.Application; import androidx.lifecycle.Lifecycle; import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.embedding.engine.plugins.lifecycle.HiddenLifecycleReference; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import java.io.File; import java.util.HashMap; import java.util.Map; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.Mock; import org.mockito.MockitoAnnotations; public class ImagePickerPluginTest { private static final int SOURCE_CAMERA = 0; private static final int SOURCE_GALLERY = 1; private static final String PICK_IMAGE = "pickImage"; private static final String PICK_MULTI_IMAGE = "pickMultiImage"; private static final String PICK_VIDEO = "pickVideo"; @Rule public ExpectedException exception = ExpectedException.none(); @SuppressWarnings("deprecation") @Mock io.flutter.plugin.common.PluginRegistry.Registrar mockRegistrar; @Mock ActivityPluginBinding mockActivityBinding; @Mock FlutterPluginBinding mockPluginBinding; @Mock Activity mockActivity; @Mock Application mockApplication; @Mock ImagePickerDelegate mockImagePickerDelegate; @Mock MethodChannel.Result mockResult; ImagePickerPlugin plugin; @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mockRegistrar.context()).thenReturn(mockApplication); when(mockActivityBinding.getActivity()).thenReturn(mockActivity); when(mockPluginBinding.getApplicationContext()).thenReturn(mockApplication); plugin = new ImagePickerPlugin(mockImagePickerDelegate, mockActivity); } @Test public void onMethodCall_WhenActivityIsNull_FinishesWithForegroundActivityRequiredError() { MethodCall call = buildMethodCall(PICK_IMAGE, SOURCE_GALLERY); ImagePickerPlugin imagePickerPluginWithNullActivity = new ImagePickerPlugin(mockImagePickerDelegate, null); imagePickerPluginWithNullActivity.onMethodCall(call, mockResult); verify(mockResult) .error("no_activity", "image_picker plugin requires a foreground activity.", null); verifyNoInteractions(mockImagePickerDelegate); } @Test public void onMethodCall_WhenCalledWithUnknownMethod_ThrowsException() { exception.expect(IllegalArgumentException.class); exception.expectMessage("Unknown method test"); plugin.onMethodCall(new MethodCall("test", null), mockResult); verifyNoInteractions(mockImagePickerDelegate); verifyNoInteractions(mockResult); } @Test public void onMethodCall_WhenCalledWithUnknownImageSource_ThrowsException() { exception.expect(IllegalArgumentException.class); exception.expectMessage("Invalid image source: -1"); plugin.onMethodCall(buildMethodCall(PICK_IMAGE, -1), mockResult); verifyNoInteractions(mockImagePickerDelegate); verifyNoInteractions(mockResult); } @Test public void onMethodCall_WhenSourceIsGallery_InvokesChooseImageFromGallery() { MethodCall call = buildMethodCall(PICK_IMAGE, SOURCE_GALLERY); plugin.onMethodCall(call, mockResult); verify(mockImagePickerDelegate).chooseImageFromGallery(eq(call), any()); verifyNoInteractions(mockResult); } @Test public void onMethodCall_InvokesChooseMultiImageFromGallery() { MethodCall call = buildMethodCall(PICK_MULTI_IMAGE); plugin.onMethodCall(call, mockResult); verify(mockImagePickerDelegate).chooseMultiImageFromGallery(eq(call), any()); verifyNoInteractions(mockResult); } @Test public void onMethodCall_WhenSourceIsCamera_InvokesTakeImageWithCamera() { MethodCall call = buildMethodCall(PICK_IMAGE, SOURCE_CAMERA); plugin.onMethodCall(call, mockResult); verify(mockImagePickerDelegate).takeImageWithCamera(eq(call), any()); verifyNoInteractions(mockResult); } @Test public void onMethodCall_PickingImage_WhenSourceIsCamera_InvokesTakeImageWithCamera_RearCamera() { MethodCall call = buildMethodCall(PICK_IMAGE, SOURCE_CAMERA); HashMap arguments = (HashMap) call.arguments; arguments.put("cameraDevice", 0); plugin.onMethodCall(call, mockResult); verify(mockImagePickerDelegate).setCameraDevice(eq(CameraDevice.REAR)); } @Test public void onMethodCall_PickingImage_WhenSourceIsCamera_InvokesTakeImageWithCamera_FrontCamera() { MethodCall call = buildMethodCall(PICK_IMAGE, SOURCE_CAMERA); HashMap arguments = (HashMap) call.arguments; arguments.put("cameraDevice", 1); plugin.onMethodCall(call, mockResult); verify(mockImagePickerDelegate).setCameraDevice(eq(CameraDevice.FRONT)); } @Test public void onMethodCall_PickingVideo_WhenSourceIsCamera_InvokesTakeImageWithCamera_RearCamera() { MethodCall call = buildMethodCall(PICK_IMAGE, SOURCE_CAMERA); HashMap arguments = (HashMap) call.arguments; arguments.put("cameraDevice", 0); plugin.onMethodCall(call, mockResult); verify(mockImagePickerDelegate).setCameraDevice(eq(CameraDevice.REAR)); } @Test public void onMethodCall_PickingVideo_WhenSourceIsCamera_InvokesTakeImageWithCamera_FrontCamera() { MethodCall call = buildMethodCall(PICK_IMAGE, SOURCE_CAMERA); HashMap arguments = (HashMap) call.arguments; arguments.put("cameraDevice", 1); plugin.onMethodCall(call, mockResult); verify(mockImagePickerDelegate).setCameraDevice(eq(CameraDevice.FRONT)); } @Test public void onResiter_WhenAcitivityIsNull_ShouldNotCrash() { when(mockRegistrar.activity()).thenReturn(null); ImagePickerPlugin.registerWith((mockRegistrar)); assertTrue( "No exception thrown when ImagePickerPlugin.registerWith ran with activity = null", true); } @Test public void onConstructor_WhenContextTypeIsActivity_ShouldNotCrash() { new ImagePickerPlugin(mockImagePickerDelegate, mockActivity); assertTrue( "No exception thrown when ImagePickerPlugin() ran with context instanceof Activity", true); } @Test public void constructDelegate_ShouldUseInternalCacheDirectory() { File mockDirectory = new File("/mockpath"); when(mockActivity.getCacheDir()).thenReturn(mockDirectory); ImagePickerDelegate delegate = plugin.constructDelegate(mockActivity); verify(mockActivity, times(1)).getCacheDir(); assertThat( "Delegate uses cache directory for storing camera captures", delegate.externalFilesDirectory, equalTo(mockDirectory)); } @Test public void onDetachedFromActivity_ShouldReleaseActivityState() { final BinaryMessenger mockBinaryMessenger = mock(BinaryMessenger.class); when(mockPluginBinding.getBinaryMessenger()).thenReturn(mockBinaryMessenger); final HiddenLifecycleReference mockLifecycleReference = mock(HiddenLifecycleReference.class); when(mockActivityBinding.getLifecycle()).thenReturn(mockLifecycleReference); final Lifecycle mockLifecycle = mock(Lifecycle.class); when(mockLifecycleReference.getLifecycle()).thenReturn(mockLifecycle); plugin.onAttachedToEngine(mockPluginBinding); plugin.onAttachedToActivity(mockActivityBinding); assertNotNull(plugin.getActivityState()); plugin.onDetachedFromActivity(); assertNull(plugin.getActivityState()); } private MethodCall buildMethodCall(String method, final int source) { final Map arguments = new HashMap<>(); arguments.put("source", source); return new MethodCall(method, arguments); } private MethodCall buildMethodCall(String method) { return new MethodCall(method, null); } } ================================================ FILE: packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java ================================================ // Copyright 2013 The Flutter 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.imagepicker; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertThat; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import java.io.File; import java.io.IOException; import org.junit.Before; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; // RobolectricTestRunner always creates a default mock bitmap when reading from file. So we cannot actually test the scaling. // But we can still test whether the original or scaled file is created. @RunWith(RobolectricTestRunner.class) public class ImageResizerTest { ImageResizer resizer; File imageFile; File externalDirectory; Bitmap originalImageBitmap; @Before public void setUp() throws IOException { MockitoAnnotations.initMocks(this); imageFile = new File(getClass().getClassLoader().getResource("pngImage.png").getFile()); originalImageBitmap = BitmapFactory.decodeFile(imageFile.getPath()); TemporaryFolder temporaryFolder = new TemporaryFolder(); temporaryFolder.create(); externalDirectory = temporaryFolder.newFolder("image_picker_testing_path"); resizer = new ImageResizer(externalDirectory, new ExifDataCopier()); } @Test public void onResizeImageIfNeeded_WhenQualityIsNull_ShoultNotResize_ReturnTheUnscaledFile() { String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, null); assertThat(outoutFile, equalTo(imageFile.getPath())); } @Test public void onResizeImageIfNeeded_WhenQualityIsNotNull_ShoulResize_ReturnResizedFile() { String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, 50); assertThat(outoutFile, equalTo(externalDirectory.getPath() + "/scaled_pngImage.png")); } @Test public void onResizeImageIfNeeded_WhenWidthIsNotNull_ShoulResize_ReturnResizedFile() { String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), 50.0, null, null); assertThat(outoutFile, equalTo(externalDirectory.getPath() + "/scaled_pngImage.png")); } @Test public void onResizeImageIfNeeded_WhenHeightIsNotNull_ShoulResize_ReturnResizedFile() { String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, 50.0, null); assertThat(outoutFile, equalTo(externalDirectory.getPath() + "/scaled_pngImage.png")); } @Test public void onResizeImageIfNeeded_WhenParentDirectoryDoesNotExists_ShouldNotCrash() { File nonExistentDirectory = new File(externalDirectory, "/nonExistent"); ImageResizer invalidResizer = new ImageResizer(nonExistentDirectory, new ExifDataCopier()); String outoutFile = invalidResizer.resizeImageIfNeeded(imageFile.getPath(), null, 50.0, null); assertThat(outoutFile, equalTo(nonExistentDirectory.getPath() + "/scaled_pngImage.png")); } } ================================================ FILE: packages/image_picker/image_picker_android/android/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker ================================================ mock-maker-inline ================================================ FILE: packages/image_picker/image_picker_android/example/README.md ================================================ # 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. ================================================ FILE: packages/image_picker/image_picker_android/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 testOptions.unitTests.includeAndroidResources = true lintOptions { disable 'InvalidPackage' } defaultConfig { applicationId "io.flutter.plugins.imagepicker.example" minSdkVersion 16 targetSdkVersion 28 multiDexEnabled true versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } testOptions { unitTests.returnDefaultValues = true } } flutter { source '../..' } dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' implementation project(':image_picker_android') implementation project(':espresso') api 'androidx.test:core:1.4.0' } ================================================ FILE: packages/image_picker/image_picker_android/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/image_picker/image_picker_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/image_picker/image_picker_android/example/android/app/src/androidTest/java/io/flutter/plugins/imagepickerexample/FlutterActivityTest.java ================================================ // Copyright 2013 The Flutter 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.imagepickerexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class FlutterActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/image_picker/image_picker_android/example/android/app/src/androidTest/java/io/flutter/plugins/imagepickerexample/ImagePickerPickTest.java ================================================ // Copyright 2013 The Flutter 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.imagepickerexample; import static androidx.test.espresso.flutter.EspressoFlutter.onFlutterWidget; import static androidx.test.espresso.flutter.action.FlutterActions.click; import static androidx.test.espresso.flutter.assertion.FlutterAssertions.matches; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withText; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withValueKey; import static androidx.test.espresso.intent.Intents.intended; import static androidx.test.espresso.intent.Intents.intending; import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction; import android.app.Activity; import android.app.Instrumentation; import android.content.Intent; import android.net.Uri; import androidx.test.espresso.intent.rule.IntentsTestRule; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestRule; public class ImagePickerPickTest { @Rule public TestRule rule = new IntentsTestRule<>(DriverExtensionActivity.class); @Test @Ignore("Doesn't run in Firebase Test Lab: https://github.com/flutter/flutter/issues/94748") public void imageIsPickedWithOriginalName() { Instrumentation.ActivityResult result = new Instrumentation.ActivityResult( Activity.RESULT_OK, new Intent().setData(Uri.parse("content://dummy/dummy.png"))); intending(hasAction(Intent.ACTION_GET_CONTENT)).respondWith(result); onFlutterWidget(withValueKey("image_picker_example_from_gallery")).perform(click()); onFlutterWidget(withText("PICK")).perform(click()); intended(hasAction(Intent.ACTION_GET_CONTENT)); onFlutterWidget(withValueKey("image_picker_example_picked_image_name")) .check(matches(withText("dummy.png"))); } } ================================================ FILE: packages/image_picker/image_picker_android/example/android/app/src/androidTest/java/io/flutter/plugins/imagepickerexample/ImagePickerTest.java ================================================ // Copyright 2013 The Flutter 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.imagepickerexample; import static org.junit.Assert.assertTrue; import androidx.test.core.app.ActivityScenario; import io.flutter.plugins.imagepicker.ImagePickerPlugin; import org.junit.Test; public class ImagePickerTest { @Test public void imagePickerPluginIsAdded() { final ActivityScenario scenario = ActivityScenario.launch(ImagePickerTestActivity.class); scenario.onActivity( activity -> { assertTrue(activity.engine.getPlugins().has(ImagePickerPlugin.class)); }); } } ================================================ FILE: packages/image_picker/image_picker_android/example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: packages/image_picker/image_picker_android/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/image_picker/image_picker_android/example/android/app/src/main/java/io/flutter/plugins/.gitignore ================================================ GeneratedPluginRegistrant.java ================================================ FILE: packages/image_picker/image_picker_android/example/android/app/src/main/java/io/flutter/plugins/imagepickerexample/DriverExtensionActivity.java ================================================ // Copyright 2013 The Flutter 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.imagepickerexample; import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity; public class DriverExtensionActivity extends FlutterActivity { @NonNull @Override public String getDartEntrypointFunctionName() { return "appMain"; } } ================================================ FILE: packages/image_picker/image_picker_android/example/android/app/src/main/java/io/flutter/plugins/imagepickerexample/DummyContentProvider.java ================================================ // Copyright 2013 The Flutter 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.imagepickerexample; import android.content.ContentProvider; import android.content.ContentValues; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.provider.MediaStore; import androidx.annotation.NonNull; import androidx.annotation.Nullable; public class DummyContentProvider extends ContentProvider { @Override public boolean onCreate() { return true; } @Nullable @Override public AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode) { return getContext().getResources().openRawResourceFd(R.raw.ic_launcher); } @Nullable @Override public Cursor query( @NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { MatrixCursor cursor = new MatrixCursor(new String[] {MediaStore.MediaColumns.DISPLAY_NAME}); cursor.addRow(new Object[] {"dummy.png"}); return cursor; } @Nullable @Override public String getType(@NonNull Uri uri) { return "image/png"; } @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { return null; } @Override public int delete( @NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { return 0; } @Override public int update( @NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { return 0; } } ================================================ FILE: packages/image_picker/image_picker_android/example/android/app/src/main/java/io/flutter/plugins/imagepickerexample/ImagePickerTestActivity.java ================================================ // Copyright 2013 The Flutter 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.imagepickerexample; import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; // Makes the FlutterEngine accessible for testing. public class ImagePickerTestActivity extends FlutterActivity { public FlutterEngine engine; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); engine = flutterEngine; } } ================================================ FILE: packages/image_picker/image_picker_android/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.1.2' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/image_picker/image_picker_android/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip ================================================ FILE: packages/image_picker/image_picker_android/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true ================================================ FILE: packages/image_picker/image_picker_android/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withInputStream { stream -> plugins.load(stream) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/image_picker/image_picker_android/example/integration_test/image_picker_test.dart ================================================ // Copyright 2013 The Flutter 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'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('placeholder test', (WidgetTester tester) async {}); } ================================================ FILE: packages/image_picker/image_picker_android/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_driver/driver_extension.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'package:video_player/video_player.dart'; void appMain() { enableFlutterDriverExtension(); main(); } 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: 'Image Picker Demo', home: MyHomePage(title: 'Image Picker Example'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, this.title}) : super(key: key); final String? title; @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { List? _imageFileList; void _setImageFileListFromFile(XFile? value) { _imageFileList = value == null ? null : [value]; } dynamic _pickImageError; bool isVideo = false; VideoPlayerController? _controller; VideoPlayerController? _toBeDisposed; String? _retrieveDataError; final ImagePickerPlatform _picker = ImagePickerPlatform.instance; final TextEditingController maxWidthController = TextEditingController(); final TextEditingController maxHeightController = TextEditingController(); final TextEditingController qualityController = TextEditingController(); Future _playVideo(XFile? file) async { if (file != null && mounted) { await _disposeVideoController(); late VideoPlayerController controller; if (kIsWeb) { controller = VideoPlayerController.network(file.path); } else { controller = VideoPlayerController.file(File(file.path)); } _controller = controller; // In web, most browsers won't honor a programmatic call to .play // if the video has a sound track (and is not muted). // Mute the video so it auto-plays in web! // This is not needed if the call to .play is the result of user // interaction (clicking on a "play" button, for example). const double volume = kIsWeb ? 0.0 : 1.0; await controller.setVolume(volume); await controller.initialize(); await controller.setLooping(true); await controller.play(); setState(() {}); } } Future _onImageButtonPressed( BuildContext context, { required ImageSource source, bool isMultiImage = false, }) async { if (_controller != null) { await _controller!.setVolume(0.0); } if (isVideo) { final XFile? file = await _picker.getVideo( source: source, maxDuration: const Duration(seconds: 10)); if (file != null && context.mounted) { _showPickedSnackBar(context, [file]); } await _playVideo(file); } else if (isMultiImage && context.mounted) { await _displayPickImageDialog(context, (double? maxWidth, double? maxHeight, int? quality) async { try { final List? pickedFileList = await _picker.getMultiImage( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: quality, ); if (pickedFileList != null && context.mounted) { _showPickedSnackBar(context, pickedFileList); } setState(() => _imageFileList = pickedFileList); } catch (e) { setState(() => _pickImageError = e); } }); } else { await _displayPickImageDialog(context, (double? maxWidth, double? maxHeight, int? quality) async { try { final XFile? pickedFile = await _picker.getImage( source: source, maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: quality, ); if (pickedFile != null && context.mounted) { _showPickedSnackBar(context, [pickedFile]); } setState(() => _setImageFileListFromFile(pickedFile)); } catch (e) { setState(() => _pickImageError = e); } }); } } @override void deactivate() { if (_controller != null) { _controller!.setVolume(0.0); _controller!.pause(); } super.deactivate(); } @override void dispose() { _disposeVideoController(); maxWidthController.dispose(); maxHeightController.dispose(); qualityController.dispose(); super.dispose(); } Future _disposeVideoController() async { if (_toBeDisposed != null) { await _toBeDisposed!.dispose(); } _toBeDisposed = _controller; _controller = null; } Widget _previewVideo() { final Text? retrieveError = _getRetrieveErrorWidget(); if (retrieveError != null) { return retrieveError; } if (_controller == null) { return const Text( 'You have not yet picked a video', textAlign: TextAlign.center, ); } return Padding( padding: const EdgeInsets.all(10.0), child: AspectRatioVideo(_controller), ); } Widget _previewImages() { final Text? retrieveError = _getRetrieveErrorWidget(); if (retrieveError != null) { return retrieveError; } if (_imageFileList != null) { return Semantics( label: 'image_picker_example_picked_images', child: ListView.builder( key: UniqueKey(), itemBuilder: (BuildContext context, int index) { final XFile image = _imageFileList![index]; return Column( mainAxisSize: MainAxisSize.min, children: [ Text(image.name, key: const Key('image_picker_example_picked_image_name')), // Why network for web? // See https://pub.dev/packages/image_picker#getting-ready-for-the-web-platform Semantics( label: 'image_picker_example_picked_image', child: kIsWeb ? Image.network(image.path) : Image.file(File(image.path)), ), ], ); }, itemCount: _imageFileList!.length, ), ); } else if (_pickImageError != null) { return Text( 'Pick image error: $_pickImageError', textAlign: TextAlign.center, ); } else { return const Text( 'You have not yet picked an image.', textAlign: TextAlign.center, ); } } Widget _handlePreview() { if (isVideo) { return _previewVideo(); } else { return _previewImages(); } } Future retrieveLostData() async { final LostDataResponse response = await _picker.getLostData(); if (response.isEmpty) { return; } if (response.file != null) { if (response.type == RetrieveType.video) { isVideo = true; await _playVideo(response.file); } else { isVideo = false; setState(() { if (response.files == null) { _setImageFileListFromFile(response.file); } else { _imageFileList = response.files; } }); } } else { _retrieveDataError = response.exception!.code; } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title!), ), body: Center( child: !kIsWeb && defaultTargetPlatform == TargetPlatform.android ? FutureBuilder( future: retrieveLostData(), builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { case ConnectionState.none: case ConnectionState.waiting: return const Text( 'You have not yet picked an image.', textAlign: TextAlign.center, ); case ConnectionState.done: return _handlePreview(); case ConnectionState.active: if (snapshot.hasError) { return Text( 'Pick image/video error: ${snapshot.error}}', textAlign: TextAlign.center, ); } else { return const Text( 'You have not yet picked an image.', textAlign: TextAlign.center, ); } } }, ) : _handlePreview(), ), floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ Semantics( label: 'image_picker_example_from_gallery', child: FloatingActionButton( key: const Key('image_picker_example_from_gallery'), onPressed: () { isVideo = false; _onImageButtonPressed(context, source: ImageSource.gallery); }, heroTag: 'image0', tooltip: 'Pick Image from gallery', child: const Icon(Icons.photo), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( onPressed: () { isVideo = false; _onImageButtonPressed( context, source: ImageSource.gallery, isMultiImage: true, ); }, heroTag: 'image1', tooltip: 'Pick Multiple Image from gallery', child: const Icon(Icons.photo_library), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( onPressed: () { isVideo = false; _onImageButtonPressed(context, source: ImageSource.camera); }, heroTag: 'image2', tooltip: 'Take a Photo', child: const Icon(Icons.camera_alt), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( backgroundColor: Colors.red, onPressed: () { isVideo = true; _onImageButtonPressed(context, source: ImageSource.gallery); }, heroTag: 'video0', tooltip: 'Pick Video from gallery', child: const Icon(Icons.video_library), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( backgroundColor: Colors.red, onPressed: () { isVideo = true; _onImageButtonPressed(context, source: ImageSource.camera); }, heroTag: 'video1', tooltip: 'Take a Video', child: const Icon(Icons.videocam), ), ), ], ), ); } Text? _getRetrieveErrorWidget() { if (_retrieveDataError != null) { final Text result = Text(_retrieveDataError!); _retrieveDataError = null; return result; } return null; } Future _displayPickImageDialog( BuildContext context, OnPickImageCallback onPick) async { return showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Add optional parameters'), content: Column( children: [ TextField( controller: maxWidthController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration( hintText: 'Enter maxWidth if desired'), ), TextField( controller: maxHeightController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration( hintText: 'Enter maxHeight if desired'), ), TextField( controller: qualityController, keyboardType: TextInputType.number, decoration: const InputDecoration( hintText: 'Enter quality if desired'), ), ], ), actions: [ TextButton( child: const Text('CANCEL'), onPressed: () { Navigator.of(context).pop(); }, ), TextButton( child: const Text('PICK'), onPressed: () { final double? width = maxWidthController.text.isNotEmpty ? double.parse(maxWidthController.text) : null; final double? height = maxHeightController.text.isNotEmpty ? double.parse(maxHeightController.text) : null; final int? quality = qualityController.text.isNotEmpty ? int.parse(qualityController.text) : null; onPick(width, height, quality); Navigator.of(context).pop(); }), ], ); }); } void _showPickedSnackBar(BuildContext context, List files) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text('Picked: ${files.map((XFile it) => it.name).join(',')}'), duration: const Duration(seconds: 2), )); } } typedef OnPickImageCallback = void Function( double? maxWidth, double? maxHeight, int? quality); class AspectRatioVideo extends StatefulWidget { const AspectRatioVideo(this.controller, {Key? key}) : super(key: key); final VideoPlayerController? controller; @override AspectRatioVideoState createState() => AspectRatioVideoState(); } class AspectRatioVideoState extends State { VideoPlayerController? get controller => widget.controller; bool initialized = false; void _onVideoControllerUpdate() { if (!mounted) { return; } if (initialized != controller!.value.isInitialized) { initialized = controller!.value.isInitialized; setState(() {}); } } @override void initState() { super.initState(); controller!.addListener(_onVideoControllerUpdate); } @override void dispose() { controller!.removeListener(_onVideoControllerUpdate); super.dispose(); } @override Widget build(BuildContext context) { if (initialized) { return Center( child: AspectRatio( aspectRatio: controller!.value.aspectRatio, child: VideoPlayer(controller!), ), ); } else { return Container(); } } } ================================================ FILE: packages/image_picker/image_picker_android/example/pubspec.yaml ================================================ name: image_picker_example description: Demonstrates how to use the image_picker plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter flutter_driver: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.1 image_picker_android: # When depending on this package from a real application you should use: # image_picker_android: ^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: ../ image_picker_platform_interface: ^2.3.0 video_player: ^2.1.4 dev_dependencies: espresso: ^0.2.0 flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/image_picker/image_picker_android/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/image_picker/image_picker_android/lib/image_picker_android.dart ================================================ // Copyright 2013 The Flutter 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:flutter/services.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/image_picker_android'); /// An Android implementation of [ImagePickerPlatform]. class ImagePickerAndroid extends ImagePickerPlatform { /// The MethodChannel that is being used by this implementation of the plugin. @visibleForTesting MethodChannel get channel => _channel; /// Registers this class as the default platform implementation. static void registerWith() { ImagePickerPlatform.instance = ImagePickerAndroid(); } @override Future pickImage({ required ImageSource source, double? maxWidth, double? maxHeight, int? imageQuality, CameraDevice preferredCameraDevice = CameraDevice.rear, }) async { final String? path = await _getImagePath( source: source, maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, preferredCameraDevice: preferredCameraDevice, ); return path != null ? PickedFile(path) : null; } @override Future?> pickMultiImage({ double? maxWidth, double? maxHeight, int? imageQuality, }) async { final List? paths = await _getMultiImagePath( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, ); if (paths == null) { return null; } return paths.map((dynamic path) => PickedFile(path as String)).toList(); } Future?> _getMultiImagePath({ double? maxWidth, double? maxHeight, int? imageQuality, }) { if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) { throw ArgumentError.value( imageQuality, 'imageQuality', 'must be between 0 and 100'); } if (maxWidth != null && maxWidth < 0) { throw ArgumentError.value(maxWidth, 'maxWidth', 'cannot be negative'); } if (maxHeight != null && maxHeight < 0) { throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative'); } return _channel.invokeMethod?>( 'pickMultiImage', { 'maxWidth': maxWidth, 'maxHeight': maxHeight, 'imageQuality': imageQuality, }, ); } Future _getImagePath({ required ImageSource source, double? maxWidth, double? maxHeight, int? imageQuality, CameraDevice preferredCameraDevice = CameraDevice.rear, bool requestFullMetadata = true, }) { if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) { throw ArgumentError.value( imageQuality, 'imageQuality', 'must be between 0 and 100'); } if (maxWidth != null && maxWidth < 0) { throw ArgumentError.value(maxWidth, 'maxWidth', 'cannot be negative'); } if (maxHeight != null && maxHeight < 0) { throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative'); } return _channel.invokeMethod( 'pickImage', { 'source': source.index, 'maxWidth': maxWidth, 'maxHeight': maxHeight, 'imageQuality': imageQuality, 'cameraDevice': preferredCameraDevice.index, 'requestFullMetadata': requestFullMetadata, }, ); } @override Future pickVideo({ required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, Duration? maxDuration, }) async { final String? path = await _getVideoPath( source: source, maxDuration: maxDuration, preferredCameraDevice: preferredCameraDevice, ); return path != null ? PickedFile(path) : null; } Future _getVideoPath({ required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, Duration? maxDuration, }) { return _channel.invokeMethod( 'pickVideo', { 'source': source.index, 'maxDuration': maxDuration?.inSeconds, 'cameraDevice': preferredCameraDevice.index }, ); } @override Future getImage({ required ImageSource source, double? maxWidth, double? maxHeight, int? imageQuality, CameraDevice preferredCameraDevice = CameraDevice.rear, }) async { final String? path = await _getImagePath( source: source, maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, preferredCameraDevice: preferredCameraDevice, ); return path != null ? XFile(path) : null; } @override Future getImageFromSource({ required ImageSource source, ImagePickerOptions options = const ImagePickerOptions(), }) async { final String? path = await _getImagePath( source: source, maxHeight: options.maxHeight, maxWidth: options.maxWidth, imageQuality: options.imageQuality, preferredCameraDevice: options.preferredCameraDevice, requestFullMetadata: options.requestFullMetadata, ); return path != null ? XFile(path) : null; } @override Future?> getMultiImage({ double? maxWidth, double? maxHeight, int? imageQuality, }) async { final List? paths = await _getMultiImagePath( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, ); if (paths == null) { return null; } return paths.map((dynamic path) => XFile(path as String)).toList(); } @override Future getVideo({ required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, Duration? maxDuration, }) async { final String? path = await _getVideoPath( source: source, maxDuration: maxDuration, preferredCameraDevice: preferredCameraDevice, ); return path != null ? XFile(path) : null; } @override Future retrieveLostData() async { final LostDataResponse result = await getLostData(); if (result.isEmpty) { return LostData.empty(); } return LostData( file: result.file != null ? PickedFile(result.file!.path) : null, exception: result.exception, type: result.type, ); } @override Future getLostData() async { List? pickedFileList; final Map? result = await _channel.invokeMapMethod('retrieve'); if (result == null) { return LostDataResponse.empty(); } assert(result.containsKey('path') != result.containsKey('errorCode')); final String? type = result['type'] as String?; assert(type == kTypeImage || type == kTypeVideo); RetrieveType? retrieveType; if (type == kTypeImage) { retrieveType = RetrieveType.image; } else if (type == kTypeVideo) { retrieveType = RetrieveType.video; } PlatformException? exception; if (result.containsKey('errorCode')) { exception = PlatformException( code: result['errorCode']! as String, message: result['errorMessage'] as String?); } final String? path = result['path'] as String?; final List? pathList = (result['pathList'] as List?)?.cast(); if (pathList != null) { pickedFileList = []; for (final String path in pathList) { pickedFileList.add(XFile(path)); } } return LostDataResponse( file: path != null ? XFile(path) : null, exception: exception, type: retrieveType, files: pickedFileList, ); } } ================================================ FILE: packages/image_picker/image_picker_android/pubspec.yaml ================================================ 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+6 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: image_picker platforms: android: package: io.flutter.plugins.imagepicker pluginClass: ImagePickerPlugin dartPluginClass: ImagePickerAndroid dependencies: flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.1 image_picker_platform_interface: ^2.5.0 dev_dependencies: flutter_test: sdk: flutter mockito: ^5.0.0 ================================================ FILE: packages/image_picker/image_picker_android/test/image_picker_android_test.dart ================================================ // Copyright 2013 The Flutter 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:image_picker_android/image_picker_android.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); final ImagePickerAndroid picker = ImagePickerAndroid(); final List log = []; dynamic returnValue = ''; setUp(() { returnValue = ''; _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { log.add(methodCall); return returnValue; }); log.clear(); }); test('registers instance', () async { ImagePickerAndroid.registerWith(); expect(ImagePickerPlatform.instance, isA()); }); group('#pickImage', () { test('passes the image source argument correctly', () async { await picker.pickImage(source: ImageSource.camera); await picker.pickImage(source: ImageSource.gallery); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 1, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), ], ); }); test('passes the width and height arguments correctly', () async { await picker.pickImage(source: ImageSource.camera); await picker.pickImage( source: ImageSource.camera, maxWidth: 10.0, ); await picker.pickImage( source: ImageSource.camera, maxHeight: 10.0, ); await picker.pickImage( source: ImageSource.camera, maxWidth: 10.0, maxHeight: 20.0, ); await picker.pickImage( source: ImageSource.camera, maxWidth: 10.0, imageQuality: 70, ); await picker.pickImage( source: ImageSource.camera, maxHeight: 10.0, imageQuality: 70, ); await picker.pickImage( source: ImageSource.camera, maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, 'cameraDevice': 0, 'requestFullMetadata': true, }), ], ); }); test('does not accept an invalid imageQuality argument', () { expect( () => picker.pickImage(imageQuality: -1, source: ImageSource.gallery), throwsArgumentError, ); expect( () => picker.pickImage(imageQuality: 101, source: ImageSource.gallery), throwsArgumentError, ); expect( () => picker.pickImage(imageQuality: -1, source: ImageSource.camera), throwsArgumentError, ); expect( () => picker.pickImage(imageQuality: 101, source: ImageSource.camera), throwsArgumentError, ); }); test('does not accept a negative width or height argument', () { expect( () => picker.pickImage(source: ImageSource.camera, maxWidth: -1.0), throwsArgumentError, ); expect( () => picker.pickImage(source: ImageSource.camera, maxHeight: -1.0), throwsArgumentError, ); }); test('handles a null image path response gracefully', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( picker.channel, (MethodCall methodCall) => null); expect(await picker.pickImage(source: ImageSource.gallery), isNull); expect(await picker.pickImage(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { await picker.pickImage(source: ImageSource.camera); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), ], ); }); test('camera position can set to front', () async { await picker.pickImage( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 1, 'requestFullMetadata': true, }), ], ); }); }); group('#pickMultiImage', () { test('calls the method correctly', () async { returnValue = ['0', '1']; await picker.pickMultiImage(); expect( log, [ isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, }), ], ); }); test('passes the width and height arguments correctly', () async { returnValue = ['0', '1']; await picker.pickMultiImage(); await picker.pickMultiImage( maxWidth: 10.0, ); await picker.pickMultiImage( maxHeight: 10.0, ); await picker.pickMultiImage( maxWidth: 10.0, maxHeight: 20.0, ); await picker.pickMultiImage( maxWidth: 10.0, imageQuality: 70, ); await picker.pickMultiImage( maxHeight: 10.0, imageQuality: 70, ); await picker.pickMultiImage( maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ); expect( log, [ isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, }), ], ); }); test('does not accept a negative width or height argument', () { returnValue = ['0', '1']; expect( () => picker.pickMultiImage(maxWidth: -1.0), throwsArgumentError, ); expect( () => picker.pickMultiImage(maxHeight: -1.0), throwsArgumentError, ); }); test('does not accept an invalid imageQuality argument', () { returnValue = ['0', '1']; expect( () => picker.pickMultiImage(imageQuality: -1), throwsArgumentError, ); expect( () => picker.pickMultiImage(imageQuality: 101), throwsArgumentError, ); }); test('handles a null image path response gracefully', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( picker.channel, (MethodCall methodCall) => null); expect(await picker.pickMultiImage(), isNull); expect(await picker.pickMultiImage(), isNull); }); }); group('#pickVideo', () { test('passes the image source argument correctly', () async { await picker.pickVideo(source: ImageSource.camera); await picker.pickVideo(source: ImageSource.gallery); expect( log, [ isMethodCall('pickVideo', arguments: { 'source': 0, 'cameraDevice': 0, 'maxDuration': null, }), isMethodCall('pickVideo', arguments: { 'source': 1, 'cameraDevice': 0, 'maxDuration': null, }), ], ); }); test('passes the duration argument correctly', () async { await picker.pickVideo(source: ImageSource.camera); await picker.pickVideo( source: ImageSource.camera, maxDuration: const Duration(seconds: 10), ); await picker.pickVideo( source: ImageSource.camera, maxDuration: const Duration(minutes: 1), ); await picker.pickVideo( source: ImageSource.camera, maxDuration: const Duration(hours: 1), ); expect( log, [ isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': null, 'cameraDevice': 0, }), isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': 10, 'cameraDevice': 0, }), isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': 60, 'cameraDevice': 0, }), isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': 3600, 'cameraDevice': 0, }), ], ); }); test('handles a null video path response gracefully', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( picker.channel, (MethodCall methodCall) => null); expect(await picker.pickVideo(source: ImageSource.gallery), isNull); expect(await picker.pickVideo(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { await picker.pickVideo(source: ImageSource.camera); expect( log, [ isMethodCall('pickVideo', arguments: { 'source': 0, 'cameraDevice': 0, 'maxDuration': null, }), ], ); }); test('camera position can set to front', () async { await picker.pickVideo( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front, ); expect( log, [ isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': null, 'cameraDevice': 1, }), ], ); }); }); group('#retrieveLostData', () { test('retrieveLostData get success response', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { return { 'type': 'image', 'path': '/example/path', }; }); final LostData response = await picker.retrieveLostData(); expect(response.type, RetrieveType.image); expect(response.file, isNotNull); expect(response.file!.path, '/example/path'); }); test('retrieveLostData get error response', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { return { 'type': 'video', 'errorCode': 'test_error_code', 'errorMessage': 'test_error_message', }; }); final LostData response = await picker.retrieveLostData(); expect(response.type, RetrieveType.video); expect(response.exception, isNotNull); expect(response.exception!.code, 'test_error_code'); expect(response.exception!.message, 'test_error_message'); }); test('retrieveLostData get null response', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { return null; }); expect((await picker.retrieveLostData()).isEmpty, true); }); test('retrieveLostData get both path and error should throw', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { return { 'type': 'video', 'errorCode': 'test_error_code', 'errorMessage': 'test_error_message', 'path': '/example/path', }; }); expect(picker.retrieveLostData(), throwsAssertionError); }); }); group('#getImage', () { test('passes the image source argument correctly', () async { await picker.getImage(source: ImageSource.camera); await picker.getImage(source: ImageSource.gallery); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 1, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), ], ); }); test('passes the width and height arguments correctly', () async { await picker.getImage(source: ImageSource.camera); await picker.getImage( source: ImageSource.camera, maxWidth: 10.0, ); await picker.getImage( source: ImageSource.camera, maxHeight: 10.0, ); await picker.getImage( source: ImageSource.camera, maxWidth: 10.0, maxHeight: 20.0, ); await picker.getImage( source: ImageSource.camera, maxWidth: 10.0, imageQuality: 70, ); await picker.getImage( source: ImageSource.camera, maxHeight: 10.0, imageQuality: 70, ); await picker.getImage( source: ImageSource.camera, maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, 'cameraDevice': 0, 'requestFullMetadata': true, }), ], ); }); test('does not accept an invalid imageQuality argument', () { expect( () => picker.getImage(imageQuality: -1, source: ImageSource.gallery), throwsArgumentError, ); expect( () => picker.getImage(imageQuality: 101, source: ImageSource.gallery), throwsArgumentError, ); expect( () => picker.getImage(imageQuality: -1, source: ImageSource.camera), throwsArgumentError, ); expect( () => picker.getImage(imageQuality: 101, source: ImageSource.camera), throwsArgumentError, ); }); test('does not accept a negative width or height argument', () { expect( () => picker.getImage(source: ImageSource.camera, maxWidth: -1.0), throwsArgumentError, ); expect( () => picker.getImage(source: ImageSource.camera, maxHeight: -1.0), throwsArgumentError, ); }); test('handles a null image path response gracefully', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( picker.channel, (MethodCall methodCall) => null); expect(await picker.getImage(source: ImageSource.gallery), isNull); expect(await picker.getImage(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { await picker.getImage(source: ImageSource.camera); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), ], ); }); test('camera position can set to front', () async { await picker.getImage( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 1, 'requestFullMetadata': true, }), ], ); }); }); group('#getMultiImage', () { test('calls the method correctly', () async { returnValue = ['0', '1']; await picker.getMultiImage(); expect( log, [ isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, }), ], ); }); test('passes the width and height arguments correctly', () async { returnValue = ['0', '1']; await picker.getMultiImage(); await picker.getMultiImage( maxWidth: 10.0, ); await picker.getMultiImage( maxHeight: 10.0, ); await picker.getMultiImage( maxWidth: 10.0, maxHeight: 20.0, ); await picker.getMultiImage( maxWidth: 10.0, imageQuality: 70, ); await picker.getMultiImage( maxHeight: 10.0, imageQuality: 70, ); await picker.getMultiImage( maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ); expect( log, [ isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, }), ], ); }); test('does not accept a negative width or height argument', () { returnValue = ['0', '1']; expect( () => picker.getMultiImage(maxWidth: -1.0), throwsArgumentError, ); expect( () => picker.getMultiImage(maxHeight: -1.0), throwsArgumentError, ); }); test('does not accept an invalid imageQuality argument', () { returnValue = ['0', '1']; expect( () => picker.getMultiImage(imageQuality: -1), throwsArgumentError, ); expect( () => picker.getMultiImage(imageQuality: 101), throwsArgumentError, ); }); test('handles a null image path response gracefully', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( picker.channel, (MethodCall methodCall) => null); expect(await picker.getMultiImage(), isNull); expect(await picker.getMultiImage(), isNull); }); }); group('#getVideo', () { test('passes the image source argument correctly', () async { await picker.getVideo(source: ImageSource.camera); await picker.getVideo(source: ImageSource.gallery); expect( log, [ isMethodCall('pickVideo', arguments: { 'source': 0, 'cameraDevice': 0, 'maxDuration': null, }), isMethodCall('pickVideo', arguments: { 'source': 1, 'cameraDevice': 0, 'maxDuration': null, }), ], ); }); test('passes the duration argument correctly', () async { await picker.getVideo(source: ImageSource.camera); await picker.getVideo( source: ImageSource.camera, maxDuration: const Duration(seconds: 10), ); await picker.getVideo( source: ImageSource.camera, maxDuration: const Duration(minutes: 1), ); await picker.getVideo( source: ImageSource.camera, maxDuration: const Duration(hours: 1), ); expect( log, [ isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': null, 'cameraDevice': 0, }), isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': 10, 'cameraDevice': 0, }), isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': 60, 'cameraDevice': 0, }), isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': 3600, 'cameraDevice': 0, }), ], ); }); test('handles a null video path response gracefully', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( picker.channel, (MethodCall methodCall) => null); expect(await picker.getVideo(source: ImageSource.gallery), isNull); expect(await picker.getVideo(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { await picker.getVideo(source: ImageSource.camera); expect( log, [ isMethodCall('pickVideo', arguments: { 'source': 0, 'cameraDevice': 0, 'maxDuration': null, }), ], ); }); test('camera position can set to front', () async { await picker.getVideo( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front, ); expect( log, [ isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': null, 'cameraDevice': 1, }), ], ); }); }); group('#getLostData', () { test('getLostData get success response', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { return { 'type': 'image', 'path': '/example/path', }; }); final LostDataResponse response = await picker.getLostData(); expect(response.type, RetrieveType.image); expect(response.file, isNotNull); expect(response.file!.path, '/example/path'); }); test('getLostData should successfully retrieve multiple files', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { return { 'type': 'image', 'path': '/example/path1', 'pathList': ['/example/path0', '/example/path1'], }; }); final LostDataResponse response = await picker.getLostData(); expect(response.type, RetrieveType.image); expect(response.file, isNotNull); expect(response.file!.path, '/example/path1'); expect(response.files!.first.path, '/example/path0'); expect(response.files!.length, 2); }); test('getLostData get error response', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { return { 'type': 'video', 'errorCode': 'test_error_code', 'errorMessage': 'test_error_message', }; }); final LostDataResponse response = await picker.getLostData(); expect(response.type, RetrieveType.video); expect(response.exception, isNotNull); expect(response.exception!.code, 'test_error_code'); expect(response.exception!.message, 'test_error_message'); }); test('getLostData get null response', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { return null; }); expect((await picker.getLostData()).isEmpty, true); }); test('getLostData get both path and error should throw', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { return { 'type': 'video', 'errorCode': 'test_error_code', 'errorMessage': 'test_error_message', 'path': '/example/path', }; }); expect(picker.getLostData(), throwsAssertionError); }); }); group('#getImageFromSource', () { test('passes the image source argument correctly', () async { await picker.getImageFromSource(source: ImageSource.camera); await picker.getImageFromSource(source: ImageSource.gallery); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 1, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), ], ); }); test('passes the width and height arguments correctly', () async { await picker.getImageFromSource(source: ImageSource.camera); await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(maxWidth: 10.0), ); await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(maxHeight: 10.0), ); await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions( maxWidth: 10.0, maxHeight: 20.0, ), ); await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions( maxWidth: 10.0, imageQuality: 70, ), ); await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions( maxHeight: 10.0, imageQuality: 70, ), ); await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions( maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ), ); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, 'cameraDevice': 0, 'requestFullMetadata': true, }), ], ); }); test('does not accept an invalid imageQuality argument', () { expect( () => picker.getImageFromSource( source: ImageSource.gallery, options: const ImagePickerOptions(imageQuality: -1), ), throwsArgumentError, ); expect( () => picker.getImageFromSource( source: ImageSource.gallery, options: const ImagePickerOptions(imageQuality: 101), ), throwsArgumentError, ); expect( () => picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(imageQuality: -1), ), throwsArgumentError, ); expect( () => picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(imageQuality: 101), ), throwsArgumentError, ); }); test('does not accept a negative width or height argument', () { expect( () => picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(maxWidth: -1.0), ), throwsArgumentError, ); expect( () => picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(maxHeight: -1.0), ), throwsArgumentError, ); }); test('handles a null image path response gracefully', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( picker.channel, (MethodCall methodCall) => null); expect( await picker.getImageFromSource(source: ImageSource.gallery), isNull); expect( await picker.getImageFromSource(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { await picker.getImageFromSource(source: ImageSource.camera); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), ], ); }); test('camera position can set to front', () async { await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions( preferredCameraDevice: CameraDevice.front, ), ); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 1, 'requestFullMetadata': true, }), ], ); }); test('passes the full metadata argument correctly', () async { await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(requestFullMetadata: false), ); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': false, }), ], ); }); }); } /// 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; ================================================ FILE: packages/image_picker/image_picker_for_web/AUTHORS ================================================ # 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> Balvinder Singh Gambhir ================================================ FILE: packages/image_picker/image_picker_for_web/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 2.1.10 * Updates code for `no_leading_underscores_for_local_identifiers` lint. ## 2.1.9 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. * Fixes violations of new analysis option use_named_constants. ## 2.1.8 * Minor fixes for new analysis options. ## 2.1.7 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 2.1.6 * Internal code cleanup for stricter analysis options. ## 2.1.5 * Removes dependency on `meta`. ## 2.1.4 * Implemented `maxWidth`, `maxHeight` and `imageQuality` when selecting images (except for gifs). ## 2.1.3 * Add `implements` to pubspec. ## 2.1.2 * Updated installation instructions in README. # 2.1.1 * Implemented `getMultiImage`. * Initialized the following `XFile` attributes for picked files: * `name`, `length`, `mimeType` and `lastModified`. # 2.1.0 * Implemented `getImage`, `getVideo` and `getFile` methods that return `XFile` instances. * Move tests to `example` directory, so they run as integration_tests with `flutter drive`. # 2.0.0 * Migrate to null safety. * Add doc comments to point out that some arguments aren't supported on the web. # 0.1.0+3 * Update Flutter SDK constraint. # 0.1.0+2 * Adds Video MIME Types for the safari browser for acception # 0.1.0+1 * Remove `android` directory. # 0.1.0 * Initial open-source release. ================================================ FILE: packages/image_picker/image_picker_for_web/LICENSE ================================================ 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. ================================================ FILE: packages/image_picker/image_picker_for_web/README.md ================================================ # image\_picker\_for\_web A web implementation of [`image_picker`][1]. ## Limitations on the web platform Since Web Browsers don't offer direct access to their users' file system, this plugin provides a `PickedFile` abstraction to make access uniform across platforms. The web version of the plugin puts network-accessible URIs as the `path` in the returned `PickedFile`. ### URL.createObjectURL() The `PickedFile` object in web is backed by [`URL.createObjectUrl` Web API](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL), which is reasonably well supported across all browsers: ![Data on support for the bloburls feature across the major browsers from caniuse.com](https://caniuse.bitsofco.de/image/bloburls.png) However, the returned `path` attribute of the `PickedFile` points to a `network` resource, and not a local path in your users' drive. See **Use the plugin** below for some examples on how to use this return value in a cross-platform way. ### input file "accept" In order to filter only video/image content, some browsers offer an [`accept` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept) in their `input type="file"` form elements: ![Data on support for the input-file-accept feature across the major browsers from caniuse.com](https://caniuse.bitsofco.de/image/input-file-accept.png) This feature is just a convenience for users, **not validation**. Users can override this setting on their browsers. You must validate in your app (or server) that the user has picked the file type that you can handle. ### input file "capture" In order to "take a photo", some mobile browsers offer a [`capture` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/capture): ![Data on support for the html-media-capture feature across the major browsers from caniuse.com](https://caniuse.bitsofco.de/image/html-media-capture.png) Each browser may implement `capture` any way they please, so it may (or may not) make a difference in your users' experience. ### pickImage() The arguments `maxWidth`, `maxHeight` and `imageQuality` are not supported for gif images. The argument `imageQuality` only works for jpeg and webp images. ### pickVideo() The argument `maxDuration` is not supported on the web. ## Usage ### Import the package This package is [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin), which means you can simply use `image_picker` normally. This package will be automatically included in your app when you do. ### Use the plugin You should be able to use `package:image_picker` _almost_ as normal. Once the user has picked a file, the returned `PickedFile` instance will contain a `network`-accessible URL (pointing to a location within the browser). The instance will also let you retrieve the bytes of the selected file across all platforms. If you want to use the path directly, your code would need look like this: ```dart ... if (kIsWeb) { Image.network(pickedFile.path); } else { Image.file(File(pickedFile.path)); } ... ``` Or, using bytes: ```dart ... Image.memory(await pickedFile.readAsBytes()) ... ``` [1]: https://pub.dev/packages/image_picker ================================================ FILE: packages/image_picker/image_picker_for_web/example/README.md ================================================ # 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. ## Testing This package uses `package:integration_test` to run its tests in a web browser. See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/wiki/Plugin-Tests#web-tests) in the Flutter wiki for instructions to setup and run the tests in this package. Check [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) for more info. ================================================ FILE: packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:convert'; import 'dart:html' as html; import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:image_picker_for_web/image_picker_for_web.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'package:integration_test/integration_test.dart'; const String expectedStringContents = 'Hello, world!'; const String otherStringContents = 'Hello again, world!'; final Uint8List bytes = utf8.encode(expectedStringContents) as Uint8List; final Uint8List otherBytes = utf8.encode(otherStringContents) as Uint8List; final Map options = { 'type': 'text/plain', 'lastModified': DateTime.utc(2017, 12, 13).millisecondsSinceEpoch, }; final html.File textFile = html.File([bytes], 'hello.txt', options); final html.File secondTextFile = html.File([otherBytes], 'secondFile.txt'); void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); // Under test... late ImagePickerPlugin plugin; setUp(() { plugin = ImagePickerPlugin(); }); testWidgets('Can select a file (Deprecated)', (WidgetTester tester) async { final html.FileUploadInputElement mockInput = html.FileUploadInputElement(); final ImagePickerPluginTestOverrides overrides = ImagePickerPluginTestOverrides() ..createInputElement = ((_, __) => mockInput) ..getMultipleFilesFromInput = ((_) => [textFile]); final ImagePickerPlugin plugin = ImagePickerPlugin(overrides: overrides); // Init the pick file dialog... final Future file = plugin.pickFile(); // Mock the browser behavior of selecting a file... mockInput.dispatchEvent(html.Event('change')); // Now the file should be available expect(file, completes); // And readable expect((await file).readAsBytes(), completion(isNotEmpty)); }); testWidgets('Can select a file', (WidgetTester tester) async { final html.FileUploadInputElement mockInput = html.FileUploadInputElement(); final ImagePickerPluginTestOverrides overrides = ImagePickerPluginTestOverrides() ..createInputElement = ((_, __) => mockInput) ..getMultipleFilesFromInput = ((_) => [textFile]); final ImagePickerPlugin plugin = ImagePickerPlugin(overrides: overrides); // Init the pick file dialog... final Future image = plugin.getImage(source: ImageSource.camera); // Mock the browser behavior of selecting a file... mockInput.dispatchEvent(html.Event('change')); // Now the file should be available expect(image, completes); // And readable final XFile file = await image; expect(file.readAsBytes(), completion(isNotEmpty)); expect(file.name, textFile.name); expect(file.length(), completion(textFile.size)); expect(file.mimeType, textFile.type); expect( file.lastModified(), completion( DateTime.fromMillisecondsSinceEpoch(textFile.lastModified!), )); }); testWidgets('Can select multiple files', (WidgetTester tester) async { final html.FileUploadInputElement mockInput = html.FileUploadInputElement(); final ImagePickerPluginTestOverrides overrides = ImagePickerPluginTestOverrides() ..createInputElement = ((_, __) => mockInput) ..getMultipleFilesFromInput = ((_) => [textFile, secondTextFile]); final ImagePickerPlugin plugin = ImagePickerPlugin(overrides: overrides); // Init the pick file dialog... final Future> files = plugin.getMultiImage(); // Mock the browser behavior of selecting a file... mockInput.dispatchEvent(html.Event('change')); // Now the file should be available expect(files, completes); // And readable expect((await files).first.readAsBytes(), completion(isNotEmpty)); // Peek into the second file... final XFile secondFile = (await files).elementAt(1); expect(secondFile.readAsBytes(), completion(isNotEmpty)); expect(secondFile.name, secondTextFile.name); expect(secondFile.length(), completion(secondTextFile.size)); }); // There's no good way of detecting when the user has "aborted" the selection. testWidgets('computeCaptureAttribute', (WidgetTester tester) async { expect( plugin.computeCaptureAttribute(ImageSource.gallery, CameraDevice.front), isNull, ); expect( plugin.computeCaptureAttribute(ImageSource.gallery, CameraDevice.rear), isNull, ); expect( plugin.computeCaptureAttribute(ImageSource.camera, CameraDevice.front), 'user', ); expect( plugin.computeCaptureAttribute(ImageSource.camera, CameraDevice.rear), 'environment', ); }); group('createInputElement', () { testWidgets('accept: any, capture: null', (WidgetTester tester) async { final html.Element input = plugin.createInputElement('any', null); expect(input.attributes, containsPair('accept', 'any')); expect(input.attributes, isNot(contains('capture'))); expect(input.attributes, isNot(contains('multiple'))); }); testWidgets('accept: any, capture: something', (WidgetTester tester) async { final html.Element input = plugin.createInputElement('any', 'something'); expect(input.attributes, containsPair('accept', 'any')); expect(input.attributes, containsPair('capture', 'something')); expect(input.attributes, isNot(contains('multiple'))); }); testWidgets('accept: any, capture: null, multi: true', (WidgetTester tester) async { final html.Element input = plugin.createInputElement('any', null, multiple: true); expect(input.attributes, containsPair('accept', 'any')); expect(input.attributes, isNot(contains('capture'))); expect(input.attributes, contains('multiple')); }); testWidgets('accept: any, capture: something, multi: true', (WidgetTester tester) async { final html.Element input = plugin.createInputElement('any', 'something', multiple: true); expect(input.attributes, containsPair('accept', 'any')); expect(input.attributes, containsPair('capture', 'something')); expect(input.attributes, contains('multiple')); }); }); } ================================================ FILE: packages/image_picker/image_picker_for_web/example/integration_test/image_resizer_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:html' as html; import 'dart:typed_data'; import 'dart:ui'; import 'package:flutter_test/flutter_test.dart'; import 'package:image_picker_for_web/src/image_resizer.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'package:integration_test/integration_test.dart'; //This is a sample 10x10 png image const String pngFileBase64Contents = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKAQMAAAC3/F3+AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABlBMVEXqQzX+/v6lfubTAAAAAWJLR0QB/wIt3gAAAAlwSFlzAAAHEwAABxMBziAPCAAAAAd0SU1FB+UJHgsdDM0ErZoAAAALSURBVAjXY2DABwAAHgABboVHMgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wOS0zMFQxMToyOToxMi0wNDowMHCDC24AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDktMzBUMTE6Mjk6MTItMDQ6MDAB3rPSAAAAAElFTkSuQmCC'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); // Under test... late ImageResizer imageResizer; late XFile pngFile; setUp(() { imageResizer = ImageResizer(); final html.File pngHtmlFile = _base64ToFile(pngFileBase64Contents, 'pngImage.png'); pngFile = XFile(html.Url.createObjectUrl(pngHtmlFile), name: pngHtmlFile.name, mimeType: pngHtmlFile.type); }); testWidgets('image is loaded correctly ', (WidgetTester tester) async { final html.ImageElement imageElement = await imageResizer.loadImage(pngFile.path); expect(imageElement.width, 10); expect(imageElement.height, 10); }); testWidgets( "canvas is loaded with image's width and height when max width and max height are null", (WidgetTester widgetTester) async { final html.ImageElement imageElement = await imageResizer.loadImage(pngFile.path); final html.CanvasElement canvas = imageResizer.resizeImageElement(imageElement, null, null); expect(canvas.width, imageElement.width); expect(canvas.height, imageElement.height); }); testWidgets( 'canvas size is scaled when max width and max height are not null', (WidgetTester widgetTester) async { final html.ImageElement imageElement = await imageResizer.loadImage(pngFile.path); final html.CanvasElement canvas = imageResizer.resizeImageElement(imageElement, 8, 8); expect(canvas.width, 8); expect(canvas.height, 8); }); testWidgets('resized image is returned after converting canvas to file', (WidgetTester widgetTester) async { final html.ImageElement imageElement = await imageResizer.loadImage(pngFile.path); final html.CanvasElement canvas = imageResizer.resizeImageElement(imageElement, null, null); final XFile resizedImage = await imageResizer.writeCanvasToFile(pngFile, canvas, null); expect(resizedImage.name, 'scaled_${pngFile.name}'); }); testWidgets('image is scaled when maxWidth is set', (WidgetTester tester) async { final XFile scaledImage = await imageResizer.resizeImageIfNeeded(pngFile, 5, null, null); expect(scaledImage.name, 'scaled_${pngFile.name}'); final Size scaledImageSize = await _getImageSize(scaledImage); expect(scaledImageSize, const Size(5, 5)); }); testWidgets('image is scaled when maxHeight is set', (WidgetTester tester) async { final XFile scaledImage = await imageResizer.resizeImageIfNeeded(pngFile, null, 6, null); expect(scaledImage.name, 'scaled_${pngFile.name}'); final Size scaledImageSize = await _getImageSize(scaledImage); expect(scaledImageSize, const Size(6, 6)); }); testWidgets('image is scaled when imageQuality is set', (WidgetTester tester) async { final XFile scaledImage = await imageResizer.resizeImageIfNeeded(pngFile, null, null, 89); expect(scaledImage.name, 'scaled_${pngFile.name}'); }); testWidgets('image is scaled when maxWidth,maxHeight,imageQuality are set', (WidgetTester tester) async { final XFile scaledImage = await imageResizer.resizeImageIfNeeded(pngFile, 3, 4, 89); expect(scaledImage.name, 'scaled_${pngFile.name}'); }); testWidgets('image is not scaled when maxWidth,maxHeight, is set', (WidgetTester tester) async { final XFile scaledImage = await imageResizer.resizeImageIfNeeded(pngFile, null, null, null); expect(scaledImage.name, pngFile.name); }); } Future _getImageSize(XFile file) async { final Completer completer = Completer(); final html.ImageElement image = html.ImageElement(src: file.path); image.onLoad.listen((html.Event event) { completer.complete(Size(image.width!.toDouble(), image.height!.toDouble())); }); image.onError.listen((html.Event event) { completer.complete(Size.zero); }); return completer.future; } html.File _base64ToFile(String data, String fileName) { final List arr = data.split(','); final String bstr = html.window.atob(arr[1]); int n = bstr.length; final Uint8List u8arr = Uint8List(n); while (n >= 1) { u8arr[n - 1] = bstr.codeUnitAt(n - 1); n--; } return html.File([u8arr], fileName); } ================================================ FILE: packages/image_picker/image_picker_for_web/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } /// App for testing class MyApp extends StatefulWidget { /// Default Constructor const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override Widget build(BuildContext context) { return const Directionality( textDirection: TextDirection.ltr, child: Text('Testing... Look at the console output for results!'), ); } } ================================================ FILE: packages/image_picker/image_picker_for_web/example/pubspec.yaml ================================================ name: image_picker_for_web_integration_tests publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter image_picker_for_web: path: ../ image_picker_platform_interface: ^2.2.0 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter js: ^0.6.3 ================================================ FILE: packages/image_picker/image_picker_for_web/example/run_test.sh ================================================ #!/usr/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. if pgrep -lf chromedriver > /dev/null; then echo "chromedriver is running." if [ $# -eq 0 ]; then echo "No target specified, running all tests..." find integration_test/ -iname *_test.dart | xargs -n1 -i -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target='{}' else echo "Running test target: $1..." set -x flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target=$1 fi else echo "chromedriver is not running." echo "Please, check the README.md for instructions on how to use run_test.sh" fi ================================================ FILE: packages/image_picker/image_picker_for_web/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/image_picker/image_picker_for_web/example/web/index.html ================================================ example ================================================ FILE: packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart ================================================ // Copyright 2013 The Flutter 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 'dart:html' as html; import 'package:flutter/foundation.dart' show visibleForTesting; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'src/image_resizer.dart'; const String _kImagePickerInputsDomId = '__image_picker_web-file-input'; const String _kAcceptImageMimeType = 'image/*'; const String _kAcceptVideoMimeType = 'video/3gpp,video/x-m4v,video/mp4,video/*'; /// The web implementation of [ImagePickerPlatform]. /// /// This class implements the `package:image_picker` functionality for the web. class ImagePickerPlugin extends ImagePickerPlatform { /// A constructor that allows tests to override the function that creates file inputs. ImagePickerPlugin({ @visibleForTesting ImagePickerPluginTestOverrides? overrides, @visibleForTesting ImageResizer? imageResizer, }) : _overrides = overrides { _imageResizer = imageResizer ?? ImageResizer(); _target = _ensureInitialized(_kImagePickerInputsDomId); } final ImagePickerPluginTestOverrides? _overrides; bool get _hasOverrides => _overrides != null; late html.Element _target; late ImageResizer _imageResizer; /// Registers this class as the default instance of [ImagePickerPlatform]. static void registerWith(Registrar registrar) { ImagePickerPlatform.instance = ImagePickerPlugin(); } /// Returns a [PickedFile] with the image that was picked. /// /// The `source` argument controls where the image comes from. This can /// be either [ImageSource.camera] or [ImageSource.gallery]. /// /// Note that the `maxWidth`, `maxHeight` and `imageQuality` arguments are not supported on the web. If any of these arguments is supplied, it'll be silently ignored by the web version of the plugin. /// /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera]. /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device. /// Defaults to [CameraDevice.rear]. /// /// If no images were picked, the return value is null. @override Future pickImage({ required ImageSource source, double? maxWidth, double? maxHeight, int? imageQuality, CameraDevice preferredCameraDevice = CameraDevice.rear, }) { final String? capture = computeCaptureAttribute(source, preferredCameraDevice); return pickFile(accept: _kAcceptImageMimeType, capture: capture); } /// Returns a [PickedFile] containing the video that was picked. /// /// The [source] argument controls where the video comes from. This can /// be either [ImageSource.camera] or [ImageSource.gallery]. /// /// Note that the `maxDuration` argument is not supported on the web. If the argument is supplied, it'll be silently ignored by the web version of the plugin. /// /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera]. /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device. /// Defaults to [CameraDevice.rear]. /// /// If no images were picked, the return value is null. @override Future pickVideo({ required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, Duration? maxDuration, }) { final String? capture = computeCaptureAttribute(source, preferredCameraDevice); return pickFile(accept: _kAcceptVideoMimeType, capture: capture); } /// Injects a file input with the specified accept+capture attributes, and /// returns the PickedFile that the user selected locally. /// /// `capture` is only supported in mobile browsers. /// See https://caniuse.com/#feat=html-media-capture @visibleForTesting Future pickFile({ String? accept, String? capture, }) { final html.FileUploadInputElement input = createInputElement(accept, capture) as html.FileUploadInputElement; _injectAndActivate(input); return _getSelectedFile(input); } /// Returns an [XFile] with the image that was picked. /// /// The `source` argument controls where the image comes from. This can /// be either [ImageSource.camera] or [ImageSource.gallery]. /// /// Note that the `maxWidth`, `maxHeight` and `imageQuality` arguments are not supported on the web. If any of these arguments is supplied, it'll be silently ignored by the web version of the plugin. /// /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera]. /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device. /// Defaults to [CameraDevice.rear]. /// /// If no images were picked, the return value is null. @override Future getImage({ required ImageSource source, double? maxWidth, double? maxHeight, int? imageQuality, CameraDevice preferredCameraDevice = CameraDevice.rear, }) async { final String? capture = computeCaptureAttribute(source, preferredCameraDevice); final List files = await getFiles( accept: _kAcceptImageMimeType, capture: capture, ); return _imageResizer.resizeImageIfNeeded( files.first, maxWidth, maxHeight, imageQuality, ); } /// Returns an [XFile] containing the video that was picked. /// /// The [source] argument controls where the video comes from. This can /// be either [ImageSource.camera] or [ImageSource.gallery]. /// /// Note that the `maxDuration` argument is not supported on the web. If the argument is supplied, it'll be silently ignored by the web version of the plugin. /// /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera]. /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device. /// Defaults to [CameraDevice.rear]. /// /// If no images were picked, the return value is null. @override Future getVideo({ required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, Duration? maxDuration, }) async { final String? capture = computeCaptureAttribute(source, preferredCameraDevice); final List files = await getFiles( accept: _kAcceptVideoMimeType, capture: capture, ); return files.first; } /// Injects a file input, and returns a list of XFile that the user selected locally. @override Future> getMultiImage({ double? maxWidth, double? maxHeight, int? imageQuality, }) async { final List images = await getFiles( accept: _kAcceptImageMimeType, multiple: true, ); final Iterable> resized = images.map( (XFile image) => _imageResizer.resizeImageIfNeeded( image, maxWidth, maxHeight, imageQuality, ), ); return Future.wait(resized); } /// Injects a file input with the specified accept+capture attributes, and /// returns a list of XFile that the user selected locally. /// /// `capture` is only supported in mobile browsers. /// /// `multiple` can be passed to allow for multiple selection of files. Defaults /// to false. /// /// See https://caniuse.com/#feat=html-media-capture @visibleForTesting Future> getFiles({ String? accept, String? capture, bool multiple = false, }) { final html.FileUploadInputElement input = createInputElement( accept, capture, multiple: multiple, ) as html.FileUploadInputElement; _injectAndActivate(input); return _getSelectedXFiles(input); } // DOM methods /// Converts plugin configuration into a proper value for the `capture` attribute. /// /// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#capture @visibleForTesting String? computeCaptureAttribute(ImageSource source, CameraDevice device) { if (source == ImageSource.camera) { return (device == CameraDevice.front) ? 'user' : 'environment'; } return null; } List? _getFilesFromInput(html.FileUploadInputElement input) { if (_hasOverrides) { return _overrides!.getMultipleFilesFromInput(input); } return input.files; } /// Handles the OnChange event from a FileUploadInputElement object /// Returns a list of selected files. List? _handleOnChangeEvent(html.Event event) { final html.FileUploadInputElement? input = event.target as html.FileUploadInputElement?; return input == null ? null : _getFilesFromInput(input); } /// Monitors an and returns the selected file. Future _getSelectedFile(html.FileUploadInputElement input) { final Completer completer = Completer(); // Observe the input until we can return something input.onChange.first.then((html.Event event) { final List? files = _handleOnChangeEvent(event); if (!completer.isCompleted && files != null) { completer.complete(PickedFile( html.Url.createObjectUrl(files.first), )); } }); input.onError.first.then((html.Event event) { if (!completer.isCompleted) { completer.completeError(event); } }); // Note that we don't bother detaching from these streams, since the // "input" gets re-created in the DOM every time the user needs to // pick a file. return completer.future; } /// Monitors an and returns the selected file(s). Future> _getSelectedXFiles(html.FileUploadInputElement input) { final Completer> completer = Completer>(); // Observe the input until we can return something input.onChange.first.then((html.Event event) { final List? files = _handleOnChangeEvent(event); if (!completer.isCompleted && files != null) { completer.complete(files.map((html.File file) { return XFile( html.Url.createObjectUrl(file), name: file.name, length: file.size, lastModified: DateTime.fromMillisecondsSinceEpoch( file.lastModified ?? DateTime.now().millisecondsSinceEpoch, ), mimeType: file.type, ); }).toList()); } }); input.onError.first.then((html.Event event) { if (!completer.isCompleted) { completer.completeError(event); } }); // Note that we don't bother detaching from these streams, since the // "input" gets re-created in the DOM every time the user needs to // pick a file. return completer.future; } /// Initializes a DOM container where we can host input elements. html.Element _ensureInitialized(String id) { html.Element? target = html.querySelector('#$id'); if (target == null) { final html.Element targetElement = html.Element.tag('flt-image-picker-inputs')..id = id; html.querySelector('body')!.children.add(targetElement); target = targetElement; } return target; } /// Creates an input element that accepts certain file types, and /// allows to `capture` from the device's cameras (where supported) @visibleForTesting html.Element createInputElement( String? accept, String? capture, { bool multiple = false, }) { if (_hasOverrides) { return _overrides!.createInputElement(accept, capture); } final html.Element element = html.FileUploadInputElement() ..accept = accept ..multiple = multiple; if (capture != null) { element.setAttribute('capture', capture); } return element; } /// Injects the file input element, and clicks on it void _injectAndActivate(html.Element element) { _target.children.clear(); _target.children.add(element); element.click(); } } // Some tools to override behavior for unit-testing /// A function that creates a file input with the passed in `accept` and `capture` attributes. @visibleForTesting typedef OverrideCreateInputFunction = html.Element Function( String? accept, String? capture, ); /// A function that extracts list of files from the file `input` passed in. @visibleForTesting typedef OverrideExtractMultipleFilesFromInputFunction = List Function(html.Element? input); /// Overrides for some of the functionality above. @visibleForTesting class ImagePickerPluginTestOverrides { /// Override the creation of the input element. late OverrideCreateInputFunction createInputElement; /// Override the extraction of the selected files from an input element. late OverrideExtractMultipleFilesFromInputFunction getMultipleFilesFromInput; } ================================================ FILE: packages/image_picker/image_picker_for_web/lib/src/image_resizer.dart ================================================ // Copyright 2013 The Flutter 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 'dart:html' as html; import 'dart:math'; import 'dart:ui'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'image_resizer_utils.dart'; /// Helper class that resizes images. class ImageResizer { /// Resizes the image if needed. /// (Does not support gif images) Future resizeImageIfNeeded(XFile file, double? maxWidth, double? maxHeight, int? imageQuality) async { if (!imageResizeNeeded(maxWidth, maxHeight, imageQuality) || file.mimeType == 'image/gif') { // Implement maxWidth and maxHeight for image/gif return file; } try { final html.ImageElement imageElement = await loadImage(file.path); final html.CanvasElement canvas = resizeImageElement(imageElement, maxWidth, maxHeight); final XFile resizedImage = await writeCanvasToFile(file, canvas, imageQuality); html.Url.revokeObjectUrl(file.path); return resizedImage; } catch (e) { return file; } } /// function that loads the blobUrl into an imageElement Future loadImage(String blobUrl) { final Completer imageLoadCompleter = Completer(); final html.ImageElement imageElement = html.ImageElement(); // ignore: unsafe_html imageElement.src = blobUrl; imageElement.onLoad.listen((html.Event event) { imageLoadCompleter.complete(imageElement); }); imageElement.onError.listen((html.Event event) { const String exception = 'Error while loading image.'; imageElement.remove(); imageLoadCompleter.completeError(exception); }); return imageLoadCompleter.future; } /// Draws image to a canvas while resizing the image to fit the [maxWidth],[maxHeight] constraints html.CanvasElement resizeImageElement( html.ImageElement source, double? maxWidth, double? maxHeight) { final Size newImageSize = calculateSizeOfDownScaledImage( Size(source.width!.toDouble(), source.height!.toDouble()), maxWidth, maxHeight); final html.CanvasElement canvas = html.CanvasElement(); canvas.width = newImageSize.width.toInt(); canvas.height = newImageSize.height.toInt(); final html.CanvasRenderingContext2D context = canvas.context2D; if (maxHeight == null && maxWidth == null) { context.drawImage(source, 0, 0); } else { context.drawImageScaled(source, 0, 0, canvas.width!, canvas.height!); } return canvas; } /// function that converts a canvas element to Xfile /// [imageQuality] is only supported for jpeg and webp images. Future writeCanvasToFile( XFile originalFile, html.CanvasElement canvas, int? imageQuality) async { final double calculatedImageQuality = (min(imageQuality ?? 100, 100)) / 100.0; final html.Blob blob = await canvas.toBlob(originalFile.mimeType, calculatedImageQuality); return XFile(html.Url.createObjectUrlFromBlob(blob), mimeType: originalFile.mimeType, name: 'scaled_${originalFile.name}', lastModified: DateTime.now(), length: blob.size); } } ================================================ FILE: packages/image_picker/image_picker_for_web/lib/src/image_resizer_utils.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:math'; import 'dart:ui'; import 'package:flutter/material.dart'; ///a function that checks if an image needs to be resized or not bool imageResizeNeeded(double? maxWidth, double? maxHeight, int? imageQuality) { return imageQuality != null ? isImageQualityValid(imageQuality) : (maxWidth != null || maxHeight != null); } /// a function that checks if image quality is between 0 to 100 bool isImageQualityValid(int imageQuality) { return imageQuality >= 0 && imageQuality <= 100; } /// a function that calculates the size of the downScaled image. /// imageWidth is the width of the image /// imageHeight is the height of the image /// maxWidth is the maximum width of the scaled image /// maxHeight is the maximum height of the scaled image Size calculateSizeOfDownScaledImage( Size imageSize, double? maxWidth, double? maxHeight) { final double widthFactor = maxWidth != null ? imageSize.width / maxWidth : 1; final double heightFactor = maxHeight != null ? imageSize.height / maxHeight : 1; final double resizeFactor = max(widthFactor, heightFactor); return resizeFactor > 1 ? imageSize ~/ resizeFactor : imageSize; } ================================================ FILE: packages/image_picker/image_picker_for_web/pubspec.yaml ================================================ name: image_picker_for_web description: Web platform implementation of image_picker repository: https://github.com/flutter/plugins/tree/main/packages/image_picker/image_picker_for_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 version: 2.1.10 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: image_picker platforms: web: pluginClass: ImagePickerPlugin fileName: image_picker_for_web.dart dependencies: flutter: sdk: flutter flutter_web_plugins: sdk: flutter image_picker_platform_interface: ^2.2.0 dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/image_picker/image_picker_for_web/test/README.md ================================================ ## test This package uses integration tests for testing. See `example/README.md` for more info. ================================================ FILE: packages/image_picker/image_picker_for_web/test/image_resizer_utils_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:ui'; import 'package:flutter_test/flutter_test.dart'; import 'package:image_picker_for_web/src/image_resizer_utils.dart'; void main() { group('Image Resizer Utils', () { group('calculateSizeOfScaledImage', () { test( "scaled image height and width are same if max width and max height are same as image's width and height", () { expect(calculateSizeOfDownScaledImage(const Size(500, 300), 500, 300), const Size(500, 300)); }); test( 'scaled image height and width are same if max width and max height are null', () { expect(calculateSizeOfDownScaledImage(const Size(500, 300), null, null), const Size(500, 300)); }); test('image size is scaled when maxWidth is set', () { const Size imageSize = Size(500, 300); const int maxWidth = 400; final Size scaledSize = calculateSizeOfDownScaledImage( Size(imageSize.width, imageSize.height), maxWidth.toDouble(), null); expect(scaledSize.height <= imageSize.height, true); expect(scaledSize.width <= maxWidth, true); }); test('image size is scaled when maxHeight is set', () { const Size imageSize = Size(500, 300); const int maxHeight = 400; final Size scaledSize = calculateSizeOfDownScaledImage( Size(imageSize.width, imageSize.height), null, maxHeight.toDouble()); expect(scaledSize.height <= maxHeight, true); expect(scaledSize.width <= imageSize.width, true); }); test('image size is scaled when both maxWidth and maxHeight is set', () { const Size imageSize = Size(1120, 2000); const int maxHeight = 1200; const int maxWidth = 99; final Size scaledSize = calculateSizeOfDownScaledImage( Size(imageSize.width, imageSize.height), maxWidth.toDouble(), maxHeight.toDouble()); expect(scaledSize.height <= maxHeight, true); expect(scaledSize.width <= maxWidth, true); }); }); group('imageResizeNeeded', () { test('image needs to be resized when maxWidth is set', () { expect(imageResizeNeeded(50, null, null), true); }); test('image needs to be resized when maxHeight is set', () { expect(imageResizeNeeded(null, 50, null), true); }); test('image needs to be resized when imageQuality is set', () { expect(imageResizeNeeded(null, null, 100), true); }); test('image will not be resized when imageQuality is not valid', () { expect(imageResizeNeeded(null, null, 101), false); expect(imageResizeNeeded(null, null, -1), false); }); }); group('isImageQualityValid', () { test('image quality is valid in 0 to 100', () { expect(isImageQualityValid(50), true); expect(isImageQualityValid(0), true); expect(isImageQualityValid(100), true); }); test( 'image quality is not valid when imageQuality is less than 0 or greater than 100', () { expect(isImageQualityValid(-1), false); expect(isImageQualityValid(101), false); }); }); }); } ================================================ FILE: packages/image_picker/image_picker_for_web/test/tests_exist_elsewhere_test.dart ================================================ // Copyright 2013 The Flutter 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: avoid_print import 'package:flutter_test/flutter_test.dart'; void main() { test('Tell the user where to find the real tests', () { print('---'); print('This package uses integration_test for its tests.'); print('See `example/README.md` for more info.'); print('---'); }); } ================================================ FILE: packages/image_picker/image_picker_ios/AUTHORS ================================================ # 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> ================================================ FILE: packages/image_picker/image_picker_ios/CHANGELOG.md ================================================ ## 0.8.6+8 * Fixes issue with images sometimes changing to incorrect orientation. ## 0.8.6+7 * Fixes issue where GIF file would not animate without `Photo Library Usage` permissions. Fixes issue where PNG and GIF files were converted to JPG, but only when they are do not have `Photo Library Usage` permissions. * 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. ## 0.8.6+2 * Fixes issue where selectable images of certain types (such as ProRAW images) could not be loaded. ## 0.8.6+1 * Fixes issue with crashing the app when picking images with PHPicker without providing `Photo Library Usage` permission. ## 0.8.6 * Adds `requestFullMetadata` option to `pickImage`, so images on iOS can be picked without `Photo Library Usage` permission. * Updates minimum Flutter version to 2.10. ## 0.8.5+6 * Updates description. * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/106316). ## 0.8.5+5 * Adds non-deprecated codepaths for iOS 13+. ## 0.8.5+4 * Suppresses warnings for pre-iOS-11 codepaths. ## 0.8.5+3 * Fixes 'messages.g.h' file not found. ## 0.8.5+2 * Minor fixes for new analysis options. ## 0.8.5+1 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.8.5 * Switches to an in-package method channel based on Pigeon. * Fixes invalid casts when selecting multiple images on versions of iOS before 14.0. ## 0.8.4+11 * Splits from `image_picker` as a federated implementation. ================================================ FILE: packages/image_picker/image_picker_ios/LICENSE ================================================ image_picker 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. -------------------------------------------------------------------------------- aFileChooser Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2011 - 2013 Paul Burke Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: packages/image_picker/image_picker_ios/README.md ================================================ # image\_picker\_ios The iOS implementation of [`image_picker`][1]. ## Usage This package is [endorsed][2], which means you can simply use `image_picker` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/image_picker [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/image_picker/image_picker_ios/example/README.md ================================================ # 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. ================================================ FILE: packages/image_picker/image_picker_ios/example/integration_test/image_picker_test.dart ================================================ // Copyright 2013 The Flutter 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'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('placeholder test', (WidgetTester tester) async {}); } ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 UIRequiredDeviceCapabilities arm64 MinimumOSVersion 9.0 ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Flutter/Debug.xcconfig ================================================ #include "Generated.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Flutter/Release.xcconfig ================================================ #include "Generated.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do platform :ios, '9.0' inherit! :search_paths # Pods for testing pod 'OCMock', '~> 3.8.1' end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Runner/.gitignore ================================================ GeneratedPluginRegistrant.h GeneratedPluginRegistrant.m ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "idiom" : "ios-marketing", "size" : "1024x1024", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Runner/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName image_picker_example CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS NSCameraUsageDescription Used to demonstrate image picker plugin NSMicrophoneUsageDescription Used to capture audio for image picker plugin NSPhotoLibraryUsageDescription Used to demonstrate image picker plugin UIBackgroundModes remote-notification UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 334733FC266813EE00DCC49E /* ImageUtilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FC8F0ED229FB90B00C8D58F /* ImageUtilTests.m */; }; 334733FD266813F100DCC49E /* MetaDataUtilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 680049252280D736006DD6AB /* MetaDataUtilTests.m */; }; 334733FE266813F400DCC49E /* PhotoAssetUtilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 68F4B463228B3AB500C25614 /* PhotoAssetUtilTests.m */; }; 334733FF266813FA00DCC49E /* ImagePickerTestImages.m in Sources */ = {isa = PBXBuildFile; fileRef = F78AF3182342D9D7008449C7 /* ImagePickerTestImages.m */; }; 33473400266813FD00DCC49E /* ImagePickerPluginTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 68B9AF71243E4B3F00927CE4 /* ImagePickerPluginTests.m */; }; 3A72BAD3FAE6E0FA9D80826B /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 35AE65F25E0B8C8214D8372B /* libPods-RunnerTests.a */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 5C9513011EC38BD300040975 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C9513001EC38BD300040975 /* GeneratedPluginRegistrant.m */; }; 680049382280F2B9006DD6AB /* pngImage.png in Resources */ = {isa = PBXBuildFile; fileRef = 680049352280F2B8006DD6AB /* pngImage.png */; }; 680049392280F2B9006DD6AB /* jpgImage.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 680049362280F2B8006DD6AB /* jpgImage.jpg */; }; 6801C8392555D726009DAF8D /* ImagePickerFromGalleryUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6801C8382555D726009DAF8D /* ImagePickerFromGalleryUITests.m */; }; 782C2B45299ECE33008DC703 /* jpgImageWithRightOrientation.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 782C2B44299ECE33008DC703 /* jpgImageWithRightOrientation.jpg */; }; 782C2B46299ECE33008DC703 /* jpgImageWithRightOrientation.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 782C2B44299ECE33008DC703 /* jpgImageWithRightOrientation.jpg */; }; 7865C5E12941326F0010E17F /* bmpImage.bmp in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5E02941326F0010E17F /* bmpImage.bmp */; }; 7865C5E22941326F0010E17F /* bmpImage.bmp in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5E02941326F0010E17F /* bmpImage.bmp */; }; 7865C5E4294132D50010E17F /* svgImage.svg in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5E3294132D50010E17F /* svgImage.svg */; }; 7865C5E5294132D50010E17F /* svgImage.svg in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5E3294132D50010E17F /* svgImage.svg */; }; 7865C5E72941374F0010E17F /* heicImage.heic in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5E62941374F0010E17F /* heicImage.heic */; }; 7865C5E82941374F0010E17F /* heicImage.heic in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5E62941374F0010E17F /* heicImage.heic */; }; 7865C5EA294137960010E17F /* icoImage.ico in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5E9294137960010E17F /* icoImage.ico */; }; 7865C5EB294137960010E17F /* icoImage.ico in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5E9294137960010E17F /* icoImage.ico */; }; 7865C5ED294137AB0010E17F /* tiffImage.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5EC294137AB0010E17F /* tiffImage.tiff */; }; 7865C5EE294137AB0010E17F /* tiffImage.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5EC294137AB0010E17F /* tiffImage.tiff */; }; 7865C5FC294157BC0010E17F /* icnsImage.icns in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5FB294157BB0010E17F /* icnsImage.icns */; }; 7865C5FD294157BC0010E17F /* icnsImage.icns in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5FB294157BB0010E17F /* icnsImage.icns */; }; 7865C5FF294252A60010E17F /* proRawImage.dng in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5FE294252A60010E17F /* proRawImage.dng */; }; 7865C600294252A60010E17F /* proRawImage.dng in Resources */ = {isa = PBXBuildFile; fileRef = 7865C5FE294252A60010E17F /* proRawImage.dng */; }; 86430DF9272D71E9002D9D6C /* gifImage.gif in Resources */ = {isa = PBXBuildFile; fileRef = 9FC8F0E8229FA49E00C8D58F /* gifImage.gif */; }; 86E9A893272754860017E6E0 /* PickerSaveImageToPathOperationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 86E9A892272754860017E6E0 /* PickerSaveImageToPathOperationTests.m */; }; 86E9A894272754A30017E6E0 /* webpImage.webp in Resources */ = {isa = PBXBuildFile; fileRef = 86E9A88F272747B90017E6E0 /* webpImage.webp */; }; 86E9A895272769130017E6E0 /* pngImage.png in Resources */ = {isa = PBXBuildFile; fileRef = 680049352280F2B8006DD6AB /* pngImage.png */; }; 86E9A896272769150017E6E0 /* jpgImage.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 680049362280F2B8006DD6AB /* jpgImage.jpg */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 9FC8F0E9229FA49E00C8D58F /* gifImage.gif in Resources */ = {isa = PBXBuildFile; fileRef = 9FC8F0E8229FA49E00C8D58F /* gifImage.gif */; }; 9FC8F0EC229FA68500C8D58F /* gifImage.gif in Resources */ = {isa = PBXBuildFile; fileRef = 9FC8F0E8229FA49E00C8D58F /* gifImage.gif */; }; BE6173D826A958B800D0974D /* ImagePickerFromLimitedGalleryUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = BE6173D726A958B800D0974D /* ImagePickerFromLimitedGalleryUITests.m */; }; F4F7A436CCA4BF276270A3AE /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EC32F6993F4529982D9519F1 /* libPods-Runner.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 334733F72668136400DCC49E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; 6801C83B2555D726009DAF8D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0C7B151765FD4249454C49AD /* 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 = ""; }; 334733F22668136400DCC49E /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 334733F62668136400DCC49E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 35AE65F25E0B8C8214D8372B /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 5A9D31B91557877A0E8EF3E7 /* 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 = ""; }; 5C9512FF1EC38BD300040975 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 5C9513001EC38BD300040975 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 680049252280D736006DD6AB /* MetaDataUtilTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MetaDataUtilTests.m; sourceTree = ""; }; 680049352280F2B8006DD6AB /* pngImage.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = pngImage.png; sourceTree = ""; }; 680049362280F2B8006DD6AB /* jpgImage.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = jpgImage.jpg; sourceTree = ""; }; 6801632E632668F4349764C9 /* 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 = ""; }; 6801C8362555D726009DAF8D /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 6801C8382555D726009DAF8D /* ImagePickerFromGalleryUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ImagePickerFromGalleryUITests.m; sourceTree = ""; }; 6801C83A2555D726009DAF8D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 68B9AF71243E4B3F00927CE4 /* ImagePickerPluginTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImagePickerPluginTests.m; sourceTree = ""; }; 68F4B463228B3AB500C25614 /* PhotoAssetUtilTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PhotoAssetUtilTests.m; sourceTree = ""; }; 782C2B44299ECE33008DC703 /* jpgImageWithRightOrientation.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = jpgImageWithRightOrientation.jpg; sourceTree = ""; }; 7865C5E02941326F0010E17F /* bmpImage.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = bmpImage.bmp; sourceTree = ""; }; 7865C5E3294132D50010E17F /* svgImage.svg */ = {isa = PBXFileReference; lastKnownFileType = text; path = svgImage.svg; sourceTree = ""; }; 7865C5E62941374F0010E17F /* heicImage.heic */ = {isa = PBXFileReference; lastKnownFileType = file; path = heicImage.heic; sourceTree = ""; }; 7865C5E9294137960010E17F /* icoImage.ico */ = {isa = PBXFileReference; lastKnownFileType = image.ico; path = icoImage.ico; sourceTree = ""; }; 7865C5EC294137AB0010E17F /* tiffImage.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = tiffImage.tiff; sourceTree = ""; }; 7865C5FB294157BB0010E17F /* icnsImage.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = icnsImage.icns; sourceTree = ""; }; 7865C5FE294252A60010E17F /* proRawImage.dng */ = {isa = PBXFileReference; lastKnownFileType = file; path = proRawImage.dng; 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 = ""; }; 86E9A88F272747B90017E6E0 /* webpImage.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = webpImage.webp; sourceTree = ""; }; 86E9A892272754860017E6E0 /* PickerSaveImageToPathOperationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PickerSaveImageToPathOperationTests.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9FC8F0E8229FA49E00C8D58F /* gifImage.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = gifImage.gif; sourceTree = ""; }; 9FC8F0ED229FB90B00C8D58F /* ImageUtilTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ImageUtilTests.m; sourceTree = ""; }; BE6173D726A958B800D0974D /* ImagePickerFromLimitedGalleryUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ImagePickerFromLimitedGalleryUITests.m; sourceTree = ""; }; DC6FCAAD4E7580C9B3C2E21D /* 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 = ""; }; EC32F6993F4529982D9519F1 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; F78AF3172342D9D7008449C7 /* ImagePickerTestImages.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ImagePickerTestImages.h; sourceTree = ""; }; F78AF3182342D9D7008449C7 /* ImagePickerTestImages.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ImagePickerTestImages.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 334733EF2668136400DCC49E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3A72BAD3FAE6E0FA9D80826B /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 6801C8332555D726009DAF8D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F4F7A436CCA4BF276270A3AE /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 334733F32668136400DCC49E /* RunnerTests */ = { isa = PBXGroup; children = ( 9FC8F0ED229FB90B00C8D58F /* ImageUtilTests.m */, 680049252280D736006DD6AB /* MetaDataUtilTests.m */, 68F4B463228B3AB500C25614 /* PhotoAssetUtilTests.m */, F78AF3172342D9D7008449C7 /* ImagePickerTestImages.h */, F78AF3182342D9D7008449C7 /* ImagePickerTestImages.m */, 68B9AF71243E4B3F00927CE4 /* ImagePickerPluginTests.m */, 86E9A892272754860017E6E0 /* PickerSaveImageToPathOperationTests.m */, 334733F62668136400DCC49E /* Info.plist */, ); path = RunnerTests; sourceTree = ""; }; 680049282280E33D006DD6AB /* TestImages */ = { isa = PBXGroup; children = ( 782C2B44299ECE33008DC703 /* jpgImageWithRightOrientation.jpg */, 86E9A88F272747B90017E6E0 /* webpImage.webp */, 9FC8F0E8229FA49E00C8D58F /* gifImage.gif */, 680049362280F2B8006DD6AB /* jpgImage.jpg */, 680049352280F2B8006DD6AB /* pngImage.png */, 7865C5E02941326F0010E17F /* bmpImage.bmp */, 7865C5E62941374F0010E17F /* heicImage.heic */, 7865C5FB294157BB0010E17F /* icnsImage.icns */, 7865C5E9294137960010E17F /* icoImage.ico */, 7865C5FE294252A60010E17F /* proRawImage.dng */, 7865C5E3294132D50010E17F /* svgImage.svg */, 7865C5EC294137AB0010E17F /* tiffImage.tiff */, ); path = TestImages; sourceTree = ""; }; 6801C8372555D726009DAF8D /* RunnerUITests */ = { isa = PBXGroup; children = ( BE6173D726A958B800D0974D /* ImagePickerFromLimitedGalleryUITests.m */, 6801C8382555D726009DAF8D /* ImagePickerFromGalleryUITests.m */, 6801C83A2555D726009DAF8D /* Info.plist */, ); path = RunnerUITests; sourceTree = ""; }; 840012C8B5EDBCF56B0E4AC1 /* Pods */ = { isa = PBXGroup; children = ( 6801632E632668F4349764C9 /* Pods-Runner.debug.xcconfig */, 5A9D31B91557877A0E8EF3E7 /* Pods-Runner.release.xcconfig */, DC6FCAAD4E7580C9B3C2E21D /* Pods-RunnerTests.debug.xcconfig */, 0C7B151765FD4249454C49AD /* Pods-RunnerTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 680049282280E33D006DD6AB /* TestImages */, 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 334733F32668136400DCC49E /* RunnerTests */, 6801C8372555D726009DAF8D /* RunnerUITests */, 97C146EF1CF9000F007C117D /* Products */, 840012C8B5EDBCF56B0E4AC1 /* Pods */, CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, 6801C8362555D726009DAF8D /* RunnerUITests.xctest */, 334733F22668136400DCC49E /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 5C9512FF1EC38BD300040975 /* GeneratedPluginRegistrant.h */, 5C9513001EC38BD300040975 /* GeneratedPluginRegistrant.m */, 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = { isa = PBXGroup; children = ( EC32F6993F4529982D9519F1 /* libPods-Runner.a */, 35AE65F25E0B8C8214D8372B /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 334733F12668136400DCC49E /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = 334733F92668136400DCC49E /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( B8739A4353234497CF76B597 /* [CP] Check Pods Manifest.lock */, 334733EE2668136400DCC49E /* Sources */, 334733EF2668136400DCC49E /* Frameworks */, 334733F02668136400DCC49E /* Resources */, ); buildRules = ( ); dependencies = ( 334733F82668136400DCC49E /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = 334733F22668136400DCC49E /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 6801C8352555D726009DAF8D /* RunnerUITests */ = { isa = PBXNativeTarget; buildConfigurationList = 6801C83F2555D726009DAF8D /* Build configuration list for PBXNativeTarget "RunnerUITests" */; buildPhases = ( 6801C8322555D726009DAF8D /* Sources */, 6801C8332555D726009DAF8D /* Frameworks */, 6801C8342555D726009DAF8D /* Resources */, ); buildRules = ( ); dependencies = ( 6801C83C2555D726009DAF8D /* PBXTargetDependency */, ); name = RunnerUITests; productName = RunnerUITests; productReference = 6801C8362555D726009DAF8D /* RunnerUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { DefaultBuildSystemTypeForWorkspace = Original; LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 334733F12668136400DCC49E = { CreatedOnToolsVersion = 12.5; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; 6801C8352555D726009DAF8D = { CreatedOnToolsVersion = 11.7; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; SystemCapabilities = { com.apple.BackgroundModes = { enabled = 1; }; }; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, 334733F12668136400DCC49E /* RunnerTests */, 6801C8352555D726009DAF8D /* RunnerUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 334733F02668136400DCC49E /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 7865C5E12941326F0010E17F /* bmpImage.bmp in Resources */, 7865C5E4294132D50010E17F /* svgImage.svg in Resources */, 86430DF9272D71E9002D9D6C /* gifImage.gif in Resources */, 7865C5FF294252A60010E17F /* proRawImage.dng in Resources */, 7865C5EA294137960010E17F /* icoImage.ico in Resources */, 7865C5E72941374F0010E17F /* heicImage.heic in Resources */, 86E9A894272754A30017E6E0 /* webpImage.webp in Resources */, 86E9A895272769130017E6E0 /* pngImage.png in Resources */, 7865C5FC294157BC0010E17F /* icnsImage.icns in Resources */, 782C2B45299ECE33008DC703 /* jpgImageWithRightOrientation.jpg in Resources */, 86E9A896272769150017E6E0 /* jpgImage.jpg in Resources */, 7865C5ED294137AB0010E17F /* tiffImage.tiff in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 6801C8342555D726009DAF8D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 9FC8F0EC229FA68500C8D58F /* gifImage.gif in Resources */, 7865C5EE294137AB0010E17F /* tiffImage.tiff in Resources */, 782C2B46299ECE33008DC703 /* jpgImageWithRightOrientation.jpg in Resources */, 7865C5E82941374F0010E17F /* heicImage.heic in Resources */, 7865C5FD294157BC0010E17F /* icnsImage.icns in Resources */, 680049382280F2B9006DD6AB /* pngImage.png in Resources */, 680049392280F2B9006DD6AB /* jpgImage.jpg in Resources */, 7865C5EB294137960010E17F /* icoImage.ico in Resources */, 7865C5E22941326F0010E17F /* bmpImage.bmp in Resources */, 7865C600294252A60010E17F /* proRawImage.dng in Resources */, 7865C5E5294132D50010E17F /* svgImage.svg in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 9FC8F0E9229FA49E00C8D58F /* gifImage.gif in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; 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"; }; AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; B8739A4353234497CF76B597 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 334733EE2668136400DCC49E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 334733FD266813F100DCC49E /* MetaDataUtilTests.m in Sources */, 334733FF266813FA00DCC49E /* ImagePickerTestImages.m in Sources */, 86E9A893272754860017E6E0 /* PickerSaveImageToPathOperationTests.m in Sources */, 334733FC266813EE00DCC49E /* ImageUtilTests.m in Sources */, 33473400266813FD00DCC49E /* ImagePickerPluginTests.m in Sources */, 334733FE266813F400DCC49E /* PhotoAssetUtilTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 6801C8322555D726009DAF8D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 6801C8392555D726009DAF8D /* ImagePickerFromGalleryUITests.m in Sources */, BE6173D826A958B800D0974D /* ImagePickerFromLimitedGalleryUITests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 5C9513011EC38BD300040975 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 334733F82668136400DCC49E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = 334733F72668136400DCC49E /* PBXContainerItemProxy */; }; 6801C83C2555D726009DAF8D /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = 6801C83B2555D726009DAF8D /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 334733FA2668136400DCC49E /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = DC6FCAAD4E7580C9B3C2E21D /* 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; }; 334733FB2668136400DCC49E /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 0C7B151765FD4249454C49AD /* 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_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; }; 6801C83D2555D726009DAF8D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = RunnerUITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.google.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = Runner; }; name = Debug; }; 6801C83E2555D726009DAF8D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = RunnerUITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.google.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = Runner; }; name = Release; }; 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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 = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.imagePickerExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.imagePickerExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 334733F92668136400DCC49E /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 334733FA2668136400DCC49E /* Debug */, 334733FB2668136400DCC49E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 6801C83F2555D726009DAF8D /* Build configuration list for PBXNativeTarget "RunnerUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( 6801C83D2555D726009DAF8D /* Debug */, 6801C83E2555D726009DAF8D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/RunnerUITests.xcscheme ================================================ ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m ================================================ // Copyright 2013 The Flutter 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 "ImagePickerTestImages.h" @import image_picker_ios; @import image_picker_ios.Test; @import UniformTypeIdentifiers; @import XCTest; #import @interface MockViewController : UIViewController @property(nonatomic, retain) UIViewController *mockPresented; @end @implementation MockViewController @synthesize mockPresented; - (UIViewController *)presentedViewController { return mockPresented; } @end @interface ImagePickerPluginTests : XCTestCase @end @implementation ImagePickerPluginTests - (void)testPluginPickImageDeviceBack { id mockUIImagePicker = OCMClassMock([UIImagePickerController class]); id mockAVCaptureDevice = OCMClassMock([AVCaptureDevice class]); // UIImagePickerControllerSourceTypeCamera is supported OCMStub(ClassMethod( [mockUIImagePicker isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])) .andReturn(YES); // UIImagePickerControllerCameraDeviceRear is supported OCMStub(ClassMethod( [mockUIImagePicker isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear])) .andReturn(YES); // AVAuthorizationStatusAuthorized is supported OCMStub([mockAVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) .andReturn(AVAuthorizationStatusAuthorized); // Run test FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; UIImagePickerController *controller = [[UIImagePickerController alloc] init]; [plugin setImagePickerControllerOverrides:@[ controller ]]; [plugin pickImageWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeCamera camera:FLTSourceCameraRear] maxSize:[[FLTMaxSize alloc] init] quality:nil fullMetadata:@YES completion:^(NSString *_Nullable result, FlutterError *_Nullable error){ }]; XCTAssertEqual(controller.cameraDevice, UIImagePickerControllerCameraDeviceRear); } - (void)testPluginPickImageDeviceFront { id mockUIImagePicker = OCMClassMock([UIImagePickerController class]); id mockAVCaptureDevice = OCMClassMock([AVCaptureDevice class]); // UIImagePickerControllerSourceTypeCamera is supported OCMStub(ClassMethod( [mockUIImagePicker isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])) .andReturn(YES); // UIImagePickerControllerCameraDeviceFront is supported OCMStub(ClassMethod( [mockUIImagePicker isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront])) .andReturn(YES); // AVAuthorizationStatusAuthorized is supported OCMStub([mockAVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) .andReturn(AVAuthorizationStatusAuthorized); // Run test FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; UIImagePickerController *controller = [[UIImagePickerController alloc] init]; [plugin setImagePickerControllerOverrides:@[ controller ]]; [plugin pickImageWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeCamera camera:FLTSourceCameraFront] maxSize:[[FLTMaxSize alloc] init] quality:nil fullMetadata:@YES completion:^(NSString *_Nullable result, FlutterError *_Nullable error){ }]; XCTAssertEqual(controller.cameraDevice, UIImagePickerControllerCameraDeviceFront); } - (void)testPluginPickVideoDeviceBack { id mockUIImagePicker = OCMClassMock([UIImagePickerController class]); id mockAVCaptureDevice = OCMClassMock([AVCaptureDevice class]); // UIImagePickerControllerSourceTypeCamera is supported OCMStub(ClassMethod( [mockUIImagePicker isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])) .andReturn(YES); // UIImagePickerControllerCameraDeviceRear is supported OCMStub(ClassMethod( [mockUIImagePicker isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear])) .andReturn(YES); // AVAuthorizationStatusAuthorized is supported OCMStub([mockAVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) .andReturn(AVAuthorizationStatusAuthorized); // Run test FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; UIImagePickerController *controller = [[UIImagePickerController alloc] init]; [plugin setImagePickerControllerOverrides:@[ controller ]]; [plugin pickVideoWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeCamera camera:FLTSourceCameraRear] maxDuration:nil completion:^(NSString *_Nullable result, FlutterError *_Nullable error){ }]; XCTAssertEqual(controller.cameraDevice, UIImagePickerControllerCameraDeviceRear); } - (void)testPluginPickVideoDeviceFront { id mockUIImagePicker = OCMClassMock([UIImagePickerController class]); id mockAVCaptureDevice = OCMClassMock([AVCaptureDevice class]); // UIImagePickerControllerSourceTypeCamera is supported OCMStub(ClassMethod( [mockUIImagePicker isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])) .andReturn(YES); // UIImagePickerControllerCameraDeviceFront is supported OCMStub(ClassMethod( [mockUIImagePicker isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront])) .andReturn(YES); // AVAuthorizationStatusAuthorized is supported OCMStub([mockAVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) .andReturn(AVAuthorizationStatusAuthorized); // Run test FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; UIImagePickerController *controller = [[UIImagePickerController alloc] init]; [plugin setImagePickerControllerOverrides:@[ controller ]]; [plugin pickVideoWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeCamera camera:FLTSourceCameraFront] maxDuration:nil completion:^(NSString *_Nullable result, FlutterError *_Nullable error){ }]; XCTAssertEqual(controller.cameraDevice, UIImagePickerControllerCameraDeviceFront); } - (void)testPickMultiImageShouldUseUIImagePickerControllerOnPreiOS14 { if (@available(iOS 14, *)) { return; } id mockUIImagePicker = OCMClassMock([UIImagePickerController class]); id photoLibrary = OCMClassMock([PHPhotoLibrary class]); OCMStub(ClassMethod([photoLibrary authorizationStatus])) .andReturn(PHAuthorizationStatusAuthorized); FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; [plugin setImagePickerControllerOverrides:@[ mockUIImagePicker ]]; [plugin pickMultiImageWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)] quality:@(50) fullMetadata:@YES completion:^(NSArray *_Nullable result, FlutterError *_Nullable error){ }]; OCMVerify(times(1), [mockUIImagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary]); } - (void)testPickImageWithoutFullMetadata API_AVAILABLE(ios(11)) { id mockUIImagePicker = OCMClassMock([UIImagePickerController class]); id photoLibrary = OCMClassMock([PHPhotoLibrary class]); FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; [plugin setImagePickerControllerOverrides:@[ mockUIImagePicker ]]; [plugin pickImageWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeGallery camera:FLTSourceCameraFront] maxSize:[[FLTMaxSize alloc] init] quality:nil fullMetadata:@NO completion:^(NSString *_Nullable result, FlutterError *_Nullable error){ }]; OCMVerify(times(0), [photoLibrary authorizationStatus]); } - (void)testPickMultiImageWithoutFullMetadata API_AVAILABLE(ios(11)) { id mockUIImagePicker = OCMClassMock([UIImagePickerController class]); id photoLibrary = OCMClassMock([PHPhotoLibrary class]); FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; [plugin setImagePickerControllerOverrides:@[ mockUIImagePicker ]]; [plugin pickMultiImageWithMaxSize:[[FLTMaxSize alloc] init] quality:nil fullMetadata:@NO completion:^(NSArray *_Nullable result, FlutterError *_Nullable error){ }]; OCMVerify(times(0), [photoLibrary authorizationStatus]); } #pragma mark - Test camera devices, no op on simulators - (void)testPluginPickImageDeviceCancelClickMultipleTimes { if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { return; } FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; UIImagePickerController *controller = [[UIImagePickerController alloc] init]; plugin.imagePickerControllerOverrides = @[ controller ]; [plugin pickImageWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeCamera camera:FLTSourceCameraRear] maxSize:[[FLTMaxSize alloc] init] quality:nil fullMetadata:@YES completion:^(NSString *_Nullable result, FlutterError *_Nullable error){ }]; // To ensure the flow does not crash by multiple cancel call [plugin imagePickerControllerDidCancel:controller]; [plugin imagePickerControllerDidCancel:controller]; } #pragma mark - Test video duration - (void)testPickingVideoWithDuration { FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; UIImagePickerController *controller = [[UIImagePickerController alloc] init]; [plugin setImagePickerControllerOverrides:@[ controller ]]; [plugin pickVideoWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeCamera camera:FLTSourceCameraRear] maxDuration:@(95) completion:^(NSString *_Nullable result, FlutterError *_Nullable error){ }]; XCTAssertEqual(controller.videoMaximumDuration, 95); } - (void)testViewController { UIWindow *window = [UIWindow new]; MockViewController *vc1 = [MockViewController new]; window.rootViewController = vc1; UIViewController *vc2 = [UIViewController new]; vc1.mockPresented = vc2; FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; XCTAssertEqual([plugin viewControllerWithWindow:window], vc2); } - (void)testPluginMultiImagePathHasNullItem { FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"]; plugin.callContext = [[FLTImagePickerMethodCallContext alloc] initWithResult:^(NSArray *_Nullable result, FlutterError *_Nullable error) { XCTAssertEqualObjects(error.code, @"create_error"); [resultExpectation fulfill]; }]; [plugin sendCallResultWithSavedPathList:@[ [NSNull null] ]]; [self waitForExpectationsWithTimeout:30 handler:nil]; } - (void)testPluginMultiImagePathHasItem { FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; NSArray *pathList = @[ @"test" ]; XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"]; plugin.callContext = [[FLTImagePickerMethodCallContext alloc] initWithResult:^(NSArray *_Nullable result, FlutterError *_Nullable error) { XCTAssertEqualObjects(result, pathList); [resultExpectation fulfill]; }]; [plugin sendCallResultWithSavedPathList:pathList]; [self waitForExpectationsWithTimeout:30 handler:nil]; } - (void)testSendsImageInvalidSourceError API_AVAILABLE(ios(14)) { id mockPickerViewController = OCMClassMock([PHPickerViewController class]); id mockItemProvider = OCMClassMock([NSItemProvider class]); // Does not conform to image, invalid source. OCMStub([mockItemProvider hasItemConformingToTypeIdentifier:OCMOCK_ANY]).andReturn(NO); PHPickerResult *failResult1 = OCMClassMock([PHPickerResult class]); OCMStub([failResult1 itemProvider]).andReturn(mockItemProvider); PHPickerResult *failResult2 = OCMClassMock([PHPickerResult class]); OCMStub([failResult2 itemProvider]).andReturn(mockItemProvider); FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"]; plugin.callContext = [[FLTImagePickerMethodCallContext alloc] initWithResult:^(NSArray *result, FlutterError *error) { XCTAssertTrue(NSThread.isMainThread); XCTAssertNil(result); XCTAssertEqualObjects(error.code, @"invalid_source"); [resultExpectation fulfill]; }]; [plugin picker:mockPickerViewController didFinishPicking:@[ failResult1, failResult2 ]]; [self waitForExpectationsWithTimeout:30 handler:nil]; } - (void)testSendsImageInvalidErrorWhenOneFails API_AVAILABLE(ios(14)) { id mockPickerViewController = OCMClassMock([PHPickerViewController class]); NSError *loadDataError = [NSError errorWithDomain:@"PHPickerDomain" code:1234 userInfo:nil]; id mockFailItemProvider = OCMClassMock([NSItemProvider class]); OCMStub([mockFailItemProvider hasItemConformingToTypeIdentifier:OCMOCK_ANY]).andReturn(YES); [[mockFailItemProvider stub] loadDataRepresentationForTypeIdentifier:OCMOCK_ANY completionHandler:[OCMArg invokeBlockWithArgs:[NSNull null], loadDataError, nil]]; PHPickerResult *failResult = OCMClassMock([PHPickerResult class]); OCMStub([failResult itemProvider]).andReturn(mockFailItemProvider); NSURL *tiffURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"tiffImage" withExtension:@"tiff"]; NSItemProvider *tiffItemProvider = [[NSItemProvider alloc] initWithContentsOfURL:tiffURL]; PHPickerResult *tiffResult = OCMClassMock([PHPickerResult class]); OCMStub([tiffResult itemProvider]).andReturn(tiffItemProvider); FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"]; plugin.callContext = [[FLTImagePickerMethodCallContext alloc] initWithResult:^(NSArray *result, FlutterError *error) { XCTAssertTrue(NSThread.isMainThread); XCTAssertNil(result); XCTAssertEqualObjects(error.code, @"invalid_image"); [resultExpectation fulfill]; }]; [plugin picker:mockPickerViewController didFinishPicking:@[ failResult, tiffResult ]]; [self waitForExpectationsWithTimeout:30 handler:nil]; } - (void)testSavesImages API_AVAILABLE(ios(14)) { id mockPickerViewController = OCMClassMock([PHPickerViewController class]); NSURL *tiffURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"tiffImage" withExtension:@"tiff"]; NSItemProvider *tiffItemProvider = [[NSItemProvider alloc] initWithContentsOfURL:tiffURL]; PHPickerResult *tiffResult = OCMClassMock([PHPickerResult class]); OCMStub([tiffResult itemProvider]).andReturn(tiffItemProvider); NSURL *pngURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"pngImage" withExtension:@"png"]; NSItemProvider *pngItemProvider = [[NSItemProvider alloc] initWithContentsOfURL:pngURL]; PHPickerResult *pngResult = OCMClassMock([PHPickerResult class]); OCMStub([pngResult itemProvider]).andReturn(pngItemProvider); FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init]; XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"]; plugin.callContext = [[FLTImagePickerMethodCallContext alloc] initWithResult:^(NSArray *result, FlutterError *error) { XCTAssertTrue(NSThread.isMainThread); XCTAssertEqual(result.count, 2); XCTAssertNil(error); [resultExpectation fulfill]; }]; [plugin picker:mockPickerViewController didFinishPicking:@[ tiffResult, pngResult ]]; [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 ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerTestImages.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import Foundation; NS_ASSUME_NONNULL_BEGIN @interface ImagePickerTestImages : NSObject @property(class, copy, readonly) NSData *JPGTestData; @property(class, copy, readonly) NSData *PNGTestData; @property(class, copy, readonly) NSData *GIFTestData; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerTestImages.m ================================================ // Copyright 2013 The Flutter 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 "ImagePickerTestImages.h" @implementation ImagePickerTestImages + (NSData *)JPGTestData { NSBundle *bundle = [NSBundle bundleForClass:self]; NSURL *url = [bundle URLForResource:@"jpgImage" withExtension:@"jpg"]; NSData *data = [NSData dataWithContentsOfURL:url]; if (!data.length) { // When the tests are run outside the example project (podspec lint) the image may not be // embedded in the test bundle. Fall back to the base64 string representation of the jpg. data = [[NSData alloc] initWithBase64EncodedString: @"/9j/4AAQSkZJRgABAQAALgAuAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABA" "AAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAAAuAAAAAQAAAC4AAAABAAOg" "AQADAAAAAQABAACgAgAEAAAAAQAAAAygAwAEAAAAAQAAAAcAAAAA/+EJc2h0dHA6Ly9ucy5hZG9iZS5jb20" "veGFwLzEuMC8APD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz" "4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiP" "iA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucy" "MiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZ" "G9iZS5jb20vcGhvdG9zaG9wLzEuMC8iIHBob3Rvc2hvcDpDcmVkaXQ9IsKpIEdvb2dsZSIvPiA8L3JkZjpSR" "EY+IDwveDp4bXBtZXRhPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI" "CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDw/eHBhY2tldCBlbmQ9In" "ciPz4A/+0AVlBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAdHAFaAAMbJUccAgAAAgACHAJuAAnCqSBHb29nbG" "UAOEJJTQQlAAAAAAAQmkt2IF3PgNJVMGnV2zijEf/AABEIAAcADAMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQA" "AAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQ" "gjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h" "5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp" "6vHy8/T19vf4+fr/xAAfAQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgv/xAC1EQACAQIEBAMEBwUEBAAB" "AncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0R" "FRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tr" "e4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2wBDAAQDAwMDAgQDAwMEBAQFBgoGBg" "UFBgwICQcKDgwPDg4MDQ0PERYTDxAVEQ0NExoTFRcYGRkZDxIbHRsYHRYYGRj/2wBDAQQEBAYFBgsGBgsYEA0" "QGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBj/3QAEAAH/2gAMAwEA" "AhEDEQA/AMWiiivzk/qo/9k=" options:0]; } return data; } + (NSData *)PNGTestData { NSBundle *bundle = [NSBundle bundleForClass:self]; NSURL *url = [bundle URLForResource:@"pngImage" withExtension:@"png"]; NSData *data = [NSData dataWithContentsOfURL:url]; if (!data.length) { // When the tests are run outside the example project (podspec lint) the image may not be // embedded in the test bundle. Fall back to the base64 string representation of the png. data = [[NSData alloc] initWithBase64EncodedString: @"iVBORw0KGgoAAAAEQ2dCSVAAIAYsuHdmAAAADUlIRFIAAAAMAAAABwgGAAAAPLKsJAAAAARnQU1BAACxjwv8Y" "QUAAAABc1JHQgCuzhzpAAAAIGNIUk0AAHomAACAhAAA+" "gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAJcEh" "ZcwAABxMAAAcTAc4gDwgAAAAOSURBVGMwdX71nxTMMKqBCAwAsfuEYQAAAABJRU5ErkJggg==" options:0]; } return data; } + (NSData *)GIFTestData { NSBundle *bundle = [NSBundle bundleForClass:self]; NSURL *url = [bundle URLForResource:@"gifImage" withExtension:@"gif"]; NSData *data = [NSData dataWithContentsOfURL:url]; if (!data.length) { // When the tests are run outside the example project (podspec lint) the image may not be // embedded in the test bundle. Fall back to the base64 string representation of the gif. data = [[NSData alloc] initWithBase64EncodedString: @"R0lGODlhDAAHAPAAAOpCNQAAACH5BABkAAAAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAADAAHAAACCISP" "qcvtD1UBACH5BABkAAAALAAAAAAMAAcAhuc/JPA/K+49Ne4+PvA7MrhYHoB+A4N9BYh+BYZ+E4xyG496HZJ" "8F5J4GaRtE6tsH7tWIr9SK7xVKJl3IKpvI7lrKc1FLc5PLNJILsdTJMFVJsZWJshWIM9XIshWJNBWLd1SK9" "BUMNFRNOlAI+9CMuNJMetHPnuCAF66F1u8FVu7GV27HGytG3utGH6rHGK1G3WxFWeuIHqlIG60IGi4JTnTDz" "jZDy/VEy/eFTnVEDzXFxflABfjBRPmBRbnBxPrABvpARntAxLuCBXuCQTyAAb1BgvwACnmDSPpDSLjECPpED" "HhFFDLGIeAFoiBFoqCF4uCHYWnHJGVJqSNJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdWgAIXCjE3PTtAPDUuByQfCzQ4Qj9BPjktBgAcC" "StJRURGQzYwJyMdDDM6SkhHS0xRCAEgD1IsKikoLzJTDgQlEBQNT05NUBMVBQMmGCEZHhsaEhEiFoEAIfkEAG" "QAAAAsAAAAAAwABwCFB+8ACewACu0ACe4ACO8AC+4ACu8ADOwAD+wAEOYAEekAA/EABfAAB/IAAfUAA/UAAP" "cAAfcAAvYAA/cBBPQABfUABvQAB/UBBvYBCfAACPEAC/AACvIACvMBAPgAAPkAAPgBAPkBAvgBAPoAAPoBA" "PsBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAAAAAAAAAAAAAAAABkfAAadjeUxEEYnk8QBoLhUHCASJJCWLyiTiIZFG3lAoO4F4SiUwScywYCQQ8" "ScEEokCG06D8pA4mBUWCQoIBwIGGQQGBgUFQQA7" options:0]; } return data; } @end ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImageUtilTests.m ================================================ // Copyright 2013 The Flutter 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 "ImagePickerTestImages.h" @import image_picker_ios; @import image_picker_ios.Test; @import XCTest; @interface ImageUtilTests : XCTestCase @end @implementation ImageUtilTests - (void)testScaledImage_ShouldBeScaled { UIImage *image = [UIImage imageWithData:ImagePickerTestImages.JPGTestData]; UIImage *newImage = [FLTImagePickerImageUtil scaledImage:image maxWidth:@3 maxHeight:@2 isMetadataAvailable:YES]; XCTAssertEqual(newImage.size.width, 3); XCTAssertEqual(newImage.size.height, 2); } - (void)testScaledImage_ShouldBeScaledWithNoMetadata { UIImage *image = [UIImage imageWithData:ImagePickerTestImages.JPGTestData]; UIImage *newImage = [FLTImagePickerImageUtil scaledImage:image maxWidth:@3 maxHeight:@2 isMetadataAvailable:NO]; XCTAssertEqual(newImage.size.width, 3); XCTAssertEqual(newImage.size.height, 2); } - (void)testScaledImage_ShouldBeCorrectRotation { NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"jpgImageWithRightOrientation" withExtension:@"jpg"]; NSData *imageData = [NSData dataWithContentsOfURL:imageURL]; UIImage *image = [UIImage imageWithData:imageData]; XCTAssertEqual(image.size.width, 130); XCTAssertEqual(image.size.height, 174); XCTAssertEqual(image.imageOrientation, UIImageOrientationRight); UIImage *newImage = [FLTImagePickerImageUtil scaledImage:image maxWidth:@10 maxHeight:@10 isMetadataAvailable:YES]; XCTAssertEqual(newImage.size.width, 10); XCTAssertEqual(newImage.size.height, 7); XCTAssertEqual(newImage.imageOrientation, UIImageOrientationUp); } - (void)testScaledGIFImage_ShouldBeScaled { // gif image that frame size is 3 and the duration is 1 second. GIFInfo *info = [FLTImagePickerImageUtil scaledGIFImage:ImagePickerTestImages.GIFTestData maxWidth:@3 maxHeight:@2]; NSArray *images = info.images; NSTimeInterval duration = info.interval; XCTAssertEqual(images.count, 3); XCTAssertEqual(duration, 1); for (UIImage *newImage in images) { XCTAssertEqual(newImage.size.width, 3); XCTAssertEqual(newImage.size.height, 2); } } @end ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/RunnerTests/Info.plist ================================================ 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 ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/RunnerTests/MetaDataUtilTests.m ================================================ // Copyright 2013 The Flutter 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 "ImagePickerTestImages.h" @import image_picker_ios; @import image_picker_ios.Test; @import XCTest; @interface MetaDataUtilTests : XCTestCase @end @implementation MetaDataUtilTests - (void)testGetImageMIMETypeFromImageData { // test jpeg XCTAssertEqual( [FLTImagePickerMetaDataUtil getImageMIMETypeFromImageData:ImagePickerTestImages.JPGTestData], FLTImagePickerMIMETypeJPEG); // test png XCTAssertEqual( [FLTImagePickerMetaDataUtil getImageMIMETypeFromImageData:ImagePickerTestImages.PNGTestData], FLTImagePickerMIMETypePNG); // test gif XCTAssertEqual( [FLTImagePickerMetaDataUtil getImageMIMETypeFromImageData:ImagePickerTestImages.GIFTestData], FLTImagePickerMIMETypeGIF); } - (void)testSuffixFromType { // test jpeg XCTAssertEqualObjects( [FLTImagePickerMetaDataUtil imageTypeSuffixFromType:FLTImagePickerMIMETypeJPEG], @".jpg"); // test png XCTAssertEqualObjects( [FLTImagePickerMetaDataUtil imageTypeSuffixFromType:FLTImagePickerMIMETypePNG], @".png"); // test gif XCTAssertEqualObjects( [FLTImagePickerMetaDataUtil imageTypeSuffixFromType:FLTImagePickerMIMETypeGIF], @".gif"); // test other XCTAssertNil([FLTImagePickerMetaDataUtil imageTypeSuffixFromType:FLTImagePickerMIMETypeOther]); } - (void)testGetMetaData { NSDictionary *metaData = [FLTImagePickerMetaDataUtil getMetaDataFromImageData:ImagePickerTestImages.JPGTestData]; NSDictionary *exif = [metaData objectForKey:(__bridge NSString *)kCGImagePropertyExifDictionary]; XCTAssertEqual([exif[(__bridge NSString *)kCGImagePropertyExifPixelXDimension] integerValue], 12); } - (void)testWriteMetaData { NSData *dataJPG = ImagePickerTestImages.JPGTestData; NSDictionary *metaData = [FLTImagePickerMetaDataUtil getMetaDataFromImageData:dataJPG]; NSString *tmpFile = [NSString stringWithFormat:@"image_picker_test.jpg"]; NSString *tmpDirectory = NSTemporaryDirectory(); NSString *tmpPath = [tmpDirectory stringByAppendingPathComponent:tmpFile]; NSData *newData = [FLTImagePickerMetaDataUtil imageFromImage:dataJPG withMetaData:metaData]; if ([[NSFileManager defaultManager] createFileAtPath:tmpPath contents:newData attributes:nil]) { NSData *savedTmpImageData = [NSData dataWithContentsOfFile:tmpPath]; NSDictionary *tmpMetaData = [FLTImagePickerMetaDataUtil getMetaDataFromImageData:savedTmpImageData]; XCTAssert([tmpMetaData isEqualToDictionary:metaData]); } else { XCTAssert(NO); } } - (void)testUpdateMetaDataBadData { NSData *imageData = [NSData data]; NSDictionary *metaData = [FLTImagePickerMetaDataUtil getMetaDataFromImageData:imageData]; NSData *newData = [FLTImagePickerMetaDataUtil imageFromImage:imageData withMetaData:metaData]; XCTAssertNil(newData); } - (void)testConvertImageToData { UIImage *imageJPG = [UIImage imageWithData:ImagePickerTestImages.JPGTestData]; NSData *convertedDataJPG = [FLTImagePickerMetaDataUtil convertImage:imageJPG usingType:FLTImagePickerMIMETypeJPEG quality:@(0.5)]; XCTAssertEqual([FLTImagePickerMetaDataUtil getImageMIMETypeFromImageData:convertedDataJPG], FLTImagePickerMIMETypeJPEG); NSData *convertedDataPNG = [FLTImagePickerMetaDataUtil convertImage:imageJPG usingType:FLTImagePickerMIMETypePNG quality:nil]; XCTAssertEqual([FLTImagePickerMetaDataUtil getImageMIMETypeFromImageData:convertedDataPNG], FLTImagePickerMIMETypePNG); } @end ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/RunnerTests/PhotoAssetUtilTests.m ================================================ // Copyright 2013 The Flutter 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 "ImagePickerTestImages.h" @import image_picker_ios; @import image_picker_ios.Test; @import XCTest; @interface PhotoAssetUtilTests : XCTestCase @end @implementation PhotoAssetUtilTests - (void)getAssetFromImagePickerInfoShouldReturnNilIfNotAvailable { NSDictionary *mockData = @{}; XCTAssertNil([FLTImagePickerPhotoAssetUtil getAssetFromImagePickerInfo:mockData]); } - (void)testGetAssetFromPHPickerResultShouldReturnNilIfNotAvailable API_AVAILABLE(ios(14)) { if (@available(iOS 14, *)) { PHPickerResult *mockData; [mockData.itemProvider loadObjectOfClass:[UIImage class] completionHandler:^(__kindof id _Nullable image, NSError *_Nullable error) { XCTAssertNil([FLTImagePickerPhotoAssetUtil getAssetFromPHPickerResult:mockData]); }]; } } - (void)testSaveImageWithOriginalImageData_ShouldSaveWithTheCorrectExtentionAndMetaData { // test jpg NSData *dataJPG = ImagePickerTestImages.JPGTestData; UIImage *imageJPG = [UIImage imageWithData:dataJPG]; NSString *savedPathJPG = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:dataJPG image:imageJPG maxWidth:nil maxHeight:nil imageQuality:nil]; XCTAssertEqualObjects([NSURL URLWithString:savedPathJPG].pathExtension, @"jpg"); NSDictionary *originalMetaDataJPG = [FLTImagePickerMetaDataUtil getMetaDataFromImageData:dataJPG]; NSData *newDataJPG = [NSData dataWithContentsOfFile:savedPathJPG]; NSDictionary *newMetaDataJPG = [FLTImagePickerMetaDataUtil getMetaDataFromImageData:newDataJPG]; XCTAssertEqualObjects(originalMetaDataJPG[@"ProfileName"], newMetaDataJPG[@"ProfileName"]); // test png NSData *dataPNG = ImagePickerTestImages.PNGTestData; UIImage *imagePNG = [UIImage imageWithData:dataPNG]; NSString *savedPathPNG = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:dataPNG image:imagePNG maxWidth:nil maxHeight:nil imageQuality:nil]; XCTAssertEqualObjects([NSURL URLWithString:savedPathPNG].pathExtension, @"png"); NSDictionary *originalMetaDataPNG = [FLTImagePickerMetaDataUtil getMetaDataFromImageData:dataPNG]; NSData *newDataPNG = [NSData dataWithContentsOfFile:savedPathPNG]; NSDictionary *newMetaDataPNG = [FLTImagePickerMetaDataUtil getMetaDataFromImageData:newDataPNG]; XCTAssertEqualObjects(originalMetaDataPNG[@"ProfileName"], newMetaDataPNG[@"ProfileName"]); } - (void)testSaveImageWithPickerInfo_ShouldSaveWithDefaultExtention { UIImage *imageJPG = [UIImage imageWithData:ImagePickerTestImages.JPGTestData]; NSString *savedPathJPG = [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:nil image:imageJPG imageQuality:nil]; // should be saved as XCTAssertEqualObjects([savedPathJPG substringFromIndex:savedPathJPG.length - 4], kFLTImagePickerDefaultSuffix); } - (void)testSaveImageWithPickerInfo_ShouldSaveWithTheCorrectExtentionAndMetaData { NSDictionary *dummyInfo = @{ UIImagePickerControllerMediaMetadata : @{ (__bridge NSString *)kCGImagePropertyExifDictionary : @{(__bridge NSString *)kCGImagePropertyExifMakerNote : @"aNote"} } }; UIImage *imageJPG = [UIImage imageWithData:ImagePickerTestImages.JPGTestData]; NSString *savedPathJPG = [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:dummyInfo image:imageJPG imageQuality:nil]; NSData *data = [NSData dataWithContentsOfFile:savedPathJPG]; NSDictionary *meta = [FLTImagePickerMetaDataUtil getMetaDataFromImageData:data]; XCTAssertEqualObjects(meta[(__bridge NSString *)kCGImagePropertyExifDictionary] [(__bridge NSString *)kCGImagePropertyExifMakerNote], @"aNote"); } - (void)testSaveImageWithOriginalImageData_ShouldSaveAsGifAnimation { // test gif NSData *dataGIF = ImagePickerTestImages.GIFTestData; UIImage *imageGIF = [UIImage imageWithData:dataGIF]; CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)dataGIF, nil); size_t numberOfFrames = CGImageSourceGetCount(imageSource); NSString *savedPathGIF = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:dataGIF image:imageGIF maxWidth:nil maxHeight:nil imageQuality:nil]; XCTAssertEqualObjects([NSURL URLWithString:savedPathGIF].pathExtension, @"gif"); NSData *newDataGIF = [NSData dataWithContentsOfFile:savedPathGIF]; CGImageSourceRef newImageSource = CGImageSourceCreateWithData((__bridge CFDataRef)newDataGIF, nil); size_t newNumberOfFrames = CGImageSourceGetCount(newImageSource); XCTAssertEqual(numberOfFrames, newNumberOfFrames); } - (void)testSaveImageWithOriginalImageData_ShouldSaveAsScalledGifAnimation { // test gif NSData *dataGIF = ImagePickerTestImages.GIFTestData; UIImage *imageGIF = [UIImage imageWithData:dataGIF]; CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)dataGIF, nil); size_t numberOfFrames = CGImageSourceGetCount(imageSource); NSString *savedPathGIF = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:dataGIF image:imageGIF maxWidth:@3 maxHeight:@2 imageQuality:nil]; NSData *newDataGIF = [NSData dataWithContentsOfFile:savedPathGIF]; UIImage *newImage = [[UIImage alloc] initWithData:newDataGIF]; XCTAssertEqual(newImage.size.width, 3); XCTAssertEqual(newImage.size.height, 2); CGImageSourceRef newImageSource = CGImageSourceCreateWithData((__bridge CFDataRef)newDataGIF, nil); size_t newNumberOfFrames = CGImageSourceGetCount(newImageSource); XCTAssertEqual(numberOfFrames, newNumberOfFrames); } @end ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/RunnerTests/PickerSaveImageToPathOperationTests.m ================================================ // Copyright 2013 The Flutter 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 image_picker_ios; @import image_picker_ios.Test; @import UniformTypeIdentifiers; @import XCTest; @interface PickerSaveImageToPathOperationTests : XCTestCase @end @implementation PickerSaveImageToPathOperationTests - (void)testSaveWebPImage API_AVAILABLE(ios(14)) { NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"webpImage" withExtension:@"webp"]; NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSavePNGImage API_AVAILABLE(ios(14)) { NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"pngImage" withExtension:@"png"]; NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"png"]; } - (void)testSaveJPGImage API_AVAILABLE(ios(14)) { NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"jpgImage" withExtension:@"jpg"]; NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveGIFImage API_AVAILABLE(ios(14)) { NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"gifImage" withExtension:@"gif"]; NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; NSData *dataGIF = [NSData dataWithContentsOfURL:imageURL]; CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)dataGIF, nil); size_t numberOfFrames = CGImageSourceGetCount(imageSource); XCTestExpectation *pathExpectation = [self expectationWithDescription:@"Path was created"]; XCTestExpectation *operationExpectation = [self expectationWithDescription:@"Operation completed"]; FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc] initWithResult:result maxHeight:@100 maxWidth:@100 desiredImageQuality:@100 fullMetadata:NO savedPathBlock:^(NSString *savedPath, FlutterError *error) { XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:savedPath]); // Ensure gif is animated. XCTAssertEqualObjects([NSURL URLWithString:savedPath].pathExtension, @"gif"); NSData *newDataGIF = [NSData dataWithContentsOfFile:savedPath]; CGImageSourceRef newImageSource = CGImageSourceCreateWithData((__bridge CFDataRef)newDataGIF, nil); size_t newNumberOfFrames = CGImageSourceGetCount(newImageSource); XCTAssertEqual(numberOfFrames, newNumberOfFrames); [pathExpectation fulfill]; }]; operation.completionBlock = ^{ [operationExpectation fulfill]; }; [operation start]; [self waitForExpectationsWithTimeout:30 handler:nil]; XCTAssertTrue(operation.isFinished); } - (void)testSaveBMPImage API_AVAILABLE(ios(14)) { NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"bmpImage" withExtension:@"bmp"]; NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveHEICImage API_AVAILABLE(ios(14)) { NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"heicImage" withExtension:@"heic"]; NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveWithOrientation API_AVAILABLE(ios(14)) { NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"jpgImageWithRightOrientation" withExtension:@"jpg"]; NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; XCTestExpectation *pathExpectation = [self expectationWithDescription:@"Path was created"]; XCTestExpectation *operationExpectation = [self expectationWithDescription:@"Operation completed"]; FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc] initWithResult:result maxHeight:@10 maxWidth:@10 desiredImageQuality:@100 fullMetadata:NO savedPathBlock:^(NSString *savedPath, FlutterError *error) { XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:savedPath]); // Ensure image retained it's orientation data. XCTAssertEqualObjects([NSURL URLWithString:savedPath].pathExtension, @"jpg"); UIImage *image = [UIImage imageWithContentsOfFile:savedPath]; XCTAssertEqual(image.imageOrientation, UIImageOrientationRight); XCTAssertEqual(image.size.width, 7); XCTAssertEqual(image.size.height, 10); [pathExpectation fulfill]; }]; operation.completionBlock = ^{ [operationExpectation fulfill]; }; [operation start]; [self waitForExpectationsWithTimeout:30 handler:nil]; XCTAssertTrue(operation.isFinished); } - (void)testSaveICNSImage API_AVAILABLE(ios(14)) { NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"icnsImage" withExtension:@"icns"]; NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveICOImage API_AVAILABLE(ios(14)) { NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"icoImage" withExtension:@"ico"]; NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveProRAWImage API_AVAILABLE(ios(14)) { NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"proRawImage" withExtension:@"dng"]; NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveSVGImage API_AVAILABLE(ios(14)) { NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"svgImage" withExtension:@"svg"]; NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testSaveTIFFImage API_AVAILABLE(ios(14)) { NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"tiffImage" withExtension:@"tiff"]; NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; [self verifySavingImageWithPickerResult:result fullMetadata:YES withExtension:@"jpg"]; } - (void)testNonexistentImage API_AVAILABLE(ios(14)) { NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"bogus" withExtension:@"png"]; NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; XCTestExpectation *errorExpectation = [self expectationWithDescription:@"invalid source error"]; FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc] initWithResult:result maxHeight:@100 maxWidth:@100 desiredImageQuality:@100 fullMetadata:YES savedPathBlock:^(NSString *savedPath, FlutterError *error) { XCTAssertEqualObjects(error.code, @"invalid_source"); [errorExpectation fulfill]; }]; [operation start]; [self waitForExpectationsWithTimeout:30 handler:nil]; } - (void)testFailingImageLoad API_AVAILABLE(ios(14)) { NSError *loadDataError = [NSError errorWithDomain:@"PHPickerDomain" code:1234 userInfo:nil]; id mockItemProvider = OCMClassMock([NSItemProvider class]); OCMStub([mockItemProvider hasItemConformingToTypeIdentifier:OCMOCK_ANY]).andReturn(YES); [[mockItemProvider stub] loadDataRepresentationForTypeIdentifier:OCMOCK_ANY completionHandler:[OCMArg invokeBlockWithArgs:[NSNull null], loadDataError, nil]]; id pickerResult = OCMClassMock([PHPickerResult class]); OCMStub([pickerResult itemProvider]).andReturn(mockItemProvider); XCTestExpectation *errorExpectation = [self expectationWithDescription:@"invalid image error"]; FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc] initWithResult:pickerResult maxHeight:@100 maxWidth:@100 desiredImageQuality:@100 fullMetadata:YES savedPathBlock:^(NSString *savedPath, FlutterError *error) { XCTAssertEqualObjects(error.code, @"invalid_image"); XCTAssertEqualObjects(error.message, loadDataError.localizedDescription); XCTAssertEqualObjects(error.details, @"PHPickerDomain"); [errorExpectation fulfill]; }]; [operation start]; [self waitForExpectationsWithTimeout:30 handler:nil]; } - (void)testSavePNGImageWithoutFullMetadata API_AVAILABLE(ios(14)) { id photoAssetUtil = OCMClassMock([PHAsset class]); NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"pngImage" withExtension:@"png"]; NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL]; PHPickerResult *result = [self createPickerResultWithProvider:itemProvider]; OCMReject([photoAssetUtil fetchAssetsWithLocalIdentifiers:OCMOCK_ANY options:OCMOCK_ANY]); [self verifySavingImageWithPickerResult:result fullMetadata:NO withExtension:@"png"]; OCMVerifyAll(photoAssetUtil); } /** * Creates a mock picker result using NSItemProvider. * * @param itemProvider an item provider that will be used as picker result */ - (PHPickerResult *)createPickerResultWithProvider:(NSItemProvider *)itemProvider API_AVAILABLE(ios(14)) { PHPickerResult *result = OCMClassMock([PHPickerResult class]); OCMStub([result itemProvider]).andReturn(itemProvider); OCMStub([result assetIdentifier]).andReturn(itemProvider.registeredTypeIdentifiers.firstObject); return result; } /** * Validates a saving process of FLTPHPickerSaveImageToPathOperation. * * FLTPHPickerSaveImageToPathOperation is responsible for saving a picked image to the disk for * later use. It is expected that the saving is always successful. * * @param result the picker result */ - (void)verifySavingImageWithPickerResult:(PHPickerResult *)result fullMetadata:(BOOL)fullMetadata withExtension:(NSString *)extension API_AVAILABLE(ios(14)) { XCTestExpectation *pathExpectation = [self expectationWithDescription:@"Path was created"]; XCTestExpectation *operationExpectation = [self expectationWithDescription:@"Operation completed"]; FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc] initWithResult:result maxHeight:@100 maxWidth:@100 desiredImageQuality:@100 fullMetadata:fullMetadata savedPathBlock:^(NSString *savedPath, FlutterError *error) { XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:savedPath]); XCTAssertEqualObjects([NSURL URLWithString:savedPath].pathExtension, extension); [pathExpectation fulfill]; }]; operation.completionBlock = ^{ [operationExpectation fulfill]; }; [operation start]; [self waitForExpectationsWithTimeout:30 handler:nil]; XCTAssertTrue(operation.isFinished); } @end ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/RunnerUITests/ImagePickerFromGalleryUITests.m ================================================ // Copyright 2013 The Flutter 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 const int kElementWaitingTime = 30; @interface ImagePickerFromGalleryUITests : XCTestCase @property(nonatomic, strong) XCUIApplication *app; @end @implementation ImagePickerFromGalleryUITests - (void)setUp { [super setUp]; // Delete the app if already exists, to test permission popups self.continueAfterFailure = NO; self.app = [[XCUIApplication alloc] init]; [self.app launch]; __weak typeof(self) weakSelf = self; [self addUIInterruptionMonitorWithDescription:@"Permission popups" handler:^BOOL(XCUIElement *_Nonnull interruptingElement) { if (@available(iOS 14, *)) { XCUIElement *allPhotoPermission = interruptingElement .buttons[@"Allow Access to All Photos"]; if (![allPhotoPermission waitForExistenceWithTimeout: kElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", weakSelf.app.debugDescription); XCTFail(@"Failed due to not able to find " @"allPhotoPermission button with %@ seconds", @(kElementWaitingTime)); } [allPhotoPermission tap]; } else { XCUIElement *ok = interruptingElement.buttons[@"OK"]; if (![ok waitForExistenceWithTimeout: kElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", weakSelf.app.debugDescription); XCTFail(@"Failed due to not able to find ok button " @"with %@ seconds", @(kElementWaitingTime)); } [ok tap]; } return YES; }]; } - (void)tearDown { [super tearDown]; [self.app terminate]; } - (void)testCancel { // Find and tap on the pick from gallery button. XCUIElement *imageFromGalleryButton = 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)); } [imageFromGalleryButton tap]; // 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)); } [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 and tap on the `Cancel` button. 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)); } [cancelButton tap]; // Find the "not picked image text". 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)); } } - (void)testPickingFromGallery { [self launchPickerAndPickWithMaxWidth:nil maxHeight:nil quality:nil]; } - (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[@"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)); } [imageFromGalleryButton tap]; 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]; } 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)); } [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 *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", @(kElementWaitingTime)); } [allPhotosCell tap]; aImage = [self.app.collectionViews elementMatchingType:XCUIElementTypeCollectionView identifier:@"PhotosGridView"] .cells.firstMatch; } os_log_error(OS_LOG_DEFAULT, "description before picking image %@", self.app.debugDescription); if (![aImage waitForExistenceWithTimeout:kElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription); XCTFail(@"Failed due to not able to find an image with %@ seconds", @(kElementWaitingTime)); } [aImage tap]; // Find the picked image. 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)); } } @end ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/RunnerUITests/ImagePickerFromLimitedGalleryUITests.m ================================================ // Copyright 2013 The Flutter 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 const int kLimitedElementWaitingTime = 30; @interface ImagePickerFromLimitedGalleryUITests : XCTestCase @property(nonatomic, strong) XCUIApplication *app; @end @implementation ImagePickerFromLimitedGalleryUITests - (void)setUp { [super setUp]; // Delete the app if already exists, to test permission popups self.continueAfterFailure = NO; self.app = [[XCUIApplication alloc] init]; [self.app launch]; __weak typeof(self) weakSelf = self; [self addUIInterruptionMonitorWithDescription:@"Permission popups" handler:^BOOL(XCUIElement *_Nonnull interruptingElement) { XCUIElement *limitedPhotoPermission = [interruptingElement.buttons elementBoundByIndex:0]; if (![limitedPhotoPermission waitForExistenceWithTimeout: kLimitedElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", weakSelf.app.debugDescription); XCTFail(@"Failed due to not able to find " @"selectPhotos button with %@ seconds", @(kLimitedElementWaitingTime)); } [limitedPhotoPermission tap]; return YES; }]; } - (void)tearDown { [super tearDown]; [self.app terminate]; } // 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. XCUIElement *imageFromGalleryButton = 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)); } [imageFromGalleryButton tap]; // Find and tap on the `pick` button. 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..."); } [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. 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)); } [aImage tap]; // Find and tap on the `Done` button. 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..."); } [doneButton tap]; // Find an image and tap on it to have access to selected photos. 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)); } [aImage tap]; // Find the picked image. 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)); } } @end ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/RunnerUITests/Info.plist ================================================ 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 ================================================ FILE: packages/image_picker/image_picker_ios/example/ios/image_picker_exampleTests/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: packages/image_picker/image_picker_ios/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'package:video_player/video_player.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: 'Image Picker Demo', home: MyHomePage(title: 'Image Picker Example'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, this.title}) : super(key: key); final String? title; @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { List? _imageFileList; void _setImageFileListFromFile(XFile? value) { _imageFileList = value == null ? null : [value]; } dynamic _pickImageError; bool isVideo = false; VideoPlayerController? _controller; VideoPlayerController? _toBeDisposed; String? _retrieveDataError; final ImagePickerPlatform _picker = ImagePickerPlatform.instance; final TextEditingController maxWidthController = TextEditingController(); final TextEditingController maxHeightController = TextEditingController(); final TextEditingController qualityController = TextEditingController(); Future _playVideo(XFile? file) async { if (file != null && mounted) { await _disposeVideoController(); late VideoPlayerController controller; if (kIsWeb) { controller = VideoPlayerController.network(file.path); } else { controller = VideoPlayerController.file(File(file.path)); } _controller = controller; // In web, most browsers won't honor a programmatic call to .play // if the video has a sound track (and is not muted). // Mute the video so it auto-plays in web! // This is not needed if the call to .play is the result of user // interaction (clicking on a "play" button, for example). const double volume = kIsWeb ? 0.0 : 1.0; await controller.setVolume(volume); await controller.initialize(); await controller.setLooping(true); await controller.play(); setState(() {}); } } Future _onImageButtonPressed(ImageSource source, {BuildContext? context, bool isMultiImage = false}) async { if (_controller != null) { await _controller!.setVolume(0.0); } if (isVideo) { final XFile? file = await _picker.getVideo( source: source, maxDuration: const Duration(seconds: 10)); await _playVideo(file); } else if (isMultiImage) { await _displayPickImageDialog(context!, (double? maxWidth, double? maxHeight, int? quality) async { try { final List pickedFileList = await _picker.getMultiImageWithOptions( options: MultiImagePickerOptions( imageOptions: ImageOptions( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: quality, ), ), ); setState(() { _imageFileList = pickedFileList; }); } catch (e) { setState(() { _pickImageError = e; }); } }); } else { await _displayPickImageDialog(context!, (double? maxWidth, double? maxHeight, int? quality) async { try { final XFile? pickedFile = await _picker.getImageFromSource( source: source, options: ImagePickerOptions( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: quality, ), ); setState(() { _setImageFileListFromFile(pickedFile); }); } catch (e) { setState(() { _pickImageError = e; }); } }); } } @override void deactivate() { if (_controller != null) { _controller!.setVolume(0.0); _controller!.pause(); } super.deactivate(); } @override void dispose() { _disposeVideoController(); maxWidthController.dispose(); maxHeightController.dispose(); qualityController.dispose(); super.dispose(); } Future _disposeVideoController() async { if (_toBeDisposed != null) { await _toBeDisposed!.dispose(); } _toBeDisposed = _controller; _controller = null; } Widget _previewVideo() { final Text? retrieveError = _getRetrieveErrorWidget(); if (retrieveError != null) { return retrieveError; } if (_controller == null) { return const Text( 'You have not yet picked a video', textAlign: TextAlign.center, ); } return Padding( padding: const EdgeInsets.all(10.0), child: AspectRatioVideo(_controller), ); } Widget _previewImages() { final Text? retrieveError = _getRetrieveErrorWidget(); if (retrieveError != null) { return retrieveError; } if (_imageFileList != null) { return Semantics( label: 'image_picker_example_picked_images', child: ListView.builder( key: UniqueKey(), itemBuilder: (BuildContext context, int index) { // Why network for web? // See https://pub.dev/packages/image_picker#getting-ready-for-the-web-platform return Semantics( label: 'image_picker_example_picked_image', child: kIsWeb ? Image.network(_imageFileList![index].path) : Image.file(File(_imageFileList![index].path)), ); }, itemCount: _imageFileList!.length, ), ); } else if (_pickImageError != null) { return Text( 'Pick image error: $_pickImageError', textAlign: TextAlign.center, ); } else { return const Text( 'You have not yet picked an image.', textAlign: TextAlign.center, ); } } Widget _handlePreview() { if (isVideo) { return _previewVideo(); } else { return _previewImages(); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title!), ), body: Center( child: _handlePreview(), ), floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ Semantics( label: 'image_picker_example_from_gallery', child: FloatingActionButton( onPressed: () { isVideo = false; _onImageButtonPressed(ImageSource.gallery, context: context); }, heroTag: 'image0', tooltip: 'Pick Image from gallery', child: const Icon(Icons.photo), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( onPressed: () { isVideo = false; _onImageButtonPressed( ImageSource.gallery, context: context, isMultiImage: true, ); }, heroTag: 'image1', tooltip: 'Pick Multiple Image from gallery', child: const Icon(Icons.photo_library), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( onPressed: () { isVideo = false; _onImageButtonPressed(ImageSource.camera, context: context); }, heroTag: 'image2', tooltip: 'Take a Photo', child: const Icon(Icons.camera_alt), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( backgroundColor: Colors.red, onPressed: () { isVideo = true; _onImageButtonPressed(ImageSource.gallery); }, heroTag: 'video0', tooltip: 'Pick Video from gallery', child: const Icon(Icons.video_library), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( backgroundColor: Colors.red, onPressed: () { isVideo = true; _onImageButtonPressed(ImageSource.camera); }, heroTag: 'video1', tooltip: 'Take a Video', child: const Icon(Icons.videocam), ), ), ], ), ); } Text? _getRetrieveErrorWidget() { if (_retrieveDataError != null) { final Text result = Text(_retrieveDataError!); _retrieveDataError = null; return result; } return null; } Future _displayPickImageDialog( BuildContext context, OnPickImageCallback onPick) async { return showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Add optional parameters'), content: Column( children: [ TextField( controller: maxWidthController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration( hintText: 'Enter maxWidth if desired'), ), TextField( controller: maxHeightController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration( hintText: 'Enter maxHeight if desired'), ), TextField( controller: qualityController, keyboardType: TextInputType.number, decoration: const InputDecoration( hintText: 'Enter quality if desired'), ), ], ), actions: [ TextButton( child: const Text('CANCEL'), onPressed: () { Navigator.of(context).pop(); }, ), TextButton( child: const Text('PICK'), onPressed: () { final double? width = maxWidthController.text.isNotEmpty ? double.parse(maxWidthController.text) : null; final double? height = maxHeightController.text.isNotEmpty ? double.parse(maxHeightController.text) : null; final int? quality = qualityController.text.isNotEmpty ? int.parse(qualityController.text) : null; onPick(width, height, quality); Navigator.of(context).pop(); }), ], ); }); } } typedef OnPickImageCallback = void Function( double? maxWidth, double? maxHeight, int? quality); class AspectRatioVideo extends StatefulWidget { const AspectRatioVideo(this.controller, {Key? key}) : super(key: key); final VideoPlayerController? controller; @override AspectRatioVideoState createState() => AspectRatioVideoState(); } class AspectRatioVideoState extends State { VideoPlayerController? get controller => widget.controller; bool initialized = false; void _onVideoControllerUpdate() { if (!mounted) { return; } if (initialized != controller!.value.isInitialized) { initialized = controller!.value.isInitialized; setState(() {}); } } @override void initState() { super.initState(); controller!.addListener(_onVideoControllerUpdate); } @override void dispose() { controller!.removeListener(_onVideoControllerUpdate); super.dispose(); } @override Widget build(BuildContext context) { if (initialized) { return Center( child: AspectRatio( aspectRatio: controller!.value.aspectRatio, child: VideoPlayer(controller!), ), ); } else { return Container(); } } } ================================================ FILE: packages/image_picker/image_picker_ios/example/pubspec.yaml ================================================ name: image_picker_example description: Demonstrates how to use the image_picker plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter image_picker_ios: # When depending on this package from a real application you should use: # image_picker_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: ../ image_picker_platform_interface: ^2.6.1 video_player: ^2.1.4 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/image_picker/image_picker_ios/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/image_picker/image_picker_ios/ios/Assets/.gitkeep ================================================ ================================================ FILE: packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import #import NS_ASSUME_NONNULL_BEGIN @interface GIFInfo : NSObject @property(strong, nonatomic, readonly) NSArray *images; @property(assign, nonatomic, readonly) NSTimeInterval interval; - (instancetype)initWithImages:(NSArray *)images interval:(NSTimeInterval)interval; @end @interface FLTImagePickerImageUtil : NSObject // Resizes the given image to fit within maxWidth (if non-nil) and maxHeight (if non-nil) + (UIImage *)scaledImage:(UIImage *)image maxWidth:(nullable NSNumber *)maxWidth maxHeight:(nullable NSNumber *)maxHeight isMetadataAvailable:(BOOL)isMetadataAvailable; // Resize all gif animation frames. + (GIFInfo *)scaledGIFImage:(NSData *)data maxWidth:(NSNumber *)maxWidth maxHeight:(NSNumber *)maxHeight; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerImageUtil.m ================================================ // Copyright 2013 The Flutter 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 "FLTImagePickerImageUtil.h" #import @interface GIFInfo () @property(strong, nonatomic, readwrite) NSArray *images; @property(assign, nonatomic, readwrite) NSTimeInterval interval; @end @implementation GIFInfo - (instancetype)initWithImages:(NSArray *)images interval:(NSTimeInterval)interval; { self = [super init]; if (self) { self.images = images; self.interval = interval; } return self; } @end @implementation FLTImagePickerImageUtil : NSObject + (UIImage *)scaledImage:(UIImage *)image maxWidth:(NSNumber *)maxWidth maxHeight:(NSNumber *)maxHeight isMetadataAvailable:(BOOL)isMetadataAvailable { double originalWidth = image.size.width; double originalHeight = image.size.height; bool hasMaxWidth = maxWidth != nil; bool hasMaxHeight = maxHeight != nil; double width = hasMaxWidth ? MIN([maxWidth doubleValue], originalWidth) : originalWidth; double height = hasMaxHeight ? MIN([maxHeight doubleValue], originalHeight) : originalHeight; bool shouldDownscaleWidth = hasMaxWidth && [maxWidth doubleValue] < originalWidth; bool shouldDownscaleHeight = hasMaxHeight && [maxHeight doubleValue] < originalHeight; bool shouldDownscale = shouldDownscaleWidth || shouldDownscaleHeight; if (shouldDownscale) { double downscaledWidth = floor((height / originalHeight) * originalWidth); double downscaledHeight = floor((width / originalWidth) * originalHeight); if (width < height) { if (!hasMaxWidth) { width = downscaledWidth; } else { height = downscaledHeight; } } else if (height < width) { if (!hasMaxHeight) { height = downscaledHeight; } else { width = downscaledWidth; } } else { if (originalWidth < originalHeight) { width = downscaledWidth; } else if (originalHeight < originalWidth) { height = downscaledHeight; } } } if (!isMetadataAvailable) { UIImage *imageToScale = [UIImage imageWithCGImage:image.CGImage scale:1 orientation:image.imageOrientation]; UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 1.0); [imageToScale drawInRect:CGRectMake(0, 0, width, height)]; UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return scaledImage; } // Scaling the image always rotate itself based on the current imageOrientation of the original // Image. Set to orientationUp for the orignal image before scaling, so the scaled image doesn't // mess up with the pixels. UIImage *imageToScale = [UIImage imageWithCGImage:image.CGImage scale:1 orientation:UIImageOrientationUp]; // The image orientation is manually set to UIImageOrientationUp which swapped the aspect ratio in // some scenarios. For example, when the original image has orientation left, the horizontal // pixels should be scaled to `width` and the vertical pixels should be scaled to `height`. After // setting the orientation to up, we end up scaling the horizontal pixels to `height` and vertical // to `width`. Below swap will solve this issue. if ([image imageOrientation] == UIImageOrientationLeft || [image imageOrientation] == UIImageOrientationRight || [image imageOrientation] == UIImageOrientationLeftMirrored || [image imageOrientation] == UIImageOrientationRightMirrored) { double temp = width; width = height; height = temp; } UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 1.0); [imageToScale drawInRect:CGRectMake(0, 0, width, height)]; UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return scaledImage; } + (GIFInfo *)scaledGIFImage:(NSData *)data maxWidth:(NSNumber *)maxWidth maxHeight:(NSNumber *)maxHeight { NSMutableDictionary *options = [NSMutableDictionary dictionary]; options[(NSString *)kCGImageSourceShouldCache] = @YES; options[(NSString *)kCGImageSourceTypeIdentifierHint] = (NSString *)kUTTypeGIF; CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, (__bridge CFDictionaryRef)options); size_t numberOfFrames = CGImageSourceGetCount(imageSource); NSMutableArray *images = [NSMutableArray arrayWithCapacity:numberOfFrames]; NSTimeInterval interval = 0.0; for (size_t index = 0; index < numberOfFrames; index++) { CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, index, (__bridge CFDictionaryRef)options); NSDictionary *properties = (NSDictionary *)CFBridgingRelease( CGImageSourceCopyPropertiesAtIndex(imageSource, index, NULL)); NSDictionary *gifProperties = properties[(NSString *)kCGImagePropertyGIFDictionary]; NSNumber *delay = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime]; if (delay == nil) { delay = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime]; } if (interval == 0.0) { interval = [delay doubleValue]; } UIImage *image = [UIImage imageWithCGImage:imageRef scale:1.0 orientation:UIImageOrientationUp]; image = [self scaledImage:image maxWidth:maxWidth maxHeight:maxHeight isMetadataAvailable:YES]; [images addObject:image]; CGImageRelease(imageRef); } CFRelease(imageSource); GIFInfo *info = [[GIFInfo alloc] initWithImages:images interval:interval]; return info; } @end ================================================ FILE: packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerMetaDataUtil.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import #import NS_ASSUME_NONNULL_BEGIN typedef enum : NSUInteger { FLTImagePickerMIMETypePNG, FLTImagePickerMIMETypeJPEG, FLTImagePickerMIMETypeGIF, FLTImagePickerMIMETypeOther, } FLTImagePickerMIMEType; extern NSString *const kFLTImagePickerDefaultSuffix; extern const FLTImagePickerMIMEType kFLTImagePickerMIMETypeDefault; @interface FLTImagePickerMetaDataUtil : NSObject // Retrieve MIME type by reading the image data. We currently only support some popular types. + (FLTImagePickerMIMEType)getImageMIMETypeFromImageData:(NSData *)imageData; // Get corresponding surfix from type. + (nullable NSString *)imageTypeSuffixFromType:(FLTImagePickerMIMEType)type; + (NSDictionary *)getMetaDataFromImageData:(NSData *)imageData; // Creates and returns data for a new image based on imageData, but with the // given metadata. // // If creating a new image fails, returns nil. + (nullable NSData *)imageFromImage:(NSData *)imageData withMetaData:(NSDictionary *)metadata; // Converting UIImage to a NSData with the type proveide. // // The quality is for JPEG type only, it defaults to 1. It throws exception if setting a non-nil // quality with type other than FLTImagePickerMIMETypeJPEG. Converting UIImage to // FLTImagePickerMIMETypeGIF or FLTImagePickerMIMETypeTIFF is not supported in iOS. This // method throws exception if trying to do so. + (nonnull NSData *)convertImage:(nonnull UIImage *)image usingType:(FLTImagePickerMIMEType)type quality:(nullable NSNumber *)quality; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerMetaDataUtil.m ================================================ // Copyright 2013 The Flutter 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 "FLTImagePickerMetaDataUtil.h" #import static const uint8_t kFirstByteJPEG = 0xFF; static const uint8_t kFirstBytePNG = 0x89; static const uint8_t kFirstByteGIF = 0x47; NSString *const kFLTImagePickerDefaultSuffix = @".jpg"; const FLTImagePickerMIMEType kFLTImagePickerMIMETypeDefault = FLTImagePickerMIMETypeJPEG; @implementation FLTImagePickerMetaDataUtil + (FLTImagePickerMIMEType)getImageMIMETypeFromImageData:(NSData *)imageData { uint8_t firstByte; [imageData getBytes:&firstByte length:1]; switch (firstByte) { case kFirstByteJPEG: return FLTImagePickerMIMETypeJPEG; case kFirstBytePNG: return FLTImagePickerMIMETypePNG; case kFirstByteGIF: return FLTImagePickerMIMETypeGIF; } return FLTImagePickerMIMETypeOther; } + (NSString *)imageTypeSuffixFromType:(FLTImagePickerMIMEType)type { switch (type) { case FLTImagePickerMIMETypeJPEG: return @".jpg"; case FLTImagePickerMIMETypePNG: return @".png"; case FLTImagePickerMIMETypeGIF: return @".gif"; default: return nil; } } + (NSDictionary *)getMetaDataFromImageData:(NSData *)imageData { CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL); NSDictionary *metadata = (NSDictionary *)CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(source, 0, NULL)); CFRelease(source); return metadata; } + (NSData *)imageFromImage:(NSData *)imageData withMetaData:(NSDictionary *)metadata { NSMutableData *targetData = [NSMutableData data]; CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL); if (source == NULL) { return nil; } CGImageDestinationRef destination = NULL; CFStringRef sourceType = CGImageSourceGetType(source); if (sourceType != NULL) { destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)targetData, sourceType, 1, nil); } if (destination == NULL) { CFRelease(source); return nil; } CGImageDestinationAddImageFromSource(destination, source, 0, (__bridge CFDictionaryRef)metadata); CGImageDestinationFinalize(destination); CFRelease(source); CFRelease(destination); return targetData; } + (NSData *)convertImage:(UIImage *)image usingType:(FLTImagePickerMIMEType)type quality:(nullable NSNumber *)quality { if (quality && type != FLTImagePickerMIMETypeJPEG) { NSLog(@"image_picker: compressing is not supported for type %@. Returning the image with " @"original quality", [FLTImagePickerMetaDataUtil imageTypeSuffixFromType:type]); } switch (type) { case FLTImagePickerMIMETypeJPEG: { CGFloat qualityFloat = (quality != nil) ? quality.floatValue : 1; return UIImageJPEGRepresentation(image, qualityFloat); } case FLTImagePickerMIMETypePNG: return UIImagePNGRepresentation(image); default: { // converts to JPEG by default. CGFloat qualityFloat = (quality != nil) ? quality.floatValue : 1; return UIImageJPEGRepresentation(image, qualityFloat); } } } @end ================================================ FILE: packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPhotoAssetUtil.h ================================================ // Copyright 2013 The Flutter 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 #import "FLTImagePickerImageUtil.h" NS_ASSUME_NONNULL_BEGIN @interface FLTImagePickerPhotoAssetUtil : NSObject + (nullable PHAsset *)getAssetFromImagePickerInfo:(NSDictionary *)info; + (nullable PHAsset *)getAssetFromPHPickerResult:(PHPickerResult *)result API_AVAILABLE(ios(14)); // Save image with correct meta data and extention copied from the original asset. // maxWidth and maxHeight are used only for GIF images. + (NSString *)saveImageWithOriginalImageData:(NSData *)originalImageData image:(UIImage *)image maxWidth:(nullable NSNumber *)maxWidth maxHeight:(nullable NSNumber *)maxHeight imageQuality:(nullable NSNumber *)imageQuality; // Save image with correct meta data and extention copied from image picker result info. + (NSString *)saveImageWithPickerInfo:(nullable NSDictionary *)info image:(UIImage *)image imageQuality:(nullable NSNumber *)imageQuality; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPhotoAssetUtil.m ================================================ // Copyright 2013 The Flutter 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 "FLTImagePickerPhotoAssetUtil.h" #import "FLTImagePickerImageUtil.h" #import "FLTImagePickerMetaDataUtil.h" #import @implementation FLTImagePickerPhotoAssetUtil + (PHAsset *)getAssetFromImagePickerInfo:(NSDictionary *)info { if (@available(iOS 11, *)) { return [info objectForKey:UIImagePickerControllerPHAsset]; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" NSURL *referenceURL = [info objectForKey:UIImagePickerControllerReferenceURL]; if (!referenceURL) { return nil; } PHFetchResult *result = [PHAsset fetchAssetsWithALAssetURLs:@[ referenceURL ] options:nil]; return result.firstObject; #pragma clang diagnostic pop } + (PHAsset *)getAssetFromPHPickerResult:(PHPickerResult *)result API_AVAILABLE(ios(14)) { PHFetchResult *fetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[ result.assetIdentifier ] options:nil]; return fetchResult.firstObject; } + (NSString *)saveImageWithOriginalImageData:(NSData *)originalImageData image:(UIImage *)image maxWidth:(NSNumber *)maxWidth maxHeight:(NSNumber *)maxHeight imageQuality:(NSNumber *)imageQuality { NSString *suffix = kFLTImagePickerDefaultSuffix; FLTImagePickerMIMEType type = kFLTImagePickerMIMETypeDefault; NSDictionary *metaData = nil; // Getting the image type from the original image data if necessary. if (originalImageData) { type = [FLTImagePickerMetaDataUtil getImageMIMETypeFromImageData:originalImageData]; suffix = [FLTImagePickerMetaDataUtil imageTypeSuffixFromType:type] ?: kFLTImagePickerDefaultSuffix; metaData = [FLTImagePickerMetaDataUtil getMetaDataFromImageData:originalImageData]; } if (type == FLTImagePickerMIMETypeGIF) { GIFInfo *gifInfo = [FLTImagePickerImageUtil scaledGIFImage:originalImageData maxWidth:maxWidth maxHeight:maxHeight]; return [self saveImageWithMetaData:metaData gifInfo:gifInfo suffix:suffix]; } else { return [self saveImageWithMetaData:metaData image:image suffix:suffix type:type imageQuality:imageQuality]; } } + (NSString *)saveImageWithPickerInfo:(nullable NSDictionary *)info image:(UIImage *)image imageQuality:(NSNumber *)imageQuality { NSDictionary *metaData = info[UIImagePickerControllerMediaMetadata]; return [self saveImageWithMetaData:metaData image:image suffix:kFLTImagePickerDefaultSuffix type:kFLTImagePickerMIMETypeDefault imageQuality:imageQuality]; } + (NSString *)saveImageWithMetaData:(NSDictionary *)metaData gifInfo:(GIFInfo *)gifInfo suffix:(NSString *)suffix { NSString *path = [self temporaryFilePath:suffix]; return [self saveImageWithMetaData:metaData gifInfo:gifInfo path:path]; } + (NSString *)saveImageWithMetaData:(NSDictionary *)metaData image:(UIImage *)image suffix:(NSString *)suffix type:(FLTImagePickerMIMEType)type imageQuality:(NSNumber *)imageQuality { NSData *data = [FLTImagePickerMetaDataUtil convertImage:image usingType:type quality:imageQuality]; if (metaData) { NSData *updatedData = [FLTImagePickerMetaDataUtil imageFromImage:data withMetaData:metaData]; // If updating the metadata fails, just save the original. if (updatedData) { data = updatedData; } } return [self createFile:data suffix:suffix]; } + (NSString *)saveImageWithMetaData:(NSDictionary *)metaData gifInfo:(GIFInfo *)gifInfo path:(NSString *)path { CGImageDestinationRef destination = CGImageDestinationCreateWithURL( (__bridge CFURLRef)[NSURL fileURLWithPath:path], kUTTypeGIF, gifInfo.images.count, NULL); NSDictionary *frameProperties = @{ (__bridge NSString *)kCGImagePropertyGIFDictionary : @{ (__bridge NSString *)kCGImagePropertyGIFDelayTime : @(gifInfo.interval), }, }; NSMutableDictionary *gifMetaProperties = [NSMutableDictionary dictionaryWithDictionary:metaData]; NSMutableDictionary *gifProperties = (NSMutableDictionary *)gifMetaProperties[(NSString *)kCGImagePropertyGIFDictionary]; if (gifMetaProperties == nil) { gifProperties = [NSMutableDictionary dictionary]; } gifProperties[(__bridge NSString *)kCGImagePropertyGIFLoopCount] = @0; CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)gifMetaProperties); for (NSInteger index = 0; index < gifInfo.images.count; index++) { UIImage *image = (UIImage *)[gifInfo.images objectAtIndex:index]; CGImageDestinationAddImage(destination, image.CGImage, (__bridge CFDictionaryRef)frameProperties); } CGImageDestinationFinalize(destination); CFRelease(destination); return path; } + (NSString *)temporaryFilePath:(NSString *)suffix { NSString *fileExtension = [@"image_picker_%@" stringByAppendingString:suffix]; NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString]; NSString *tmpFile = [NSString stringWithFormat:fileExtension, guid]; NSString *tmpDirectory = NSTemporaryDirectory(); NSString *tmpPath = [tmpDirectory stringByAppendingPathComponent:tmpFile]; return tmpPath; } + (NSString *)createFile:(NSData *)data suffix:(NSString *)suffix { NSString *tmpPath = [self temporaryFilePath:suffix]; if ([[NSFileManager defaultManager] createFileAtPath:tmpPath contents:data attributes:nil]) { return tmpPath; } else { nil; } return tmpPath; } @end ================================================ FILE: packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import #import NS_ASSUME_NONNULL_BEGIN @interface FLTImagePickerPlugin : NSObject @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m ================================================ // Copyright 2013 The Flutter 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 "FLTImagePickerPlugin.h" #import "FLTImagePickerPlugin_Test.h" #import #import #import #import #import #import #import "FLTImagePickerImageUtil.h" #import "FLTImagePickerMetaDataUtil.h" #import "FLTImagePickerPhotoAssetUtil.h" #import "FLTPHPickerSaveImageToPathOperation.h" #import "messages.g.h" @implementation FLTImagePickerMethodCallContext - (instancetype)initWithResult:(nonnull FlutterResultAdapter)result { if (self = [super init]) { _result = [result copy]; } return self; } @end #pragma mark - @interface FLTImagePickerPlugin () /** * The PHPickerViewController instance used to pick multiple * images. */ @property(strong, nonatomic) PHPickerViewController *pickerViewController API_AVAILABLE(ios(14)); /** * The UIImagePickerController instances that will be used when a new * controller would normally be created. Each call to * createImagePickerController will remove the current first element from * the array. */ @property(strong, nonatomic) NSMutableArray *imagePickerControllerOverrides; @end typedef NS_ENUM(NSInteger, ImagePickerClassType) { UIImagePickerClassType, PHPickerClassType }; @implementation FLTImagePickerPlugin + (void)registerWithRegistrar:(NSObject *)registrar { FLTImagePickerPlugin *instance = [[FLTImagePickerPlugin alloc] init]; FLTImagePickerApiSetup(registrar.messenger, instance); } - (UIImagePickerController *)createImagePickerController { if ([self.imagePickerControllerOverrides count] > 0) { UIImagePickerController *controller = [self.imagePickerControllerOverrides firstObject]; [self.imagePickerControllerOverrides removeObjectAtIndex:0]; return controller; } return [[UIImagePickerController alloc] init]; } - (void)setImagePickerControllerOverrides: (NSArray *)imagePickerControllers { _imagePickerControllerOverrides = [imagePickerControllers mutableCopy]; } - (UIViewController *)viewControllerWithWindow:(UIWindow *)window { UIWindow *windowToUse = window; if (windowToUse == nil) { for (UIWindow *window in [UIApplication sharedApplication].windows) { if (window.isKeyWindow) { windowToUse = window; break; } } } UIViewController *topController = windowToUse.rootViewController; while (topController.presentedViewController) { topController = topController.presentedViewController; } return topController; } /** * Returns the UIImagePickerControllerCameraDevice to use given [source]. * * @param source The source specification from Dart. */ - (UIImagePickerControllerCameraDevice)cameraDeviceForSource:(FLTSourceSpecification *)source { switch (source.camera) { case FLTSourceCameraFront: return UIImagePickerControllerCameraDeviceFront; case FLTSourceCameraRear: return UIImagePickerControllerCameraDeviceRear; } } - (void)launchPHPickerWithContext:(nonnull FLTImagePickerMethodCallContext *)context API_AVAILABLE(ios(14)) { PHPickerConfiguration *config = [[PHPickerConfiguration alloc] initWithPhotoLibrary:PHPhotoLibrary.sharedPhotoLibrary]; config.selectionLimit = context.maxImageCount; config.filter = [PHPickerFilter imagesFilter]; _pickerViewController = [[PHPickerViewController alloc] initWithConfiguration:config]; _pickerViewController.delegate = self; _pickerViewController.presentationController.delegate = self; self.callContext = context; if (context.requestFullMetadata) { [self checkPhotoAuthorizationForAccessLevel]; } else { [self showPhotoLibraryWithPHPicker:_pickerViewController]; } } - (void)launchUIImagePickerWithSource:(nonnull FLTSourceSpecification *)source context:(nonnull FLTImagePickerMethodCallContext *)context { UIImagePickerController *imagePickerController = [self createImagePickerController]; imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; imagePickerController.delegate = self; imagePickerController.mediaTypes = @[ (NSString *)kUTTypeImage ]; self.callContext = context; switch (source.type) { case FLTSourceTypeCamera: [self checkCameraAuthorizationWithImagePicker:imagePickerController camera:[self cameraDeviceForSource:source]]; break; case FLTSourceTypeGallery: if (@available(iOS 11, *)) { if (context.requestFullMetadata) { [self checkPhotoAuthorizationWithImagePicker:imagePickerController]; } else { [self showPhotoLibraryWithImagePicker:imagePickerController]; } } else { // Prior to iOS 11, accessing gallery requires authorization [self checkPhotoAuthorizationWithImagePicker:imagePickerController]; } break; default: [self sendCallResultWithError:[FlutterError errorWithCode:@"invalid_source" message:@"Invalid image source." details:nil]]; break; } } #pragma mark - FLTImagePickerApi - (void)pickImageWithSource:(nonnull FLTSourceSpecification *)source maxSize:(nonnull FLTMaxSize *)maxSize quality:(nullable NSNumber *)imageQuality fullMetadata:(NSNumber *)fullMetadata completion: (nonnull void (^)(NSString *_Nullable, FlutterError *_Nullable))completion { [self cancelInProgressCall]; FLTImagePickerMethodCallContext *context = [[FLTImagePickerMethodCallContext alloc] initWithResult:^void(NSArray *paths, FlutterError *error) { if (paths && paths.count != 1) { completion(nil, [FlutterError errorWithCode:@"invalid_result" message:@"Incorrect number of return paths provided" details:nil]); } completion(paths.firstObject, error); }]; context.maxSize = maxSize; context.imageQuality = imageQuality; context.maxImageCount = 1; context.requestFullMetadata = [fullMetadata boolValue]; if (source.type == FLTSourceTypeGallery) { // Capture is not possible with PHPicker if (@available(iOS 14, *)) { [self launchPHPickerWithContext:context]; } else { [self launchUIImagePickerWithSource:source context:context]; } } else { [self launchUIImagePickerWithSource:source context:context]; } } - (void)pickMultiImageWithMaxSize:(nonnull FLTMaxSize *)maxSize quality:(nullable NSNumber *)imageQuality fullMetadata:(NSNumber *)fullMetadata completion:(nonnull void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { FLTImagePickerMethodCallContext *context = [[FLTImagePickerMethodCallContext alloc] initWithResult:completion]; context.maxSize = maxSize; context.imageQuality = imageQuality; context.requestFullMetadata = [fullMetadata boolValue]; if (@available(iOS 14, *)) { [self launchPHPickerWithContext:context]; } else { // Camera is ignored for gallery mode, so the value here is arbitrary. [self launchUIImagePickerWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeGallery camera:FLTSourceCameraRear] context:context]; } } - (void)pickVideoWithSource:(nonnull FLTSourceSpecification *)source maxDuration:(nullable NSNumber *)maxDurationSeconds completion: (nonnull void (^)(NSString *_Nullable, FlutterError *_Nullable))completion { FLTImagePickerMethodCallContext *context = [[FLTImagePickerMethodCallContext alloc] initWithResult:^void(NSArray *paths, FlutterError *error) { if (paths && paths.count != 1) { completion(nil, [FlutterError errorWithCode:@"invalid_result" message:@"Incorrect number of return paths provided" details:nil]); } completion(paths.firstObject, error); }]; context.maxImageCount = 1; UIImagePickerController *imagePickerController = [self createImagePickerController]; imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; imagePickerController.delegate = self; imagePickerController.mediaTypes = @[ (NSString *)kUTTypeMovie, (NSString *)kUTTypeAVIMovie, (NSString *)kUTTypeVideo, (NSString *)kUTTypeMPEG4 ]; imagePickerController.videoQuality = UIImagePickerControllerQualityTypeHigh; if (maxDurationSeconds) { NSTimeInterval max = [maxDurationSeconds doubleValue]; imagePickerController.videoMaximumDuration = max; } self.callContext = context; switch (source.type) { case FLTSourceTypeCamera: [self checkCameraAuthorizationWithImagePicker:imagePickerController camera:[self cameraDeviceForSource:source]]; break; case FLTSourceTypeGallery: [self checkPhotoAuthorizationWithImagePicker:imagePickerController]; break; default: [self sendCallResultWithError:[FlutterError errorWithCode:@"invalid_source" message:@"Invalid video source." details:nil]]; break; } } #pragma mark - /** * If a call is still in progress, cancels it by returning an error and then clearing state. * * TODO(stuartmorgan): Eliminate this, and instead track context per image picker (e.g., using * associated objects). */ - (void)cancelInProgressCall { if (self.callContext) { [self sendCallResultWithError:[FlutterError errorWithCode:@"multiple_request" message:@"Cancelled by a second request" details:nil]]; self.callContext = nil; } } - (void)showCamera:(UIImagePickerControllerCameraDevice)device withImagePicker:(UIImagePickerController *)imagePickerController { @synchronized(self) { if (imagePickerController.beingPresented) { return; } } // Camera is not available on simulators if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera] && [UIImagePickerController isCameraDeviceAvailable:device]) { imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera; imagePickerController.cameraDevice = device; [[self viewControllerWithWindow:nil] presentViewController:imagePickerController animated:YES completion:nil]; } else { UIAlertController *cameraErrorAlert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Error", @"Alert title when camera unavailable") message:NSLocalizedString(@"Camera not available.", "Alert message when camera unavailable") preferredStyle:UIAlertControllerStyleAlert]; [cameraErrorAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedString( @"OK", @"Alert button when camera unavailable") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){ }]]; [[self viewControllerWithWindow:nil] presentViewController:cameraErrorAlert animated:YES completion:nil]; [self sendCallResultWithSavedPathList:nil]; } } - (void)checkCameraAuthorizationWithImagePicker:(UIImagePickerController *)imagePickerController camera:(UIImagePickerControllerCameraDevice)device { AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; switch (status) { case AVAuthorizationStatusAuthorized: [self showCamera:device withImagePicker:imagePickerController]; break; case AVAuthorizationStatusNotDetermined: { [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { dispatch_async(dispatch_get_main_queue(), ^{ if (granted) { [self showCamera:device withImagePicker:imagePickerController]; } else { [self errorNoCameraAccess:AVAuthorizationStatusDenied]; } }); }]; break; } case AVAuthorizationStatusDenied: case AVAuthorizationStatusRestricted: default: [self errorNoCameraAccess:status]; break; } } - (void)checkPhotoAuthorizationWithImagePicker:(UIImagePickerController *)imagePickerController { PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus]; switch (status) { case PHAuthorizationStatusNotDetermined: { [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { dispatch_async(dispatch_get_main_queue(), ^{ if (status == PHAuthorizationStatusAuthorized) { [self showPhotoLibraryWithImagePicker:imagePickerController]; } else { [self errorNoPhotoAccess:status]; } }); }]; break; } case PHAuthorizationStatusAuthorized: [self showPhotoLibraryWithImagePicker:imagePickerController]; break; case PHAuthorizationStatusDenied: case PHAuthorizationStatusRestricted: default: [self errorNoPhotoAccess:status]; break; } } - (void)checkPhotoAuthorizationForAccessLevel API_AVAILABLE(ios(14)) { PHAccessLevel requestedAccessLevel = PHAccessLevelReadWrite; PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatusForAccessLevel:requestedAccessLevel]; switch (status) { case PHAuthorizationStatusNotDetermined: { [PHPhotoLibrary requestAuthorizationForAccessLevel:requestedAccessLevel handler:^(PHAuthorizationStatus status) { dispatch_async(dispatch_get_main_queue(), ^{ if (status == PHAuthorizationStatusAuthorized) { [self showPhotoLibraryWithPHPicker:self-> _pickerViewController]; } else if (status == PHAuthorizationStatusLimited) { [self showPhotoLibraryWithPHPicker:self-> _pickerViewController]; } else { [self errorNoPhotoAccess:status]; } }); }]; break; } case PHAuthorizationStatusAuthorized: case PHAuthorizationStatusLimited: [self showPhotoLibraryWithPHPicker:_pickerViewController]; break; case PHAuthorizationStatusDenied: case PHAuthorizationStatusRestricted: default: [self errorNoPhotoAccess:status]; break; } } - (void)errorNoCameraAccess:(AVAuthorizationStatus)status { switch (status) { case AVAuthorizationStatusRestricted: [self sendCallResultWithError:[FlutterError errorWithCode:@"camera_access_restricted" message:@"The user is not allowed to use the camera." details:nil]]; break; case AVAuthorizationStatusDenied: default: [self sendCallResultWithError:[FlutterError errorWithCode:@"camera_access_denied" message:@"The user did not allow camera access." details:nil]]; break; } } - (void)errorNoPhotoAccess:(PHAuthorizationStatus)status { switch (status) { case PHAuthorizationStatusRestricted: [self sendCallResultWithError:[FlutterError errorWithCode:@"photo_access_restricted" message:@"The user is not allowed to use the photo." details:nil]]; break; case PHAuthorizationStatusDenied: default: [self sendCallResultWithError:[FlutterError errorWithCode:@"photo_access_denied" message:@"The user did not allow photo access." details:nil]]; break; } } - (void)showPhotoLibraryWithPHPicker:(PHPickerViewController *)pickerViewController API_AVAILABLE(ios(14)) { [[self viewControllerWithWindow:nil] presentViewController:pickerViewController animated:YES completion:nil]; } - (void)showPhotoLibraryWithImagePicker:(UIImagePickerController *)imagePickerController { imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; [[self viewControllerWithWindow:nil] presentViewController:imagePickerController animated:YES completion:nil]; } - (NSNumber *)getDesiredImageQuality:(NSNumber *)imageQuality { if (![imageQuality isKindOfClass:[NSNumber class]]) { imageQuality = @1; } else if (imageQuality.intValue < 0 || imageQuality.intValue > 100) { imageQuality = @1; } else { imageQuality = @([imageQuality floatValue] / 100); } return imageQuality; } #pragma mark - UIAdaptivePresentationControllerDelegate - (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController { [self sendCallResultWithSavedPathList:nil]; } #pragma mark - PHPickerViewControllerDelegate - (void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray *)results API_AVAILABLE(ios(14)) { [picker dismissViewControllerAnimated:YES completion:nil]; if (results.count == 0) { [self sendCallResultWithSavedPathList:nil]; return; } __block NSOperationQueue *saveQueue = [[NSOperationQueue alloc] init]; saveQueue.name = @"Flutter Save Image Queue"; saveQueue.qualityOfService = NSQualityOfServiceUserInitiated; FLTImagePickerMethodCallContext *currentCallContext = self.callContext; NSNumber *maxWidth = currentCallContext.maxSize.width; NSNumber *maxHeight = currentCallContext.maxSize.height; NSNumber *imageQuality = currentCallContext.imageQuality; NSNumber *desiredImageQuality = [self getDesiredImageQuality:imageQuality]; BOOL requestFullMetadata = currentCallContext.requestFullMetadata; NSMutableArray *pathList = [[NSMutableArray alloc] initWithCapacity:results.count]; __block FlutterError *saveError = nil; __weak typeof(self) weakSelf = self; // This operation will be executed on the main queue after // all selected files have been saved. NSBlockOperation *sendListOperation = [NSBlockOperation blockOperationWithBlock:^{ if (saveError != nil) { [weakSelf sendCallResultWithError:saveError]; } else { [weakSelf sendCallResultWithSavedPathList:pathList]; } // Retain queue until here. saveQueue = nil; }]; [results enumerateObjectsUsingBlock:^(PHPickerResult *result, NSUInteger index, BOOL *stop) { // NSNull means it hasn't saved yet. [pathList addObject:[NSNull null]]; FLTPHPickerSaveImageToPathOperation *saveOperation = [[FLTPHPickerSaveImageToPathOperation alloc] initWithResult:result maxHeight:maxHeight maxWidth:maxWidth desiredImageQuality:desiredImageQuality fullMetadata:requestFullMetadata savedPathBlock:^(NSString *savedPath, FlutterError *error) { if (savedPath != nil) { pathList[index] = savedPath; } else { saveError = error; } }]; [sendListOperation addDependency:saveOperation]; [saveQueue addOperation:saveOperation]; }]; // Schedule the final Flutter callback on the main queue // to be run after all images have been saved. [NSOperationQueue.mainQueue addOperation:sendListOperation]; } #pragma mark - UIImagePickerControllerDelegate - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { NSURL *videoURL = info[UIImagePickerControllerMediaURL]; [picker dismissViewControllerAnimated:YES completion:nil]; // The method dismissViewControllerAnimated does not immediately prevent // further didFinishPickingMediaWithInfo invocations. A nil check is necessary // to prevent below code to be unwantly executed multiple times and cause a // crash. if (!self.callContext) { return; } if (videoURL != nil) { if (@available(iOS 13.0, *)) { NSString *fileName = [videoURL lastPathComponent]; NSURL *destination = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; if ([[NSFileManager defaultManager] isReadableFileAtPath:[videoURL path]]) { NSError *error; if (![[videoURL path] isEqualToString:[destination path]]) { [[NSFileManager defaultManager] copyItemAtURL:videoURL toURL:destination error:&error]; if (error) { [self sendCallResultWithError:[FlutterError errorWithCode:@"flutter_image_picker_copy_video_error" message:@"Could not cache the video file." details:nil]]; return; } } videoURL = destination; } } [self sendCallResultWithSavedPathList:@[ videoURL.path ]]; } else { UIImage *image = info[UIImagePickerControllerEditedImage]; if (image == nil) { image = info[UIImagePickerControllerOriginalImage]; } NSNumber *maxWidth = self.callContext.maxSize.width; NSNumber *maxHeight = self.callContext.maxSize.height; NSNumber *imageQuality = self.callContext.imageQuality; NSNumber *desiredImageQuality = [self getDesiredImageQuality:imageQuality]; PHAsset *originalAsset; if (_callContext.requestFullMetadata) { // Full metadata are available only in PHAsset, which requires gallery permission. originalAsset = [FLTImagePickerPhotoAssetUtil getAssetFromImagePickerInfo:info]; } if (maxWidth != nil || maxHeight != nil) { image = [FLTImagePickerImageUtil scaledImage:image maxWidth:maxWidth maxHeight:maxHeight isMetadataAvailable:YES]; } if (!originalAsset) { // Image picked without an original asset (e.g. User took a photo directly) [self saveImageWithPickerInfo:info image:image imageQuality:desiredImageQuality]; } else { void (^resultHandler)(NSData *imageData, NSString *dataUTI, NSDictionary *info) = ^( NSData *_Nullable imageData, NSString *_Nullable dataUTI, NSDictionary *_Nullable info) { // maxWidth and maxHeight are used only for GIF images. [self saveImageWithOriginalImageData:imageData image:image maxWidth:maxWidth maxHeight:maxHeight imageQuality:desiredImageQuality]; }; if (@available(iOS 13.0, *)) { [[PHImageManager defaultManager] requestImageDataAndOrientationForAsset:originalAsset options:nil resultHandler:^(NSData *_Nullable imageData, NSString *_Nullable dataUTI, CGImagePropertyOrientation orientation, NSDictionary *_Nullable info) { resultHandler(imageData, dataUTI, info); }]; } else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [[PHImageManager defaultManager] requestImageDataForAsset:originalAsset options:nil resultHandler:^(NSData *_Nullable imageData, NSString *_Nullable dataUTI, UIImageOrientation orientation, NSDictionary *_Nullable info) { resultHandler(imageData, dataUTI, info); }]; #pragma clang diagnostic pop } } } } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { [picker dismissViewControllerAnimated:YES completion:nil]; [self sendCallResultWithSavedPathList:nil]; } #pragma mark - - (void)saveImageWithOriginalImageData:(NSData *)originalImageData image:(UIImage *)image maxWidth:(NSNumber *)maxWidth maxHeight:(NSNumber *)maxHeight imageQuality:(NSNumber *)imageQuality { NSString *savedPath = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:originalImageData image:image maxWidth:maxWidth maxHeight:maxHeight imageQuality:imageQuality]; [self sendCallResultWithSavedPathList:@[ savedPath ]]; } - (void)saveImageWithPickerInfo:(NSDictionary *)info image:(UIImage *)image imageQuality:(NSNumber *)imageQuality { NSString *savedPath = [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:info image:image imageQuality:imageQuality]; [self sendCallResultWithSavedPathList:@[ savedPath ]]; } - (void)sendCallResultWithSavedPathList:(nullable NSArray *)pathList { if (!self.callContext) { return; } if ([pathList containsObject:[NSNull null]]) { self.callContext.result(nil, [FlutterError errorWithCode:@"create_error" message:@"pathList's items should not be null" details:nil]); } else { self.callContext.result(pathList, nil); } self.callContext = nil; } /** * Sends the given error via `callContext.result` as the result of the original platform channel * method call, clearing the in-progress call state. * * @param error The error to return. */ - (void)sendCallResultWithError:(FlutterError *)error { if (!self.callContext) { return; } self.callContext.result(nil, error); self.callContext = nil; } @end ================================================ FILE: packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This header is available in the Test module. Import via "@import image_picker_ios_ios.Test;" #import #import "messages.g.h" NS_ASSUME_NONNULL_BEGIN /** * The return hander used for all method calls, which internally adapts the provided result list * to return either a list or a single element depending on the original call. */ typedef void (^FlutterResultAdapter)(NSArray *_Nullable, FlutterError *_Nullable); /** * A container class for context to use when handling a method call from the Dart side. */ @interface FLTImagePickerMethodCallContext : NSObject /** * Initializes a new context that calls |result| on completion of the operation. */ - (instancetype)initWithResult:(nonnull FlutterResultAdapter)result; /** The callback to provide results to the Dart caller. */ @property(nonatomic, copy, nonnull) FlutterResultAdapter result; /** * The maximum size to enforce on the results. * * If nil, no resizing is done. */ @property(nonatomic, strong, nullable) FLTMaxSize *maxSize; /** * The image quality to resample the results to. * * If nil, no resampling is done. */ @property(nonatomic, strong, nullable) NSNumber *imageQuality; /** Maximum number of images to select. 0 indicates no maximum. */ @property(nonatomic, assign) int maxImageCount; /** Whether the image should be picked with full metadata (requires gallery permissions) */ @property(nonatomic, assign) BOOL requestFullMetadata; @end #pragma mark - /** Methods exposed for unit testing. */ @interface FLTImagePickerPlugin () /** * The context of the Flutter method call that is currently being handled, if any. */ @property(strong, nonatomic, nullable) FLTImagePickerMethodCallContext *callContext; - (UIViewController *)viewControllerWithWindow:(nullable UIWindow *)window; /** * Validates the provided paths list, then sends it via `callContext.result` as the result of the * original platform channel method call, clearing the in-progress call state. * * @param pathList The paths to return. nil indicates a cancelled operation. */ - (void)sendCallResultWithSavedPathList:(nullable NSArray *)pathList; /** * Tells the delegate that the user cancelled the pick operation. * * Your delegate’s implementation of this method should dismiss the picker view * by calling the dismissModalViewControllerAnimated: method of the parent * view controller. * * Implementation of this method is optional, but expected. * * @param picker The controller object managing the image picker interface. */ - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; /** * Sets UIImagePickerController instances that will be used when a new * controller would normally be created. Each call to * createImagePickerController will remove the current first element from * the array. * * Should be used for testing purposes only. */ - (void)setImagePickerControllerOverrides: (NSArray *)imagePickerControllers; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.h ================================================ // Copyright 2013 The Flutter 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 "FLTImagePickerImageUtil.h" #import "FLTImagePickerMetaDataUtil.h" #import "FLTImagePickerPhotoAssetUtil.h" NS_ASSUME_NONNULL_BEGIN /// Returns either the saved path, or an error. Both cannot be set. typedef void (^FLTGetSavedPath)(NSString *_Nullable savedPath, FlutterError *_Nullable error); /*! @class FLTPHPickerSaveImageToPathOperation @brief The FLTPHPickerSaveImageToPathOperation class @discussion This class was implemented to handle saved image paths and populate the pathList with the final result by using GetSavedPath type block. @superclass SuperClass: NSOperation\n @helps It helps FLTImagePickerPlugin class. */ @interface FLTPHPickerSaveImageToPathOperation : NSOperation - (instancetype)initWithResult:(PHPickerResult *)result maxHeight:(NSNumber *)maxHeight maxWidth:(NSNumber *)maxWidth desiredImageQuality:(NSNumber *)desiredImageQuality fullMetadata:(BOOL)fullMetadata savedPathBlock:(FLTGetSavedPath)savedPathBlock API_AVAILABLE(ios(14)); @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/image_picker/image_picker_ios/ios/Classes/FLTPHPickerSaveImageToPathOperation.m ================================================ // Copyright 2013 The Flutter 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 "FLTPHPickerSaveImageToPathOperation.h" #import API_AVAILABLE(ios(14)) @interface FLTPHPickerSaveImageToPathOperation () @property(strong, nonatomic) PHPickerResult *result; @property(strong, nonatomic) NSNumber *maxHeight; @property(strong, nonatomic) NSNumber *maxWidth; @property(strong, nonatomic) NSNumber *desiredImageQuality; @property(assign, nonatomic) BOOL requestFullMetadata; @end @implementation FLTPHPickerSaveImageToPathOperation { BOOL executing; BOOL finished; FLTGetSavedPath getSavedPath; } - (instancetype)initWithResult:(PHPickerResult *)result maxHeight:(NSNumber *)maxHeight maxWidth:(NSNumber *)maxWidth desiredImageQuality:(NSNumber *)desiredImageQuality fullMetadata:(BOOL)fullMetadata savedPathBlock:(FLTGetSavedPath)savedPathBlock API_AVAILABLE(ios(14)) { if (self = [super init]) { if (result) { self.result = result; self.maxHeight = maxHeight; self.maxWidth = maxWidth; self.desiredImageQuality = desiredImageQuality; self.requestFullMetadata = fullMetadata; getSavedPath = savedPathBlock; executing = NO; finished = NO; } else { return nil; } return self; } else { return nil; } } - (BOOL)isConcurrent { return YES; } - (BOOL)isExecuting { return executing; } - (BOOL)isFinished { return finished; } - (void)setFinished:(BOOL)isFinished { [self willChangeValueForKey:@"isFinished"]; self->finished = isFinished; [self didChangeValueForKey:@"isFinished"]; } - (void)setExecuting:(BOOL)isExecuting { [self willChangeValueForKey:@"isExecuting"]; self->executing = isExecuting; [self didChangeValueForKey:@"isExecuting"]; } - (void)completeOperationWithPath:(NSString *)savedPath error:(FlutterError *)error { getSavedPath(savedPath, error); [self setExecuting:NO]; [self setFinished:YES]; } - (void)start { if ([self isCancelled]) { [self setFinished:YES]; return; } if (@available(iOS 14, *)) { [self setExecuting:YES]; // This supports uniform types that conform to UTTypeImage. // This includes UTTypeHEIC, UTTypeHEIF, UTTypeLivePhoto, UTTypeICO, UTTypeICNS, UTTypePNG // UTTypeGIF, UTTypeJPEG, UTTypeWebP, UTTypeTIFF, UTTypeBMP, UTTypeSVG, UTTypeRAWImage if ([self.result.itemProvider hasItemConformingToTypeIdentifier:UTTypeImage.identifier]) { [self.result.itemProvider loadDataRepresentationForTypeIdentifier:UTTypeImage.identifier completionHandler:^(NSData *_Nullable data, NSError *_Nullable error) { if (data != nil) { [self processImage:data]; } else { FlutterError *flutterError = [FlutterError errorWithCode:@"invalid_image" message:error.localizedDescription details:error.domain]; [self completeOperationWithPath:nil error:flutterError]; } }]; } else { FlutterError *flutterError = [FlutterError errorWithCode:@"invalid_source" message:@"Invalid image source." details:nil]; [self completeOperationWithPath:nil error:flutterError]; } } else { [self setFinished:YES]; } } /** * Processes the image. */ - (void)processImage:(NSData *)pickerImageData API_AVAILABLE(ios(14)) { UIImage *localImage = [[UIImage alloc] initWithData:pickerImageData]; PHAsset *originalAsset; // Only if requested, fetch the full "PHAsset" metadata, which requires "Photo Library Usage" // permissions. if (self.requestFullMetadata) { originalAsset = [FLTImagePickerPhotoAssetUtil getAssetFromPHPickerResult:self.result]; } if (self.maxWidth != nil || self.maxHeight != nil) { localImage = [FLTImagePickerImageUtil scaledImage:localImage maxWidth:self.maxWidth maxHeight:self.maxHeight isMetadataAvailable:YES]; } if (originalAsset) { void (^resultHandler)(NSData *imageData, NSString *dataUTI, NSDictionary *info) = ^(NSData *_Nullable imageData, NSString *_Nullable dataUTI, NSDictionary *_Nullable info) { // maxWidth and maxHeight are used only for GIF images. NSString *savedPath = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:imageData image:localImage maxWidth:self.maxWidth maxHeight:self.maxHeight imageQuality:self.desiredImageQuality]; [self completeOperationWithPath:savedPath error:nil]; }; if (@available(iOS 13.0, *)) { [[PHImageManager defaultManager] requestImageDataAndOrientationForAsset:originalAsset options:nil resultHandler:^(NSData *_Nullable imageData, NSString *_Nullable dataUTI, CGImagePropertyOrientation orientation, NSDictionary *_Nullable info) { resultHandler(imageData, dataUTI, info); }]; } else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [[PHImageManager defaultManager] requestImageDataForAsset:originalAsset options:nil resultHandler:^(NSData *_Nullable imageData, NSString *_Nullable dataUTI, UIImageOrientation orientation, NSDictionary *_Nullable info) { resultHandler(imageData, dataUTI, info); }]; #pragma clang diagnostic pop } } else { // Image picked without an original asset (e.g. User pick image without permission) // maxWidth and maxHeight are used only for GIF images. NSString *savedPath = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:pickerImageData image:localImage maxWidth:self.maxWidth maxHeight:self.maxHeight imageQuality:self.desiredImageQuality]; [self completeOperationWithPath:savedPath error:nil]; } } @end ================================================ FILE: packages/image_picker/image_picker_ios/ios/Classes/ImagePickerPlugin.modulemap ================================================ framework module image_picker_ios { umbrella header "image_picker_ios-umbrella.h" export * module * { export * } explicit module Test { header "FLTImagePickerPlugin_Test.h" header "FLTImagePickerImageUtil.h" header "FLTImagePickerMetaDataUtil.h" header "FLTImagePickerPhotoAssetUtil.h" header "FLTPHPickerSaveImageToPathOperation.h" } } ================================================ FILE: packages/image_picker/image_picker_ios/ios/Classes/image_picker_ios-umbrella.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/image_picker/image_picker_ios/ios/Classes/messages.g.h ================================================ // Copyright 2013 The Flutter 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.0.3), do not edit directly. // See also: https://pub.dev/packages/pigeon #import @protocol FlutterBinaryMessenger; @protocol FlutterMessageCodec; @class FlutterError; @class FlutterStandardTypedData; NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSUInteger, FLTSourceCamera) { FLTSourceCameraRear = 0, FLTSourceCameraFront = 1, }; typedef NS_ENUM(NSUInteger, FLTSourceType) { FLTSourceTypeCamera = 0, FLTSourceTypeGallery = 1, }; @class FLTMaxSize; @class FLTSourceSpecification; @interface FLTMaxSize : NSObject + (instancetype)makeWithWidth:(nullable NSNumber *)width height:(nullable NSNumber *)height; @property(nonatomic, strong, nullable) NSNumber *width; @property(nonatomic, strong, nullable) NSNumber *height; @end @interface FLTSourceSpecification : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithType:(FLTSourceType)type camera:(FLTSourceCamera)camera; @property(nonatomic, assign) FLTSourceType type; @property(nonatomic, assign) FLTSourceCamera camera; @end /// The codec used by FLTImagePickerApi. NSObject *FLTImagePickerApiGetCodec(void); @protocol FLTImagePickerApi - (void)pickImageWithSource:(FLTSourceSpecification *)source maxSize:(FLTMaxSize *)maxSize quality:(nullable NSNumber *)imageQuality fullMetadata:(NSNumber *)requestFullMetadata completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; - (void)pickMultiImageWithMaxSize:(FLTMaxSize *)maxSize quality:(nullable NSNumber *)imageQuality fullMetadata:(NSNumber *)requestFullMetadata completion:(void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion; - (void)pickVideoWithSource:(FLTSourceSpecification *)source maxDuration:(nullable NSNumber *)maxDurationSeconds completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; @end extern void FLTImagePickerApiSetup(id binaryMessenger, NSObject *_Nullable api); NS_ASSUME_NONNULL_END ================================================ FILE: packages/image_picker/image_picker_ios/ios/Classes/messages.g.m ================================================ // Copyright 2013 The Flutter 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.0.3), 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, }; } static id GetNullableObject(NSDictionary *dict, id key) { id result = dict[key]; return (result == [NSNull null]) ? nil : result; } static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { id result = array[key]; return (result == [NSNull null]) ? nil : result; } @interface FLTMaxSize () + (FLTMaxSize *)fromMap:(NSDictionary *)dict; - (NSDictionary *)toMap; @end @interface FLTSourceSpecification () + (FLTSourceSpecification *)fromMap:(NSDictionary *)dict; - (NSDictionary *)toMap; @end @implementation FLTMaxSize + (instancetype)makeWithWidth:(nullable NSNumber *)width height:(nullable NSNumber *)height { FLTMaxSize *pigeonResult = [[FLTMaxSize alloc] init]; pigeonResult.width = width; pigeonResult.height = height; return pigeonResult; } + (FLTMaxSize *)fromMap:(NSDictionary *)dict { FLTMaxSize *pigeonResult = [[FLTMaxSize alloc] init]; pigeonResult.width = GetNullableObject(dict, @"width"); pigeonResult.height = GetNullableObject(dict, @"height"); return pigeonResult; } - (NSDictionary *)toMap { return [NSDictionary dictionaryWithObjectsAndKeys:(self.width ? self.width : [NSNull null]), @"width", (self.height ? self.height : [NSNull null]), @"height", nil]; } @end @implementation FLTSourceSpecification + (instancetype)makeWithType:(FLTSourceType)type camera:(FLTSourceCamera)camera { FLTSourceSpecification *pigeonResult = [[FLTSourceSpecification alloc] init]; pigeonResult.type = type; pigeonResult.camera = camera; return pigeonResult; } + (FLTSourceSpecification *)fromMap:(NSDictionary *)dict { FLTSourceSpecification *pigeonResult = [[FLTSourceSpecification alloc] init]; pigeonResult.type = [GetNullableObject(dict, @"type") integerValue]; pigeonResult.camera = [GetNullableObject(dict, @"camera") integerValue]; return pigeonResult; } - (NSDictionary *)toMap { return [NSDictionary dictionaryWithObjectsAndKeys:@(self.type), @"type", @(self.camera), @"camera", nil]; } @end @interface FLTImagePickerApiCodecReader : FlutterStandardReader @end @implementation FLTImagePickerApiCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { case 128: return [FLTMaxSize fromMap:[self readValue]]; case 129: return [FLTSourceSpecification fromMap:[self readValue]]; default: return [super readValueOfType:type]; } } @end @interface FLTImagePickerApiCodecWriter : FlutterStandardWriter @end @implementation FLTImagePickerApiCodecWriter - (void)writeValue:(id)value { if ([value isKindOfClass:[FLTMaxSize class]]) { [self writeByte:128]; [self writeValue:[value toMap]]; } else if ([value isKindOfClass:[FLTSourceSpecification class]]) { [self writeByte:129]; [self writeValue:[value toMap]]; } else { [super writeValue:value]; } } @end @interface FLTImagePickerApiCodecReaderWriter : FlutterStandardReaderWriter @end @implementation FLTImagePickerApiCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { return [[FLTImagePickerApiCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { return [[FLTImagePickerApiCodecReader alloc] initWithData:data]; } @end NSObject *FLTImagePickerApiGetCodec() { static dispatch_once_t sPred = 0; static FlutterStandardMessageCodec *sSharedObject = nil; dispatch_once(&sPred, ^{ FLTImagePickerApiCodecReaderWriter *readerWriter = [[FLTImagePickerApiCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } void FLTImagePickerApiSetup(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.ImagePickerApi.pickImage" binaryMessenger:binaryMessenger codec:FLTImagePickerApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (pickImageWithSource:maxSize:quality:fullMetadata:completion:)], @"FLTImagePickerApi api (%@) doesn't respond to " @"@selector(pickImageWithSource:maxSize:quality:fullMetadata:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FLTSourceSpecification *arg_source = GetNullableObjectAtIndex(args, 0); FLTMaxSize *arg_maxSize = GetNullableObjectAtIndex(args, 1); NSNumber *arg_imageQuality = GetNullableObjectAtIndex(args, 2); NSNumber *arg_requestFullMetadata = GetNullableObjectAtIndex(args, 3); [api pickImageWithSource:arg_source maxSize:arg_maxSize quality:arg_imageQuality fullMetadata:arg_requestFullMetadata completion:^(NSString *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.ImagePickerApi.pickMultiImage" binaryMessenger:binaryMessenger codec:FLTImagePickerApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (pickMultiImageWithMaxSize:quality:fullMetadata:completion:)], @"FLTImagePickerApi api (%@) doesn't respond to " @"@selector(pickMultiImageWithMaxSize:quality:fullMetadata:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FLTMaxSize *arg_maxSize = GetNullableObjectAtIndex(args, 0); NSNumber *arg_imageQuality = GetNullableObjectAtIndex(args, 1); NSNumber *arg_requestFullMetadata = GetNullableObjectAtIndex(args, 2); [api pickMultiImageWithMaxSize:arg_maxSize quality:arg_imageQuality fullMetadata:arg_requestFullMetadata completion:^(NSArray *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.ImagePickerApi.pickVideo" binaryMessenger:binaryMessenger codec:FLTImagePickerApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(pickVideoWithSource:maxDuration:completion:)], @"FLTImagePickerApi api (%@) doesn't respond to " @"@selector(pickVideoWithSource:maxDuration:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FLTSourceSpecification *arg_source = GetNullableObjectAtIndex(args, 0); NSNumber *arg_maxDurationSeconds = GetNullableObjectAtIndex(args, 1); [api pickVideoWithSource:arg_source maxDuration:arg_maxDurationSeconds completion:^(NSString *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; }]; } else { [channel setMessageHandler:nil]; } } } ================================================ FILE: packages/image_picker/image_picker_ios/ios/image_picker_ios.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| s.name = 'image_picker_ios' s.version = '0.0.1' s.summary = 'Flutter plugin that shows an image picker.' s.description = <<-DESC A Flutter plugin for picking images from the image library, and taking new pictures with the camera. 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/image_picker_ios' } s.documentation_url = 'https://pub.dev/packages/image_picker_ios' s.source_files = 'Classes/**/*.{h,m}' s.public_header_files = 'Classes/**/*.h' s.module_map = 'Classes/ImagePickerPlugin.modulemap' s.dependency 'Flutter' s.platform = :ios, '9.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } end ================================================ FILE: packages/image_picker/image_picker_ios/lib/image_picker_ios.dart ================================================ // Copyright 2013 The Flutter 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:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'src/messages.g.dart'; // Converts an [ImageSource] to the corresponding Pigeon API enum value. SourceType _convertSource(ImageSource source) { switch (source) { case ImageSource.camera: return SourceType.camera; case ImageSource.gallery: return SourceType.gallery; } // 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. SourceCamera _convertCamera(CameraDevice camera) { switch (camera) { case CameraDevice.front: return SourceCamera.front; case CameraDevice.rear: return SourceCamera.rear; } // 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. class ImagePickerIOS extends ImagePickerPlatform { final ImagePickerApi _hostApi = ImagePickerApi(); /// Registers this class as the default platform implementation. static void registerWith() { ImagePickerPlatform.instance = ImagePickerIOS(); } @override Future pickImage({ required ImageSource source, double? maxWidth, double? maxHeight, int? imageQuality, CameraDevice preferredCameraDevice = CameraDevice.rear, }) async { final String? path = await _pickImageAsPath( source: source, options: ImagePickerOptions( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, preferredCameraDevice: preferredCameraDevice, ), ); return path != null ? PickedFile(path) : null; } @override Future getImageFromSource({ required ImageSource source, ImagePickerOptions options = const ImagePickerOptions(), }) async { final String? path = await _pickImageAsPath( source: source, options: options, ); return path != null ? XFile(path) : null; } @override Future?> pickMultiImage({ double? maxWidth, double? maxHeight, int? imageQuality, }) async { final List? paths = await _pickMultiImageAsPath( options: MultiImagePickerOptions( imageOptions: ImageOptions( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, ), ), ); if (paths == null) { return null; } return paths.map((dynamic path) => PickedFile(path as String)).toList(); } @override Future> getMultiImageWithOptions({ MultiImagePickerOptions options = const MultiImagePickerOptions(), }) async { final List? paths = await _pickMultiImageAsPath(options: options); if (paths == null) { return []; } return paths.map((String path) => XFile(path)).toList(); } Future?> _pickMultiImageAsPath({ MultiImagePickerOptions options = const MultiImagePickerOptions(), }) async { final int? imageQuality = options.imageOptions.imageQuality; if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) { throw ArgumentError.value( imageQuality, 'imageQuality', 'must be between 0 and 100'); } final double? maxWidth = options.imageOptions.maxWidth; if (maxWidth != null && maxWidth < 0) { throw ArgumentError.value(maxWidth, 'maxWidth', 'cannot be negative'); } final double? maxHeight = options.imageOptions.maxHeight; if (maxHeight != null && maxHeight < 0) { throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative'); } // TODO(stuartmorgan): Remove the cast once Pigeon supports non-nullable // generics, https://github.com/flutter/flutter/issues/97848 return (await _hostApi.pickMultiImage( MaxSize(width: maxWidth, height: maxHeight), imageQuality, options.imageOptions.requestFullMetadata)) ?.cast(); } Future _pickImageAsPath({ required ImageSource source, ImagePickerOptions options = const ImagePickerOptions(), }) { final int? imageQuality = options.imageQuality; if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) { throw ArgumentError.value( imageQuality, 'imageQuality', 'must be between 0 and 100'); } final double? maxHeight = options.maxHeight; final double? maxWidth = options.maxWidth; if (maxWidth != null && maxWidth < 0) { throw ArgumentError.value(maxWidth, 'maxWidth', 'cannot be negative'); } if (maxHeight != null && maxHeight < 0) { throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative'); } return _hostApi.pickImage( SourceSpecification( type: _convertSource(source), camera: _convertCamera(options.preferredCameraDevice), ), MaxSize(width: maxWidth, height: maxHeight), imageQuality, options.requestFullMetadata, ); } @override Future pickVideo({ required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, Duration? maxDuration, }) async { final String? path = await _pickVideoAsPath( source: source, maxDuration: maxDuration, preferredCameraDevice: preferredCameraDevice, ); return path != null ? PickedFile(path) : null; } Future _pickVideoAsPath({ required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, Duration? maxDuration, }) { return _hostApi.pickVideo( SourceSpecification( type: _convertSource(source), camera: _convertCamera(preferredCameraDevice)), maxDuration?.inSeconds); } @override Future getImage({ required ImageSource source, double? maxWidth, double? maxHeight, int? imageQuality, CameraDevice preferredCameraDevice = CameraDevice.rear, }) async { final String? path = await _pickImageAsPath( source: source, options: ImagePickerOptions( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, preferredCameraDevice: preferredCameraDevice, ), ); return path != null ? XFile(path) : null; } @override Future?> getMultiImage({ double? maxWidth, double? maxHeight, int? imageQuality, }) async { final List? paths = await _pickMultiImageAsPath( options: MultiImagePickerOptions( imageOptions: ImageOptions( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, ), ), ); if (paths == null) { return null; } return paths.map((String path) => XFile(path)).toList(); } @override Future getVideo({ required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, Duration? maxDuration, }) async { final String? path = await _pickVideoAsPath( source: source, maxDuration: maxDuration, preferredCameraDevice: preferredCameraDevice, ); return path != null ? XFile(path) : null; } } ================================================ FILE: packages/image_picker/image_picker_ios/lib/src/messages.g.dart ================================================ // Copyright 2013 The Flutter 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.0.3), 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'; enum SourceCamera { rear, front, } enum SourceType { camera, gallery, } class MaxSize { MaxSize({ this.width, this.height, }); double? width; double? height; Object encode() { final Map pigeonMap = {}; pigeonMap['width'] = width; pigeonMap['height'] = height; return pigeonMap; } static MaxSize decode(Object message) { final Map pigeonMap = message as Map; return MaxSize( width: pigeonMap['width'] as double?, height: pigeonMap['height'] as double?, ); } } class SourceSpecification { SourceSpecification({ required this.type, this.camera, }); SourceType type; SourceCamera? camera; Object encode() { final Map pigeonMap = {}; pigeonMap['type'] = type.index; pigeonMap['camera'] = camera?.index; return pigeonMap; } static SourceSpecification decode(Object message) { final Map pigeonMap = message as Map; return SourceSpecification( type: SourceType.values[pigeonMap['type']! as int], camera: pigeonMap['camera'] != null ? SourceCamera.values[pigeonMap['camera']! as int] : null, ); } } class _ImagePickerApiCodec extends StandardMessageCodec { const _ImagePickerApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is MaxSize) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is SourceSpecification) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return MaxSize.decode(readValue(buffer)!); case 129: return SourceSpecification.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } class ImagePickerApi { /// Constructor for [ImagePickerApi]. 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. ImagePickerApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = _ImagePickerApiCodec(); Future pickImage(SourceSpecification arg_source, MaxSize arg_maxSize, int? arg_imageQuality, bool arg_requestFullMetadata) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.ImagePickerApi.pickImage', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([ arg_source, arg_maxSize, arg_imageQuality, arg_requestFullMetadata ]) 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?> pickMultiImage(MaxSize arg_maxSize, int? arg_imageQuality, bool arg_requestFullMetadata) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.ImagePickerApi.pickMultiImage', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send( [arg_maxSize, arg_imageQuality, arg_requestFullMetadata]) 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 List?)?.cast(); } } Future pickVideo( SourceSpecification arg_source, int? arg_maxDurationSeconds) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.ImagePickerApi.pickVideo', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_source, arg_maxDurationSeconds]) 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?); } } } ================================================ FILE: packages/image_picker/image_picker_ios/pigeons/copyright.txt ================================================ Copyright 2013 The Flutter Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. ================================================ FILE: packages/image_picker/image_picker_ios/pigeons/messages.dart ================================================ // Copyright 2013 The Flutter 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', dartTestOut: 'test/test_api.g.dart', objcHeaderOut: 'ios/Classes/messages.g.h', objcSourceOut: 'ios/Classes/messages.g.m', objcOptions: ObjcOptions( prefix: 'FLT', ), copyrightHeader: 'pigeons/copyright.txt', )) class MaxSize { MaxSize(this.width, this.height); double? width; double? height; } // Corresponds to `CameraDevice` from the platform interface package. enum SourceCamera { rear, front } // Corresponds to `ImageSource` from the platform interface package. enum SourceType { camera, gallery } class SourceSpecification { SourceSpecification(this.type, this.camera); SourceType type; SourceCamera? camera; } @HostApi(dartHostTestHandler: 'TestHostImagePickerApi') abstract class ImagePickerApi { @async @ObjCSelector('pickImageWithSource:maxSize:quality:fullMetadata:') String? pickImage(SourceSpecification source, MaxSize maxSize, int? imageQuality, bool requestFullMetadata); @async @ObjCSelector('pickMultiImageWithMaxSize:quality:fullMetadata:') List? pickMultiImage( MaxSize maxSize, int? imageQuality, bool requestFullMetadata); @async @ObjCSelector('pickVideoWithSource:maxDuration:') String? pickVideo(SourceSpecification source, int? maxDurationSeconds); } ================================================ FILE: packages/image_picker/image_picker_ios/pubspec.yaml ================================================ 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+8 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: image_picker platforms: ios: dartPluginClass: ImagePickerIOS pluginClass: FLTImagePickerPlugin dependencies: flutter: sdk: flutter image_picker_platform_interface: ^2.6.1 dev_dependencies: flutter_test: sdk: flutter mockito: ^5.0.0 pigeon: ^3.0.2 ================================================ FILE: packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; 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.g.dart'; @immutable class _LoggedMethodCall { const _LoggedMethodCall(this.name, {required this.arguments}); final String name; final Map arguments; @override bool operator ==(Object other) { return other is _LoggedMethodCall && name == other.name && mapEquals(arguments, other.arguments); } @override int get hashCode => Object.hash(name, arguments); @override String toString() { return 'MethodCall: $name $arguments'; } } class _ApiLogger implements TestHostImagePickerApi { // The value to return from future calls. dynamic returnValue = ''; final List<_LoggedMethodCall> calls = <_LoggedMethodCall>[]; @override Future pickImage( SourceSpecification source, MaxSize maxSize, int? imageQuality, bool requestFullMetadata, ) async { // Flatten arguments for easy comparison. calls.add(_LoggedMethodCall('pickImage', arguments: { 'source': source.type, 'cameraDevice': source.camera, 'maxWidth': maxSize.width, 'maxHeight': maxSize.height, 'imageQuality': imageQuality, 'requestFullMetadata': requestFullMetadata, })); return returnValue as String?; } @override Future?> pickMultiImage( MaxSize maxSize, int? imageQuality, bool requestFullMetadata, ) async { calls.add(_LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': maxSize.width, 'maxHeight': maxSize.height, 'imageQuality': imageQuality, 'requestFullMetadata': requestFullMetadata, })); return returnValue as List?; } @override Future pickVideo( SourceSpecification source, int? maxDurationSeconds) async { calls.add(_LoggedMethodCall('pickVideo', arguments: { 'source': source.type, 'cameraDevice': source.camera, 'maxDuration': maxDurationSeconds, })); return returnValue as String?; } } void main() { TestWidgetsFlutterBinding.ensureInitialized(); final ImagePickerIOS picker = ImagePickerIOS(); late _ApiLogger log; setUp(() { log = _ApiLogger(); TestHostImagePickerApi.setup(log); }); test('registration', () async { ImagePickerIOS.registerWith(); expect(ImagePickerPlatform.instance, isA()); }); group('#pickImage', () { test('passes the image source argument correctly', () async { await picker.pickImage(source: ImageSource.camera); await picker.pickImage(source: ImageSource.gallery); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.gallery, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), ], ); }); test('passes the width and height arguments correctly', () async { await picker.pickImage(source: ImageSource.camera); await picker.pickImage( source: ImageSource.camera, maxWidth: 10.0, ); await picker.pickImage( source: ImageSource.camera, maxHeight: 10.0, ); await picker.pickImage( source: ImageSource.camera, maxWidth: 10.0, maxHeight: 20.0, ); await picker.pickImage( source: ImageSource.camera, maxWidth: 10.0, imageQuality: 70, ); await picker.pickImage( source: ImageSource.camera, maxHeight: 10.0, imageQuality: 70, ); await picker.pickImage( source: ImageSource.camera, maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), ], ); }); test('does not accept a invalid imageQuality argument', () { expect( () => picker.pickImage(imageQuality: -1, source: ImageSource.gallery), throwsArgumentError, ); expect( () => picker.pickImage(imageQuality: 101, source: ImageSource.gallery), throwsArgumentError, ); expect( () => picker.pickImage(imageQuality: -1, source: ImageSource.camera), throwsArgumentError, ); expect( () => picker.pickImage(imageQuality: 101, source: ImageSource.camera), throwsArgumentError, ); }); test('does not accept a negative width or height argument', () { expect( () => picker.pickImage(source: ImageSource.camera, maxWidth: -1.0), throwsArgumentError, ); expect( () => picker.pickImage(source: ImageSource.camera, maxHeight: -1.0), throwsArgumentError, ); }); test('handles a null image path response gracefully', () async { log.returnValue = null; expect(await picker.pickImage(source: ImageSource.gallery), isNull); expect(await picker.pickImage(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { await picker.pickImage(source: ImageSource.camera); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), ], ); }); test('camera position can set to front', () async { await picker.pickImage( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.front, 'requestFullMetadata': true, }), ], ); }); }); group('#pickMultiImage', () { test('calls the method correctly', () async { log.returnValue = ['0', '1']; await picker.pickMultiImage(); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), ], ); }); test('passes the width and height arguments correctly', () async { log.returnValue = ['0', '1']; await picker.pickMultiImage(); await picker.pickMultiImage( maxWidth: 10.0, ); await picker.pickMultiImage( maxHeight: 10.0, ); await picker.pickMultiImage( maxWidth: 10.0, maxHeight: 20.0, ); await picker.pickMultiImage( maxWidth: 10.0, imageQuality: 70, ); await picker.pickMultiImage( maxHeight: 10.0, imageQuality: 70, ); await picker.pickMultiImage( maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, 'requestFullMetadata': true, }), ], ); }); test('does not accept a negative width or height argument', () { expect( () => picker.pickMultiImage(maxWidth: -1.0), throwsArgumentError, ); expect( () => picker.pickMultiImage(maxHeight: -1.0), throwsArgumentError, ); }); test('does not accept a invalid imageQuality argument', () { expect( () => picker.pickMultiImage(imageQuality: -1), throwsArgumentError, ); expect( () => picker.pickMultiImage(imageQuality: 101), throwsArgumentError, ); }); test('handles a null image path response gracefully', () async { log.returnValue = null; expect(await picker.pickMultiImage(), isNull); }); }); group('#pickVideo', () { test('passes the image source argument correctly', () async { await picker.pickVideo(source: ImageSource.camera); await picker.pickVideo(source: ImageSource.gallery); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickVideo', arguments: { 'source': SourceType.camera, 'cameraDevice': SourceCamera.rear, 'maxDuration': null, }), const _LoggedMethodCall('pickVideo', arguments: { 'source': SourceType.gallery, 'cameraDevice': SourceCamera.rear, 'maxDuration': null, }), ], ); }); test('passes the duration argument correctly', () async { await picker.pickVideo(source: ImageSource.camera); await picker.pickVideo( source: ImageSource.camera, maxDuration: const Duration(seconds: 10), ); await picker.pickVideo( source: ImageSource.camera, maxDuration: const Duration(minutes: 1), ); await picker.pickVideo( source: ImageSource.camera, maxDuration: const Duration(hours: 1), ); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickVideo', arguments: { 'source': SourceType.camera, 'maxDuration': null, 'cameraDevice': SourceCamera.rear, }), const _LoggedMethodCall('pickVideo', arguments: { 'source': SourceType.camera, 'maxDuration': 10, 'cameraDevice': SourceCamera.rear, }), const _LoggedMethodCall('pickVideo', arguments: { 'source': SourceType.camera, 'maxDuration': 60, 'cameraDevice': SourceCamera.rear, }), const _LoggedMethodCall('pickVideo', arguments: { 'source': SourceType.camera, 'maxDuration': 3600, 'cameraDevice': SourceCamera.rear, }), ], ); }); test('handles a null video path response gracefully', () async { log.returnValue = null; expect(await picker.pickVideo(source: ImageSource.gallery), isNull); expect(await picker.pickVideo(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { await picker.pickVideo(source: ImageSource.camera); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickVideo', arguments: { 'source': SourceType.camera, 'cameraDevice': SourceCamera.rear, 'maxDuration': null, }), ], ); }); test('camera position can set to front', () async { await picker.pickVideo( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front, ); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickVideo', arguments: { 'source': SourceType.camera, 'maxDuration': null, 'cameraDevice': SourceCamera.front, }), ], ); }); }); group('#getImage', () { test('passes the image source argument correctly', () async { await picker.getImage(source: ImageSource.camera); await picker.getImage(source: ImageSource.gallery); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.gallery, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), ], ); }); test('passes the width and height arguments correctly', () async { await picker.getImage(source: ImageSource.camera); await picker.getImage( source: ImageSource.camera, maxWidth: 10.0, ); await picker.getImage( source: ImageSource.camera, maxHeight: 10.0, ); await picker.getImage( source: ImageSource.camera, maxWidth: 10.0, maxHeight: 20.0, ); await picker.getImage( source: ImageSource.camera, maxWidth: 10.0, imageQuality: 70, ); await picker.getImage( source: ImageSource.camera, maxHeight: 10.0, imageQuality: 70, ); await picker.getImage( source: ImageSource.camera, maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), ], ); }); test('does not accept a invalid imageQuality argument', () { expect( () => picker.getImage(imageQuality: -1, source: ImageSource.gallery), throwsArgumentError, ); expect( () => picker.getImage(imageQuality: 101, source: ImageSource.gallery), throwsArgumentError, ); expect( () => picker.getImage(imageQuality: -1, source: ImageSource.camera), throwsArgumentError, ); expect( () => picker.getImage(imageQuality: 101, source: ImageSource.camera), throwsArgumentError, ); }); test('does not accept a negative width or height argument', () { expect( () => picker.getImage(source: ImageSource.camera, maxWidth: -1.0), throwsArgumentError, ); expect( () => picker.getImage(source: ImageSource.camera, maxHeight: -1.0), throwsArgumentError, ); }); test('handles a null image path response gracefully', () async { log.returnValue = null; expect(await picker.getImage(source: ImageSource.gallery), isNull); expect(await picker.getImage(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { await picker.getImage(source: ImageSource.camera); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), ], ); }); test('camera position can set to front', () async { await picker.getImage( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.front, 'requestFullMetadata': true, }), ], ); }); }); group('#getMultiImage', () { test('calls the method correctly', () async { log.returnValue = ['0', '1']; await picker.getMultiImage(); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), ], ); }); test('passes the width and height arguments correctly', () async { log.returnValue = ['0', '1']; await picker.getMultiImage(); await picker.getMultiImage( maxWidth: 10.0, ); await picker.getMultiImage( maxHeight: 10.0, ); await picker.getMultiImage( maxWidth: 10.0, maxHeight: 20.0, ); await picker.getMultiImage( maxWidth: 10.0, imageQuality: 70, ); await picker.getMultiImage( maxHeight: 10.0, imageQuality: 70, ); await picker.getMultiImage( maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, 'requestFullMetadata': true, }), ], ); }); test('does not accept a negative width or height argument', () { log.returnValue = ['0', '1']; expect( () => picker.getMultiImage(maxWidth: -1.0), throwsArgumentError, ); expect( () => picker.getMultiImage(maxHeight: -1.0), throwsArgumentError, ); }); test('does not accept a invalid imageQuality argument', () { log.returnValue = ['0', '1']; expect( () => picker.getMultiImage(imageQuality: -1), throwsArgumentError, ); expect( () => picker.getMultiImage(imageQuality: 101), throwsArgumentError, ); }); test('handles a null image path response gracefully', () async { log.returnValue = null; expect(await picker.getMultiImage(), isNull); expect(await picker.getMultiImage(), isNull); }); }); group('#getVideo', () { test('passes the image source argument correctly', () async { await picker.getVideo(source: ImageSource.camera); await picker.getVideo(source: ImageSource.gallery); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickVideo', arguments: { 'source': SourceType.camera, 'cameraDevice': SourceCamera.rear, 'maxDuration': null, }), const _LoggedMethodCall('pickVideo', arguments: { 'source': SourceType.gallery, 'cameraDevice': SourceCamera.rear, 'maxDuration': null, }), ], ); }); test('passes the duration argument correctly', () async { await picker.getVideo(source: ImageSource.camera); await picker.getVideo( source: ImageSource.camera, maxDuration: const Duration(seconds: 10), ); await picker.getVideo( source: ImageSource.camera, maxDuration: const Duration(minutes: 1), ); await picker.getVideo( source: ImageSource.camera, maxDuration: const Duration(hours: 1), ); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickVideo', arguments: { 'source': SourceType.camera, 'maxDuration': null, 'cameraDevice': SourceCamera.rear, }), const _LoggedMethodCall('pickVideo', arguments: { 'source': SourceType.camera, 'maxDuration': 10, 'cameraDevice': SourceCamera.rear, }), const _LoggedMethodCall('pickVideo', arguments: { 'source': SourceType.camera, 'maxDuration': 60, 'cameraDevice': SourceCamera.rear, }), const _LoggedMethodCall('pickVideo', arguments: { 'source': SourceType.camera, 'maxDuration': 3600, 'cameraDevice': SourceCamera.rear, }), ], ); }); test('handles a null video path response gracefully', () async { log.returnValue = null; expect(await picker.getVideo(source: ImageSource.gallery), isNull); expect(await picker.getVideo(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { await picker.getVideo(source: ImageSource.camera); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickVideo', arguments: { 'source': SourceType.camera, 'cameraDevice': SourceCamera.rear, 'maxDuration': null, }), ], ); }); test('camera position can set to front', () async { await picker.getVideo( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front, ); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickVideo', arguments: { 'source': SourceType.camera, 'maxDuration': null, 'cameraDevice': SourceCamera.front, }), ], ); }); }); group('#getImageFromSource', () { test('passes the image source argument correctly', () async { await picker.getImageFromSource(source: ImageSource.camera); await picker.getImageFromSource(source: ImageSource.gallery); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.gallery, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), ], ); }); test('passes the width and height arguments correctly', () async { await picker.getImageFromSource(source: ImageSource.camera); await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(maxWidth: 10.0), ); await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(maxHeight: 10.0), ); await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions( maxWidth: 10.0, maxHeight: 20.0, ), ); await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions( maxWidth: 10.0, imageQuality: 70, ), ); await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions( maxHeight: 10.0, imageQuality: 70, ), ); await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions( maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ), ); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), ], ); }); test('does not accept a invalid imageQuality argument', () { expect( () => picker.getImageFromSource( source: ImageSource.gallery, options: const ImagePickerOptions(imageQuality: -1), ), throwsArgumentError, ); expect( () => picker.getImageFromSource( source: ImageSource.gallery, options: const ImagePickerOptions(imageQuality: 101), ), throwsArgumentError, ); expect( () => picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(imageQuality: -1), ), throwsArgumentError, ); expect( () => picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(imageQuality: 101), ), throwsArgumentError, ); }); test('does not accept a negative width or height argument', () { expect( () => picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(maxWidth: -1.0), ), throwsArgumentError, ); expect( () => picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(maxHeight: -1.0), ), throwsArgumentError, ); }); test('handles a null image path response gracefully', () async { log.returnValue = null; expect( await picker.getImageFromSource(source: ImageSource.gallery), isNull); expect( await picker.getImageFromSource(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { await picker.getImageFromSource(source: ImageSource.camera); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), ], ); }); test('camera position can set to front', () async { await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(preferredCameraDevice: CameraDevice.front), ); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.front, 'requestFullMetadata': true, }), ], ); }); test('Request full metadata argument defaults to true', () async { await picker.getImageFromSource(source: ImageSource.gallery); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.gallery, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': true, }), ], ); }); test('passes the request full metadata argument correctly', () async { await picker.getImageFromSource( source: ImageSource.gallery, options: const ImagePickerOptions(requestFullMetadata: false), ); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.gallery, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, 'requestFullMetadata': false, }), ], ); }); }); group('#getMultiImageWithOptions', () { test('calls the method correctly', () async { log.returnValue = ['0', '1']; await picker.getMultiImageWithOptions(); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), ], ); }); test('passes the width and height arguments correctly', () async { log.returnValue = ['0', '1']; await picker.getMultiImageWithOptions(); await picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions(maxWidth: 10.0), ), ); await picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions(maxHeight: 10.0), ), ); await picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions(maxWidth: 10.0, maxHeight: 20.0), ), ); await picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions(maxWidth: 10.0, imageQuality: 70), ), ); await picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions(maxHeight: 10.0, imageQuality: 70), ), ); await picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions( maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ), ), ); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, 'requestFullMetadata': true, }), const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, 'requestFullMetadata': true, }), ], ); }); test('does not accept a negative width or height argument', () { log.returnValue = ['0', '1']; expect( () => picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions(maxWidth: -1.0), ), ), throwsArgumentError, ); expect( () => picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions(maxHeight: -1.0), ), ), throwsArgumentError, ); }); test('does not accept a invalid imageQuality argument', () { log.returnValue = ['0', '1']; expect( () => picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions(imageQuality: -1), ), ), throwsArgumentError, ); expect( () => picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions(imageQuality: 101), ), ), throwsArgumentError, ); }); test('handles a null image path response gracefully', () async { log.returnValue = null; expect(await picker.getMultiImageWithOptions(), isEmpty); }); test('Request full metadata argument defaults to true', () async { log.returnValue = ['0', '1']; await picker.getMultiImageWithOptions(); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), ], ); }); test('Passes the request full metadata argument correctly', () async { log.returnValue = ['0', '1']; await picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions(requestFullMetadata: false), ), ); expect( log.calls, <_LoggedMethodCall>[ const _LoggedMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': false, }), ], ); }); }); } ================================================ FILE: packages/image_picker/image_picker_ios/test/test_api.g.dart ================================================ // Copyright 2013 The Flutter 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.0.3), 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:typed_data' show Uint8List, Int32List, Int64List, Float64List; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#106316) // ignore: unnecessary_import import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; // Manually changed due to https://github.com/flutter/flutter/issues/97744 import 'package:image_picker_ios/src/messages.g.dart'; class _TestHostImagePickerApiCodec extends StandardMessageCodec { const _TestHostImagePickerApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is MaxSize) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is SourceSpecification) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return MaxSize.decode(readValue(buffer)!); case 129: return SourceSpecification.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } abstract class TestHostImagePickerApi { static const MessageCodec codec = _TestHostImagePickerApiCodec(); Future pickImage(SourceSpecification source, MaxSize maxSize, int? imageQuality, bool requestFullMetadata); Future?> pickMultiImage( MaxSize maxSize, int? imageQuality, bool requestFullMetadata); Future pickVideo( SourceSpecification source, int? maxDurationSeconds); static void setup(TestHostImagePickerApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.ImagePickerApi.pickImage', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.ImagePickerApi.pickImage was null.'); final List args = (message as List?)!; final SourceSpecification? arg_source = (args[0] as SourceSpecification?); assert(arg_source != null, 'Argument for dev.flutter.pigeon.ImagePickerApi.pickImage was null, expected non-null SourceSpecification.'); final MaxSize? arg_maxSize = (args[1] as MaxSize?); assert(arg_maxSize != null, 'Argument for dev.flutter.pigeon.ImagePickerApi.pickImage was null, expected non-null MaxSize.'); final int? arg_imageQuality = (args[2] as int?); final bool? arg_requestFullMetadata = (args[3] as bool?); assert(arg_requestFullMetadata != null, 'Argument for dev.flutter.pigeon.ImagePickerApi.pickImage was null, expected non-null bool.'); final String? output = await api.pickImage(arg_source!, arg_maxSize!, arg_imageQuality, arg_requestFullMetadata!); return {'result': output}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.ImagePickerApi.pickMultiImage', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.ImagePickerApi.pickMultiImage was null.'); final List args = (message as List?)!; final MaxSize? arg_maxSize = (args[0] as MaxSize?); assert(arg_maxSize != null, 'Argument for dev.flutter.pigeon.ImagePickerApi.pickMultiImage was null, expected non-null MaxSize.'); final int? arg_imageQuality = (args[1] as int?); final bool? arg_requestFullMetadata = (args[2] as bool?); assert(arg_requestFullMetadata != null, 'Argument for dev.flutter.pigeon.ImagePickerApi.pickMultiImage was null, expected non-null bool.'); final List? output = await api.pickMultiImage( arg_maxSize!, arg_imageQuality, arg_requestFullMetadata!); return {'result': output}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.ImagePickerApi.pickVideo', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.ImagePickerApi.pickVideo was null.'); final List args = (message as List?)!; final SourceSpecification? arg_source = (args[0] as SourceSpecification?); assert(arg_source != null, 'Argument for dev.flutter.pigeon.ImagePickerApi.pickVideo was null, expected non-null SourceSpecification.'); final int? arg_maxDurationSeconds = (args[1] as int?); final String? output = await api.pickVideo(arg_source!, arg_maxDurationSeconds); return {'result': output}; }); } } } } ================================================ FILE: packages/image_picker/image_picker_platform_interface/AUTHORS ================================================ # 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> ================================================ FILE: packages/image_picker/image_picker_platform_interface/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 2.6.2 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 2.6.1 * Exports new types added for `getMultiImageWithOptions` in 2.6.0. ## 2.6.0 * Deprecates `getMultiImage` in favor of a new method `getMultiImageWithOptions`. * Adds `requestFullMetadata` option that allows disabling extra permission requests on certain platforms. * Moves optional image picking parameters to `MultiImagePickerOptions` class. ## 2.5.0 * Deprecates `getImage` in favor of a new method `getImageFromSource`. * Adds `requestFullMetadata` option that allows disabling extra permission requests on certain platforms. * Moves optional image picking parameters to `ImagePickerOptions` class. * Minor fixes for new analysis options. ## 2.4.4 * Internal code cleanup for stricter analysis options. ## 2.4.3 * Removes dependency on `meta`. ## 2.4.2 * Update to use the `verify` method introduced in plugin_platform_interface 2.1.0. ## 2.4.1 * Reverts the changes from 2.4.0, which was a breaking change that was incorrectly marked as a non-breaking change. ## 2.4.0 * Add `forceFullMetadata` option to `pickImage`. * To keep this non-breaking `forceFullMetadata` defaults to `true`, so the plugin tries to get the full image metadata which may require extra permission requests on certain platforms. * If `forceFullMetadata` is set to `false`, the plugin fetches the image in a way that reduces permission requests from the platform (e.g on iOS the plugin won’t ask for the `NSPhotoLibraryUsageDescription` permission). ## 2.3.0 * Updated `LostDataResponse` to include a `files` property, in case more than one file was recovered. ## 2.2.0 * Added new methods that return `XFile` (from `package:cross_file`) * `getImage` (will deprecate `pickImage`) * `getVideo` (will deprecate `pickVideo`) * `getMultiImage` (will deprecate `pickMultiImage`) _`PickedFile` will also be marked as deprecated in an upcoming release._ ## 2.1.0 * Add `pickMultiImage` method. ## 2.0.1 * Update platform_plugin_interface version requirement. ## 2.0.0 * Migrate to null safety. * Breaking Changes: * Removed the deprecated methods: `ImagePickerPlatform.retrieveLostDataAsDartIoFile`,`ImagePickerPlatform.pickImagePath` and `ImagePickerPlatform.pickVideoPath`. * Removed deprecated class: `LostDataResponse`. ## 1.1.6 * Fix test asset file location. ## 1.1.5 * Update Flutter SDK constraint. ## 1.1.4 * Pass `Uri`s to `package:http` methods, instead of strings, in preparation for a major version update in `http`. ## 1.1.3 * Update documentation of `pickImage()` regarding HEIC images. ## 1.1.2 * Update documentation of `pickImage()` regarding compression support for specific image types. ## 1.1.1 * Update documentation of getImage() about Android's disability to preference front/rear camera. ## 1.1.0 * Introduce PickedFile type for the new API. ## 1.0.1 * Update lower bound of dart dependency to 2.1.0. ## 1.0.0 * Initial release. ================================================ FILE: packages/image_picker/image_picker_platform_interface/LICENSE ================================================ 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. ================================================ FILE: packages/image_picker/image_picker_platform_interface/README.md ================================================ # image_picker_platform_interface A common platform interface for the [`image_picker`][1] plugin. This interface allows platform-specific implementations of the `image_picker` plugin, as well as the plugin itself, to ensure they are supporting the same interface. # Usage To implement a new platform-specific implementation of `image_picker`, extend [`ImagePickerPlatform`][2] with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `ImagePickerPlatform` by calling `ImagePickerPlatform.instance = MyImagePickerPlatform()`. # Note on breaking changes Strongly prefer non-breaking changes (such as adding a method to the interface) over breaking changes for this package. See https://flutter.dev/go/platform-interface-breaking-changes for a discussion on why a less-clean interface is preferable to a breaking change. [1]: ../image_picker [2]: lib/image_picker_platform_interface.dart ================================================ FILE: packages/image_picker/image_picker_platform_interface/lib/image_picker_platform_interface.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'package:cross_file/cross_file.dart'; export 'package:image_picker_platform_interface/src/platform_interface/image_picker_platform.dart'; export 'package:image_picker_platform_interface/src/types/types.dart'; ================================================ FILE: packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart ================================================ // Copyright 2013 The Flutter 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:flutter/services.dart'; import '../../image_picker_platform_interface.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/image_picker'); /// An implementation of [ImagePickerPlatform] that uses method channels. class MethodChannelImagePicker extends ImagePickerPlatform { /// The MethodChannel that is being used by this implementation of the plugin. @visibleForTesting MethodChannel get channel => _channel; @override Future pickImage({ required ImageSource source, double? maxWidth, double? maxHeight, int? imageQuality, CameraDevice preferredCameraDevice = CameraDevice.rear, }) async { final String? path = await _getImagePath( source: source, maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, preferredCameraDevice: preferredCameraDevice, ); return path != null ? PickedFile(path) : null; } @override Future?> pickMultiImage({ double? maxWidth, double? maxHeight, int? imageQuality, }) async { final List? paths = await _getMultiImagePath( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, ); if (paths == null) { return null; } return paths.map((dynamic path) => PickedFile(path as String)).toList(); } Future?> _getMultiImagePath({ double? maxWidth, double? maxHeight, int? imageQuality, bool requestFullMetadata = true, }) { if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) { throw ArgumentError.value( imageQuality, 'imageQuality', 'must be between 0 and 100'); } if (maxWidth != null && maxWidth < 0) { throw ArgumentError.value(maxWidth, 'maxWidth', 'cannot be negative'); } if (maxHeight != null && maxHeight < 0) { throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative'); } return _channel.invokeMethod?>( 'pickMultiImage', { 'maxWidth': maxWidth, 'maxHeight': maxHeight, 'imageQuality': imageQuality, 'requestFullMetadata': requestFullMetadata, }, ); } Future _getImagePath({ required ImageSource source, double? maxWidth, double? maxHeight, int? imageQuality, CameraDevice preferredCameraDevice = CameraDevice.rear, bool requestFullMetadata = true, }) { if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) { throw ArgumentError.value( imageQuality, 'imageQuality', 'must be between 0 and 100'); } if (maxWidth != null && maxWidth < 0) { throw ArgumentError.value(maxWidth, 'maxWidth', 'cannot be negative'); } if (maxHeight != null && maxHeight < 0) { throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative'); } return _channel.invokeMethod( 'pickImage', { 'source': source.index, 'maxWidth': maxWidth, 'maxHeight': maxHeight, 'imageQuality': imageQuality, 'cameraDevice': preferredCameraDevice.index, 'requestFullMetadata': requestFullMetadata, }, ); } @override Future pickVideo({ required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, Duration? maxDuration, }) async { final String? path = await _getVideoPath( source: source, maxDuration: maxDuration, preferredCameraDevice: preferredCameraDevice, ); return path != null ? PickedFile(path) : null; } Future _getVideoPath({ required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, Duration? maxDuration, }) { return _channel.invokeMethod( 'pickVideo', { 'source': source.index, 'maxDuration': maxDuration?.inSeconds, 'cameraDevice': preferredCameraDevice.index }, ); } @override Future retrieveLostData() async { final Map? result = await _channel.invokeMapMethod('retrieve'); if (result == null) { return LostData.empty(); } assert(result.containsKey('path') != result.containsKey('errorCode')); final String? type = result['type'] as String?; assert(type == kTypeImage || type == kTypeVideo); RetrieveType? retrieveType; if (type == kTypeImage) { retrieveType = RetrieveType.image; } else if (type == kTypeVideo) { retrieveType = RetrieveType.video; } PlatformException? exception; if (result.containsKey('errorCode')) { exception = PlatformException( code: result['errorCode']! as String, message: result['errorMessage'] as String?); } final String? path = result['path'] as String?; return LostData( file: path != null ? PickedFile(path) : null, exception: exception, type: retrieveType, ); } @override Future getImage({ required ImageSource source, double? maxWidth, double? maxHeight, int? imageQuality, CameraDevice preferredCameraDevice = CameraDevice.rear, }) async { final String? path = await _getImagePath( source: source, maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, preferredCameraDevice: preferredCameraDevice, ); return path != null ? XFile(path) : null; } @override Future getImageFromSource({ required ImageSource source, ImagePickerOptions options = const ImagePickerOptions(), }) async { final String? path = await _getImagePath( source: source, maxHeight: options.maxHeight, maxWidth: options.maxWidth, imageQuality: options.imageQuality, preferredCameraDevice: options.preferredCameraDevice, requestFullMetadata: options.requestFullMetadata, ); return path != null ? XFile(path) : null; } @override Future?> getMultiImage({ double? maxWidth, double? maxHeight, int? imageQuality, }) async { final List? paths = await _getMultiImagePath( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, ); if (paths == null) { return null; } return paths.map((dynamic path) => XFile(path as String)).toList(); } @override Future> getMultiImageWithOptions({ MultiImagePickerOptions options = const MultiImagePickerOptions(), }) async { final List? paths = await _getMultiImagePath( maxWidth: options.imageOptions.maxWidth, maxHeight: options.imageOptions.maxHeight, imageQuality: options.imageOptions.imageQuality, requestFullMetadata: options.imageOptions.requestFullMetadata, ); if (paths == null) { return []; } return paths.map((dynamic path) => XFile(path as String)).toList(); } @override Future getVideo({ required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, Duration? maxDuration, }) async { final String? path = await _getVideoPath( source: source, maxDuration: maxDuration, preferredCameraDevice: preferredCameraDevice, ); return path != null ? XFile(path) : null; } @override Future getLostData() async { List? pickedFileList; final Map? result = await _channel.invokeMapMethod('retrieve'); if (result == null) { return LostDataResponse.empty(); } assert(result.containsKey('path') != result.containsKey('errorCode')); final String? type = result['type'] as String?; assert(type == kTypeImage || type == kTypeVideo); RetrieveType? retrieveType; if (type == kTypeImage) { retrieveType = RetrieveType.image; } else if (type == kTypeVideo) { retrieveType = RetrieveType.video; } PlatformException? exception; if (result.containsKey('errorCode')) { exception = PlatformException( code: result['errorCode']! as String, message: result['errorMessage'] as String?); } final String? path = result['path'] as String?; final List? pathList = (result['pathList'] as List?)?.cast(); if (pathList != null) { pickedFileList = []; for (final String path in pathList) { pickedFileList.add(XFile(path)); } } return LostDataResponse( file: path != null ? XFile(path) : null, exception: exception, type: retrieveType, files: pickedFileList, ); } } ================================================ FILE: packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart ================================================ // Copyright 2013 The Flutter 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:cross_file/cross_file.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import '../method_channel/method_channel_image_picker.dart'; import '../types/types.dart'; /// The interface that implementations of image_picker must implement. /// /// Platform implementations should extend this class rather than implement it as `image_picker` /// does not consider newly added methods to be breaking changes. Extending this class /// (using `extends`) ensures that the subclass will get the default implementation, while /// platform implementations that `implements` this interface will be broken by newly added /// [ImagePickerPlatform] methods. abstract class ImagePickerPlatform extends PlatformInterface { /// Constructs a ImagePickerPlatform. ImagePickerPlatform() : super(token: _token); static final Object _token = Object(); static ImagePickerPlatform _instance = MethodChannelImagePicker(); /// The default instance of [ImagePickerPlatform] to use. /// /// Defaults to [MethodChannelImagePicker]. static ImagePickerPlatform get instance => _instance; /// Platform-specific plugins should set this with their own platform-specific /// class that extends [ImagePickerPlatform] when they register themselves. // TODO(amirh): Extract common platform interface logic. // https://github.com/flutter/flutter/issues/43368 static set instance(ImagePickerPlatform instance) { PlatformInterface.verify(instance, _token); _instance = instance; } // Next version of the API. /// Returns a [PickedFile] with the image that was picked. /// /// The `source` argument controls where the image comes from. This can /// be either [ImageSource.camera] or [ImageSource.gallery]. /// /// Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and above only support HEIC images if used /// in addition to a size modification, of which the usage is explained below. /// /// If specified, the image will be at most `maxWidth` wide and /// `maxHeight` tall. Otherwise the image will be returned at it's /// original width and height. /// /// The `imageQuality` argument modifies the quality of the image, ranging from 0-100 /// where 100 is the original/max quality. If `imageQuality` is null, the image with /// the original quality will be returned. Compression is only supported for certain /// image types such as JPEG. If compression is not supported for the image that is picked, /// a warning message will be logged. /// /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera]. /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device. /// Defaults to [CameraDevice.rear]. Note that Android has no documented parameter for an intent to specify if /// the front or rear camera should be opened, this function is not guaranteed /// to work on an Android device. /// /// In Android, the MainActivity can be destroyed for various reasons. If that happens, the result will be lost /// in this call. You can then call [retrieveLostData] when your app relaunches to retrieve the lost data. /// /// If no images were picked, the return value is null. Future pickImage({ required ImageSource source, double? maxWidth, double? maxHeight, int? imageQuality, CameraDevice preferredCameraDevice = CameraDevice.rear, }) { throw UnimplementedError('pickImage() has not been implemented.'); } /// Returns a [List] with the images that were picked. /// /// The images come from the [ImageSource.gallery]. /// /// Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and above only support HEIC images if used /// in addition to a size modification, of which the usage is explained below. /// /// If specified, the image will be at most `maxWidth` wide and /// `maxHeight` tall. Otherwise the image will be returned at it's /// original width and height. /// /// The `imageQuality` argument modifies the quality of the images, ranging from 0-100 /// where 100 is the original/max quality. If `imageQuality` is null, the images with /// the original quality will be returned. Compression is only supported for certain /// image types such as JPEG. If compression is not supported for the image that is picked, /// a warning message will be logged. /// /// If no images were picked, the return value is null. Future?> pickMultiImage({ double? maxWidth, double? maxHeight, int? imageQuality, }) { throw UnimplementedError('pickMultiImage() has not been implemented.'); } /// Returns a [PickedFile] containing the video that was picked. /// /// The [source] argument controls where the video comes from. This can /// be either [ImageSource.camera] or [ImageSource.gallery]. /// /// The [maxDuration] argument specifies the maximum duration of the captured video. If no [maxDuration] is specified, /// the maximum duration will be infinite. /// /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera]. /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device. /// Defaults to [CameraDevice.rear]. /// /// In Android, the MainActivity can be destroyed for various fo reasons. If that happens, the result will be lost /// in this call. You can then call [retrieveLostData] when your app relaunches to retrieve the lost data. /// /// If no images were picked, the return value is null. Future pickVideo({ required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, Duration? maxDuration, }) { throw UnimplementedError('pickVideo() has not been implemented.'); } /// Retrieves any previously picked file, that was lost due to the MainActivity being destroyed. /// In case multiple files were lost, only the last file will be recovered. (Android only). /// /// Image or video can be lost if the MainActivity is destroyed. And there is no guarantee that the MainActivity is always alive. /// Call this method to retrieve the lost data and process the data according to your APP's business logic. /// /// Returns a [LostData] object if successfully retrieved the lost data. The [LostData] object can represent either a /// successful image/video selection, or a failure. /// /// Calling this on a non-Android platform will throw [UnimplementedError] exception. /// /// See also: /// * [LostData], for what's included in the response. /// * [Android Activity Lifecycle](https://developer.android.com/reference/android/app/Activity.html), for more information on MainActivity destruction. Future retrieveLostData() { throw UnimplementedError('retrieveLostData() has not been implemented.'); } /// This method is deprecated in favor of [getImageFromSource] and will be removed in a future update. /// /// Returns an [XFile] with the image that was picked. /// /// The `source` argument controls where the image comes from. This can /// be either [ImageSource.camera] or [ImageSource.gallery]. /// /// Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and above only support HEIC images if used /// in addition to a size modification, of which the usage is explained below. /// /// If specified, the image will be at most `maxWidth` wide and /// `maxHeight` tall. Otherwise the image will be returned at it's /// original width and height. /// /// The `imageQuality` argument modifies the quality of the image, ranging from 0-100 /// where 100 is the original/max quality. If `imageQuality` is null, the image with /// the original quality will be returned. Compression is only supported for certain /// image types such as JPEG. If compression is not supported for the image that is picked, /// a warning message will be logged. /// /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera]. /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device. /// Defaults to [CameraDevice.rear]. Note that Android has no documented parameter for an intent to specify if /// the front or rear camera should be opened, this function is not guaranteed /// to work on an Android device. /// /// In Android, the MainActivity can be destroyed for various reasons. If that happens, the result will be lost /// in this call. You can then call [getLostData] when your app relaunches to retrieve the lost data. /// /// If no images were picked, the return value is null. Future getImage({ required ImageSource source, double? maxWidth, double? maxHeight, int? imageQuality, CameraDevice preferredCameraDevice = CameraDevice.rear, }) { throw UnimplementedError('getImage() has not been implemented.'); } /// This method is deprecated in favor of [getMultiImageWithOptions] and will be removed in a future update. /// /// Returns a [List] with the images that were picked. /// /// The images come from the [ImageSource.gallery]. /// /// Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and above only support HEIC images if used /// in addition to a size modification, of which the usage is explained below. /// /// If specified, the image will be at most `maxWidth` wide and /// `maxHeight` tall. Otherwise the image will be returned at it's /// original width and height. /// /// The `imageQuality` argument modifies the quality of the images, ranging from 0-100 /// where 100 is the original/max quality. If `imageQuality` is null, the images with /// the original quality will be returned. Compression is only supported for certain /// image types such as JPEG. If compression is not supported for the image that is picked, /// a warning message will be logged. /// /// If no images were picked, the return value is null. Future?> getMultiImage({ double? maxWidth, double? maxHeight, int? imageQuality, }) { throw UnimplementedError('getMultiImage() has not been implemented.'); } /// Returns a [XFile] containing the video that was picked. /// /// The [source] argument controls where the video comes from. This can /// be either [ImageSource.camera] or [ImageSource.gallery]. /// /// The [maxDuration] argument specifies the maximum duration of the captured video. If no [maxDuration] is specified, /// the maximum duration will be infinite. /// /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera]. /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device. /// Defaults to [CameraDevice.rear]. /// /// In Android, the MainActivity can be destroyed for various fo reasons. If that happens, the result will be lost /// in this call. You can then call [getLostData] when your app relaunches to retrieve the lost data. /// /// If no images were picked, the return value is null. Future getVideo({ required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, Duration? maxDuration, }) { throw UnimplementedError('getVideo() has not been implemented.'); } /// Retrieves any previously picked files, that were lost due to the MainActivity being destroyed. (Android only) /// /// Image or video can be lost if the MainActivity is destroyed. And there is no guarantee that the MainActivity is /// always alive. Call this method to retrieve the lost data and process the data according to your APP's business logic. /// /// Returns a [LostDataResponse] object if successfully retrieved the lost data. The [LostDataResponse] object can /// represent either a successful image/video selection, or a failure. /// /// Calling this on a non-Android platform will throw [UnimplementedError] exception. /// /// See also: /// * [LostDataResponse], for what's included in the response. /// * [Android Activity Lifecycle](https://developer.android.com/reference/android/app/Activity.html), for more /// information on MainActivity destruction. Future getLostData() { throw UnimplementedError('getLostData() has not been implemented.'); } /// Returns an [XFile] with the image that was picked. /// /// The `source` argument controls where the image comes from. This can /// be either [ImageSource.camera] or [ImageSource.gallery]. /// /// The `options` argument controls additional settings that can be used when /// picking an image. See [ImagePickerOptions] for more details. /// /// Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and /// above only support HEIC images if used in addition to a size modification, /// of which the usage is explained in [ImagePickerOptions]. /// /// In Android, the MainActivity can be destroyed for various reasons. If that /// happens, the result will be lost in this call. You can then call [getLostData] /// when your app relaunches to retrieve the lost data. /// /// If no images were picked, the return value is null. Future getImageFromSource({ required ImageSource source, ImagePickerOptions options = const ImagePickerOptions(), }) { return getImage( source: source, maxHeight: options.maxHeight, maxWidth: options.maxWidth, imageQuality: options.imageQuality, preferredCameraDevice: options.preferredCameraDevice, ); } /// Returns a [List] with the images that were picked. /// /// The images come from the [ImageSource.gallery]. /// /// The `options` argument controls additional settings that can be used when /// picking an image. See [MultiImagePickerOptions] for more details. /// /// If no images were picked, returns an empty list. Future> getMultiImageWithOptions({ MultiImagePickerOptions options = const MultiImagePickerOptions(), }) async { final List? pickedImages = await getMultiImage( maxWidth: options.imageOptions.maxWidth, maxHeight: options.imageOptions.maxHeight, imageQuality: options.imageOptions.imageQuality, ); return pickedImages ?? []; } } ================================================ FILE: packages/image_picker/image_picker_platform_interface/lib/src/types/camera_device.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Which camera to use when picking images/videos while source is `ImageSource.camera`. /// /// Not every device supports both of the positions. enum CameraDevice { /// Use the rear camera. /// /// In most of the cases, it is the default configuration. rear, /// Use the front camera. /// /// Supported on all iPhones/iPads and some Android devices. front, } ================================================ FILE: packages/image_picker/image_picker_platform_interface/lib/src/types/image_options.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Specifies image-specific options for picking. class ImageOptions { /// Creates an instance with the given [maxHeight], [maxWidth], [imageQuality] /// and [requestFullMetadata]. const ImageOptions({ this.maxHeight, this.maxWidth, this.imageQuality, this.requestFullMetadata = true, }); /// The maximum width of the image, in pixels. /// /// If null, the image will only be resized if [maxHeight] is specified. final double? maxWidth; /// The maximum height of the image, in pixels. /// /// If null, the image will only be resized if [maxWidth] is specified. final double? maxHeight; /// Modifies the quality of the image, ranging from 0-100 where 100 is the /// original/max quality. /// /// Compression is only supported for certain image types such as JPEG. If /// compression is not supported for the image that is picked, a warning /// message will be logged. /// /// If null, the image will be returned with the original quality. final int? imageQuality; /// If true, requests full image metadata, which may require extra permissions /// on some platforms, (e.g., NSPhotoLibraryUsageDescription on iOS). // // Defaults to true. final bool requestFullMetadata; } ================================================ FILE: packages/image_picker/image_picker_platform_interface/lib/src/types/image_picker_options.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'types.dart'; /// Specifies options for picking a single image from the device's camera or gallery. class ImagePickerOptions { /// Creates an instance with the given [maxHeight], [maxWidth], [imageQuality], /// [referredCameraDevice] and [requestFullMetadata]. const ImagePickerOptions({ this.maxHeight, this.maxWidth, this.imageQuality, this.preferredCameraDevice = CameraDevice.rear, this.requestFullMetadata = true, }); /// The maximum width of the image, in pixels. /// /// If null, the image will only be resized if [maxHeight] is specified. final double? maxWidth; /// The maximum height of the image, in pixels. /// /// If null, the image will only be resized if [maxWidth] is specified. final double? maxHeight; /// Modifies the quality of the image, ranging from 0-100 where 100 is the /// original/max quality. /// /// Compression is only supported for certain image types such as JPEG. If /// compression is not supported for the image that is picked, a warning /// message will be logged. /// /// If null, the image will be returned with the original quality. final int? imageQuality; /// Used to specify the camera to use when the `source` is [ImageSource.camera]. /// /// Ignored if the source is not [ImageSource.camera], or the chosen camera is not /// supported on the device. Defaults to [CameraDevice.rear]. final CameraDevice preferredCameraDevice; /// If true, requests full image metadata, which may require extra permissions /// on some platforms, (e.g., NSPhotoLibraryUsageDescription on iOS). // // Defaults to true. final bool requestFullMetadata; } ================================================ FILE: packages/image_picker/image_picker_platform_interface/lib/src/types/image_source.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Specifies the source where the picked image should come from. enum ImageSource { /// Opens up the device camera, letting the user to take a new picture. camera, /// Opens the user's photo gallery. gallery, } ================================================ FILE: packages/image_picker/image_picker_platform_interface/lib/src/types/lost_data_response.dart ================================================ // Copyright 2013 The Flutter 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:cross_file/cross_file.dart'; import 'package:flutter/services.dart'; import 'types.dart'; /// The response object of [ImagePicker.getLostData]. /// /// Only applies to Android. /// See also: /// * [ImagePicker.getLostData] for more details on retrieving lost data. class LostDataResponse { /// Creates an instance with the given [file], [exception], and [type]. Any of /// the params may be null, but this is never considered to be empty. LostDataResponse({ this.file, this.exception, this.type, this.files, }); /// Initializes an instance with all member params set to null and considered /// to be empty. LostDataResponse.empty() : file = null, exception = null, type = null, _empty = true, files = null; /// Whether it is an empty response. /// /// An empty response should have [file], [exception] and [type] to be null. bool get isEmpty => _empty; /// The file that was lost in a previous [getImage], [getMultiImage] or [getVideo] call due to MainActivity being destroyed. /// /// Can be null if [exception] exists. final XFile? file; /// The exception of the last [getImage], [getMultiImage] or [getVideo]. /// /// If the last [getImage], [getMultiImage] or [getVideo] threw some exception before the MainActivity destruction, /// this variable keeps that exception. /// You should handle this exception as if the [getImage], [getMultiImage] or [getVideo] got an exception when /// the MainActivity was not destroyed. /// /// Note that it is not the exception that caused the destruction of the MainActivity. final PlatformException? exception; /// Can either be [RetrieveType.image] or [RetrieveType.video]; /// /// If the lost data is empty, this will be null. final RetrieveType? type; bool _empty = false; /// The list of files that were lost in a previous [getMultiImage] call due to MainActivity being destroyed. /// /// When [files] is populated, [file] will refer to the last item in the [files] list. /// /// Can be null if [exception] exists. final List? files; } ================================================ FILE: packages/image_picker/image_picker_platform_interface/lib/src/types/multi_image_picker_options.dart ================================================ // Copyright 2013 The Flutter 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 'image_options.dart'; /// Specifies options for picking multiple images from the device's gallery. class MultiImagePickerOptions { /// Creates an instance with the given [imageOptions]. const MultiImagePickerOptions({ this.imageOptions = const ImageOptions(), }); /// The image-specific options for picking. final ImageOptions imageOptions; } ================================================ FILE: packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/base.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter/foundation.dart' show immutable; /// The interface for a PickedFile. /// /// A PickedFile is a container that wraps the path of a selected /// file by the user and (in some platforms, like web) the bytes /// with the contents of the file. /// /// This class is a very limited subset of dart:io [File], so all /// the methods should seem familiar. @immutable abstract class PickedFileBase { /// Construct a PickedFile // ignore: avoid_unused_constructor_parameters const PickedFileBase(String path); /// Get the path of the picked file. /// /// This should only be used as a backwards-compatibility clutch /// for mobile apps, or cosmetic reasons only (to show the user /// the path they've picked). /// /// Accessing the data contained in the picked file by its path /// is platform-dependant (and won't work on web), so use the /// byte getters in the PickedFile instance instead. String get path { throw UnimplementedError('.path has not been implemented.'); } /// Synchronously read the entire file contents as a string using the given [Encoding]. /// /// By default, `encoding` is [utf8]. /// /// Throws Exception if the operation fails. Future readAsString({Encoding encoding = utf8}) { throw UnimplementedError('readAsString() has not been implemented.'); } /// Synchronously read the entire file contents as a list of bytes. /// /// Throws Exception if the operation fails. Future readAsBytes() { throw UnimplementedError('readAsBytes() has not been implemented.'); } /// Create a new independent [Stream] for the contents of this file. /// /// If `start` is present, the file will be read from byte-offset `start`. Otherwise from the beginning (index 0). /// /// If `end` is present, only up to byte-index `end` will be read. Otherwise, until end of file. /// /// In order to make sure that system resources are freed, the stream must be read to completion or the subscription on the stream must be cancelled. Stream openRead([int? start, int? end]) { throw UnimplementedError('openRead() has not been implemented.'); } } ================================================ FILE: packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/html.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:convert'; import 'dart:typed_data'; import 'package:http/http.dart' as http show readBytes; import './base.dart'; /// A PickedFile that works on web. /// /// It wraps the bytes of a selected file. class PickedFile extends PickedFileBase { /// Construct a PickedFile object from its ObjectUrl. /// /// Optionally, this can be initialized with `bytes` /// so no http requests are performed to retrieve files later. const PickedFile(this.path, {Uint8List? bytes}) : _initBytes = bytes, super(path); @override final String path; final Uint8List? _initBytes; Future get _bytes async { if (_initBytes != null) { return Future.value(UnmodifiableUint8ListView(_initBytes!)); } return http.readBytes(Uri.parse(path)); } @override Future readAsString({Encoding encoding = utf8}) async { return encoding.decode(await _bytes); } @override Future readAsBytes() async { return Future.value(await _bytes); } @override Stream openRead([int? start, int? end]) async* { final Uint8List bytes = await _bytes; yield bytes.sublist(start ?? 0, end ?? bytes.length); } } ================================================ FILE: packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/io.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import './base.dart'; /// A PickedFile backed by a dart:io File. class PickedFile extends PickedFileBase { /// Construct a PickedFile object backed by a dart:io File. PickedFile(String path) : _file = File(path), super(path); final File _file; @override String get path { return _file.path; } @override Future readAsString({Encoding encoding = utf8}) { return _file.readAsString(encoding: encoding); } @override Future readAsBytes() { return _file.readAsBytes(); } @override Stream openRead([int? start, int? end]) { return _file .openRead(start ?? 0, end) .map((List chunk) => Uint8List.fromList(chunk)); } } ================================================ FILE: packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/lost_data.dart ================================================ // Copyright 2013 The Flutter 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 '../types.dart'; /// The response object of [ImagePicker.retrieveLostData]. /// /// Only applies to Android. /// See also: /// * [ImagePicker.retrieveLostData] for more details on retrieving lost data. class LostData { /// Creates an instance with the given [file], [exception], and [type]. Any of /// the params may be null, but this is never considered to be empty. LostData({this.file, this.exception, this.type}); /// Initializes an instance with all member params set to null and considered /// to be empty. LostData.empty() : file = null, exception = null, type = null, _empty = true; /// Whether it is an empty response. /// /// An empty response should have [file], [exception] and [type] to be null. bool get isEmpty => _empty; /// The file that was lost in a previous [pickImage] or [pickVideo] call due to MainActivity being destroyed. /// /// Can be null if [exception] exists. final PickedFile? file; /// The exception of the last [pickImage] or [pickVideo]. /// /// If the last [pickImage] or [pickVideo] threw some exception before the MainActivity destruction, this variable keeps that /// exception. /// You should handle this exception as if the [pickImage] or [pickVideo] got an exception when the MainActivity was not destroyed. /// /// Note that it is not the exception that caused the destruction of the MainActivity. final PlatformException? exception; /// Can either be [RetrieveType.image] or [RetrieveType.video]; /// /// If the lost data is empty, this will be null. final RetrieveType? type; bool _empty = false; } ================================================ FILE: packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/picked_file.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'lost_data.dart'; export 'unsupported.dart' if (dart.library.html) 'html.dart' if (dart.library.io) 'io.dart'; ================================================ FILE: packages/image_picker/image_picker_platform_interface/lib/src/types/picked_file/unsupported.dart ================================================ // Copyright 2013 The Flutter 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 './base.dart'; /// A PickedFile is a cross-platform, simplified File abstraction. /// /// It wraps the bytes of a selected file, and its (platform-dependant) path. class PickedFile extends PickedFileBase { /// Construct a PickedFile object, from its `bytes`. /// /// Optionally, you may pass a `path`. See caveats in [PickedFileBase.path]. PickedFile(String path) : super(path) { throw UnimplementedError( 'PickedFile is not available in your current platform.'); } } ================================================ FILE: packages/image_picker/image_picker_platform_interface/lib/src/types/retrieve_type.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// The type of the retrieved data in a [LostDataResponse]. enum RetrieveType { /// A static picture. See [ImagePicker.pickImage]. image, /// A video. See [ImagePicker.pickVideo]. video } ================================================ FILE: packages/image_picker/image_picker_platform_interface/lib/src/types/types.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'camera_device.dart'; export 'image_options.dart'; export 'image_picker_options.dart'; export 'image_source.dart'; export 'lost_data_response.dart'; export 'multi_image_picker_options.dart'; export 'picked_file/picked_file.dart'; export 'retrieve_type.dart'; /// Denotes that an image is being picked. const String kTypeImage = 'image'; /// Denotes that a video is being picked. const String kTypeVideo = 'video'; ================================================ FILE: packages/image_picker/image_picker_platform_interface/pubspec.yaml ================================================ name: image_picker_platform_interface description: A common platform interface for the image_picker plugin. repository: https://github.com/flutter/plugins/tree/main/packages/image_picker/image_picker_platform_interface issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes version: 2.6.2 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: cross_file: ^0.3.1+1 flutter: sdk: flutter http: ^0.13.0 plugin_platform_interface: ^2.1.0 dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/image_picker/image_picker_platform_interface/test/assets/hello.txt ================================================ Hello, world! ================================================ FILE: packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart ================================================ // Copyright 2013 The Flutter 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:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'package:image_picker_platform_interface/src/method_channel/method_channel_image_picker.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$MethodChannelImagePicker', () { final MethodChannelImagePicker picker = MethodChannelImagePicker(); final List log = []; dynamic returnValue = ''; setUp(() { returnValue = ''; _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { log.add(methodCall); return returnValue; }); log.clear(); }); group('#pickImage', () { test('passes the image source argument correctly', () async { await picker.pickImage(source: ImageSource.camera); await picker.pickImage(source: ImageSource.gallery); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 1, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), ], ); }); test('passes the width and height arguments correctly', () async { await picker.pickImage(source: ImageSource.camera); await picker.pickImage( source: ImageSource.camera, maxWidth: 10.0, ); await picker.pickImage( source: ImageSource.camera, maxHeight: 10.0, ); await picker.pickImage( source: ImageSource.camera, maxWidth: 10.0, maxHeight: 20.0, ); await picker.pickImage( source: ImageSource.camera, maxWidth: 10.0, imageQuality: 70, ); await picker.pickImage( source: ImageSource.camera, maxHeight: 10.0, imageQuality: 70, ); await picker.pickImage( source: ImageSource.camera, maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, 'cameraDevice': 0, 'requestFullMetadata': true, }), ], ); }); test('does not accept an invalid imageQuality argument', () { expect( () => picker.pickImage(imageQuality: -1, source: ImageSource.gallery), throwsArgumentError, ); expect( () => picker.pickImage(imageQuality: 101, source: ImageSource.gallery), throwsArgumentError, ); expect( () => picker.pickImage(imageQuality: -1, source: ImageSource.camera), throwsArgumentError, ); expect( () => picker.pickImage(imageQuality: 101, source: ImageSource.camera), throwsArgumentError, ); }); test('does not accept a negative width or height argument', () { expect( () => picker.pickImage(source: ImageSource.camera, maxWidth: -1.0), throwsArgumentError, ); expect( () => picker.pickImage(source: ImageSource.camera, maxHeight: -1.0), throwsArgumentError, ); }); test('handles a null image path response gracefully', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( picker.channel, (MethodCall methodCall) => null); expect(await picker.pickImage(source: ImageSource.gallery), isNull); expect(await picker.pickImage(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { await picker.pickImage(source: ImageSource.camera); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), ], ); }); test('camera position can set to front', () async { await picker.pickImage( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 1, 'requestFullMetadata': true, }), ], ); }); }); group('#pickMultiImage', () { test('calls the method correctly', () async { returnValue = ['0', '1']; await picker.pickMultiImage(); expect( log, [ isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), ], ); }); test('passes the width and height arguments correctly', () async { returnValue = ['0', '1']; await picker.pickMultiImage(); await picker.pickMultiImage( maxWidth: 10.0, ); await picker.pickMultiImage( maxHeight: 10.0, ); await picker.pickMultiImage( maxWidth: 10.0, maxHeight: 20.0, ); await picker.pickMultiImage( maxWidth: 10.0, imageQuality: 70, ); await picker.pickMultiImage( maxHeight: 10.0, imageQuality: 70, ); await picker.pickMultiImage( maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ); expect( log, [ isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, 'requestFullMetadata': true, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, 'requestFullMetadata': true, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, 'requestFullMetadata': true, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, 'requestFullMetadata': true, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, 'requestFullMetadata': true, }), ], ); }); test('does not accept a negative width or height argument', () { returnValue = ['0', '1']; expect( () => picker.pickMultiImage(maxWidth: -1.0), throwsArgumentError, ); expect( () => picker.pickMultiImage(maxHeight: -1.0), throwsArgumentError, ); }); test('does not accept an invalid imageQuality argument', () { returnValue = ['0', '1']; expect( () => picker.pickMultiImage(imageQuality: -1), throwsArgumentError, ); expect( () => picker.pickMultiImage(imageQuality: 101), throwsArgumentError, ); }); test('handles a null image path response gracefully', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( picker.channel, (MethodCall methodCall) => null); expect(await picker.pickMultiImage(), isNull); expect(await picker.pickMultiImage(), isNull); }); }); group('#pickVideo', () { test('passes the image source argument correctly', () async { await picker.pickVideo(source: ImageSource.camera); await picker.pickVideo(source: ImageSource.gallery); expect( log, [ isMethodCall('pickVideo', arguments: { 'source': 0, 'cameraDevice': 0, 'maxDuration': null, }), isMethodCall('pickVideo', arguments: { 'source': 1, 'cameraDevice': 0, 'maxDuration': null, }), ], ); }); test('passes the duration argument correctly', () async { await picker.pickVideo(source: ImageSource.camera); await picker.pickVideo( source: ImageSource.camera, maxDuration: const Duration(seconds: 10), ); await picker.pickVideo( source: ImageSource.camera, maxDuration: const Duration(minutes: 1), ); await picker.pickVideo( source: ImageSource.camera, maxDuration: const Duration(hours: 1), ); expect( log, [ isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': null, 'cameraDevice': 0, }), isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': 10, 'cameraDevice': 0, }), isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': 60, 'cameraDevice': 0, }), isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': 3600, 'cameraDevice': 0, }), ], ); }); test('handles a null video path response gracefully', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( picker.channel, (MethodCall methodCall) => null); expect(await picker.pickVideo(source: ImageSource.gallery), isNull); expect(await picker.pickVideo(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { await picker.pickVideo(source: ImageSource.camera); expect( log, [ isMethodCall('pickVideo', arguments: { 'source': 0, 'cameraDevice': 0, 'maxDuration': null, }), ], ); }); test('camera position can set to front', () async { await picker.pickVideo( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front, ); expect( log, [ isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': null, 'cameraDevice': 1, }), ], ); }); }); group('#retrieveLostData', () { test('retrieveLostData get success response', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { return { 'type': 'image', 'path': '/example/path', }; }); final LostData response = await picker.retrieveLostData(); expect(response.type, RetrieveType.image); expect(response.file, isNotNull); expect(response.file!.path, '/example/path'); }); test('retrieveLostData get error response', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { return { 'type': 'video', 'errorCode': 'test_error_code', 'errorMessage': 'test_error_message', }; }); final LostData response = await picker.retrieveLostData(); expect(response.type, RetrieveType.video); expect(response.exception, isNotNull); expect(response.exception!.code, 'test_error_code'); expect(response.exception!.message, 'test_error_message'); }); test('retrieveLostData get null response', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { return null; }); expect((await picker.retrieveLostData()).isEmpty, true); }); test('retrieveLostData get both path and error should throw', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { return { 'type': 'video', 'errorCode': 'test_error_code', 'errorMessage': 'test_error_message', 'path': '/example/path', }; }); expect(picker.retrieveLostData(), throwsAssertionError); }); }); group('#getImage', () { test('passes the image source argument correctly', () async { await picker.getImage(source: ImageSource.camera); await picker.getImage(source: ImageSource.gallery); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 1, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), ], ); }); test('passes the width and height arguments correctly', () async { await picker.getImage(source: ImageSource.camera); await picker.getImage( source: ImageSource.camera, maxWidth: 10.0, ); await picker.getImage( source: ImageSource.camera, maxHeight: 10.0, ); await picker.getImage( source: ImageSource.camera, maxWidth: 10.0, maxHeight: 20.0, ); await picker.getImage( source: ImageSource.camera, maxWidth: 10.0, imageQuality: 70, ); await picker.getImage( source: ImageSource.camera, maxHeight: 10.0, imageQuality: 70, ); await picker.getImage( source: ImageSource.camera, maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, 'cameraDevice': 0, 'requestFullMetadata': true, }), ], ); }); test('does not accept an invalid imageQuality argument', () { expect( () => picker.getImage(imageQuality: -1, source: ImageSource.gallery), throwsArgumentError, ); expect( () => picker.getImage(imageQuality: 101, source: ImageSource.gallery), throwsArgumentError, ); expect( () => picker.getImage(imageQuality: -1, source: ImageSource.camera), throwsArgumentError, ); expect( () => picker.getImage(imageQuality: 101, source: ImageSource.camera), throwsArgumentError, ); }); test('does not accept a negative width or height argument', () { expect( () => picker.getImage(source: ImageSource.camera, maxWidth: -1.0), throwsArgumentError, ); expect( () => picker.getImage(source: ImageSource.camera, maxHeight: -1.0), throwsArgumentError, ); }); test('handles a null image path response gracefully', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( picker.channel, (MethodCall methodCall) => null); expect(await picker.getImage(source: ImageSource.gallery), isNull); expect(await picker.getImage(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { await picker.getImage(source: ImageSource.camera); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), ], ); }); test('camera position can set to front', () async { await picker.getImage( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 1, 'requestFullMetadata': true, }), ], ); }); }); group('#getMultiImage', () { test('calls the method correctly', () async { returnValue = ['0', '1']; await picker.getMultiImage(); expect( log, [ isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), ], ); }); test('passes the width and height arguments correctly', () async { returnValue = ['0', '1']; await picker.getMultiImage(); await picker.getMultiImage( maxWidth: 10.0, ); await picker.getMultiImage( maxHeight: 10.0, ); await picker.getMultiImage( maxWidth: 10.0, maxHeight: 20.0, ); await picker.getMultiImage( maxWidth: 10.0, imageQuality: 70, ); await picker.getMultiImage( maxHeight: 10.0, imageQuality: 70, ); await picker.getMultiImage( maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ); expect( log, [ isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, 'requestFullMetadata': true, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, 'requestFullMetadata': true, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, 'requestFullMetadata': true, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, 'requestFullMetadata': true, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, 'requestFullMetadata': true, }), ], ); }); test('does not accept a negative width or height argument', () { returnValue = ['0', '1']; expect( () => picker.getMultiImage(maxWidth: -1.0), throwsArgumentError, ); expect( () => picker.getMultiImage(maxHeight: -1.0), throwsArgumentError, ); }); test('does not accept an invalid imageQuality argument', () { returnValue = ['0', '1']; expect( () => picker.getMultiImage(imageQuality: -1), throwsArgumentError, ); expect( () => picker.getMultiImage(imageQuality: 101), throwsArgumentError, ); }); test('handles a null image path response gracefully', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( picker.channel, (MethodCall methodCall) => null); expect(await picker.getMultiImage(), isNull); expect(await picker.getMultiImage(), isNull); }); }); group('#getVideo', () { test('passes the image source argument correctly', () async { await picker.getVideo(source: ImageSource.camera); await picker.getVideo(source: ImageSource.gallery); expect( log, [ isMethodCall('pickVideo', arguments: { 'source': 0, 'cameraDevice': 0, 'maxDuration': null, }), isMethodCall('pickVideo', arguments: { 'source': 1, 'cameraDevice': 0, 'maxDuration': null, }), ], ); }); test('passes the duration argument correctly', () async { await picker.getVideo(source: ImageSource.camera); await picker.getVideo( source: ImageSource.camera, maxDuration: const Duration(seconds: 10), ); await picker.getVideo( source: ImageSource.camera, maxDuration: const Duration(minutes: 1), ); await picker.getVideo( source: ImageSource.camera, maxDuration: const Duration(hours: 1), ); expect( log, [ isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': null, 'cameraDevice': 0, }), isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': 10, 'cameraDevice': 0, }), isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': 60, 'cameraDevice': 0, }), isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': 3600, 'cameraDevice': 0, }), ], ); }); test('handles a null video path response gracefully', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( picker.channel, (MethodCall methodCall) => null); expect(await picker.getVideo(source: ImageSource.gallery), isNull); expect(await picker.getVideo(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { await picker.getVideo(source: ImageSource.camera); expect( log, [ isMethodCall('pickVideo', arguments: { 'source': 0, 'cameraDevice': 0, 'maxDuration': null, }), ], ); }); test('camera position can set to front', () async { await picker.getVideo( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front, ); expect( log, [ isMethodCall('pickVideo', arguments: { 'source': 0, 'maxDuration': null, 'cameraDevice': 1, }), ], ); }); }); group('#getLostData', () { test('getLostData get success response', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { return { 'type': 'image', 'path': '/example/path', }; }); final LostDataResponse response = await picker.getLostData(); expect(response.type, RetrieveType.image); expect(response.file, isNotNull); expect(response.file!.path, '/example/path'); }); test('getLostData should successfully retrieve multiple files', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { return { 'type': 'image', 'path': '/example/path1', 'pathList': ['/example/path0', '/example/path1'], }; }); final LostDataResponse response = await picker.getLostData(); expect(response.type, RetrieveType.image); expect(response.file, isNotNull); expect(response.file!.path, '/example/path1'); expect(response.files!.first.path, '/example/path0'); expect(response.files!.length, 2); }); test('getLostData get error response', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { return { 'type': 'video', 'errorCode': 'test_error_code', 'errorMessage': 'test_error_message', }; }); final LostDataResponse response = await picker.getLostData(); expect(response.type, RetrieveType.video); expect(response.exception, isNotNull); expect(response.exception!.code, 'test_error_code'); expect(response.exception!.message, 'test_error_message'); }); test('getLostData get null response', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { return null; }); expect((await picker.getLostData()).isEmpty, true); }); test('getLostData get both path and error should throw', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(picker.channel, (MethodCall methodCall) async { return { 'type': 'video', 'errorCode': 'test_error_code', 'errorMessage': 'test_error_message', 'path': '/example/path', }; }); expect(picker.getLostData(), throwsAssertionError); }); }); group('#getImageFromSource', () { test('passes the image source argument correctly', () async { await picker.getImageFromSource(source: ImageSource.camera); await picker.getImageFromSource(source: ImageSource.gallery); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 1, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), ], ); }); test('passes the width and height arguments correctly', () async { await picker.getImageFromSource(source: ImageSource.camera); await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(maxWidth: 10.0), ); await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(maxHeight: 10.0), ); await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions( maxWidth: 10.0, maxHeight: 20.0, ), ); await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions( maxWidth: 10.0, imageQuality: 70, ), ); await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions( maxHeight: 10.0, imageQuality: 70, ), ); await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions( maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ), ); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, 'cameraDevice': 0, 'requestFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, 'cameraDevice': 0, 'requestFullMetadata': true, }), ], ); }); test('does not accept an invalid imageQuality argument', () { expect( () => picker.getImageFromSource( source: ImageSource.gallery, options: const ImagePickerOptions(imageQuality: -1), ), throwsArgumentError, ); expect( () => picker.getImageFromSource( source: ImageSource.gallery, options: const ImagePickerOptions(imageQuality: 101), ), throwsArgumentError, ); expect( () => picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(imageQuality: -1), ), throwsArgumentError, ); expect( () => picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(imageQuality: 101), ), throwsArgumentError, ); }); test('does not accept a negative width or height argument', () { expect( () => picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(maxWidth: -1.0), ), throwsArgumentError, ); expect( () => picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(maxHeight: -1.0), ), throwsArgumentError, ); }); test('handles a null image path response gracefully', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( picker.channel, (MethodCall methodCall) => null); expect(await picker.getImageFromSource(source: ImageSource.gallery), isNull); expect(await picker.getImageFromSource(source: ImageSource.camera), isNull); }); test('camera position defaults to back', () async { await picker.getImageFromSource(source: ImageSource.camera); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': true, }), ], ); }); test('camera position can set to front', () async { await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions( preferredCameraDevice: CameraDevice.front, ), ); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 1, 'requestFullMetadata': true, }), ], ); }); test('passes the full metadata argument correctly', () async { await picker.getImageFromSource( source: ImageSource.camera, options: const ImagePickerOptions(requestFullMetadata: false), ); expect( log, [ isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, 'requestFullMetadata': false, }), ], ); }); }); group('#getMultiImageWithOptions', () { test('calls the method correctly', () async { returnValue = ['0', '1']; await picker.getMultiImageWithOptions(); expect( log, [ isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), ], ); }); test('passes the width, height and imageQuality arguments correctly', () async { returnValue = ['0', '1']; await picker.getMultiImageWithOptions(); await picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions(maxWidth: 10.0), ), ); await picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions(maxHeight: 10.0), ), ); await picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions( maxWidth: 10.0, maxHeight: 20.0, ), ), ); await picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions( maxWidth: 10.0, imageQuality: 70, ), ), ); await picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions( maxHeight: 10.0, imageQuality: 70, ), ), ); await picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions( maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70, ), ), ); expect( log, [ isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, 'requestFullMetadata': true, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, 'requestFullMetadata': true, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, 'requestFullMetadata': true, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, 'requestFullMetadata': true, }), isMethodCall('pickMultiImage', arguments: { 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, 'requestFullMetadata': true, }), ], ); }); test('does not accept a negative width or height argument', () { returnValue = ['0', '1']; expect( () => picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions(maxWidth: -1.0), ), ), throwsArgumentError, ); expect( () => picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions(maxHeight: -1.0), ), ), throwsArgumentError, ); }); test('does not accept an invalid imageQuality argument', () { returnValue = ['0', '1']; expect( () => picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions(imageQuality: -1), ), ), throwsArgumentError, ); expect( () => picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions(imageQuality: 101), ), ), throwsArgumentError, ); }); test('handles a null image path response gracefully', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( picker.channel, (MethodCall methodCall) => null); expect(await picker.getMultiImage(), isNull); expect(await picker.getMultiImage(), isNull); }); test('Request full metadata argument defaults to true', () async { returnValue = ['0', '1']; await picker.getMultiImageWithOptions(); expect( log, [ isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, }), ], ); }); test('passes the request full metadata argument correctly', () async { returnValue = ['0', '1']; await picker.getMultiImageWithOptions( options: const MultiImagePickerOptions( imageOptions: ImageOptions(requestFullMetadata: false), ), ); expect( log, [ isMethodCall('pickMultiImage', arguments: { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': false, }), ], ); }); }); }); } /// 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; ================================================ FILE: packages/image_picker/image_picker_platform_interface/test/picked_file_html_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @TestOn('chrome') // Uses web-only Flutter SDK import 'dart:convert'; import 'dart:html' as html; import 'package:flutter_test/flutter_test.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; const String expectedStringContents = 'Hello, world!'; final List bytes = utf8.encode(expectedStringContents); final html.File textFile = html.File(>[bytes], 'hello.txt'); final String textFileUrl = html.Url.createObjectUrl(textFile); void main() { group('Create with an objectUrl', () { final PickedFile pickedFile = PickedFile(textFileUrl); test('Can be read as a string', () async { expect(await pickedFile.readAsString(), equals(expectedStringContents)); }); test('Can be read as bytes', () async { expect(await pickedFile.readAsBytes(), equals(bytes)); }); test('Can be read as a stream', () async { expect(await pickedFile.openRead().first, equals(bytes)); }); test('Stream can be sliced', () async { expect( await pickedFile.openRead(2, 5).first, equals(bytes.sublist(2, 5))); }); }); } ================================================ FILE: packages/image_picker/image_picker_platform_interface/test/picked_file_io_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @TestOn('vm') // Uses dart:io import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; final String pathPrefix = Directory.current.path.endsWith('test') ? './assets/' : './test/assets/'; final String path = '${pathPrefix}hello.txt'; const String expectedStringContents = 'Hello, world!'; final Uint8List bytes = Uint8List.fromList(utf8.encode(expectedStringContents)); final File textFile = File(path); final String textFilePath = textFile.path; void main() { group('Create with an objectUrl', () { final PickedFile pickedFile = PickedFile(textFilePath); test('Can be read as a string', () async { expect(await pickedFile.readAsString(), equals(expectedStringContents)); }); test('Can be read as bytes', () async { expect(await pickedFile.readAsBytes(), equals(bytes)); }); test('Can be read as a stream', () async { expect(await pickedFile.openRead().first, equals(bytes)); }); test('Stream can be sliced', () async { expect( await pickedFile.openRead(2, 5).first, equals(bytes.sublist(2, 5))); }); }); } ================================================ FILE: packages/image_picker/image_picker_windows/AUTHORS ================================================ # 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. Alexandre Zollinger Chohfi ================================================ FILE: packages/image_picker/image_picker_windows/CHANGELOG.md ================================================ ## 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. * Updates minimum Flutter version to 2.10. ## 0.1.0+2 * Minor fixes for new analysis options. ## 0.1.0+1 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.1.0 * Initial Windows support. ================================================ FILE: packages/image_picker/image_picker_windows/LICENSE ================================================ 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. ================================================ FILE: packages/image_picker/image_picker_windows/README.md ================================================ # image\_picker\_windows A Windows implementation of [`image_picker`][1]. ### pickImage() The arguments `source`, `maxWidth`, `maxHeight`, `imageQuality`, and `preferredCameraDevice` are not supported on Windows. ### pickVideo() The arguments `source`, `preferredCameraDevice`, and `maxDuration` are not supported on Windows. ## Usage ### Import the package This package is not yet [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin), which means you need to add not only the `image_picker`, as well as the `image_picker_windows`. ================================================ FILE: packages/image_picker/image_picker_windows/example/README.md ================================================ # 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. ================================================ FILE: packages/image_picker/image_picker_windows/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'package:video_player/video_player.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: 'Image Picker Demo', home: MyHomePage(title: 'Image Picker Example'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, this.title}) : super(key: key); final String? title; @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { List? _imageFileList; // This must be called from within a setState() callback void _setImageFileListFromFile(PickedFile? value) { _imageFileList = value == null ? null : [value]; } dynamic _pickImageError; bool _isVideo = false; VideoPlayerController? _controller; VideoPlayerController? _toBeDisposed; String? _retrieveDataError; final ImagePickerPlatform _picker = ImagePickerPlatform.instance; final TextEditingController maxWidthController = TextEditingController(); final TextEditingController maxHeightController = TextEditingController(); final TextEditingController qualityController = TextEditingController(); Future _playVideo(PickedFile? file) async { if (file != null && mounted) { await _disposeVideoController(); final VideoPlayerController controller = VideoPlayerController.file(File(file.path)); _controller = controller; await controller.setVolume(1.0); await controller.initialize(); await controller.setLooping(true); await controller.play(); setState(() {}); } } Future _handleMultiImagePicked(BuildContext context) async { await _displayPickImageDialog(context, (double? maxWidth, double? maxHeight, int? quality) async { try { final List? pickedFileList = await _picker.pickMultiImage( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: quality, ); setState(() { _imageFileList = pickedFileList; }); } catch (e) { setState(() { _pickImageError = e; }); } }); } Future _handleSingleImagePicked( BuildContext context, ImageSource source) async { await _displayPickImageDialog(context, (double? maxWidth, double? maxHeight, int? quality) async { try { final PickedFile? pickedFile = await _picker.pickImage( source: source, maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: quality, ); setState(() { _setImageFileListFromFile(pickedFile); }); } catch (e) { setState(() { _pickImageError = e; }); } }); } Future _onImageButtonPressed(ImageSource source, {required BuildContext context, bool isMultiImage = false}) async { if (_controller != null) { await _controller!.setVolume(0.0); } 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); } } } @override void deactivate() { if (_controller != null) { _controller!.setVolume(0.0); _controller!.pause(); } super.deactivate(); } @override void dispose() { _disposeVideoController(); maxWidthController.dispose(); maxHeightController.dispose(); qualityController.dispose(); super.dispose(); } Future _disposeVideoController() async { if (_toBeDisposed != null) { await _toBeDisposed!.dispose(); } _toBeDisposed = _controller; _controller = null; } Widget _previewVideo() { final Text? retrieveError = _getRetrieveErrorWidget(); if (retrieveError != null) { return retrieveError; } if (_controller == null) { return const Text( 'You have not yet picked a video', textAlign: TextAlign.center, ); } return Padding( padding: const EdgeInsets.all(10.0), child: AspectRatioVideo(_controller), ); } Widget _previewImages() { final Text? retrieveError = _getRetrieveErrorWidget(); if (retrieveError != null) { return retrieveError; } if (_imageFileList != null) { return Semantics( label: 'image_picker_example_picked_images', child: ListView.builder( key: UniqueKey(), itemBuilder: (BuildContext context, int index) { return Semantics( label: 'image_picker_example_picked_image', child: Image.file(File(_imageFileList![index].path)), ); }, itemCount: _imageFileList!.length, ), ); } else if (_pickImageError != null) { return Text( 'Pick image error: $_pickImageError', textAlign: TextAlign.center, ); } else { return const Text( 'You have not yet picked an image.', textAlign: TextAlign.center, ); } } Widget _handlePreview() { if (_isVideo) { return _previewVideo(); } else { return _previewImages(); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title!), ), body: Center( child: _handlePreview(), ), floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ Semantics( label: 'image_picker_example_from_gallery', child: FloatingActionButton( onPressed: () { _isVideo = false; _onImageButtonPressed(ImageSource.gallery, context: context); }, heroTag: 'image0', tooltip: 'Pick Image from gallery', child: const Icon(Icons.photo), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( onPressed: () { _isVideo = false; _onImageButtonPressed( ImageSource.gallery, context: context, isMultiImage: true, ); }, heroTag: 'image1', tooltip: 'Pick Multiple Image from gallery', child: const Icon(Icons.photo_library), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( onPressed: () { _isVideo = false; _onImageButtonPressed(ImageSource.camera, context: context); }, heroTag: 'image2', tooltip: 'Take a Photo', child: const Icon(Icons.camera_alt), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( backgroundColor: Colors.red, onPressed: () { _isVideo = true; _onImageButtonPressed(ImageSource.gallery, context: context); }, heroTag: 'video0', tooltip: 'Pick Video from gallery', child: const Icon(Icons.video_library), ), ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( backgroundColor: Colors.red, onPressed: () { _isVideo = true; _onImageButtonPressed(ImageSource.camera, context: context); }, heroTag: 'video1', tooltip: 'Take a Video', child: const Icon(Icons.videocam), ), ), ], ), ); } Text? _getRetrieveErrorWidget() { if (_retrieveDataError != null) { final Text result = Text(_retrieveDataError!); _retrieveDataError = null; return result; } return null; } Future _displayPickImageDialog( BuildContext context, OnPickImageCallback onPick) async { return showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('Add optional parameters'), content: Column( children: [ TextField( controller: maxWidthController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration( hintText: 'Enter maxWidth if desired'), ), TextField( controller: maxHeightController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration( hintText: 'Enter maxHeight if desired'), ), TextField( controller: qualityController, keyboardType: TextInputType.number, decoration: const InputDecoration( hintText: 'Enter quality if desired'), ), ], ), actions: [ TextButton( child: const Text('CANCEL'), onPressed: () { Navigator.of(context).pop(); }, ), TextButton( child: const Text('PICK'), onPressed: () { final double? width = maxWidthController.text.isNotEmpty ? double.parse(maxWidthController.text) : null; final double? height = maxHeightController.text.isNotEmpty ? double.parse(maxHeightController.text) : null; final int? quality = qualityController.text.isNotEmpty ? int.parse(qualityController.text) : null; onPick(width, height, quality); Navigator.of(context).pop(); }), ], ); }); } } typedef OnPickImageCallback = void Function( double? maxWidth, double? maxHeight, int? quality); class AspectRatioVideo extends StatefulWidget { const AspectRatioVideo(this.controller, {Key? key}) : super(key: key); final VideoPlayerController? controller; @override AspectRatioVideoState createState() => AspectRatioVideoState(); } class AspectRatioVideoState extends State { VideoPlayerController? get controller => widget.controller; bool initialized = false; void _onVideoControllerUpdate() { if (!mounted) { return; } if (initialized != controller!.value.isInitialized) { initialized = controller!.value.isInitialized; setState(() {}); } } @override void initState() { super.initState(); controller!.addListener(_onVideoControllerUpdate); } @override void dispose() { controller!.removeListener(_onVideoControllerUpdate); super.dispose(); } @override Widget build(BuildContext context) { if (initialized) { return Center( child: AspectRatio( aspectRatio: controller!.value.aspectRatio, child: VideoPlayer(controller!), ), ); } else { return Container(); } } } ================================================ FILE: packages/image_picker/image_picker_windows/example/pubspec.yaml ================================================ name: example description: Example for image_picker_windows implementation. publish_to: 'none' version: 1.0.0 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter image_picker_platform_interface: ^2.4.3 image_picker_windows: # When depending on this package from a real application you should use: # image_picker_windows: ^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: .. video_player: ^2.1.4 dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/image_picker/image_picker_windows/example/windows/.gitignore ================================================ flutter/ephemeral/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: packages/image_picker/image_picker_windows/example/windows/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) project(example LANGUAGES CXX) set(BINARY_NAME "example") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" CACHE STRING "" FORCE) else() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() endif() set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") # Use Unicode for all projects. add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") target_compile_options(${TARGET} PRIVATE /EHsc) target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build add_subdirectory("runner") # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # Support files are copied into place next to the executable, so that it can # run in place. This is done instead of making a separate bundle (as on Linux) # so that building and running from within Visual Studio will work. set(BUILD_BUNDLE_DIR "$") # Make the "install" step default, as it's required to run. set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" CONFIGURATIONS Profile;Release COMPONENT Runtime) ================================================ FILE: packages/image_picker/image_picker_windows/example/windows/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "flutter_export.h" "flutter_windows.h" "flutter_messenger.h" "flutter_plugin_registrar.h" "flutter_texture_registrar.h" ) list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") add_dependencies(flutter flutter_assemble) # === Wrapper === list(APPEND CPP_WRAPPER_SOURCES_CORE "core_implementations.cc" "standard_codec.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_PLUGIN "plugin_registrar.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_APP "flutter_engine.cc" "flutter_view_controller.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") # Wrapper sources needed for a plugin. add_library(flutter_wrapper_plugin STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ) apply_standard_settings(flutter_wrapper_plugin) set_target_properties(flutter_wrapper_plugin PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(flutter_wrapper_plugin PROPERTIES CXX_VISIBILITY_PRESET hidden) target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) target_include_directories(flutter_wrapper_plugin PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_plugin flutter_assemble) # Wrapper sources needed for the runner. add_library(flutter_wrapper_app STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_APP} ) apply_standard_settings(flutter_wrapper_app) target_link_libraries(flutter_wrapper_app PUBLIC flutter) target_include_directories(flutter_wrapper_app PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_app flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" windows-x64 $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ) ================================================ FILE: packages/image_picker/image_picker_windows/example/windows/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/image_picker/image_picker_windows/example/windows/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) project(runner LANGUAGES CXX) add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) apply_standard_settings(${BINARY_NAME}) target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") add_dependencies(${BINARY_NAME} flutter_assemble) ================================================ FILE: packages/image_picker/image_picker_windows/example/windows/runner/Runner.rc ================================================ // Microsoft Visual C++ generated resource script. // #pragma code_page(65001) #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APP_ICON ICON "resources\\app_icon.ico" ///////////////////////////////////////////////////////////////////////////// // // Version // #ifdef FLUTTER_BUILD_NUMBER #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER #else #define VERSION_AS_NUMBER 1,0,0 #endif #ifdef FLUTTER_BUILD_NAME #define VERSION_AS_STRING #FLUTTER_BUILD_NAME #else #define VERSION_AS_STRING "1.0.0" #endif VS_VERSION_INFO VERSIONINFO FILEVERSION VERSION_AS_NUMBER PRODUCTVERSION VERSION_AS_NUMBER FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "com.example" "\0" VALUE "FileDescription", "example" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "example" "\0" VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0" VALUE "OriginalFilename", "example.exe" "\0" VALUE "ProductName", "example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: packages/image_picker/image_picker_windows/example/windows/runner/flutter_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "flutter_window.h" #include #include "flutter/generated_plugin_registrant.h" FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {} FlutterWindow::~FlutterWindow() {} bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opportunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) { return *result; } } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); } ================================================ FILE: packages/image_picker/image_picker_windows/example/windows/runner/flutter_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_ #include #include #include #include "win32_window.h" // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { public: // Creates a new FlutterWindow hosting a Flutter view running |project|. explicit FlutterWindow(const flutter::DartProject& project); virtual ~FlutterWindow(); protected: // Win32Window: bool OnCreate() override; void OnDestroy() override; LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; private: // The project to run. flutter::DartProject project_; // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; }; #endif // RUNNER_FLUTTER_WINDOW_H_ ================================================ FILE: packages/image_picker/image_picker_windows/example/windows/runner/main.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include "flutter_window.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t* command_line, _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { CreateAndAttachConsole(); } // Initialize COM, so that it is available for use in the library and/or // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); flutter::DartProject project(L"data"); std::vector command_line_arguments = GetCommandLineArguments(); project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.CreateAndShow(L"example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); ::MSG msg; while (::GetMessage(&msg, nullptr, 0, 0)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } ::CoUninitialize(); return EXIT_SUCCESS; } ================================================ FILE: packages/image_picker/image_picker_windows/example/windows/runner/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Runner.rc // #define IDI_APP_ICON 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: packages/image_picker/image_picker_windows/example/windows/runner/runner.exe.manifest ================================================ PerMonitorV2 ================================================ FILE: packages/image_picker/image_picker_windows/example/windows/runner/utils.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "utils.h" #include #include #include #include #include void CreateAndAttachConsole() { if (::AllocConsole()) { FILE* unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } if (freopen_s(&unused, "CONOUT$", "w", stderr)) { _dup2(_fileno(stdout), 2); } std::ios::sync_with_stdio(); FlutterDesktopResyncOutputStreams(); } } std::vector GetCommandLineArguments() { // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. int argc; wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); if (argv == nullptr) { return std::vector(); } std::vector command_line_arguments; // Skip the first argument as it's the binary name. for (int i = 1; i < argc; i++) { command_line_arguments.push_back(Utf8FromUtf16(argv[i])); } ::LocalFree(argv); return command_line_arguments; } std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); } int target_length = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, nullptr, 0, nullptr, nullptr); if (target_length == 0) { return std::string(); } std::string utf8_string; utf8_string.resize(target_length); int converted_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); } return utf8_string; } ================================================ FILE: packages/image_picker/image_picker_windows/example/windows/runner/utils.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_UTILS_H_ #define RUNNER_UTILS_H_ #include #include // Creates a console for the process, and redirects stdout and stderr to // it for both the runner and the Flutter library. void CreateAndAttachConsole(); // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string // encoded in UTF-8. Returns an empty std::string on failure. std::string Utf8FromUtf16(const wchar_t* utf16_string); // Gets the command line arguments passed in as a std::vector, // encoded in UTF-8. Returns an empty std::vector on failure. std::vector GetCommandLineArguments(); #endif // RUNNER_UTILS_H_ ================================================ FILE: packages/image_picker/image_picker_windows/example/windows/runner/win32_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "win32_window.h" #include #include "resource.h" namespace { constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); // Scale helper to convert logical scaler values to physical using passed in // scale factor int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); if (!user32_module) { return; } auto enable_non_client_dpi_scaling = reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); FreeLibrary(user32_module); } } } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; // Returns the singleton registar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; } // Returns the name of the window class, registering the class if it hasn't // previously been registered. const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; bool class_registered_ = false; }; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; RegisterClass(&window_class); class_registered_ = true; } return kWindowClassName; } void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, const Size& size) { Destroy(); const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), static_cast(origin.y)}; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } return OnCreate(); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { case WM_DESTROY: window_handle_ = nullptr; Destroy(); if (quit_on_close_) { PostQuitMessage(0); } return 0; case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left; LONG newHeight = newRectSize->bottom - newRectSize->top; SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_SIZE: { RECT rect = GetClientArea(); if (child_content_ != nullptr) { // Size and position the child window. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); } return 0; } case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_); } return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } void Win32Window::Destroy() { OnDestroy(); if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, true); SetFocus(child_content_); } RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } void Win32Window::OnDestroy() { // No-op; provided for subclasses. } ================================================ FILE: packages/image_picker/image_picker_windows/example/windows/runner/win32_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_ #include #include #include #include // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { public: struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) : width(width), height(height) {} }; Win32Window(); virtual ~Win32Window(); // Creates and shows a win32 window with |title| and position and size using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a // consistent size to will treat the width height passed in to this function // as logical pixels and scale to appropriate for the default monitor. Returns // true if the window was created successfully. bool CreateAndShow(const std::wstring& title, const Point& origin, const Size& size); // Release OS resources associated with window. void Destroy(); // Inserts |content| into the window tree. void SetChildContent(HWND content); // Returns the backing Window handle to enable clients to set icon and other // window properties. Returns nullptr if the window has been destroyed. HWND GetHandle(); // If true, closing this window will quit the application. void SetQuitOnClose(bool quit_on_close); // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Called when CreateAndShow is called, allowing subclass window-related // setup. Subclasses should return false if setup fails. virtual bool OnCreate(); // Called when Destroy is called. virtual void OnDestroy(); private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically // responsponds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; bool quit_on_close_ = false; // window handle for top level window. HWND window_handle_ = nullptr; // window handle for hosted content. HWND child_content_ = nullptr; }; #endif // RUNNER_WIN32_WINDOW_H_ ================================================ FILE: packages/image_picker/image_picker_windows/lib/image_picker_windows.dart ================================================ // Copyright 2013 The Flutter 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:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:file_selector_windows/file_selector_windows.dart'; import 'package:flutter/foundation.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; /// The Windows implementation of [ImagePickerPlatform]. /// /// This class implements the `package:image_picker` functionality for /// Windows. class ImagePickerWindows extends ImagePickerPlatform { /// Constructs a ImagePickerWindows. ImagePickerWindows(); /// List of image extensions used when picking images @visibleForTesting static const List imageFormats = [ 'jpg', 'jpeg', 'png', 'bmp', 'webp', 'gif', 'tif', 'tiff', 'apng' ]; /// List of video extensions used when picking videos @visibleForTesting static const List videoFormats = [ 'mov', 'wmv', 'mkv', 'mp4', 'webm', 'avi', 'mpeg', 'mpg' ]; /// The file selector used to prompt the user to select images or videos. @visibleForTesting static FileSelectorPlatform fileSelector = FileSelectorWindows(); /// Registers this class as the default instance of [ImagePickerPlatform]. static void registerWith() { ImagePickerPlatform.instance = ImagePickerWindows(); } // `maxWidth`, `maxHeight`, `imageQuality` and `preferredCameraDevice` // arguments are not supported on Windows. If any of these arguments // is supplied, it'll be silently ignored by the Windows version of // the plugin. `source` is not implemented for `ImageSource.camera` // and will throw an exception. @override Future pickImage({ required ImageSource source, double? maxWidth, double? maxHeight, int? imageQuality, CameraDevice preferredCameraDevice = CameraDevice.rear, }) async { final XFile? file = await getImage( source: source, maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, preferredCameraDevice: preferredCameraDevice); if (file != null) { return PickedFile(file.path); } return null; } // `preferredCameraDevice` and `maxDuration` arguments are not // supported on Windows. If any of these arguments is supplied, // it'll be silently ignored by the Windows version of the plugin. // `source` is not implemented for `ImageSource.camera` and will // throw an exception. @override Future pickVideo({ required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, Duration? maxDuration, }) async { final XFile? file = await getVideo( source: source, preferredCameraDevice: preferredCameraDevice, maxDuration: maxDuration); if (file != null) { return PickedFile(file.path); } return null; } // `maxWidth`, `maxHeight`, `imageQuality`, and `preferredCameraDevice` // arguments are not supported on Windows. If any of these arguments // is supplied, it'll be silently ignored by the Windows version // of the plugin. `source` is not implemented for `ImageSource.camera` // and will throw an exception. @override Future getImage({ required ImageSource source, double? maxWidth, double? maxHeight, int? imageQuality, CameraDevice preferredCameraDevice = CameraDevice.rear, }) async { if (source != ImageSource.gallery) { // TODO(azchohfi): Support ImageSource.camera. // See https://github.com/flutter/flutter/issues/102115 throw UnimplementedError( 'ImageSource.gallery is currently the only supported source on Windows'); } const XTypeGroup typeGroup = XTypeGroup(label: 'images', extensions: imageFormats); final XFile? file = await fileSelector .openFile(acceptedTypeGroups: [typeGroup]); return file; } // `preferredCameraDevice` and `maxDuration` arguments are not // supported on Windows. If any of these arguments is supplied, // it'll be silently ignored by the Windows version of the plugin. // `source` is not implemented for `ImageSource.camera` and will // throw an exception. @override Future getVideo({ required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, Duration? maxDuration, }) async { if (source != ImageSource.gallery) { // TODO(azchohfi): Support ImageSource.camera. // See https://github.com/flutter/flutter/issues/102115 throw UnimplementedError( 'ImageSource.gallery is currently the only supported source on Windows'); } const XTypeGroup typeGroup = XTypeGroup(label: 'videos', extensions: videoFormats); final XFile? file = await fileSelector .openFile(acceptedTypeGroups: [typeGroup]); return file; } // `maxWidth`, `maxHeight`, and `imageQuality` arguments are not // supported on Windows. If any of these arguments is supplied, // it'll be silently ignored by the Windows version of the plugin. @override Future> getMultiImage({ double? maxWidth, double? maxHeight, int? imageQuality, }) async { const XTypeGroup typeGroup = XTypeGroup(label: 'images', extensions: imageFormats); final List files = await fileSelector .openFiles(acceptedTypeGroups: [typeGroup]); return files; } } ================================================ FILE: packages/image_picker/image_picker_windows/pubspec.yaml ================================================ 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+4 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: image_picker platforms: windows: dartPluginClass: ImagePickerWindows dependencies: file_selector_platform_interface: ^2.2.0 file_selector_windows: ^0.8.2 flutter: sdk: flutter image_picker_platform_interface: ^2.4.3 dev_dependencies: build_runner: ^2.1.5 flutter_test: sdk: flutter mockito: ^5.0.16 ================================================ FILE: packages/image_picker/image_picker_windows/test/image_picker_windows_test.dart ================================================ // Copyright 2013 The Flutter 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_test/flutter_test.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'package:image_picker_windows/image_picker_windows.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'image_picker_windows_test.mocks.dart'; @GenerateMocks([FileSelectorPlatform]) 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; setUp(() { mockFileSelectorPlatform = MockFileSelectorPlatform(); when(mockFileSelectorPlatform.openFile( acceptedTypeGroups: anyNamed('acceptedTypeGroups'))) .thenAnswer((_) async => null); when(mockFileSelectorPlatform.openFiles( acceptedTypeGroups: anyNamed('acceptedTypeGroups'))) .thenAnswer((_) async => List.empty()); ImagePickerWindows.fileSelector = mockFileSelectorPlatform; }); test('registered instance', () { ImagePickerWindows.registerWith(); expect(ImagePickerPlatform.instance, isA()); }); group('images', () { test('pickImage passes the accepted type groups correctly', () async { await plugin.pickImage(source: ImageSource.gallery); final VerificationResult result = verify( mockFileSelectorPlatform.openFile( acceptedTypeGroups: captureAnyNamed('acceptedTypeGroups'))); expect(capturedTypeGroups(result)[0].extensions, ImagePickerWindows.imageFormats); }); test('pickImage throws UnimplementedError when source is camera', () async { expect(() async => plugin.pickImage(source: ImageSource.camera), throwsA(isA())); }); test('getImage passes the accepted type groups correctly', () async { await plugin.getImage(source: ImageSource.gallery); final VerificationResult result = verify( mockFileSelectorPlatform.openFile( acceptedTypeGroups: captureAnyNamed('acceptedTypeGroups'))); expect(capturedTypeGroups(result)[0].extensions, ImagePickerWindows.imageFormats); }); test('getImage throws UnimplementedError when source is camera', () async { expect(() async => plugin.getImage(source: ImageSource.camera), throwsA(isA())); }); test('getMultiImage passes the accepted type groups correctly', () async { await plugin.getMultiImage(); final VerificationResult result = verify( mockFileSelectorPlatform.openFiles( acceptedTypeGroups: captureAnyNamed('acceptedTypeGroups'))); expect(capturedTypeGroups(result)[0].extensions, ImagePickerWindows.imageFormats); }); }); group('videos', () { test('pickVideo passes the accepted type groups correctly', () async { await plugin.pickVideo(source: ImageSource.gallery); final VerificationResult result = verify( mockFileSelectorPlatform.openFile( acceptedTypeGroups: captureAnyNamed('acceptedTypeGroups'))); expect(capturedTypeGroups(result)[0].extensions, ImagePickerWindows.videoFormats); }); test('pickVideo throws UnimplementedError when source is camera', () async { expect(() async => plugin.pickVideo(source: ImageSource.camera), throwsA(isA())); }); test('getVideo passes the accepted type groups correctly', () async { await plugin.getVideo(source: ImageSource.gallery); final VerificationResult result = verify( mockFileSelectorPlatform.openFile( acceptedTypeGroups: captureAnyNamed('acceptedTypeGroups'))); expect(capturedTypeGroups(result)[0].extensions, ImagePickerWindows.videoFormats); }); test('getVideo throws UnimplementedError when source is camera', () async { expect(() async => plugin.getVideo(source: ImageSource.camera), throwsA(isA())); }); }); }); } ================================================ FILE: packages/image_picker/image_picker_windows/test/image_picker_windows_test.mocks.dart ================================================ // Mocks generated by Mockito 5.1.0 from annotations // in image_picker_windows/example/windows/flutter/ephemeral/.plugin_symlinks/image_picker_windows/test/image_picker_windows_test.dart. // Do not manually edit this file. import 'dart:async' as _i3; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart' as _i2; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: 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 /// A class which mocks [FileSelectorPlatform]. /// /// See the documentation for Mockito's code generation for more information. class MockFileSelectorPlatform extends _i1.Mock implements _i2.FileSelectorPlatform { MockFileSelectorPlatform() { _i1.throwOnMissingStub(this); } @override _i3.Future<_i2.XFile?> openFile( {List<_i2.XTypeGroup>? acceptedTypeGroups, String? initialDirectory, String? confirmButtonText}) => (super.noSuchMethod( Invocation.method(#openFile, [], { #acceptedTypeGroups: acceptedTypeGroups, #initialDirectory: initialDirectory, #confirmButtonText: confirmButtonText }), returnValue: Future<_i2.XFile?>.value()) as _i3.Future<_i2.XFile?>); @override _i3.Future> openFiles( {List<_i2.XTypeGroup>? acceptedTypeGroups, String? initialDirectory, String? confirmButtonText}) => (super.noSuchMethod( Invocation.method(#openFiles, [], { #acceptedTypeGroups: acceptedTypeGroups, #initialDirectory: initialDirectory, #confirmButtonText: confirmButtonText }), returnValue: Future>.value(<_i2.XFile>[])) as _i3.Future>); @override _i3.Future getSavePath( {List<_i2.XTypeGroup>? acceptedTypeGroups, String? initialDirectory, String? suggestedName, String? confirmButtonText}) => (super.noSuchMethod( Invocation.method(#getSavePath, [], { #acceptedTypeGroups: acceptedTypeGroups, #initialDirectory: initialDirectory, #suggestedName: suggestedName, #confirmButtonText: confirmButtonText }), returnValue: Future.value()) as _i3.Future); @override _i3.Future getDirectoryPath( {String? initialDirectory, String? confirmButtonText}) => (super.noSuchMethod( Invocation.method(#getDirectoryPath, [], { #initialDirectory: initialDirectory, #confirmButtonText: confirmButtonText }), returnValue: Future.value()) as _i3.Future); } ================================================ FILE: packages/in_app_purchase/in_app_purchase/AUTHORS ================================================ # 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> Maurits van Beusekom ================================================ FILE: packages/in_app_purchase/in_app_purchase/CHANGELOG.md ================================================ ## 3.1.4 * Updates iOS minimum version in README. ## 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. ## 3.0.8 * Updates minimum Flutter version to 2.10. * Bumps minimum in_app_purchase_android to 0.2.3. ## 3.0.7 * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 3.0.6 * Ignores deprecation warnings for upcoming styleFrom button API changes. ## 3.0.5 * Updates references to the obsolete master branch. ## 3.0.4 * Minor fixes for new analysis options. ## 3.0.3 * Removes unnecessary imports. * Adds OS version support information to README. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 3.0.2 * Adds additional explanation on why it is important to complete a purchase. ## 3.0.1 * Internal code cleanup for stricter analysis options. ## 3.0.0 * **BREAKING CHANGE** Updates `restorePurchases` to emit an empty list of purchases on StoreKit when there are no purchases to restore (same as Android). * This change was listed in the CHANGELOG for 2.0.0, but the change was accidentally not included in 2.0.0. ## 2.0.1 * Removes the instructions on initializing the plugin since this functionality is deprecated. ## 2.0.0 * **BREAKING CHANGES**: * Adds a new `PurchaseStatus` named `canceled`. This means developers can distinguish between an error and user cancellation. * ~~Updates `restorePurchases` to emit an empty list of purchases on StoreKit when there are no purchases to restore (same as Android).~~ * Renames `in_app_purchase_ios` to `in_app_purchase_storekit`. * Renames `InAppPurchaseIosPlatform` to `InAppPurchaseStoreKitPlatform`. * Renames `InAppPurchaseIosPlatformAddition` to `InAppPurchaseStoreKitPlatformAddition`. * Deprecates the `InAppPurchaseAndroidPlatformAddition.enablePendingPurchases()` method and `InAppPurchaseAndroidPlatformAddition.enablePendingPurchase` property. * Adds support for promotional offers on the store_kit_wrappers Dart API. * Fixes integration tests. * Updates example app Android compileSdkVersion to 31. ## 1.0.9 * Handle purchases with `PurchaseStatus.restored` correctly in the example App. * Updated dependencies on `in_app_purchase_android` and `in_app_purchase_ios` to their latest versions (version 0.1.5 and 0.1.3+5 respectively). ## 1.0.8 * Fix repository link in pubspec.yaml. ## 1.0.7 * Remove references to the Android V1 embedding. ## 1.0.6 * Added import flutter foundation dependency in README.md to be able to use `defaultTargetPlatform`. ## 1.0.5 * Add explanation for casting `ProductDetails` and `PurchaseDetails` to platform specific implementations in the readme. ## 1.0.4 * Fix `Restoring previous purchases` link in the README.md. ## 1.0.3 * Added a "Restore purchases" button to conform to Apple's StoreKit guidelines on [restoring products](https://developer.apple.com/documentation/storekit/in-app_purchase/restoring_purchased_products?language=objc); * Corrected an error in a example snippet displayed in the README.md. ## 1.0.2 * Fix ignoring "autoConsume" param in "InAppPurchase.instance.buyConsumable". ## 1.0.1 * Migrate maven repository from jcenter to mavenCentral. ## 1.0.0 * Stable release of in_app_purchase plugin. ## 0.6.0+1 * Added a reference to the in-app purchase codelab in the README.md. ## 0.6.0 As part of implementing federated architecture and making the interface compatible for other platforms this version contains the following **breaking changes**: * Changes to the platform agnostic interface: * If you used `InAppPurchaseConnection.instance` to access generic In App Purchase APIs, please use `InAppPurchase.instance` instead; * The `InAppPurchaseConnection.purchaseUpdatedStream` has been renamed to `InAppPurchase.purchaseStream`; * The `InAppPurchaseConnection.queryPastPurchases` method has been removed. Instead, you should use `InAppPurchase.restorePurchases`. This method emits each restored purchase on the `InAppPurchase.purchaseStream`, the `PurchaseDetails` object will be marked with a `status` of `PurchaseStatus.restored`; * The `InAppPurchase.completePurchase` method no longer returns an instance `BillingWrapperResult` class (which was Android specific). Instead it will return a completed `Future` if the method executed successfully, in case of errors it will complete with an `InAppPurchaseException` describing the error. * Android specific changes: * The Android specific `InAppPurchaseConnection.consumePurchase` and `InAppPurchaseConnection.enablePendingPurchases` methods have been removed from the platform agnostic interface and moved to the Android specific `InAppPurchaseAndroidPlatformAddition` class: * `InAppPurchaseAndroidPlatformAddition.enablePendingPurchases` is a static method that should be called when initializing your App. Access the method like this: `InAppPurchaseAndroidPlatformAddition.enablePendingPurchases()` (make sure to add the following import: `import 'package:in_app_purchase_android/in_app_purchase_android.dart';`); * To use the `InAppPurchaseAndroidPlatformAddition.consumePurchase` method, acquire an instance using the `InAppPurchase.getPlatformAddition` method. For example: ```dart // Acquire the InAppPurchaseAndroidPlatformAddition instance. InAppPurchaseAndroidPlatformAddition androidAddition = InAppPurchase.instance.getPlatformAddition(); // Consume an Android purchase. BillingResultWrapper billingResult = await androidAddition.consumePurchase(purchase); ``` * The [billing_client_wrappers](https://pub.dev/documentation/in_app_purchase_android/latest/billing_client_wrappers/billing_client_wrappers-library.html) have been moved into the [in_app_purchase_android](https://pub.dev/packages/in_app_purchase_android) package. They are still available through the [in_app_purchase](https://pub.dev/packages/in_app_purchase) plugin but to use them it is necessary to import the correct package when using them: `import 'package:in_app_purchase_android/billing_client_wrappers.dart';`; * iOS specific changes: * The iOS specific methods `InAppPurchaseConnection.presentCodeRedemptionSheet` and `InAppPurchaseConnection.refreshPurchaseVerificationData` methods have been removed from the platform agnostic interface and moved into the iOS specific `InAppPurchaseIosPlatformAddition` class. To use them acquire an instance through the `InAppPurchase.getPlatformAddition` method like so: ```dart // Acquire the InAppPurchaseIosPlatformAddition instance. InAppPurchaseIosPlatformAddition iosAddition = InAppPurchase.instance.getPlatformAddition(); // Present the code redemption sheet. await iosAddition.presentCodeRedemptionSheet(); // Refresh purchase verification data. PurchaseVerificationData? verificationData = await iosAddition.refreshPurchaseVerificationData(); ``` * The [store_kit_wrappers](https://pub.dev/documentation/in_app_purchase_ios/latest/store_kit_wrappers/store_kit_wrappers-library.html) have been moved into the [in_app_purchase_ios](https://pub.dev/packages/in_app_purchase_ios) package. They are still available in the [in_app_purchase](https://pub.dev/packages/in_app_purchase) plugin, but to use them it is necessary to import the correct package when using them: `import 'package:in_app_purchase_ios/store_kit_wrappers.dart';`; * Update the minimum supported Flutter version to 1.20.0. ## 0.5.2 * Added `rawPrice` and `currencyCode` to the ProductDetails model. ## 0.5.1+3 * Configured the iOS example App to make use of StoreKit Testing on iOS 14 and higher. ## 0.5.1+2 * Update README to provide a better instruction of the plugin. ## 0.5.1+1 * Fix error message when trying to consume purchase on iOS. ## 0.5.1 * [iOS] Introduce `SKPaymentQueueWrapper.presentCodeRedemptionSheet` ## 0.5.0 * Migrate to Google Billing Library 3.0 * Add `obfuscatedProfileId`, `purchaseToken` in [BillingClientWrapper.launchBillingFlow]. * **Breaking Change** * Removed `developerPayload` in [BillingClientWrapper.acknowledgePurchase], [BillingClientWrapper.consumeAsync], [InAppPurchaseConnection.completePurchase], [InAppPurchaseConnection.consumePurchase]. * Removed `isRewarded` from [SkuDetailsWrapper]. * [SkuDetailsWrapper.introductoryPriceCycles] now returns `int` instead of `String`. * Above breaking changes are inline with the breaking changes introduced in [Google Play Billing 3.0 release](https://developer.android.com/google/play/billing/release-notes#3-0). * Additional information on some the changes: * [Dropping reward SKU support](https://support.google.com/googleplay/android-developer/answer/9155268?hl=en) * [Developer payload](https://developer.android.com/google/play/billing/developer-payload) ## 0.4.1 * Support InApp subscription upgrade/downgrade. ## 0.4.0 * Migrate to nullsafety. * Deprecate `sandboxTesting`, introduce `simulatesAskToBuyInSandbox`. * **Breaking Change:** * Removed `callbackChannel` in `channels.dart`, see https://github.com/flutter/flutter/issues/69225. ## 0.3.5+2 * Migrate deprecated references. ## 0.3.5+1 * Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets. ## 0.3.5 * [Android] Fixed: added support for the SERVICE_TIMEOUT (-3) response code. ## 0.3.4+18 * Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) ## 0.3.4+17 * Update Flutter SDK constraint. ## 0.3.4+16 * Add Dartdocs to all public APIs. ## 0.3.4+15 * Update android compileSdkVersion to 29. ## 0.3.4+14 * Add test target to iOS example app Podfile ## 0.3.4+13 * Android Code Inspection and Clean up. ## 0.3.4+12 * [iOS] Fixed: finishing purchases upon payment dialog cancellation. ## 0.3.4+11 * [iOS] Fixed: crash when sending null for simulatesAskToBuyInSandbox parameter. ## 0.3.4+10 * Fixed typo 'verity' for 'verify'. ## 0.3.4+9 * [iOS] Fixed: purchase dialog not showing always. * [iOS] Fixed: completing purchases could fail. * [iOS] Fixed: restorePurchases caused hang (call never returned). ## 0.3.4+8 * [iOS] Fixed: purchase dialog not showing always. * [iOS] Fixed: completing purchases could fail. * [iOS] Fixed: restorePurchases caused hang (call never returned). ## 0.3.4+7 * iOS: Fix typo of the `simulatesAskToBuyInSandbox` key. ## 0.3.4+6 * iOS: Fix the bug that prevent restored subscription transactions from being completed ## 0.3.4+5 * Added necessary README docs for getting started with Android. ## 0.3.4+4 * Update package:e2e -> package:integration_test ## 0.3.4+3 * Fixed typo 'manuelly' for 'manually'. ## 0.3.4+2 * Update package:e2e reference to use the local version in the flutter/plugins repository. ## 0.3.4+1 * iOS: Fix the bug that `SKPaymentQueueWrapper.transactions` doesn't return all transactions. * iOS: Fix the app crashes if `InAppPurchaseConnection.instance` is called in the `main()`. ## 0.3.4 * Expose SKError code to client apps. ## 0.3.3+2 * Post-v2 Android embedding cleanups. ## 0.3.3+1 * Update documentations for `InAppPurchase.completePurchase` and update README. ## 0.3.3 * Introduce `SKPaymentQueueWrapper.transactions`. ## 0.3.2+2 * Fix CocoaPods podspec lint warnings. ## 0.3.2+1 * iOS: Fix only transactions with SKPaymentTransactionStatePurchased and SKPaymentTransactionStateFailed can be finished. * iOS: Only one pending transaction of a given product is allowed. ## 0.3.2 * Remove Android dependencies fallback. * Require Flutter SDK 1.12.13+hotfix.5 or greater. ## 0.3.1+2 * Fix potential casting crash on Android v1 embedding when registering life cycle callbacks. * Remove hard-coded legacy xcode build setting. ## 0.3.1+1 * Add `pedantic` to dev_dependency. ## 0.3.1 * Android: Fix a bug where the `BillingClient` is disconnected when app goes to the background. * Android: Make sure the `BillingClient` object is disconnected before the activity is destroyed. * Android: Fix minor compiler warning. * Fix typo in CHANGELOG. ## 0.3.0+3 * Fix pendingCompletePurchase flag status to allow to complete purchases. ## 0.3.0+2 * Update te example app to avoid using deprecated api. ## 0.3.0+1 * Fixing usage example. No functional changes. ## 0.3.0 * Migrate the `Google Play Library` to 2.0.3. * Introduce a new class `BillingResultWrapper` which contains a detailed result of a BillingClient operation. * **[Breaking Change]:** All the BillingClient methods that previously return a `BillingResponse` now return a `BillingResultWrapper`, including: `launchBillingFlow`, `startConnection` and `consumeAsync`. * **[Breaking Change]:** The `SkuDetailsResponseWrapper` now contains a `billingResult` field in place of `billingResponse` field. * A `billingResult` field is added to the `PurchasesResultWrapper`. * Other Updates to the "billing_client_wrappers": * Updates to the `PurchaseWrapper`: Add `developerPayload`, `purchaseState` and `isAcknowledged` fields. * Updates to the `SkuDetailsWrapper`: Add `originalPrice` and `originalPriceAmountMicros` fields. * **[Breaking Change]:** The `BillingClient.queryPurchaseHistory` is updated to return a `PurchasesHistoryResult`, which contains a list of `PurchaseHistoryRecordWrapper` instead of `PurchaseWrapper`. A `PurchaseHistoryRecordWrapper` object has the same fields and values as A `PurchaseWrapper` object, except that a `PurchaseHistoryRecordWrapper` object does not contain `isAutoRenewing`, `orderId` and `packageName`. * Add a new `BillingClient.acknowledgePurchase` API. Starting from this version, the developer has to acknowledge any purchase on Android using this API within 3 days of purchase, or the user will be refunded. Note that if a product is "consumed" via `BillingClient.consumeAsync`, it is implicitly acknowledged. * **[Breaking Change]:** Added `enablePendingPurchases` in `BillingClientWrapper`. The application has to call this method before calling `BillingClientWrapper.startConnection`. See [enablePendingPurchases](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder.html#enablependingpurchases) for more information. * Updates to the "InAppPurchaseConnection": * **[Breaking Change]:** `InAppPurchaseConnection.completePurchase` now returns a `Future` instead of `Future`. A new optional parameter `{String developerPayload}` has also been added to the API. On Android, this API does not throw an exception anymore, it instead acknowledge the purchase. If a purchase is not completed within 3 days on Android, the user will be refunded. * **[Breaking Change]:** `InAppPurchaseConnection.consumePurchase` now returns a `Future` instead of `Future`. A new optional parameter `{String developerPayload}` has also been added to the API. * A new boolean field `pendingCompletePurchase` has been added to the `PurchaseDetails` class. Which can be used as an indicator of whether to call `InAppPurchaseConnection.completePurchase` on the purchase. * **[Breaking Change]:** Added `enablePendingPurchases` in `InAppPurchaseConnection`. The application has to call this method when initializing the `InAppPurchaseConnection` on Android. See [enablePendingPurchases](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder.html#enablependingpurchases) for more information. * Misc: Some documentation updates reflecting the `BillingClient` migration and some documentation fixes. * Refer to [Google Play Billing Library Release Note](https://developer.android.com/google/play/billing/billing_library_releases_notes#release-2_0) for a detailed information on the update. ## 0.2.2+6 * Correct a comment. ## 0.2.2+5 * Update version of json_annotation to ^3.0.0 and json_serializable to ^3.2.0. Resolve conflicts with other packages e.g. flutter_tools from sdk. ## 0.2.2+4 * Remove the deprecated `author:` field from pubspec.yaml * Migrate the plugin to the pubspec platforms manifest. * Require Flutter SDK 1.10.0 or greater. ## 0.2.2+3 * Fix failing pedantic lints. None of these fixes should have any change in functionality. ## 0.2.2+2 * Include lifecycle dependency as a compileOnly one on Android to resolve potential version conflicts with other transitive libraries. ## 0.2.2+1 * Android: Use android.arch.lifecycle instead of androidx.lifecycle:lifecycle in `build.gradle` to support apps that has not been migrated to AndroidX. ## 0.2.2 * Support the v2 Android embedder. * Update to AndroidX. * Migrate to using the new e2e test binding. * Add a e2e test. ## 0.2.1+5 * Define clang module for iOS. * Fix iOS build warning. ## 0.2.1+4 * Update and migrate iOS example project. ## 0.2.1+3 * Android : Improved testability. ## 0.2.1+2 * Android: Require a non-null Activity to use the `launchBillingFlow` method. ## 0.2.1+1 * Remove skipped driver test. ## 0.2.1 * iOS: Add currencyCode to priceLocale on productDetails. ## 0.2.0+8 * Add dependency on `androidx.annotation:annotation:1.0.0`. ## 0.2.0+7 * Make Gradle version compatible with the Android Gradle plugin version. ## 0.2.0+6 * Add missing `hashCode` implementations. ## 0.2.0+5 * iOS: Support unsupported UserInfo value types on NSError. ## 0.2.0+4 * Fixed code error in `README.md` and adjusted links to work on Pub. ## 0.2.0+3 * Update the `README.md` so that the code samples compile with the latest Flutter/Dart version. ## 0.2.0+2 * Fix a google_play_connection purchase update listener regression introduced in 0.2.0+1. ## 0.2.0+1 * Fix an issue the type is not casted before passing to `PurchasesResultWrapper.fromJson`. ## 0.2.0 * [Breaking Change] Rename 'PurchaseError' to 'IAPError'. * [Breaking Change] Rename 'PurchaseSource' to 'IAPSource'. ## 0.1.1+3 * Expanded description in `pubspec.yaml` and fixed typo in `README.md`. ## 0.1.1+2 * Add missing template type parameter to `invokeMethod` calls. * Bump minimum Flutter version to 1.5.0. * Replace invokeMethod with invokeMapMethod wherever necessary. ## 0.1.1+1 * Make `AdditionalSteps`(Used in the unit test) a void function. ## 0.1.1 * Some error messages from iOS are slightly changed. * `ProductDetailsResponse` returned by `queryProductDetails()` now contains an `PurchaseError` object that represents any error that might occurred during the request. * If the device is not connected to the internet, `queryPastPurchases()` on iOS now have the error stored in the response instead of throwing. * Clean up minor iOS warning. * Example app shows how to handle error when calling `queryProductDetails()` and `queryProductDetails()`. ## 0.1.0+4 * Change the `buy` methods to return `Future` instead of `void` in order to propagate `launchBillingFlow` failures up through `google_play_connection`. ## 0.1.0+3 * Guard against multiple onSetupFinished() calls. ## 0.1.0+2 * Fix bug where error only purchases updates weren't propagated correctly in `google_play_connection.dart`. ## 0.1.0+1 * Add more consumable handling to the example app. ## 0.1.0 Beta release. * Ability to list products, load previous purchases, and make purchases. * Simplified Dart API that's been unified for ease of use. * Platform-specific APIs more directly exposing `StoreKit` and `BillingClient`. Includes: * 5ba657dc [in_app_purchase] Remove extraneous download logic (#1560) * 01bb8796 [in_app_purchase] Minor doc updates (#1555) * 1a4d493f [in_app_purchase] Only fetch owned purchases (#1540) * d63c51cf [in_app_purchase] Add auto-consume errors to PurchaseDetails (#1537) * 959da97f [in_app_purchase] Minor doc updates (#1536) * b82ae1a6 [in_app_purchase] Rename the unified API (#1517) * d1ad723a [in_app_purchase]remove SKDownloadWrapper and related code. (#1474) * 7c1e8b8a [in_app_purchase]make payment unified APIs (#1421) * 80233db6 [in_app_purchase] Add references to the original object for PurchaseDetails and ProductDetails (#1448) * 8c180f0d [in_app_purchase]load purchase (#1380) * e9f141bc [in_app_purchase] Iap refactor (#1381) * d3b3d60c add driver test command to cirrus (#1342) * aee12523 [in_app_purchase] refactoring and tests (#1322) * 6d7b4592 [in_app_purchase] Adds Dart BillingClient APIs for loading purchases (#1286) * 5567a9c8 [in_app_purchase]retrieve receipt (#1303) * 3475f1b7 [in_app_purchase]restore purchases (#1299) * a533148d [in_app_purchase] payment queue dart ios (#1249) * 10030840 [in_app_purchase] Minor bugfixes and code cleanup (#1284) * 347f508d [in_app_purchase] Fix CI formatting errors. (#1281) * fad02d87 [in_app_purchase] Java API for querying purchases (#1259) * bc501915 [In_app_purchase]SKProduct related fixes (#1252) * f92ba3a1 IAP make payment objc (#1231) * 62b82522 [IAP] Add the Dart API for launchBillingFlow (#1232) * b40a4acf [IAP] Add Java call for launchBillingFlow (#1230) * 4ff06cd1 [In_app_purchase]remove categories (#1222) * 0e72ca56 [In_app_purchase]fix requesthandler crash (#1199) * 81dff2be Iap getproductlist basic draft (#1169) * db139b28 Iap iOS add payment dart wrappers (#1178) * 2e5fbb9b Fix the param map passed down to the platform channel when calling querySkuDetails (#1194) * 4a84bac1 Mark some packages as unpublishable (#1193) * 51696552 Add a gradle warning to the AndroidX plugins (#1138) * 832ab832 Iap add payment objc translators (#1172) * d0e615cf Revert "IAP add payment translators in objc (#1126)" (#1171) * 09a5a36e IAP add payment translators in objc (#1126) * a100fbf9 Expose nslocale and expose currencySymbol instead of currencyCode to match android (#1162) * 1c982efd Using json serializer for skproduct wrapper and related classes (#1147) * 3039a261 Iap productlist ios (#1068) * 2a1593da [IAP] Update dev deps to match flutter_driver (#1118) * 9f87cbe5 [IAP] Update README (#1112) * 59e84d85 Migrate independent plugins to AndroidX (#1103) * a027ccd6 [IAP] Generate boilerplate serializers (#1090) * 909cf1c2 [IAP] Fetch SkuDetails from Google Play (#1084) * 6bbaa7e5 [IAP] Add missing license headers (#1083) * 5347e877 [IAP] Clean up Dart unit tests (#1082) * fe03e407 [IAP] Check if the payment processor is available (#1057) * 43ee28cf Fix `Manifest versionCode not found` (#1076) * 4d702ad7 Supress `strong_mode_implicit_dynamic_method` for `invokeMethod` calls. (#1065) * 809ccde7 Doc and build script updates to the IAP plugin (#1024) * 052b71a9 Update the IAP README (#933) * 54f9c4e2 Upgrade Android Gradle Plugin to 3.2.1 (#916) * ced3e99d Set all gradle-wrapper versions to 4.10.2 (#915) * eaa1388b Reconfigure Cirrus to use clang 7 (#905) * 9b153920 Update gradle dependencies. (#881) * 1aef7d92 Enable lint unnecessary_new (#701) ## 0.0.2 * Added missing flutter_test package dependency. * Added missing flutter version requirements. ## 0.0.1 * Initial release. ================================================ FILE: packages/in_app_purchase/in_app_purchase/LICENSE ================================================ 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. ================================================ FILE: packages/in_app_purchase/in_app_purchase/README.md ================================================ A storefront-independent API for purchases in Flutter apps. This plugin supports in-app purchases (_IAP_) through an _underlying store_, which can be the App Store (on iOS and macOS) or Google Play (on Android). | | Android | iOS | macOS | |-------------|---------|-------|--------| | **Support** | SDK 16+ | 11.0+ | 10.15+ |

An animated image of the iOS in-app purchase UI      An animated image of the Android in-app purchase UI

## Features Use this plugin in your Flutter app to: * Show in-app products that are available for sale from the underlying store. Products can include consumables, permanent upgrades, and subscriptions. * Load in-app products that the user owns. * Send the user to the underlying store to purchase products. * Present a UI for redeeming subscription offer codes. (iOS 14 only) ## Getting started This plugin relies on the App Store and Google Play for making in-app purchases. It exposes a unified surface, but you still need to understand and configure your app with each store. Both stores have extensive guides: * [App Store documentation](https://developer.apple.com/in-app-purchase/) * [Google Play documentation](https://developer.android.com/google/play/billing/billing_overview) > NOTE: Further in this document the App Store and Google Play will be referred > to as "the store" or "the underlying store", except when a feature is specific > to a particular store. For a list of steps for configuring in-app purchases in both stores, see the [example app README](https://github.com/flutter/plugins/blob/main/packages/in_app_purchase/in_app_purchase/example/README.md). Once you've configured your in-app purchases in their respective stores, you can start using the plugin. Two basic options are available: 1. A generic, idiomatic Flutter API: [in_app_purchase](https://pub.dev/documentation/in_app_purchase/latest/in_app_purchase/in_app_purchase-library.html). This API supports most use cases for loading and making purchases. 2. Platform-specific Dart APIs: [store_kit_wrappers](https://pub.dev/documentation/in_app_purchase_storekit/latest/store_kit_wrappers/store_kit_wrappers-library.html) and [billing_client_wrappers](https://pub.dev/documentation/in_app_purchase_android/latest/billing_client_wrappers/billing_client_wrappers-library.html). These APIs expose platform-specific behavior and allow for more fine-tuned control when needed. However, if you use one of these APIs, your purchase-handling logic is significantly different for the different storefronts. See also the codelab for [in-app purchases in Flutter](https://codelabs.developers.google.com/codelabs/flutter-in-app-purchases) for a detailed guide on adding in-app purchase support to a Flutter App. ## Usage This section has examples of code for the following tasks: * [Listening to purchase updates](#listening-to-purchase-updates) * [Connecting to the underlying store](#connecting-to-the-underlying-store) * [Loading products for sale](#loading-products-for-sale) * [Restoring previous purchases](#restoring-previous-purchases) * [Making a purchase](#making-a-purchase) * [Completing a purchase](#completing-a-purchase) * [Upgrading or downgrading an existing in-app subscription](#upgrading-or-downgrading-an-existing-in-app-subscription) * [Accessing platform specific product or purchase properties](#accessing-platform-specific-product-or-purchase-properties) * [Presenting a code redemption sheet (iOS 14)](#presenting-a-code-redemption-sheet-ios-14) **Note:** It is not necessary to depend on `com.android.billingclient:billing` in your own app's `android/app/build.gradle` file. If you choose to do so know that conflicts might occur. ### Listening to purchase updates In your app's `initState` method, subscribe to any incoming purchases. These can propagate from either underlying store. You should always start listening to purchase update as early as possible to be able to catch all purchase updates, including the ones from the previous app session. To listen to the update: ```dart class _MyAppState extends State { StreamSubscription> _subscription; @override void initState() { final Stream purchaseUpdated = InAppPurchase.instance.purchaseStream; _subscription = purchaseUpdated.listen((purchaseDetailsList) { _listenToPurchaseUpdated(purchaseDetailsList); }, onDone: () { _subscription.cancel(); }, onError: (error) { // handle error here. }); super.initState(); } @override void dispose() { _subscription.cancel(); super.dispose(); } ``` Here is an example of how to handle purchase updates: ```dart void _listenToPurchaseUpdated(List purchaseDetailsList) { purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async { if (purchaseDetails.status == PurchaseStatus.pending) { _showPendingUI(); } else { if (purchaseDetails.status == PurchaseStatus.error) { _handleError(purchaseDetails.error!); } else if (purchaseDetails.status == PurchaseStatus.purchased || purchaseDetails.status == PurchaseStatus.restored) { bool valid = await _verifyPurchase(purchaseDetails); if (valid) { _deliverProduct(purchaseDetails); } else { _handleInvalidPurchase(purchaseDetails); } } if (purchaseDetails.pendingCompletePurchase) { await InAppPurchase.instance .completePurchase(purchaseDetails); } } }); } ``` ### Connecting to the underlying store ```dart final bool available = await InAppPurchase.instance.isAvailable(); if (!available) { // The store cannot be reached or accessed. Update the UI accordingly. } ``` ### Loading products for sale ```dart // Set literals require Dart 2.2. Alternatively, use // `Set _kIds = ['product1', 'product2'].toSet()`. const Set _kIds = {'product1', 'product2'}; final ProductDetailsResponse response = await InAppPurchase.instance.queryProductDetails(_kIds); if (response.notFoundIDs.isNotEmpty) { // Handle the error. } List products = response.productDetails; ``` ### Restoring previous purchases Restored purchases will be emitted on the `InAppPurchase.purchaseStream`, make sure to validate restored purchases following the best practices for each underlying store: * [Verifying App Store purchases](https://developer.apple.com/documentation/storekit/in-app_purchase/validating_receipts_with_the_app_store) * [Verifying Google Play purchases](https://developer.android.com/google/play/billing/security#verify) ```dart await InAppPurchase.instance.restorePurchases(); ``` Note that the App Store does not have any APIs for querying consumable products, and Google Play considers consumable products to no longer be owned once they're marked as consumed and fails to return them here. For restoring these across devices you'll need to persist them on your own server and query that as well. ### Making a purchase Both underlying stores handle consumable and non-consumable products differently. If you're using `InAppPurchase`, you need to make a distinction here and call the right purchase method for each type. ```dart final ProductDetails productDetails = ... // Saved earlier from queryProductDetails(). final PurchaseParam purchaseParam = PurchaseParam(productDetails: productDetails); if (_isConsumable(productDetails)) { InAppPurchase.instance.buyConsumable(purchaseParam: purchaseParam); } else { InAppPurchase.instance.buyNonConsumable(purchaseParam: purchaseParam); } // From here the purchase flow will be handled by the underlying store. // Updates will be delivered to the `InAppPurchase.instance.purchaseStream`. ``` ### Completing a purchase The `InAppPurchase.purchaseStream` will send purchase updates after initiating the purchase flow using `InAppPurchase.buyConsumable` or `InAppPurchase.buyNonConsumable`. After verifying the purchase receipt and the delivering the content to the user it is important to call `InAppPurchase.completePurchase` to tell the underlying store that the purchase has been completed. Calling `InAppPurchase.completePurchase` will inform the underlying store that the app verified and processed the purchase and the store can proceed to finalize the transaction and bill the end user's payment account. > **Warning:** Failure to call `InAppPurchase.completePurchase` and > get a successful response within 3 days of the purchase will result a refund. ### Upgrading or downgrading an existing in-app subscription To upgrade/downgrade an existing in-app subscription in Google Play, you need to provide an instance of `ChangeSubscriptionParam` with the old `PurchaseDetails` that the user needs to migrate from, and an optional `ProrationMode` with the `GooglePlayPurchaseParam` object while calling `InAppPurchase.buyNonConsumable`. The App Store does not require this because it provides a subscription grouping mechanism. Each subscription you offer must be assigned to a subscription group. Grouping related subscriptions together can help prevent users from accidentally purchasing multiple subscriptions. Refer to the [Creating a Subscription Group](https://developer.apple.com/app-store/subscriptions/#groups) section of [Apple's subscription guide](https://developer.apple.com/app-store/subscriptions/). ```dart final PurchaseDetails oldPurchaseDetails = ...; PurchaseParam purchaseParam = GooglePlayPurchaseParam( productDetails: productDetails, changeSubscriptionParam: ChangeSubscriptionParam( oldPurchaseDetails: oldPurchaseDetails, prorationMode: ProrationMode.immediateWithTimeProration)); InAppPurchase.instance .buyNonConsumable(purchaseParam: purchaseParam); ``` ### Confirming subscription price changes When the price of a subscription is changed the consumer will need to confirm that price change. If the consumer does not confirm the price change the subscription will not be auto-renewed. By default on both iOS and Android the consumer will automatically get a popup to confirm the price change, but App developers can override this mechanism and show the popup on a later moment so it doesn't interrupt the critical flow of the App. This works different for each of the stores. #### Google Play Store (Android) When the subscription price is raised, the consumer should approve the price change within 7 days. The official documentation can be found [here](https://support.google.com/googleplay/android-developer/answer/140504?hl=en#zippy=%2Cprice-changes). When the price is lowered the consumer will automatically receive the lower price and does not have to approve the price change. After 7 days the consumer will be notified through email and notifications on Google Play to agree with the new price. App developers have 7 days to explain the consumer that the price is going to change and ask them to accept this change. App developers have to keep track of whether or not the price change is already accepted within the app or in the backend. The [Google Play API](https://developers.google.com/android-publisher/api-ref/rest/v3/purchases.subscriptions) can be used to check whether or not the price change is accepted by the consumer by reading the `priceChange` property on a subscription object. The `InAppPurchaseAndroidPlatformAddition` can be used to show the price change confirmation flow. The additions contain the function `launchPriceChangeConfirmationFlow` which needs the SKU code of the subscription. ```dart //import for InAppPurchaseAndroidPlatformAddition import 'package:in_app_purchase_android/in_app_purchase_android.dart'; //import for BillingResponse import 'package:in_app_purchase_android/billing_client_wrappers.dart'; if (Platform.isAndroid) { final InAppPurchaseAndroidPlatformAddition androidAddition = _inAppPurchase .getPlatformAddition(); var priceChangeConfirmationResult = await androidAddition.launchPriceChangeConfirmationFlow( sku: 'purchaseId', ); if (priceChangeConfirmationResult.responseCode == BillingResponse.ok){ // TODO acknowledge price change }else{ // TODO show error } } ``` #### Apple App Store (iOS) When the price of a subscription is raised iOS will also show a popup in the app. The StoreKit Payment Queue will notify the app that it wants to show a price change confirmation popup. By default the queue will get the response that it can continue and show the popup. However, it is possible to prevent this popup via the 'InAppPurchaseStoreKitPlatformAddition' and show the popup at a different time, for example after clicking a button. To know when the App Store wants to show a popup and prevent this from happening a queue delegate can be registered. The `InAppPurchaseStoreKitPlatformAddition` contains a `setDelegate(SKPaymentQueueDelegateWrapper? delegate)` function that can be used to set a delegate or remove one by setting it to `null`. ```dart //import for InAppPurchaseStoreKitPlatformAddition import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; Future initStoreInfo() async { if (Platform.isIOS) { var iosPlatformAddition = _inAppPurchase .getPlatformAddition(); await iosPlatformAddition.setDelegate(ExamplePaymentQueueDelegate()); } } @override Future disposeStore() { if (Platform.isIOS) { var iosPlatformAddition = _inAppPurchase .getPlatformAddition(); await iosPlatformAddition.setDelegate(null); } } ``` The delegate that is set should implement `SKPaymentQueueDelegateWrapper` and handle `shouldContinueTransaction` and `shouldShowPriceConsent`. When setting `shouldShowPriceConsent` to false the default popup will not be shown and the app needs to show this later. ```dart // import for SKPaymentQueueDelegateWrapper import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; class ExamplePaymentQueueDelegate implements SKPaymentQueueDelegateWrapper { @override bool shouldContinueTransaction( SKPaymentTransactionWrapper transaction, SKStorefrontWrapper storefront) { return true; } @override bool shouldShowPriceConsent() { return false; } } ``` The dialog can be shown by calling `showPriceConsentIfNeeded` on the `InAppPurchaseStoreKitPlatformAddition`. This future will complete immediately when the dialog is shown. A confirmed transaction will be delivered on the `purchaseStream`. ```dart if (Platform.isIOS) { var iapStoreKitPlatformAddition = _inAppPurchase .getPlatformAddition(); await iapStoreKitPlatformAddition.showPriceConsentIfNeeded(); } ``` ### Accessing platform specific product or purchase properties The function `_inAppPurchase.queryProductDetails(productIds);` provides a `ProductDetailsResponse` with a list of purchasable products of type `List`. This `ProductDetails` class is a platform independent class containing properties only available on all endorsed platforms. However, in some cases it is necessary to access platform specific properties. The `ProductDetails` instance is of subtype `GooglePlayProductDetails` when the platform is Android and `AppStoreProductDetails` on iOS. Accessing the skuDetails (on Android) or the skProduct (on iOS) provides all the information that is available in the original platform objects. This is an example on how to get the `introductoryPricePeriod` on Android: ```dart //import for GooglePlayProductDetails import 'package:in_app_purchase_android/in_app_purchase_android.dart'; //import for SkuDetailsWrapper import 'package:in_app_purchase_android/billing_client_wrappers.dart'; if (productDetails is GooglePlayProductDetails) { SkuDetailsWrapper skuDetails = (productDetails as GooglePlayProductDetails).skuDetails; print(skuDetails.introductoryPricePeriod); } ``` And this is the way to get the subscriptionGroupIdentifier of a subscription on iOS: ```dart //import for AppStoreProductDetails import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; //import for SKProductWrapper import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; if (productDetails is AppStoreProductDetails) { SKProductWrapper skProduct = (productDetails as AppStoreProductDetails).skProduct; print(skProduct.subscriptionGroupIdentifier); } ``` The `purchaseStream` provides objects of type `PurchaseDetails`. PurchaseDetails' provides all information that is available on all endorsed platforms, such as purchaseID and transactionDate. In addition, it is possible to access the platform specific properties. The `PurchaseDetails` object is of subtype `GooglePlayPurchaseDetails` when the platform is Android and `AppStorePurchaseDetails` on iOS. Accessing the billingClientPurchase, resp. skPaymentTransaction provides all the information that is available in the original platform objects. This is an example on how to get the `originalJson` on Android: ```dart //import for GooglePlayPurchaseDetails import 'package:in_app_purchase_android/in_app_purchase_android.dart'; //import for PurchaseWrapper import 'package:in_app_purchase_android/billing_client_wrappers.dart'; if (purchaseDetails is GooglePlayPurchaseDetails) { PurchaseWrapper billingClientPurchase = (purchaseDetails as GooglePlayPurchaseDetails).billingClientPurchase; print(billingClientPurchase.originalJson); } ``` How to get the `transactionState` of a purchase in iOS: ```dart //import for AppStorePurchaseDetails import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; //import for SKProductWrapper import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; if (purchaseDetails is AppStorePurchaseDetails) { SKPaymentTransactionWrapper skProduct = (purchaseDetails as AppStorePurchaseDetails).skPaymentTransaction; print(skProduct.transactionState); } ``` Please note that it is required to import `in_app_purchase_android` and/or `in_app_purchase_storekit`. ### Presenting a code redemption sheet (iOS 14) The following code brings up a sheet that enables the user to redeem offer codes that you've set up in App Store Connect. For more information on redeeming offer codes, see [Implementing Offer Codes in Your App](https://developer.apple.com/documentation/storekit/in-app_purchase/subscriptions_and_offers/implementing_offer_codes_in_your_app). ```dart InAppPurchaseStoreKitPlatformAddition iosPlatformAddition = InAppPurchase.getPlatformAddition(); iosPlatformAddition.presentCodeRedemptionSheet(); ``` > **note:** The `InAppPurchaseStoreKitPlatformAddition` is defined in the `in_app_purchase_storekit.dart` > file so you need to import it into the file you will be using `InAppPurchaseStoreKitPlatformAddition`: > ```dart > import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; > ``` ## Contributing to this plugin If you would like to contribute to the plugin, check out our [contribution guide](https://github.com/flutter/plugins/blob/main/CONTRIBUTING.md). ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/.metadata ================================================ # 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: 4d5e5d1ee6ccfa32ff97e9a4097bb9ca845628b6 channel: master ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/README.md ================================================ # In App Purchase Example Demonstrates how to use the In App Purchase (IAP) Plugin. ## Getting Started ### Preparation There's a significant amount of setup required for testing in app purchases successfully, including registering new app IDs and store entries to use for testing in both the Play Developer Console and App Store Connect. Both Google Play and the App Store require developers to configure an app with in-app items for purchase to call their in-app-purchase APIs. Both stores have extensive documentation on how to do this, and we've also included a high level guide below. * [In-App Purchase (App Store)](https://developer.apple.com/in-app-purchase/) * [Google Play Billing Overview](https://developer.android.com/google/play/billing/billing_overview) ### Android 1. Create a new app in the [Play Developer Console](https://play.google.com/apps/publish/) (PDC). 2. Sign up for a merchant's account in the PDC. 3. Create IAPs in the PDC available for purchase in the app. The example assumes the following SKU IDs exist: - `consumable`: A managed product. - `upgrade`: A managed product. - `subscription_silver`: A lower level subscription. - `subscription_gold`: A higher level subscription. Make sure that all the products are set to `ACTIVE`. 4. Update `APP_ID` in `example/android/app/build.gradle` to match your package ID in the PDC. 5. Create an `example/android/keystore.properties` file with all your signing information. `keystore.example.properties` exists as an example to follow. It's impossible to use any of the `BillingClient` APIs from an unsigned APK. See [here](https://developer.android.com/studio/publish/app-signing#secure-shared-keystore) and [here](https://developer.android.com/studio/publish/app-signing#sign-apk) for more information. 6. Build a signed apk. `flutter build apk` will work for this, the gradle files in this project have been configured to sign even debug builds. 7. Upload the signed APK from step 6 to the PDC, and publish that to the alpha test channel. Add your test account as an approved tester. The `BillingClient` APIs won't work unless the app has been fully published to the alpha channel and is being used by an authorized test account. See [here](https://support.google.com/googleplay/android-developer/answer/3131213) for more info. 8. Sign in to the test device with the test account from step #7. Then use `flutter run` to install the app to the device and test like normal. ### iOS When using Xcode 12 and iOS 14 or higher you can run the example in the simulator or on a device without having to configure an App in App Store Connect. The example app is set up to use StoreKit Testing configured in the `example/ios/Runner/Configuration.storekit` file (as documented in the article [Setting Up StoreKit Testing in Xcode](https://developer.apple.com/documentation/xcode/setting_up_storekit_testing_in_xcode?language=objc)). To run the application take the following steps (note that it will only work when running from Xcode): 1. Open the example app with Xcode, `File > Open File` `example/ios/Runner.xcworkspace`; 2. Within Xcode edit the current scheme, `Product > Scheme > Edit Scheme...` (or press `Command + Shift + ,`); 3. Enable StoreKit testing: a. Select the `Run` action; b. Click `Options` in the action settings; c. Select the `Configuration.storekit` for the StoreKit Configuration option. 4. Click the `Close` button to close the scheme editor; 5. Select the device you want to run the example App on; 6. Run the application using `Product > Run` (or hit the run button). When testing on pre-iOS 14 you can't run the example app on a simulator and you will need to configure an app in App Store Connect. You can do so by following the steps below: 1. Follow ["Workflow for configuring in-app purchases"](https://help.apple.com/app-store-connect/#/devb57be10e7), a detailed guide on all the steps needed to enable IAPs for an app. Complete steps 1 ("Sign a Paid Applications Agreement") and 2 ("Configure in-app purchases"). For step #2, "Configure in-app purchases in App Store Connect," you'll want to create the following products: - A consumable with product ID `consumable` - An upgrade with product ID `upgrade` - An auto-renewing subscription with product ID `subscription_silver` - An non-renewing subscription with product ID `subscription_gold` 2. In XCode, `File > Open File` `example/ios/Runner.xcworkspace`. Update the Bundle ID to match the Bundle ID of the app created in step #1. 3. [Create a Sandbox tester account](https://help.apple.com/app-store-connect/#/dev8b997bee1) to test the in-app purchases with. 4. Use `flutter run` to install the app and test it. Note that you need to test it on a real device instead of a simulator. Next click on one of the products in the example App, this enables the "SANDBOX ACCOUNT" section in the iOS settings. You will now be asked to sign in with your sandbox test account to complete the purchase (no worries you won't be charged). If for some reason you aren't asked to sign-in or the wrong user is listed, go into the iOS settings ("Settings" -> "App Store" -> "SANDBOX ACCOUNT") and update your sandbox account from there. This procedure is explained in great detail in the [Testing In-App Purchases with Sandbox](https://developer.apple.com/documentation/storekit/in-app_purchase/testing_in-app_purchases_with_sandbox?language=objc) article. **Important:** signing into any production service (including iTunes!) with the sandbox test account will permanently invalidate it. ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } // Load the build signing secrets from a local `keystore.properties` file. // TODO(YOU): Create release keys and a `keystore.properties` file. See // `example/README.md` for more info and `keystore.example.properties` for an // example. def keystorePropertiesFile = rootProject.file("keystore.properties") def keystoreProperties = new Properties() def configured = true try { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } catch (IOException e) { configured = false logger.error('Release signing information not found.') } project.ext { // TODO(YOU): Create release keys and a `keystore.properties` file. See // `example/README.md` for more info and `keystore.example.properties` for an // example. APP_ID = configured ? keystoreProperties['appId'] : "io.flutter.plugins.inapppurchaseexample.DEFAULT_DO_NOT_USE" KEYSTORE_STORE_FILE = configured ? rootProject.file(keystoreProperties['storeFile']) : null KEYSTORE_STORE_PASSWORD = keystoreProperties['storePassword'] KEYSTORE_KEY_ALIAS = keystoreProperties['keyAlias'] KEYSTORE_KEY_PASSWORD = keystoreProperties['keyPassword'] VERSION_CODE = configured ? keystoreProperties['versionCode'].toInteger() : 1 VERSION_NAME = configured ? keystoreProperties['versionName'] : "0.0.1" } if (project.APP_ID == "io.flutter.plugins.inapppurchaseexample.DEFAULT_DO_NOT_USE") { configured = false logger.error('Unique package name not set, defaulting to "io.flutter.plugins.inapppurchaseexample.DEFAULT_DO_NOT_USE".') } // Log a final error message if we're unable to create a release key signed // build for an app configured in the Play Developer Console. Apks built in this // condition won't be able to call any of the BillingClient APIs. if (!configured) { logger.error('The app could not be configured for release signing. In app purchases will not be testable. See `example/README.md` for more info and instructions.') } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { signingConfigs { release { storeFile project.KEYSTORE_STORE_FILE storePassword project.KEYSTORE_STORE_PASSWORD keyAlias project.KEYSTORE_KEY_ALIAS keyPassword project.KEYSTORE_KEY_PASSWORD } } compileSdkVersion 31 lintOptions { disable 'InvalidPackage' } defaultConfig { applicationId project.APP_ID minSdkVersion 16 targetSdkVersion 28 versionCode project.VERSION_CODE versionName project.VERSION_NAME testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { // Google Play Billing APIs only work with apps signed for production. debug { if (configured) { signingConfig signingConfigs.release } else { signingConfig signingConfigs.debug } } release { if (configured) { signingConfig signingConfigs.release } else { signingConfig signingConfigs.debug } } } testOptions { unitTests.returnDefaultValues = true } } flutter { source '../..' } dependencies { implementation 'com.android.billingclient:billing:3.0.2' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:5.0.0' testImplementation 'org.json:json:20220924' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/android/app/src/androidTest/java/io/flutter/plugins/inapppurchaseexample/FlutterActivityTest.java ================================================ // Copyright 2013 The Flutter 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.inapppurchaseexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class FlutterActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/android/keystore.example.properties ================================================ storePassword=??? keyPassword=??? keyAlias=??? storeFile=??? appId=io.flutter.plugins.inapppurchaseexample.DEFAULT_DO_NOT_USE versionCode=1 versionName=0.0.1 ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/integration_test/in_app_purchase_test.dart ================================================ // Copyright 2013 The Flutter 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:in_app_purchase/in_app_purchase.dart'; import 'package:integration_test/integration_test.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('Can create InAppPurchase instance', (WidgetTester tester) async { final InAppPurchase iapInstance = InAppPurchase.instance; expect(iapInstance, isNotNull); }); } ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 11.0 ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Flutter/Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Flutter/Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "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" } } ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Runner/Configuration.storekit ================================================ { "products" : [ { "displayPrice" : "0.99", "familyShareable" : false, "internalID" : "AE10D05D", "localizations" : [ { "description" : "A consumable product.", "displayName" : "Consumable", "locale" : "en_US" } ], "productID" : "consumable", "referenceName" : "consumable", "type" : "Consumable" }, { "displayPrice" : "10.99", "familyShareable" : false, "internalID" : "FABCF067", "localizations" : [ { "description" : "An non-consumable product.", "displayName" : "Upgrade", "locale" : "en_US" } ], "productID" : "upgrade", "referenceName" : "upgrade", "type" : "NonConsumable" } ], "settings" : { }, "subscriptionGroups" : [ { "id" : "D0FEE8D8", "localizations" : [ ], "name" : "Example Subscriptions", "subscriptions" : [ { "adHocOffers" : [ ], "displayPrice" : "3.99", "familyShareable" : false, "groupNumber" : 1, "internalID" : "922EB597", "introductoryOffer" : null, "localizations" : [ { "description" : "A lower level subscription.", "displayName" : "Subscription Silver", "locale" : "en_US" } ], "productID" : "subscription_silver", "recurringSubscriptionPeriod" : "P1M", "referenceName" : "subscription_silver", "subscriptionGroupID" : "D0FEE8D8", "type" : "RecurringSubscription" }, { "adHocOffers" : [ ], "displayPrice" : "5.99", "familyShareable" : false, "groupNumber" : 2, "internalID" : "0BC7FF5E", "introductoryOffer" : null, "localizations" : [ { "description" : "A higher level subscription.", "displayName" : "Subscription Gold", "locale" : "en_US" } ], "productID" : "subscription_gold", "recurringSubscriptionPeriod" : "P1M", "referenceName" : "subscription_gold", "subscriptionGroupID" : "D0FEE8D8", "type" : "RecurringSubscription" } ] } ], "version" : { "major" : 1, "minor" : 0 } } ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName in_app_purchase_example CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 861D0D93B0757D95C8A69620 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B2AB6BE1D4E2232AB5D4A002 /* 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 */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; A5279298219369C600FF69E6 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5279297219369C600FF69E6 /* StoreKit.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; A5279297219369C600FF69E6 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; ACAF3B1D3B61187149C0FF81 /* Pods-in_app_purchase_pluginTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-in_app_purchase_pluginTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-in_app_purchase_pluginTests/Pods-in_app_purchase_pluginTests.release.xcconfig"; sourceTree = ""; }; B2AB6BE1D4E2232AB5D4A002 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; BE95F46E12942F78BF67E55B /* 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 = ""; }; CC2B3FFB29B2574DEDD718A6 /* Pods-in_app_purchase_pluginTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-in_app_purchase_pluginTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-in_app_purchase_pluginTests/Pods-in_app_purchase_pluginTests.debug.xcconfig"; sourceTree = ""; }; DE7EEEE26E27ACC04BA9951D /* 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 = ""; }; E20838C66ABCD8667B0BB95D /* libPods-in_app_purchase_pluginTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-in_app_purchase_pluginTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; F6E5D5F926131C4800C68BED /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Configuration.storekit; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 861D0D93B0757D95C8A69620 /* libPods-Runner.a in Frameworks */, A5279298219369C600FF69E6 /* StoreKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 2D4BBB2E0E7B18550E80D50C /* Pods */ = { isa = PBXGroup; children = ( DE7EEEE26E27ACC04BA9951D /* Pods-Runner.debug.xcconfig */, BE95F46E12942F78BF67E55B /* Pods-Runner.release.xcconfig */, CC2B3FFB29B2574DEDD718A6 /* Pods-in_app_purchase_pluginTests.debug.xcconfig */, ACAF3B1D3B61187149C0FF81 /* Pods-in_app_purchase_pluginTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 2D4BBB2E0E7B18550E80D50C /* Pods */, E4DB99639FAD8ADED6B572FC /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, F6E5D5F926131C4800C68BED /* Configuration.storekit */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; E4DB99639FAD8ADED6B572FC /* Frameworks */ = { isa = PBXGroup; children = ( A5279297219369C600FF69E6 /* StoreKit.framework */, B2AB6BE1D4E2232AB5D4A002 /* libPods-Runner.a */, E20838C66ABCD8667B0BB95D /* libPods-in_app_purchase_pluginTests.a */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 5DF63B80D489A62B306EA07A /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { DefaultBuildSystemTypeForWorkspace = Original; LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; SystemCapabilities = { com.apple.InAppPurchase = { enabled = 1; }; }; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 5DF63B80D489A62B306EA07A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.inAppPurchaseExample; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.inAppPurchaseExample; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/lib/consumable_store.dart ================================================ // Copyright 2013 The Flutter 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:shared_preferences/shared_preferences.dart'; // ignore: avoid_classes_with_only_static_members /// A store of consumable items. /// /// This is a development prototype tha stores consumables in the shared /// preferences. Do not use this in real world apps. class ConsumableStore { static const String _kPrefKey = 'consumables'; static Future _writes = Future.value(); /// Adds a consumable with ID `id` to the store. /// /// The consumable is only added after the returned Future is complete. static Future save(String id) { _writes = _writes.then((void _) => _doSave(id)); return _writes; } /// Consumes a consumable with ID `id` from the store. /// /// The consumable was only consumed after the returned Future is complete. static Future consume(String id) { _writes = _writes.then((void _) => _doConsume(id)); return _writes; } /// Returns the list of consumables from the store. static Future> load() async { return (await SharedPreferences.getInstance()).getStringList(_kPrefKey) ?? []; } static Future _doSave(String id) async { final List cached = await load(); final SharedPreferences prefs = await SharedPreferences.getInstance(); cached.add(id); await prefs.setStringList(_kPrefKey, cached); } static Future _doConsume(String id) async { final List cached = await load(); final SharedPreferences prefs = await SharedPreferences.getInstance(); cached.remove(id); await prefs.setStringList(_kPrefKey, cached); } } ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; import 'package:flutter/material.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:in_app_purchase_android/billing_client_wrappers.dart'; import 'package:in_app_purchase_android/in_app_purchase_android.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; import 'consumable_store.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); runApp(_MyApp()); } // Auto-consume must be true on iOS. // To try without auto-consume on another platform, change `true` to `false` here. final bool _kAutoConsume = Platform.isIOS || true; const String _kConsumableId = 'consumable'; const String _kUpgradeId = 'upgrade'; const String _kSilverSubscriptionId = 'subscription_silver'; const String _kGoldSubscriptionId = 'subscription_gold'; const List _kProductIds = [ _kConsumableId, _kUpgradeId, _kSilverSubscriptionId, _kGoldSubscriptionId, ]; class _MyApp extends StatefulWidget { @override State<_MyApp> createState() => _MyAppState(); } class _MyAppState extends State<_MyApp> { final InAppPurchase _inAppPurchase = InAppPurchase.instance; late StreamSubscription> _subscription; List _notFoundIds = []; List _products = []; List _purchases = []; List _consumables = []; bool _isAvailable = false; bool _purchasePending = false; bool _loading = true; String? _queryProductError; @override void initState() { final Stream> purchaseUpdated = _inAppPurchase.purchaseStream; _subscription = purchaseUpdated.listen((List purchaseDetailsList) { _listenToPurchaseUpdated(purchaseDetailsList); }, onDone: () { _subscription.cancel(); }, onError: (Object error) { // handle error here. }); initStoreInfo(); super.initState(); } Future initStoreInfo() async { final bool isAvailable = await _inAppPurchase.isAvailable(); if (!isAvailable) { setState(() { _isAvailable = isAvailable; _products = []; _purchases = []; _notFoundIds = []; _consumables = []; _purchasePending = false; _loading = false; }); return; } if (Platform.isIOS) { final InAppPurchaseStoreKitPlatformAddition iosPlatformAddition = _inAppPurchase .getPlatformAddition(); await iosPlatformAddition.setDelegate(ExamplePaymentQueueDelegate()); } final ProductDetailsResponse productDetailResponse = await _inAppPurchase.queryProductDetails(_kProductIds.toSet()); if (productDetailResponse.error != null) { setState(() { _queryProductError = productDetailResponse.error!.message; _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _purchases = []; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = []; _purchasePending = false; _loading = false; }); return; } if (productDetailResponse.productDetails.isEmpty) { setState(() { _queryProductError = null; _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _purchases = []; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = []; _purchasePending = false; _loading = false; }); return; } final List consumables = await ConsumableStore.load(); setState(() { _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = consumables; _purchasePending = false; _loading = false; }); } @override void dispose() { if (Platform.isIOS) { final InAppPurchaseStoreKitPlatformAddition iosPlatformAddition = _inAppPurchase .getPlatformAddition(); iosPlatformAddition.setDelegate(null); } _subscription.cancel(); super.dispose(); } @override Widget build(BuildContext context) { final List stack = []; if (_queryProductError == null) { stack.add( ListView( children: [ _buildConnectionCheckTile(), _buildProductList(), _buildConsumableBox(), _buildRestoreButton(), ], ), ); } else { stack.add(Center( child: Text(_queryProductError!), )); } if (_purchasePending) { stack.add( // TODO(goderbauer): Make this const when that's available on stable. // ignore: prefer_const_constructors Stack( children: const [ Opacity( opacity: 0.3, child: ModalBarrier(dismissible: false, color: Colors.grey), ), Center( child: CircularProgressIndicator(), ), ], ), ); } return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('IAP Example'), ), body: Stack( children: stack, ), ), ); } Card _buildConnectionCheckTile() { if (_loading) { return const Card(child: ListTile(title: Text('Trying to connect...'))); } final Widget storeHeader = ListTile( leading: Icon(_isAvailable ? Icons.check : Icons.block, color: _isAvailable ? Colors.green : ThemeData.light().colorScheme.error), title: Text('The store is ${_isAvailable ? 'available' : 'unavailable'}.'), ); final List children = [storeHeader]; if (!_isAvailable) { children.addAll([ const Divider(), ListTile( title: Text('Not connected', style: TextStyle(color: ThemeData.light().colorScheme.error)), subtitle: const Text( 'Unable to connect to the payments processor. Has this app been configured correctly? See the example README for instructions.'), ), ]); } return Card(child: Column(children: children)); } Card _buildProductList() { if (_loading) { return const Card( child: ListTile( leading: CircularProgressIndicator(), title: Text('Fetching products...'))); } if (!_isAvailable) { return const Card(); } const ListTile productHeader = ListTile(title: Text('Products for Sale')); final List productList = []; if (_notFoundIds.isNotEmpty) { productList.add(ListTile( title: Text('[${_notFoundIds.join(", ")}] not found', style: TextStyle(color: ThemeData.light().colorScheme.error)), subtitle: const Text( 'This app needs special configuration to run. Please see example/README.md for instructions.'))); } // This loading previous purchases code is just a demo. Please do not use this as it is. // In your app you should always verify the purchase data using the `verificationData` inside the [PurchaseDetails] object before trusting it. // We recommend that you use your own server to verify the purchase data. final Map purchases = Map.fromEntries( _purchases.map((PurchaseDetails purchase) { if (purchase.pendingCompletePurchase) { _inAppPurchase.completePurchase(purchase); } return MapEntry(purchase.productID, purchase); })); productList.addAll(_products.map( (ProductDetails productDetails) { final PurchaseDetails? previousPurchase = purchases[productDetails.id]; return ListTile( title: Text( productDetails.title, ), subtitle: Text( productDetails.description, ), trailing: previousPurchase != null ? IconButton( onPressed: () => confirmPriceChange(context), icon: const Icon(Icons.upgrade)) : TextButton( style: TextButton.styleFrom( backgroundColor: Colors.green[800], // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: Colors.white, ), onPressed: () { late PurchaseParam purchaseParam; if (Platform.isAndroid) { // NOTE: If you are making a subscription purchase/upgrade/downgrade, we recommend you to // verify the latest status of you your subscription by using server side receipt validation // and update the UI accordingly. The subscription purchase status shown // inside the app may not be accurate. final GooglePlayPurchaseDetails? oldSubscription = _getOldSubscription(productDetails, purchases); purchaseParam = GooglePlayPurchaseParam( productDetails: productDetails, changeSubscriptionParam: (oldSubscription != null) ? ChangeSubscriptionParam( oldPurchaseDetails: oldSubscription, prorationMode: ProrationMode.immediateWithTimeProration, ) : null); } else { purchaseParam = PurchaseParam( productDetails: productDetails, ); } if (productDetails.id == _kConsumableId) { _inAppPurchase.buyConsumable( purchaseParam: purchaseParam, autoConsume: _kAutoConsume); } else { _inAppPurchase.buyNonConsumable( purchaseParam: purchaseParam); } }, child: Text(productDetails.price), ), ); }, )); return Card( child: Column( children: [productHeader, const Divider()] + productList)); } Card _buildConsumableBox() { if (_loading) { return const Card( child: ListTile( leading: CircularProgressIndicator(), title: Text('Fetching consumables...'))); } if (!_isAvailable || _notFoundIds.contains(_kConsumableId)) { return const Card(); } const ListTile consumableHeader = ListTile(title: Text('Purchased consumables')); final List tokens = _consumables.map((String id) { return GridTile( child: IconButton( icon: const Icon( Icons.stars, size: 42.0, color: Colors.orange, ), splashColor: Colors.yellowAccent, onPressed: () => consume(id), ), ); }).toList(); return Card( child: Column(children: [ consumableHeader, const Divider(), GridView.count( crossAxisCount: 5, shrinkWrap: true, padding: const EdgeInsets.all(16.0), children: tokens, ) ])); } Widget _buildRestoreButton() { if (_loading) { return Container(); } return Padding( padding: const EdgeInsets.all(4.0), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( style: TextButton.styleFrom( backgroundColor: Theme.of(context).primaryColor, // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: Colors.white, ), onPressed: () => _inAppPurchase.restorePurchases(), child: const Text('Restore purchases'), ), ], ), ); } Future consume(String id) async { await ConsumableStore.consume(id); final List consumables = await ConsumableStore.load(); setState(() { _consumables = consumables; }); } void showPendingUI() { setState(() { _purchasePending = true; }); } Future deliverProduct(PurchaseDetails purchaseDetails) async { // IMPORTANT!! Always verify purchase details before delivering the product. if (purchaseDetails.productID == _kConsumableId) { await ConsumableStore.save(purchaseDetails.purchaseID!); final List consumables = await ConsumableStore.load(); setState(() { _purchasePending = false; _consumables = consumables; }); } else { setState(() { _purchases.add(purchaseDetails); _purchasePending = false; }); } } void handleError(IAPError error) { setState(() { _purchasePending = false; }); } Future _verifyPurchase(PurchaseDetails purchaseDetails) { // IMPORTANT!! Always verify a purchase before delivering the product. // For the purpose of an example, we directly return true. return Future.value(true); } void _handleInvalidPurchase(PurchaseDetails purchaseDetails) { // handle invalid purchase here if _verifyPurchase` failed. } Future _listenToPurchaseUpdated( List purchaseDetailsList) async { for (final PurchaseDetails purchaseDetails in purchaseDetailsList) { if (purchaseDetails.status == PurchaseStatus.pending) { showPendingUI(); } else { if (purchaseDetails.status == PurchaseStatus.error) { handleError(purchaseDetails.error!); } else if (purchaseDetails.status == PurchaseStatus.purchased || purchaseDetails.status == PurchaseStatus.restored) { final bool valid = await _verifyPurchase(purchaseDetails); if (valid) { deliverProduct(purchaseDetails); } else { _handleInvalidPurchase(purchaseDetails); return; } } if (Platform.isAndroid) { if (!_kAutoConsume && purchaseDetails.productID == _kConsumableId) { final InAppPurchaseAndroidPlatformAddition androidAddition = _inAppPurchase.getPlatformAddition< InAppPurchaseAndroidPlatformAddition>(); await androidAddition.consumePurchase(purchaseDetails); } } if (purchaseDetails.pendingCompletePurchase) { await _inAppPurchase.completePurchase(purchaseDetails); } } } } Future confirmPriceChange(BuildContext context) async { if (Platform.isAndroid) { final InAppPurchaseAndroidPlatformAddition androidAddition = _inAppPurchase .getPlatformAddition(); final BillingResultWrapper priceChangeConfirmationResult = await androidAddition.launchPriceChangeConfirmationFlow( sku: 'purchaseId', ); 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) { final InAppPurchaseStoreKitPlatformAddition iapStoreKitPlatformAddition = _inAppPurchase .getPlatformAddition(); await iapStoreKitPlatformAddition.showPriceConsentIfNeeded(); } } GooglePlayPurchaseDetails? _getOldSubscription( ProductDetails productDetails, Map purchases) { // This is just to demonstrate a subscription upgrade or downgrade. // This method assumes that you have only 2 subscriptions under a group, 'subscription_silver' & 'subscription_gold'. // The 'subscription_silver' subscription can be upgraded to 'subscription_gold' and // the 'subscription_gold' subscription can be downgraded to 'subscription_silver'. // Please remember to replace the logic of finding the old subscription Id as per your app. // The old subscription is only required on Android since Apple handles this internally // by using the subscription group feature in iTunesConnect. GooglePlayPurchaseDetails? oldSubscription; if (productDetails.id == _kSilverSubscriptionId && purchases[_kGoldSubscriptionId] != null) { oldSubscription = purchases[_kGoldSubscriptionId]! as GooglePlayPurchaseDetails; } else if (productDetails.id == _kGoldSubscriptionId && purchases[_kSilverSubscriptionId] != null) { oldSubscription = purchases[_kSilverSubscriptionId]! as GooglePlayPurchaseDetails; } return oldSubscription; } } /// Example implementation of the /// [`SKPaymentQueueDelegate`](https://developer.apple.com/documentation/storekit/skpaymentqueuedelegate?language=objc). /// /// The payment queue delegate can be implementated to provide information /// needed to complete transactions. class ExamplePaymentQueueDelegate implements SKPaymentQueueDelegateWrapper { @override bool shouldContinueTransaction( SKPaymentTransactionWrapper transaction, SKStorefrontWrapper storefront) { return true; } @override bool shouldShowPriceConsent() { return false; } } ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/macos/Flutter/Flutter-Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/macos/Flutter/Flutter-Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/macos/Podfile ================================================ platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_macos_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) end end ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/macos/Runner/AppDelegate.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } } ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/macos/Runner/Base.lproj/MainMenu.xib ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/macos/Runner/Configs/AppInfo.xcconfig ================================================ // Application-level settings for the Runner target. // // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the // future. If not, the values below would default to using the project name when this becomes a // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. PRODUCT_NAME = example // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.example // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2022 dev.flutter.plugins. All rights reserved. ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/macos/Runner/Configs/Warnings.xcconfig ================================================ WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings GCC_WARN_UNDECLARED_SELECTOR = YES CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE CLANG_WARN__DUPLICATE_METHOD_MATCH = YES CLANG_WARN_PRAGMA_PACK = YES CLANG_WARN_STRICT_PROTOTYPES = YES CLANG_WARN_COMMA = YES GCC_WARN_STRICT_SELECTOR_MATCH = YES CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES GCC_WARN_SHADOW = YES CLANG_WARN_UNREACHABLE_CODE = YES ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/macos/Runner/DebugProfile.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/macos/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/macos/Runner/MainFlutterWindow.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { let flutterViewController = FlutterViewController.init() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } } ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/macos/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 51; objects = { /* Begin PBXAggregateTarget section */ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { isa = PBXAggregateTarget; buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; buildPhases = ( 33CC111E2044C6BF0003C045 /* ShellScript */, ); dependencies = ( ); name = "Flutter Assemble"; productName = FLX; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 1936C695A67BE3AC115E6938 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 91FC7522CD18DEF95EA2F630 /* Pods_Runner.framework */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC111A2044C6BA0003C045; remoteInfo = FLX; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 33CC110E2044A8840003C045 /* Bundle Framework */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Bundle Framework"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0B65A69DF81DCCDA43899BF5 /* 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 = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 684854F04C44509BBCC60AB6 /* 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 = ""; }; 91FC7522CD18DEF95EA2F630 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; BB0D94179B02A04FD98DA5BE /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 1936C695A67BE3AC115E6938 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 27C7C1505089764F862054EC /* Pods */ = { isa = PBXGroup; children = ( 684854F04C44509BBCC60AB6 /* Pods-Runner.debug.xcconfig */, BB0D94179B02A04FD98DA5BE /* Pods-Runner.release.xcconfig */, 0B65A69DF81DCCDA43899BF5 /* Pods-Runner.profile.xcconfig */, ); path = Pods; sourceTree = ""; }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( 33E5194F232828860026EE4D /* AppInfo.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, ); path = Configs; sourceTree = ""; }; 33CC10E42044A3C60003C045 = { isa = PBXGroup; children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, 27C7C1505089764F862054EC /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* example.app */, ); name = Products; sourceTree = ""; }; 33CC11242044D66E0003C045 /* Resources */ = { isa = PBXGroup; children = ( 33CC10F22044A3C60003C045 /* Assets.xcassets */, 33CC10F42044A3C60003C045 /* MainMenu.xib */, 33CC10F72044A3C60003C045 /* Info.plist */, ); name = Resources; path = ..; sourceTree = ""; }; 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, ); path = Flutter; sourceTree = ""; }; 33FAB671232836740065AC1E /* Runner */ = { isa = PBXGroup; children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, 33CC11242044D66E0003C045 /* Resources */, 33BA886A226E78AF003329D5 /* Configs */, ); path = Runner; sourceTree = ""; }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( 91FC7522CD18DEF95EA2F630 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( D69320C4691D46AC439743E0 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, 4225BF8FBCA00DC792D87EF7 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; }; }; }; 33CC111A2044C6BA0003C045 = { CreatedOnToolsVersion = 9.2; ProvisioningStyle = Manual; }; }; }; buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 33CC10E42044A3C60003C045; productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( Flutter/ephemeral/FlutterInputs.xcfilelist, ); inputPaths = ( Flutter/ephemeral/tripwire, ); outputFileListPaths = ( Flutter/ephemeral/FlutterOutputs.xcfilelist, ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; 4225BF8FBCA00DC792D87EF7 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); 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 = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; D69320C4691D46AC439743E0 /* [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", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 33CC10F52044A3C60003C045 /* Base */, ); name = MainMenu.xib; path = Runner; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Profile; }; 338D0CEA231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Profile; }; 338D0CEB231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Profile; }; 33CC10F92044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 33CC10FA2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; 33CC10FC2044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; 33CC10FD2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Release; }; 33CC111C2044C6BA0003C045 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 33CC111D2044C6BA0003C045 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10F92044A3C60003C045 /* Debug */, 33CC10FA2044A3C60003C045 /* Release */, 338D0CE9231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10FC2044A3C60003C045 /* Debug */, 33CC10FD2044A3C60003C045 /* Release */, 338D0CEA231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC111C2044C6BA0003C045 /* Debug */, 33CC111D2044C6BA0003C045 /* Release */, 338D0CEB231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/pubspec.yaml ================================================ name: in_app_purchase_example description: Demonstrates how to use the in_app_purchase plugin. publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter in_app_purchase: # When depending on this package from a real application you should use: # in_app_purchase: ^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: ../ in_app_purchase_android: ^0.2.1 in_app_purchase_storekit: ^0.3.4 shared_preferences: ^2.0.0 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/in_app_purchase/in_app_purchase/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/in_app_purchase/in_app_purchase/lib/in_app_purchase.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:in_app_purchase_android/in_app_purchase_android.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; export 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart' show IAPError, InAppPurchaseException, ProductDetails, ProductDetailsResponse, PurchaseDetails, PurchaseParam, PurchaseVerificationData, PurchaseStatus; /// Basic API for making in app purchases across multiple platforms. class InAppPurchase implements InAppPurchasePlatformAdditionProvider { InAppPurchase._(); static InAppPurchase? _instance; /// The instance of the [InAppPurchase] to use. static InAppPurchase get instance => _getOrCreateInstance(); static InAppPurchase _getOrCreateInstance() { if (_instance != null) { return _instance!; } if (defaultTargetPlatform == TargetPlatform.android) { InAppPurchaseAndroidPlatform.registerPlatform(); } else if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) { InAppPurchaseStoreKitPlatform.registerPlatform(); } _instance = InAppPurchase._(); return _instance!; } @override T getPlatformAddition() { return InAppPurchasePlatformAddition.instance as T; } /// Listen to this broadcast stream to get real time update for purchases. /// /// This stream will never close as long as the app is active. /// /// Purchase updates can happen in several situations: /// * When a purchase is triggered by user in the app. /// * When a purchase is triggered by user from the platform-specific store front. /// * When a purchase is restored on the device by the user in the app. /// * If a purchase is not completed ([completePurchase] is not called on the /// purchase object) from the last app session. Purchase updates will happen /// when a new app session starts instead. /// /// IMPORTANT! You must subscribe to this stream as soon as your app launches, /// preferably before returning your main App Widget in main(). Otherwise you /// will miss purchase updated made before this stream is subscribed to. /// /// We also recommend listening to the stream with one subscription at a given /// time. If you choose to have multiple subscription at the same time, you /// should be careful at the fact that each subscription will receive all the /// events after they start to listen. Stream> get purchaseStream => InAppPurchasePlatform.instance.purchaseStream; /// Returns `true` if the payment platform is ready and available. Future isAvailable() => InAppPurchasePlatform.instance.isAvailable(); /// Query product details for the given set of IDs. /// /// Identifiers in the underlying payment platform, for example, [App Store /// Connect](https://appstoreconnect.apple.com/) for iOS and [Google Play /// Console](https://play.google.com/) for Android. Future queryProductDetails(Set identifiers) => InAppPurchasePlatform.instance.queryProductDetails(identifiers); /// Buy a non consumable product or subscription. /// /// Non consumable items can only be bought once. For example, a purchase that /// unlocks a special content in your app. Subscriptions are also non /// consumable products. /// /// You always need to restore all the non consumable products for user when /// they switch their phones. /// /// This method does not return the result of the purchase. Instead, after /// triggering this method, purchase updates will be sent to /// [purchaseStream]. You should [Stream.listen] to [purchaseStream] to get /// [PurchaseDetails] objects in different [PurchaseDetails.status] and update /// your UI accordingly. When the [PurchaseDetails.status] is /// [PurchaseStatus.purchased], [PurchaseStatus.restored] or /// [PurchaseStatus.error] you should deliver the content or handle the error, /// then call [completePurchase] to finish the purchasing process. /// /// This method does return whether or not the purchase request was initially /// sent successfully. /// /// Consumable items are defined differently by the different underlying /// payment platforms, and there's no way to query for whether or not the /// [ProductDetail] is a consumable at runtime. /// /// See also: /// /// * [buyConsumable], for buying a consumable product. /// * [restorePurchases], for restoring non consumable products. /// /// Calling this method for consumable items will cause unwanted behaviors! Future buyNonConsumable({required PurchaseParam purchaseParam}) => InAppPurchasePlatform.instance.buyNonConsumable( purchaseParam: purchaseParam, ); /// Buy a consumable product. /// /// Consumable items can be "consumed" to mark that they've been used and then /// bought additional times. For example, a health potion. /// /// To restore consumable purchases across devices, you should keep track of /// those purchase on your own server and restore the purchase for your users. /// Consumed products are no longer considered to be "owned" by payment /// platforms and will not be delivered by calling [restorePurchases]. /// /// Consumable items are defined differently by the different underlying /// payment platforms, and there's no way to query for whether or not the /// [ProductDetail] is a consumable at runtime. /// /// `autoConsume` is provided as a utility and will instruct the plugin to /// automatically consume the product after a succesful purchase. /// `autoConsume` is `true` by default. /// /// This method does not return the result of the purchase. Instead, after /// triggering this method, purchase updates will be sent to /// [purchaseStream]. You should [Stream.listen] to /// [purchaseStream] to get [PurchaseDetails] objects in different /// [PurchaseDetails.status] and update your UI accordingly. When the /// [PurchaseDetails.status] is [PurchaseStatus.purchased] or /// [PurchaseStatus.error], you should deliver the content or handle the /// error, then call [completePurchase] to finish the purchasing process. /// /// This method does return whether or not the purchase request was initially /// sent succesfully. /// /// See also: /// /// * [buyNonConsumable], for buying a non consumable product or /// subscription. /// * [restorePurchases], for restoring non consumable products. /// /// Calling this method for non consumable items will cause unwanted /// behaviors! Future buyConsumable({ required PurchaseParam purchaseParam, bool autoConsume = true, }) => InAppPurchasePlatform.instance.buyConsumable( purchaseParam: purchaseParam, autoConsume: autoConsume, ); /// Mark that purchased content has been delivered to the user. /// /// You are responsible for completing every [PurchaseDetails] whose /// [PurchaseDetails.status] is [PurchaseStatus.purchased] or /// [PurchaseStatus.restored]. /// Completing a [PurchaseStatus.pending] purchase will cause an exception. /// For convenience, [PurchaseDetails.pendingCompletePurchase] indicates if a /// purchase is pending for completion. /// /// The method will throw a [PurchaseException] when the purchase could not be /// finished. Depending on the [PurchaseException.errorCode] the developer /// should try to complete the purchase via this method again, or retry the /// [completePurchase] method at a later time. If the /// [PurchaseException.errorCode] indicates you should not retry there might /// be some issue with the app's code or the configuration of the app in the /// respective store. The developer is responsible to fix this issue. The /// [PurchaseException.message] field might provide more information on what /// went wrong. Future completePurchase(PurchaseDetails purchase) => InAppPurchasePlatform.instance.completePurchase(purchase); /// Restore all previous purchases. /// /// The `applicationUserName` should match whatever was sent in the initial /// `PurchaseParam`, if anything. If no `applicationUserName` was specified in the initial /// `PurchaseParam`, use `null`. /// /// Restored purchases are delivered through the [purchaseStream] with a /// status of [PurchaseStatus.restored]. You should listen for these purchases, /// validate their receipts, deliver the content and mark the purchase complete /// by calling the [finishPurchase] method for each purchase. /// /// This does not return consumed products. If you want to restore unused /// consumable products, you need to persist consumable product information /// for your user on your own server. /// /// See also: /// /// * [refreshPurchaseVerificationData], for reloading failed /// [PurchaseDetails.verificationData]. Future restorePurchases({String? applicationUserName}) => InAppPurchasePlatform.instance.restorePurchases( applicationUserName: applicationUserName, ); } ================================================ FILE: packages/in_app_purchase/in_app_purchase/pubspec.yaml ================================================ 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.4 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: platforms: android: default_package: in_app_purchase_android ios: default_package: in_app_purchase_storekit macos: default_package: in_app_purchase_storekit dependencies: flutter: sdk: flutter in_app_purchase_android: ^0.2.3 in_app_purchase_platform_interface: ^1.0.0 in_app_purchase_storekit: ^0.3.4 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: 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 ================================================ FILE: packages/in_app_purchase/in_app_purchase/test/in_app_purchase_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; void main() { group('InAppPurchase', () { final ProductDetails productDetails = ProductDetails( id: 'id', title: 'title', description: 'description', price: 'price', rawPrice: 0.0, currencyCode: 'currencyCode', ); final PurchaseDetails purchaseDetails = PurchaseDetails( productID: 'productID', verificationData: PurchaseVerificationData( localVerificationData: 'localVerificationData', serverVerificationData: 'serverVerificationData', source: 'source', ), transactionDate: 'transactionDate', status: PurchaseStatus.purchased, ); late InAppPurchase inAppPurchase; late MockInAppPurchasePlatform fakePlatform; setUp(() { debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia; fakePlatform = MockInAppPurchasePlatform(); InAppPurchasePlatform.instance = fakePlatform; inAppPurchase = InAppPurchase.instance; }); tearDown(() { // Restore the default target platform debugDefaultTargetPlatformOverride = null; }); test('isAvailable', () async { final bool isAvailable = await inAppPurchase.isAvailable(); expect(isAvailable, true); expect(fakePlatform.log, [ isMethodCall('isAvailable', arguments: null), ]); }); test('purchaseStream', () async { final bool isEmptyStream = await inAppPurchase.purchaseStream.isEmpty; expect(isEmptyStream, true); expect(fakePlatform.log, [ isMethodCall('purchaseStream', arguments: null), ]); }); test('queryProductDetails', () async { final ProductDetailsResponse response = await inAppPurchase.queryProductDetails({}); expect(response.notFoundIDs.isEmpty, true); expect(response.productDetails.isEmpty, true); expect(fakePlatform.log, [ isMethodCall('queryProductDetails', arguments: null), ]); }); test('buyNonConsumable', () async { final bool result = await inAppPurchase.buyNonConsumable( purchaseParam: PurchaseParam( productDetails: productDetails, ), ); expect(result, true); expect(fakePlatform.log, [ isMethodCall('buyNonConsumable', arguments: null), ]); }); test('buyConsumable', () async { final PurchaseParam purchaseParam = PurchaseParam(productDetails: productDetails); final bool result = await inAppPurchase.buyConsumable( purchaseParam: purchaseParam, ); expect(result, true); expect(fakePlatform.log, [ isMethodCall('buyConsumable', arguments: { 'purchaseParam': purchaseParam, 'autoConsume': true, }), ]); }); test('buyConsumable with autoConsume=false', () async { final PurchaseParam purchaseParam = PurchaseParam(productDetails: productDetails); final bool result = await inAppPurchase.buyConsumable( purchaseParam: purchaseParam, autoConsume: false, ); expect(result, true); expect(fakePlatform.log, [ isMethodCall('buyConsumable', arguments: { 'purchaseParam': purchaseParam, 'autoConsume': false, }), ]); }); test('completePurchase', () async { await inAppPurchase.completePurchase(purchaseDetails); expect(fakePlatform.log, [ isMethodCall('completePurchase', arguments: null), ]); }); test('restorePurchases', () async { await inAppPurchase.restorePurchases(); expect(fakePlatform.log, [ isMethodCall('restorePurchases', arguments: null), ]); }); }); } class MockInAppPurchasePlatform extends Fake with MockPlatformInterfaceMixin implements InAppPurchasePlatform { final List log = []; @override Future isAvailable() { log.add(const MethodCall('isAvailable')); return Future.value(true); } @override Stream> get purchaseStream { log.add(const MethodCall('purchaseStream')); return const Stream>.empty(); } @override Future queryProductDetails(Set identifiers) { log.add(const MethodCall('queryProductDetails')); return Future.value(ProductDetailsResponse( productDetails: [], notFoundIDs: [], )); } @override Future buyNonConsumable({required PurchaseParam purchaseParam}) { log.add(const MethodCall('buyNonConsumable')); return Future.value(true); } @override Future buyConsumable({ required PurchaseParam purchaseParam, bool autoConsume = true, }) { log.add(MethodCall('buyConsumable', { 'purchaseParam': purchaseParam, 'autoConsume': autoConsume, })); return Future.value(true); } @override Future completePurchase(PurchaseDetails purchase) { log.add(const MethodCall('completePurchase')); return Future.value(); } @override Future restorePurchases({String? applicationUserName}) { log.add(const MethodCall('restorePurchases')); return Future.value(); } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/AUTHORS ================================================ # 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> Maurits van Beusekom ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md ================================================ ## 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. ## 0.2.3+6 * Updates android gradle plugin to 7.3.1. ## 0.2.3+5 * Updates imports for `prefer_relative_imports`. ## 0.2.3+4 * Updates minimum Flutter version to 2.10. * Adds IMMEDIATE_AND_CHARGE_FULL_PRICE to the `ProrationMode`. ## 0.2.3+3 * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 0.2.3+2 * Fixes incorrect json key in `queryPurchasesAsync` that fixes restore purchases functionality. ## 0.2.3+1 * Updates `json_serializable` to fix warnings in generated code. ## 0.2.3 * Upgrades Google Play Billing Library to 5.0 * Migrates APIs to support breaking changes in new Google Play Billing API * `PurchaseWrapper` and `PurchaseHistoryRecordWrapper` now handles `skus` a list of sku strings. `sku` is deprecated. ## 0.2.2+8 * Ignores deprecation warnings for upcoming styleFrom button API changes. ## 0.2.2+7 * Updates references to the obsolete master branch. ## 0.2.2+6 * Enables mocking models by changing overridden operator == parameter type from `dynamic` to `Object`. ## 0.2.2+5 * Minor fixes for new analysis options. ## 0.2.2+4 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.2.2+3 * Migrates from `ui.hash*` to `Object.hash*`. * Updates minimum Flutter version to 2.5.0. ## 0.2.2+2 * Internal code cleanup for stricter analysis options. ## 0.2.2+1 * Removes the dependency on `meta`. ## 0.2.2 * Fixes the `purchaseStream` incorrectly reporting `PurchaseStatus.error` when user upgrades subscription by deferred proration mode. ## 0.2.1 * Deprecated the `InAppPurchaseAndroidPlatformAddition.enablePendingPurchases()` method and `InAppPurchaseAndroidPlatformAddition.enablePendingPurchase` property. Since Google Play no longer accepts App submissions that don't support pending purchases it is no longer necessary to acknowledge this through code. * Updates example app Android compileSdkVersion to 31. ## 0.2.0 * BREAKING CHANGE : Refactor to handle new `PurchaseStatus` named `canceled`. This means developers can distinguish between an error and user cancellation. ## 0.1.6 * Require Dart SDK >= 2.14. * Update `json_annotation` dependency to `^4.3.0`. ## 0.1.5+1 * Fix a broken link in the README. ## 0.1.5 * Introduced the `SkuDetailsWrapper.introductoryPriceAmountMicros` field of the correct type (`int`) and deprecated the `SkuDetailsWrapper.introductoryPriceMicros` field. * Update dev_dependency `build_runner` to ^2.0.0 and `json_serializable` to ^5.0.2. ## 0.1.4+7 * Ensure that the `SkuDetailsWrapper.introductoryPriceMicros` is populated correctly. ## 0.1.4+6 * Ensure that purchases correctly indicate whether they are acknowledged or not. The `PurchaseDetails.pendingCompletePurchase` field now correctly indicates if the purchase still needs to be completed. ## 0.1.4+5 * Add `implements` to pubspec. * Updated Android lint settings. ## 0.1.4+4 * Removed dependency on the `test` package. ## 0.1.4+3 * Updated installation instructions in README. ## 0.1.4+2 * Added price currency symbol to SkuDetailsWrapper. ## 0.1.4+1 * Fixed typos. ## 0.1.4 * Added support for launchPriceChangeConfirmationFlow in the BillingClientWrapper and in InAppPurchaseAndroidPlatformAddition. ## 0.1.3+1 * Add payment proxy. ## 0.1.3 * Added support for isFeatureSupported in the BillingClientWrapper and in InAppPurchaseAndroidPlatformAddition. ## 0.1.2 * Added support for the obfuscatedAccountId and obfuscatedProfileId in the PurchaseWrapper. ## 0.1.1 * Added support to request a list of active subscriptions and non-consumed one-time purchases on Android, through the `InAppPurchaseAndroidPlatformAddition.queryPastPurchases` method. ## 0.1.0+1 * Migrate maven repository from jcenter to mavenCentral. ## 0.1.0 * Initial open-source release. ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/LICENSE ================================================ 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. ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/README.md ================================================ # in\_app\_purchase\_android The Android implementation of [`in_app_purchase`][1]. ## Usage This package has been [endorsed][2], meaning that you only need to add `in_app_purchase` as a dependency in your `pubspec.yaml`. This package will be automatically included in your app when you do. If you wish to use the Android package only, you can [add `in_app_purchase_android` directly][3]. ## Contributing This plugin uses [json_serializable](https://pub.dev/packages/json_serializable) for the many data structs passed between the underlying platform layers and Dart. After editing any of the serialized data structs, rebuild the serializers by running `flutter packages pub run build_runner build --delete-conflicting-outputs`. `flutter packages pub run build_runner watch --delete-conflicting-outputs` will watch the filesystem for changes. If you would like to contribute to the plugin, check out our [contribution guide](https://github.com/flutter/plugins/blob/main/CONTRIBUTING.md). [1]: https://pub.dev/packages/in_app_purchase [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin [3]: https://pub.dev/packages/in_app_purchase_android/install ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/android/build.gradle ================================================ group 'io.flutter.plugins.inapppurchase' version '1.0-SNAPSHOT' buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.3.1' } } rootProject.allprojects { repositories { google() mavenCentral() } } apply plugin: 'com.android.library' android { compileSdkVersion 31 defaultConfig { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } testOptions { unitTests.includeAndroidResources = true unitTests.returnDefaultValues = true unitTests.all { testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" outputs.upToDateWhen {false} showStandardStreams = true } } } } dependencies { 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.5.1' } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/android/settings.gradle ================================================ rootProject.name = 'in_app_purchase' ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java ================================================ // Copyright 2013 The Flutter 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.inapppurchase; import android.content.Context; import androidx.annotation.NonNull; import com.android.billingclient.api.BillingClient; import io.flutter.plugin.common.MethodChannel; /** Responsible for creating a {@link BillingClient} object. */ interface BillingClientFactory { /** * Creates and returns a {@link BillingClient}. * * @param context The context used to create the {@link BillingClient}. * @param channel The method channel used to create the {@link BillingClient}. * @return The {@link BillingClient} object that is created. */ BillingClient createBillingClient(@NonNull Context context, @NonNull MethodChannel channel); } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java ================================================ // Copyright 2013 The Flutter 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.inapppurchase; import android.content.Context; import com.android.billingclient.api.BillingClient; import io.flutter.plugin.common.MethodChannel; /** The implementation for {@link BillingClientFactory} for the plugin. */ final class BillingClientFactoryImpl implements BillingClientFactory { @Override public BillingClient createBillingClient(Context context, MethodChannel channel) { BillingClient.Builder builder = BillingClient.newBuilder(context).enablePendingPurchases(); return builder.setListener(new PluginPurchaseListener(channel)).build(); } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java ================================================ // Copyright 2013 The Flutter 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.inapppurchase; import android.app.Activity; import android.app.Application; import android.content.Context; import androidx.annotation.VisibleForTesting; import com.android.billingclient.api.BillingClient; 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.plugin.common.MethodChannel; /** Wraps a {@link BillingClient} instance and responds to Dart calls for it. */ public class InAppPurchasePlugin implements FlutterPlugin, ActivityAware { static final String PROXY_PACKAGE_KEY = "PROXY_PACKAGE"; // The proxy value has to match the value in library's AndroidManifest.xml. // This is important that the is not changed, so we hard code the value here then having // a unit test to make sure. If there is a strong reason to change the value, please inform the // code owner of this package. static final String PROXY_VALUE = "io.flutter.plugins.inapppurchase"; @VisibleForTesting static final class MethodNames { static final String IS_READY = "BillingClient#isReady()"; static final String START_CONNECTION = "BillingClient#startConnection(BillingClientStateListener)"; static final String END_CONNECTION = "BillingClient#endConnection()"; static final String ON_DISCONNECT = "BillingClientStateListener#onBillingServiceDisconnected()"; static final String QUERY_SKU_DETAILS = "BillingClient#querySkuDetailsAsync(SkuDetailsParams, SkuDetailsResponseListener)"; static final String LAUNCH_BILLING_FLOW = "BillingClient#launchBillingFlow(Activity, BillingFlowParams)"; static final String ON_PURCHASES_UPDATED = "PurchasesUpdatedListener#onPurchasesUpdated(int, List)"; static final String QUERY_PURCHASES = "BillingClient#queryPurchases(String)"; static final String QUERY_PURCHASES_ASYNC = "BillingClient#queryPurchasesAsync(String)"; static final String QUERY_PURCHASE_HISTORY_ASYNC = "BillingClient#queryPurchaseHistoryAsync(String, PurchaseHistoryResponseListener)"; static final String CONSUME_PURCHASE_ASYNC = "BillingClient#consumeAsync(String, ConsumeResponseListener)"; static final String ACKNOWLEDGE_PURCHASE = "BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)"; static final String IS_FEATURE_SUPPORTED = "BillingClient#isFeatureSupported(String)"; static final String LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW = "BillingClient#launchPriceChangeConfirmationFlow (Activity, PriceChangeFlowParams, PriceChangeConfirmationListener)"; static final String GET_CONNECTION_STATE = "BillingClient#getConnectionState()"; private MethodNames() {}; } private MethodChannel methodChannel; private MethodCallHandlerImpl methodCallHandler; /** Plugin registration. */ @SuppressWarnings("deprecation") public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { InAppPurchasePlugin plugin = new InAppPurchasePlugin(); registrar.activity().getIntent().putExtra(PROXY_PACKAGE_KEY, PROXY_VALUE); ((Application) registrar.context().getApplicationContext()) .registerActivityLifecycleCallbacks(plugin.methodCallHandler); } @Override public void onAttachedToEngine(FlutterPlugin.FlutterPluginBinding binding) { setupMethodChannel( /*activity=*/ null, binding.getBinaryMessenger(), binding.getApplicationContext()); } @Override public void onDetachedFromEngine(FlutterPlugin.FlutterPluginBinding binding) { teardownMethodChannel(); } @Override public void onAttachedToActivity(ActivityPluginBinding binding) { binding.getActivity().getIntent().putExtra(PROXY_PACKAGE_KEY, PROXY_VALUE); methodCallHandler.setActivity(binding.getActivity()); } @Override public void onDetachedFromActivity() { methodCallHandler.setActivity(null); methodCallHandler.onDetachedFromActivity(); } @Override public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { onAttachedToActivity(binding); } @Override public void onDetachedFromActivityForConfigChanges() { methodCallHandler.setActivity(null); } private void setupMethodChannel(Activity activity, BinaryMessenger messenger, Context context) { methodChannel = new MethodChannel(messenger, "plugins.flutter.io/in_app_purchase"); methodCallHandler = new MethodCallHandlerImpl(activity, context, methodChannel, new BillingClientFactoryImpl()); methodChannel.setMethodCallHandler(methodCallHandler); } private void teardownMethodChannel() { methodChannel.setMethodCallHandler(null); methodChannel = null; methodCallHandler = null; } @VisibleForTesting void setMethodCallHandler(MethodCallHandlerImpl methodCallHandler) { this.methodCallHandler = methodCallHandler; } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java ================================================ // Copyright 2013 The Flutter 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.inapppurchase; import static io.flutter.plugins.inapppurchase.Translator.fromPurchaseHistoryRecordList; import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList; import static io.flutter.plugins.inapppurchase.Translator.fromSkuDetailsList; import android.app.Activity; import android.app.Application; import android.content.Context; import android.os.Bundle; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.billingclient.api.AcknowledgePurchaseParams; import com.android.billingclient.api.AcknowledgePurchaseResponseListener; import com.android.billingclient.api.BillingClient; import com.android.billingclient.api.BillingClientStateListener; import com.android.billingclient.api.BillingFlowParams; import com.android.billingclient.api.BillingFlowParams.ProrationMode; import com.android.billingclient.api.BillingResult; import com.android.billingclient.api.ConsumeParams; import com.android.billingclient.api.ConsumeResponseListener; import com.android.billingclient.api.PriceChangeFlowParams; import com.android.billingclient.api.Purchase; import com.android.billingclient.api.PurchaseHistoryRecord; import com.android.billingclient.api.PurchaseHistoryResponseListener; import com.android.billingclient.api.PurchasesResponseListener; import com.android.billingclient.api.QueryPurchaseHistoryParams; import com.android.billingclient.api.QueryPurchasesParams; import com.android.billingclient.api.SkuDetails; import com.android.billingclient.api.SkuDetailsParams; import com.android.billingclient.api.SkuDetailsResponseListener; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import java.util.HashMap; import java.util.List; import java.util.Map; /** Handles method channel for the plugin. */ class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler, Application.ActivityLifecycleCallbacks { private static final String TAG = "InAppPurchasePlugin"; private static final String LOAD_SKU_DOC_URL = "https://github.com/flutter/plugins/blob/main/packages/in_app_purchase/in_app_purchase/README.md#loading-products-for-sale"; @Nullable private BillingClient billingClient; private final BillingClientFactory billingClientFactory; @Nullable private Activity activity; private final Context applicationContext; private final MethodChannel methodChannel; private HashMap cachedSkus = new HashMap<>(); /** Constructs the MethodCallHandlerImpl */ MethodCallHandlerImpl( @Nullable Activity activity, @NonNull Context applicationContext, @NonNull MethodChannel methodChannel, @NonNull BillingClientFactory billingClientFactory) { this.billingClientFactory = billingClientFactory; this.applicationContext = applicationContext; this.activity = activity; this.methodChannel = methodChannel; } /** * Sets the activity. Should be called as soon as the the activity is available. When the activity * becomes unavailable, call this method again with {@code null}. */ void setActivity(@Nullable Activity activity) { this.activity = activity; } @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {} @Override public void onActivityStarted(Activity activity) {} @Override public void onActivityResumed(Activity activity) {} @Override public void onActivityPaused(Activity activity) {} @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} @Override public void onActivityDestroyed(Activity activity) { if (this.activity == activity && this.applicationContext != null) { ((Application) this.applicationContext).unregisterActivityLifecycleCallbacks(this); endBillingClientConnection(); } } @Override public void onActivityStopped(Activity activity) {} void onDetachedFromActivity() { endBillingClientConnection(); } @Override public void onMethodCall(MethodCall call, MethodChannel.Result result) { switch (call.method) { case InAppPurchasePlugin.MethodNames.IS_READY: isReady(result); break; case InAppPurchasePlugin.MethodNames.START_CONNECTION: startConnection((int) call.argument("handle"), result); break; case InAppPurchasePlugin.MethodNames.END_CONNECTION: endConnection(result); break; case InAppPurchasePlugin.MethodNames.QUERY_SKU_DETAILS: List skusList = call.argument("skusList"); querySkuDetailsAsync((String) call.argument("skuType"), skusList, result); break; case InAppPurchasePlugin.MethodNames.LAUNCH_BILLING_FLOW: launchBillingFlow( (String) call.argument("sku"), (String) call.argument("accountId"), (String) call.argument("obfuscatedProfileId"), (String) call.argument("oldSku"), (String) call.argument("purchaseToken"), call.hasArgument("prorationMode") ? (int) call.argument("prorationMode") : ProrationMode.UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY, result); break; case InAppPurchasePlugin.MethodNames.QUERY_PURCHASES: // Legacy method name. queryPurchasesAsync((String) call.argument("skuType"), result); break; case InAppPurchasePlugin.MethodNames.QUERY_PURCHASES_ASYNC: queryPurchasesAsync((String) call.argument("skuType"), result); break; case InAppPurchasePlugin.MethodNames.QUERY_PURCHASE_HISTORY_ASYNC: Log.e("flutter", (String) call.argument("skuType")); queryPurchaseHistoryAsync((String) call.argument("skuType"), result); break; case InAppPurchasePlugin.MethodNames.CONSUME_PURCHASE_ASYNC: consumeAsync((String) call.argument("purchaseToken"), result); break; case InAppPurchasePlugin.MethodNames.ACKNOWLEDGE_PURCHASE: acknowledgePurchase((String) call.argument("purchaseToken"), result); break; case InAppPurchasePlugin.MethodNames.IS_FEATURE_SUPPORTED: isFeatureSupported((String) call.argument("feature"), result); break; case InAppPurchasePlugin.MethodNames.LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW: launchPriceChangeConfirmationFlow((String) call.argument("sku"), result); break; case InAppPurchasePlugin.MethodNames.GET_CONNECTION_STATE: getConnectionState(result); break; default: result.notImplemented(); } } private void endConnection(final MethodChannel.Result result) { endBillingClientConnection(); result.success(null); } private void endBillingClientConnection() { if (billingClient != null) { billingClient.endConnection(); billingClient = null; } } private void isReady(MethodChannel.Result result) { if (billingClientError(result)) { return; } result.success(billingClient.isReady()); } // TODO(garyq): Migrate to new subscriptions API: https://developer.android.com/google/play/billing/migrate-gpblv5 private void querySkuDetailsAsync( final String skuType, final List skusList, final MethodChannel.Result result) { if (billingClientError(result)) { return; } SkuDetailsParams params = SkuDetailsParams.newBuilder().setType(skuType).setSkusList(skusList).build(); billingClient.querySkuDetailsAsync( params, new SkuDetailsResponseListener() { @Override public void onSkuDetailsResponse( BillingResult billingResult, List skuDetailsList) { updateCachedSkus(skuDetailsList); final Map skuDetailsResponse = new HashMap<>(); skuDetailsResponse.put("billingResult", Translator.fromBillingResult(billingResult)); skuDetailsResponse.put("skuDetailsList", fromSkuDetailsList(skuDetailsList)); result.success(skuDetailsResponse); } }); } private void launchBillingFlow( String sku, @Nullable String accountId, @Nullable String obfuscatedProfileId, @Nullable String oldSku, @Nullable String purchaseToken, int prorationMode, MethodChannel.Result result) { if (billingClientError(result)) { return; } SkuDetails skuDetails = cachedSkus.get(sku); if (skuDetails == null) { result.error( "NOT_FOUND", String.format( "Details for sku %s are not available. It might because skus were not fetched prior to the call. Please fetch the skus first. An example of how to fetch the skus could be found here: %s", sku, LOAD_SKU_DOC_URL), null); return; } if (oldSku == null && prorationMode != ProrationMode.UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY) { result.error( "IN_APP_PURCHASE_REQUIRE_OLD_SKU", "launchBillingFlow failed because oldSku is null. You must provide a valid oldSku in order to use a proration mode.", null); return; } else if (oldSku != null && !cachedSkus.containsKey(oldSku)) { result.error( "IN_APP_PURCHASE_INVALID_OLD_SKU", String.format( "Details for sku %s are not available. It might because skus were not fetched prior to the call. Please fetch the skus first. An example of how to fetch the skus could be found here: %s", oldSku, LOAD_SKU_DOC_URL), null); return; } if (activity == null) { result.error( "ACTIVITY_UNAVAILABLE", "Details for sku " + sku + " are not available. This method must be run with the app in foreground.", null); return; } BillingFlowParams.Builder paramsBuilder = BillingFlowParams.newBuilder().setSkuDetails(skuDetails); if (accountId != null && !accountId.isEmpty()) { paramsBuilder.setObfuscatedAccountId(accountId); } if (obfuscatedProfileId != null && !obfuscatedProfileId.isEmpty()) { paramsBuilder.setObfuscatedProfileId(obfuscatedProfileId); } BillingFlowParams.SubscriptionUpdateParams.Builder subscriptionUpdateParamsBuilder = BillingFlowParams.SubscriptionUpdateParams.newBuilder(); if (oldSku != null && !oldSku.isEmpty() && purchaseToken != null) { subscriptionUpdateParamsBuilder.setOldPurchaseToken(purchaseToken); // The proration mode value has to match one of the following declared in // https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode subscriptionUpdateParamsBuilder.setReplaceProrationMode(prorationMode); paramsBuilder.setSubscriptionUpdateParams(subscriptionUpdateParamsBuilder.build()); } result.success( Translator.fromBillingResult( billingClient.launchBillingFlow(activity, paramsBuilder.build()))); } private void consumeAsync(String purchaseToken, final MethodChannel.Result result) { if (billingClientError(result)) { return; } ConsumeResponseListener listener = new ConsumeResponseListener() { @Override public void onConsumeResponse(BillingResult billingResult, String outToken) { result.success(Translator.fromBillingResult(billingResult)); } }; ConsumeParams.Builder paramsBuilder = ConsumeParams.newBuilder().setPurchaseToken(purchaseToken); ConsumeParams params = paramsBuilder.build(); billingClient.consumeAsync(params, listener); } private void queryPurchasesAsync(String skuType, MethodChannel.Result result) { if (billingClientError(result)) { return; } // Like in our connect call, consider the billing client responding a "success" here regardless // of status code. QueryPurchasesParams.Builder paramsBuilder = QueryPurchasesParams.newBuilder(); paramsBuilder.setProductType(skuType); billingClient.queryPurchasesAsync( paramsBuilder.build(), new PurchasesResponseListener() { @Override public void onQueryPurchasesResponse( BillingResult billingResult, List purchasesList) { final Map serialized = new HashMap<>(); // The response code is no longer passed, as part of billing 4.0, so we pass OK here // as success is implied by calling this callback. serialized.put("responseCode", BillingClient.BillingResponseCode.OK); serialized.put("billingResult", Translator.fromBillingResult(billingResult)); serialized.put("purchasesList", fromPurchasesList(purchasesList)); result.success(serialized); } }); } private void queryPurchaseHistoryAsync(String skuType, final MethodChannel.Result result) { if (billingClientError(result)) { return; } billingClient.queryPurchaseHistoryAsync( QueryPurchaseHistoryParams.newBuilder().setProductType(skuType).build(), new PurchaseHistoryResponseListener() { @Override public void onPurchaseHistoryResponse( BillingResult billingResult, List purchasesList) { final Map serialized = new HashMap<>(); serialized.put("billingResult", Translator.fromBillingResult(billingResult)); serialized.put( "purchaseHistoryRecordList", fromPurchaseHistoryRecordList(purchasesList)); result.success(serialized); } }); } private void getConnectionState(final MethodChannel.Result result) { if (billingClientError(result)) { return; } final Map serialized = new HashMap<>(); serialized.put("connectionState", billingClient.getConnectionState()); result.success(serialized); } private void startConnection(final int handle, final MethodChannel.Result result) { if (billingClient == null) { billingClient = billingClientFactory.createBillingClient(applicationContext, methodChannel); } billingClient.startConnection( new BillingClientStateListener() { private boolean alreadyFinished = false; @Override public void onBillingSetupFinished(BillingResult billingResult) { if (alreadyFinished) { Log.d(TAG, "Tried to call onBillingSetupFinished multiple times."); return; } alreadyFinished = true; // Consider the fact that we've finished a success, leave it to the Dart side to // validate the responseCode. result.success(Translator.fromBillingResult(billingResult)); } @Override public void onBillingServiceDisconnected() { final Map arguments = new HashMap<>(); arguments.put("handle", handle); methodChannel.invokeMethod(InAppPurchasePlugin.MethodNames.ON_DISCONNECT, arguments); } }); } private void acknowledgePurchase(String purchaseToken, final MethodChannel.Result result) { if (billingClientError(result)) { return; } AcknowledgePurchaseParams params = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchaseToken).build(); billingClient.acknowledgePurchase( params, new AcknowledgePurchaseResponseListener() { @Override public void onAcknowledgePurchaseResponse(BillingResult billingResult) { result.success(Translator.fromBillingResult(billingResult)); } }); } private void updateCachedSkus(@Nullable List skuDetailsList) { if (skuDetailsList == null) { return; } for (SkuDetails skuDetails : skuDetailsList) { cachedSkus.put(skuDetails.getSku(), skuDetails); } } private void launchPriceChangeConfirmationFlow(String sku, MethodChannel.Result result) { if (activity == null) { result.error( "ACTIVITY_UNAVAILABLE", "launchPriceChangeConfirmationFlow is not available. " + "This method must be run with the app in foreground.", null); return; } if (billingClientError(result)) { return; } // Note that assert doesn't work on Android (see https://stackoverflow.com/a/6176529/5167831 and https://stackoverflow.com/a/8164195/5167831) // and that this assert is only added to silence the analyser. The actual null check // is handled by the `billingClientError()` call. assert billingClient != null; SkuDetails skuDetails = cachedSkus.get(sku); if (skuDetails == null) { result.error( "NOT_FOUND", String.format( "Details for sku %s are not available. It might because skus were not fetched prior to the call. Please fetch the skus first. An example of how to fetch the skus could be found here: %s", sku, LOAD_SKU_DOC_URL), null); return; } PriceChangeFlowParams params = new PriceChangeFlowParams.Builder().setSkuDetails(skuDetails).build(); billingClient.launchPriceChangeConfirmationFlow( activity, params, billingResult -> { result.success(Translator.fromBillingResult(billingResult)); }); } private boolean billingClientError(MethodChannel.Result result) { if (billingClient != null) { return false; } result.error("UNAVAILABLE", "BillingClient is unset. Try reconnecting.", null); return true; } private void isFeatureSupported(String feature, MethodChannel.Result result) { if (billingClientError(result)) { return; } assert billingClient != null; BillingResult billingResult = billingClient.isFeatureSupported(feature); result.success(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK); } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java ================================================ // Copyright 2013 The Flutter 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.inapppurchase; import static io.flutter.plugins.inapppurchase.Translator.fromBillingResult; import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList; import androidx.annotation.Nullable; import com.android.billingclient.api.BillingResult; import com.android.billingclient.api.Purchase; import com.android.billingclient.api.PurchasesUpdatedListener; import io.flutter.plugin.common.MethodChannel; import java.util.HashMap; import java.util.List; import java.util.Map; class PluginPurchaseListener implements PurchasesUpdatedListener { private final MethodChannel channel; PluginPurchaseListener(MethodChannel channel) { this.channel = channel; } @Override public void onPurchasesUpdated(BillingResult billingResult, @Nullable List purchases) { final Map callbackArgs = new HashMap<>(); callbackArgs.put("billingResult", fromBillingResult(billingResult)); callbackArgs.put("responseCode", billingResult.getResponseCode()); callbackArgs.put("purchasesList", fromPurchasesList(purchases)); channel.invokeMethod(InAppPurchasePlugin.MethodNames.ON_PURCHASES_UPDATED, callbackArgs); } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java ================================================ // Copyright 2013 The Flutter 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.inapppurchase; import androidx.annotation.Nullable; import com.android.billingclient.api.AccountIdentifiers; import com.android.billingclient.api.BillingResult; import com.android.billingclient.api.Purchase; import com.android.billingclient.api.PurchaseHistoryRecord; import com.android.billingclient.api.SkuDetails; import java.util.ArrayList; import java.util.Collections; import java.util.Currency; import java.util.HashMap; import java.util.List; import java.util.Locale; /** Handles serialization of {@link com.android.billingclient.api.BillingClient} related objects. */ /*package*/ class Translator { static HashMap fromSkuDetail(SkuDetails detail) { HashMap info = new HashMap<>(); info.put("title", detail.getTitle()); info.put("description", detail.getDescription()); info.put("freeTrialPeriod", detail.getFreeTrialPeriod()); info.put("introductoryPrice", detail.getIntroductoryPrice()); info.put("introductoryPriceAmountMicros", detail.getIntroductoryPriceAmountMicros()); info.put("introductoryPriceCycles", detail.getIntroductoryPriceCycles()); info.put("introductoryPricePeriod", detail.getIntroductoryPricePeriod()); info.put("price", detail.getPrice()); info.put("priceAmountMicros", detail.getPriceAmountMicros()); info.put("priceCurrencyCode", detail.getPriceCurrencyCode()); info.put("priceCurrencySymbol", currencySymbolFromCode(detail.getPriceCurrencyCode())); info.put("sku", detail.getSku()); info.put("type", detail.getType()); info.put("subscriptionPeriod", detail.getSubscriptionPeriod()); info.put("originalPrice", detail.getOriginalPrice()); info.put("originalPriceAmountMicros", detail.getOriginalPriceAmountMicros()); return info; } static List> fromSkuDetailsList( @Nullable List skuDetailsList) { if (skuDetailsList == null) { return Collections.emptyList(); } ArrayList> output = new ArrayList<>(); for (SkuDetails detail : skuDetailsList) { output.add(fromSkuDetail(detail)); } return output; } static HashMap fromPurchase(Purchase purchase) { HashMap info = new HashMap<>(); List skus = purchase.getSkus(); info.put("orderId", purchase.getOrderId()); info.put("packageName", purchase.getPackageName()); info.put("purchaseTime", purchase.getPurchaseTime()); info.put("purchaseToken", purchase.getPurchaseToken()); info.put("signature", purchase.getSignature()); info.put("skus", skus); info.put("isAutoRenewing", purchase.isAutoRenewing()); info.put("originalJson", purchase.getOriginalJson()); info.put("developerPayload", purchase.getDeveloperPayload()); info.put("isAcknowledged", purchase.isAcknowledged()); info.put("purchaseState", purchase.getPurchaseState()); info.put("quantity", purchase.getQuantity()); AccountIdentifiers accountIdentifiers = purchase.getAccountIdentifiers(); if (accountIdentifiers != null) { info.put("obfuscatedAccountId", accountIdentifiers.getObfuscatedAccountId()); info.put("obfuscatedProfileId", accountIdentifiers.getObfuscatedProfileId()); } return info; } static HashMap fromPurchaseHistoryRecord( PurchaseHistoryRecord purchaseHistoryRecord) { HashMap info = new HashMap<>(); List skus = purchaseHistoryRecord.getSkus(); info.put("purchaseTime", purchaseHistoryRecord.getPurchaseTime()); info.put("purchaseToken", purchaseHistoryRecord.getPurchaseToken()); info.put("signature", purchaseHistoryRecord.getSignature()); info.put("skus", skus); info.put("developerPayload", purchaseHistoryRecord.getDeveloperPayload()); info.put("originalJson", purchaseHistoryRecord.getOriginalJson()); info.put("quantity", purchaseHistoryRecord.getQuantity()); return info; } static List> fromPurchasesList(@Nullable List purchases) { if (purchases == null) { return Collections.emptyList(); } List> serialized = new ArrayList<>(); for (Purchase purchase : purchases) { serialized.add(fromPurchase(purchase)); } return serialized; } static List> fromPurchaseHistoryRecordList( @Nullable List purchaseHistoryRecords) { if (purchaseHistoryRecords == null) { return Collections.emptyList(); } List> serialized = new ArrayList<>(); for (PurchaseHistoryRecord purchaseHistoryRecord : purchaseHistoryRecords) { serialized.add(fromPurchaseHistoryRecord(purchaseHistoryRecord)); } return serialized; } static HashMap fromBillingResult(BillingResult billingResult) { HashMap info = new HashMap<>(); info.put("responseCode", billingResult.getResponseCode()); info.put("debugMessage", billingResult.getDebugMessage()); return info; } /** * Gets the symbol of for the given currency code for the default {@link Locale.Category#DISPLAY * DISPLAY} locale. For example, for the US Dollar, the symbol is "$" if the default locale is the * US, while for other locales it may be "US$". If no symbol can be determined, the ISO 4217 * currency code is returned. * * @param currencyCode the ISO 4217 code of the currency * @return the symbol of this currency code for the default {@link Locale.Category#DISPLAY * DISPLAY} locale * @exception NullPointerException if currencyCode is null * @exception IllegalArgumentException if currencyCode is not a supported ISO 4217 * code. */ static String currencySymbolFromCode(String currencyCode) { return Currency.getInstance(currencyCode).getSymbol(); } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/android/src/test/java/android/text/TextUtils.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package android.text; public class TextUtils { public static boolean isEmpty(CharSequence str) { return str == null || str.length() == 0; } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/android/src/test/java/android/util/Log.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package android.util; public class Log { public static int d(String tag, String msg) { System.out.println("DEBUG: " + tag + ": " + msg); return 0; } public static int i(String tag, String msg) { System.out.println("INFO: " + tag + ": " + msg); return 0; } public static int w(String tag, String msg) { System.out.println("WARN: " + tag + ": " + msg); return 0; } public static int e(String tag, String msg) { System.out.println("ERROR: " + tag + ": " + msg); return 0; } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java ================================================ // Copyright 2013 The Flutter 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.inapppurchase; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.when; import android.app.Activity; import android.app.Application; import android.content.Context; import android.content.Intent; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.PluginRegistry; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class InAppPurchasePluginTest { static final String PROXY_PACKAGE_KEY = "PROXY_PACKAGE"; @Mock Activity activity; @Mock Context context; @Mock PluginRegistry.Registrar mockRegistrar; // For v1 embedding @Mock BinaryMessenger mockMessenger; @Mock Application mockApplication; @Mock Intent mockIntent; @Mock ActivityPluginBinding activityPluginBinding; @Mock FlutterPlugin.FlutterPluginBinding flutterPluginBinding; @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mockRegistrar.activity()).thenReturn(activity); when(mockRegistrar.messenger()).thenReturn(mockMessenger); when(mockRegistrar.context()).thenReturn(context); when(activity.getIntent()).thenReturn(mockIntent); when(activityPluginBinding.getActivity()).thenReturn(activity); when(flutterPluginBinding.getBinaryMessenger()).thenReturn(mockMessenger); when(flutterPluginBinding.getApplicationContext()).thenReturn(context); } @Test public void registerWith_doNotCrashWhenRegisterContextIsActivity_V1Embedding() { when(mockRegistrar.context()).thenReturn(activity); when(activity.getApplicationContext()).thenReturn(mockApplication); InAppPurchasePlugin.registerWith(mockRegistrar); } // The PROXY_PACKAGE_KEY value of this test (io.flutter.plugins.inapppurchase) should never be changed. // In case there's a strong reason to change it, please inform the current code owner of the plugin. @Test public void registerWith_proxyIsSet_V1Embedding() { when(mockRegistrar.context()).thenReturn(activity); when(activity.getApplicationContext()).thenReturn(mockApplication); InAppPurchasePlugin.registerWith(mockRegistrar); // The `PROXY_PACKAGE_KEY` value is hard coded in the plugin code as "io.flutter.plugins.inapppurchase". // We cannot use `BuildConfig.LIBRARY_PACKAGE_NAME` directly in the plugin code because whether to read BuildConfig.APPLICATION_ID or LIBRARY_PACKAGE_NAME // depends on the "APP's" Android Gradle plugin version. Newer versions of AGP use LIBRARY_PACKAGE_NAME, whereas older ones use BuildConfig.APPLICATION_ID. Mockito.verify(mockIntent).putExtra(PROXY_PACKAGE_KEY, "io.flutter.plugins.inapppurchase"); assertEquals("io.flutter.plugins.inapppurchase", BuildConfig.LIBRARY_PACKAGE_NAME); } // The PROXY_PACKAGE_KEY value of this test (io.flutter.plugins.inapppurchase) should never be changed. // In case there's a strong reason to change it, please inform the current code owner of the plugin. @Test public void attachToActivity_proxyIsSet_V2Embedding() { InAppPurchasePlugin plugin = new InAppPurchasePlugin(); plugin.onAttachedToEngine(flutterPluginBinding); plugin.onAttachedToActivity(activityPluginBinding); // The `PROXY_PACKAGE_KEY` value is hard coded in the plugin code as "io.flutter.plugins.inapppurchase". // We cannot use `BuildConfig.LIBRARY_PACKAGE_NAME` directly in the plugin code because whether to read BuildConfig.APPLICATION_ID or LIBRARY_PACKAGE_NAME // depends on the "APP's" Android Gradle plugin version. Newer versions of AGP use LIBRARY_PACKAGE_NAME, whereas older ones use BuildConfig.APPLICATION_ID. Mockito.verify(mockIntent).putExtra(PROXY_PACKAGE_KEY, "io.flutter.plugins.inapppurchase"); assertEquals("io.flutter.plugins.inapppurchase", BuildConfig.LIBRARY_PACKAGE_NAME); } } // We cannot use `BuildConfig.LIBRARY_PACKAGE_NAME` directly in the plugin code because whether to read BuildConfig.APPLICATION_ID or LIBRARY_PACKAGE_NAME // depends on the "APP's" Android Gradle plugin version. Newer versions of AGP use LIBRARY_PACKAGE_NAME, whereas older ones use BuildConfig.APPLICATION_ID. ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java ================================================ // Copyright 2013 The Flutter 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.inapppurchase; import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.ACKNOWLEDGE_PURCHASE; import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.CONSUME_PURCHASE_ASYNC; import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.END_CONNECTION; import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.IS_FEATURE_SUPPORTED; import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.IS_READY; import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.LAUNCH_BILLING_FLOW; import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW; import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.ON_DISCONNECT; import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.ON_PURCHASES_UPDATED; import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.QUERY_PURCHASES_ASYNC; import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.QUERY_PURCHASE_HISTORY_ASYNC; import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.QUERY_SKU_DETAILS; import static io.flutter.plugins.inapppurchase.InAppPurchasePlugin.MethodNames.START_CONNECTION; import static io.flutter.plugins.inapppurchase.Translator.fromBillingResult; import static io.flutter.plugins.inapppurchase.Translator.fromPurchaseHistoryRecordList; import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList; import static io.flutter.plugins.inapppurchase.Translator.fromSkuDetailsList; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static java.util.Collections.unmodifiableList; import static java.util.stream.Collectors.toList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.contains; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.refEq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; 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 androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.billingclient.api.AcknowledgePurchaseParams; import com.android.billingclient.api.AcknowledgePurchaseResponseListener; import com.android.billingclient.api.BillingClient; import com.android.billingclient.api.BillingClient.SkuType; import com.android.billingclient.api.BillingClientStateListener; import com.android.billingclient.api.BillingFlowParams; import com.android.billingclient.api.BillingResult; import com.android.billingclient.api.ConsumeParams; import com.android.billingclient.api.ConsumeResponseListener; import com.android.billingclient.api.PriceChangeConfirmationListener; import com.android.billingclient.api.PriceChangeFlowParams; import com.android.billingclient.api.Purchase; import com.android.billingclient.api.PurchaseHistoryRecord; import com.android.billingclient.api.PurchaseHistoryResponseListener; import com.android.billingclient.api.PurchasesResponseListener; import com.android.billingclient.api.QueryPurchaseHistoryParams; import com.android.billingclient.api.QueryPurchasesParams; import com.android.billingclient.api.SkuDetails; import com.android.billingclient.api.SkuDetailsParams; import com.android.billingclient.api.SkuDetailsResponseListener; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.Result; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.json.JSONException; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; public class MethodCallHandlerTest { private MethodCallHandlerImpl methodChannelHandler; private BillingClientFactory factory; @Mock BillingClient mockBillingClient; @Mock MethodChannel mockMethodChannel; @Spy Result result; @Mock Activity activity; @Mock Context context; @Mock ActivityPluginBinding mockActivityPluginBinding; @Before public void setUp() { MockitoAnnotations.openMocks(this); factory = (@NonNull Context context, @NonNull MethodChannel channel) -> mockBillingClient; methodChannelHandler = new MethodCallHandlerImpl(activity, context, mockMethodChannel, factory); when(mockActivityPluginBinding.getActivity()).thenReturn(activity); } @Test public void invalidMethod() { MethodCall call = new MethodCall("invalid", null); methodChannelHandler.onMethodCall(call, result); verify(result, times(1)).notImplemented(); } @Test public void isReady_true() { mockStartConnection(); MethodCall call = new MethodCall(IS_READY, null); when(mockBillingClient.isReady()).thenReturn(true); methodChannelHandler.onMethodCall(call, result); verify(result).success(true); } @Test public void isReady_false() { mockStartConnection(); MethodCall call = new MethodCall(IS_READY, null); when(mockBillingClient.isReady()).thenReturn(false); methodChannelHandler.onMethodCall(call, result); verify(result).success(false); } @Test public void isReady_clientDisconnected() { MethodCall disconnectCall = new MethodCall(END_CONNECTION, null); methodChannelHandler.onMethodCall(disconnectCall, mock(Result.class)); MethodCall isReadyCall = new MethodCall(IS_READY, null); methodChannelHandler.onMethodCall(isReadyCall, result); verify(result).error(contains("UNAVAILABLE"), contains("BillingClient"), any()); verify(result, never()).success(any()); } @Test public void startConnection() { ArgumentCaptor captor = mockStartConnection(); verify(result, never()).success(any()); BillingResult billingResult = BillingResult.newBuilder() .setResponseCode(100) .setDebugMessage("dummy debug message") .build(); captor.getValue().onBillingSetupFinished(billingResult); verify(result, times(1)).success(fromBillingResult(billingResult)); } @Test public void startConnection_multipleCalls() { Map arguments = new HashMap<>(); arguments.put("handle", 1); MethodCall call = new MethodCall(START_CONNECTION, arguments); ArgumentCaptor captor = ArgumentCaptor.forClass(BillingClientStateListener.class); doNothing().when(mockBillingClient).startConnection(captor.capture()); methodChannelHandler.onMethodCall(call, result); verify(result, never()).success(any()); BillingResult billingResult1 = BillingResult.newBuilder() .setResponseCode(100) .setDebugMessage("dummy debug message") .build(); BillingResult billingResult2 = BillingResult.newBuilder() .setResponseCode(200) .setDebugMessage("dummy debug message") .build(); BillingResult billingResult3 = BillingResult.newBuilder() .setResponseCode(300) .setDebugMessage("dummy debug message") .build(); captor.getValue().onBillingSetupFinished(billingResult1); captor.getValue().onBillingSetupFinished(billingResult2); captor.getValue().onBillingSetupFinished(billingResult3); verify(result, times(1)).success(fromBillingResult(billingResult1)); verify(result, times(1)).success(any()); } @Test public void endConnection() { // Set up a connected BillingClient instance final int disconnectCallbackHandle = 22; Map arguments = new HashMap<>(); arguments.put("handle", disconnectCallbackHandle); MethodCall connectCall = new MethodCall(START_CONNECTION, arguments); ArgumentCaptor captor = ArgumentCaptor.forClass(BillingClientStateListener.class); doNothing().when(mockBillingClient).startConnection(captor.capture()); methodChannelHandler.onMethodCall(connectCall, mock(Result.class)); final BillingClientStateListener stateListener = captor.getValue(); // Disconnect the connected client MethodCall disconnectCall = new MethodCall(END_CONNECTION, null); methodChannelHandler.onMethodCall(disconnectCall, result); // Verify that the client is disconnected and that the OnDisconnect callback has // been triggered verify(result, times(1)).success(any()); verify(mockBillingClient, times(1)).endConnection(); stateListener.onBillingServiceDisconnected(); Map expectedInvocation = new HashMap<>(); expectedInvocation.put("handle", disconnectCallbackHandle); verify(mockMethodChannel, times(1)).invokeMethod(ON_DISCONNECT, expectedInvocation); } @Test public void querySkuDetailsAsync() { // Connect a billing client and set up the SKU query listeners establishConnectedBillingClient(/* arguments= */ null, /* result= */ null); String skuType = BillingClient.SkuType.INAPP; List skusList = asList("id1", "id2"); HashMap arguments = new HashMap<>(); arguments.put("skuType", skuType); arguments.put("skusList", skusList); MethodCall queryCall = new MethodCall(QUERY_SKU_DETAILS, arguments); // Query for SKU details methodChannelHandler.onMethodCall(queryCall, result); // Assert the arguments were forwarded correctly to BillingClient ArgumentCaptor paramCaptor = ArgumentCaptor.forClass(SkuDetailsParams.class); ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(SkuDetailsResponseListener.class); verify(mockBillingClient).querySkuDetailsAsync(paramCaptor.capture(), listenerCaptor.capture()); assertEquals(paramCaptor.getValue().getSkuType(), skuType); assertEquals(paramCaptor.getValue().getSkusList(), skusList); // Assert that we handed result BillingClient's response int responseCode = 200; List skuDetailsResponse = asList(buildSkuDetails("foo")); BillingResult billingResult = BillingResult.newBuilder() .setResponseCode(100) .setDebugMessage("dummy debug message") .build(); listenerCaptor.getValue().onSkuDetailsResponse(billingResult, skuDetailsResponse); ArgumentCaptor> resultCaptor = ArgumentCaptor.forClass(HashMap.class); verify(result).success(resultCaptor.capture()); HashMap resultData = resultCaptor.getValue(); assertEquals(resultData.get("billingResult"), fromBillingResult(billingResult)); assertEquals(resultData.get("skuDetailsList"), fromSkuDetailsList(skuDetailsResponse)); } @Test public void querySkuDetailsAsync_clientDisconnected() { // Disconnect the Billing client and prepare a querySkuDetails call MethodCall disconnectCall = new MethodCall(END_CONNECTION, null); methodChannelHandler.onMethodCall(disconnectCall, mock(Result.class)); String skuType = BillingClient.SkuType.INAPP; List skusList = asList("id1", "id2"); HashMap arguments = new HashMap<>(); arguments.put("skuType", skuType); arguments.put("skusList", skusList); MethodCall queryCall = new MethodCall(QUERY_SKU_DETAILS, arguments); // Query for SKU details methodChannelHandler.onMethodCall(queryCall, result); // Assert that we sent an error back. verify(result).error(contains("UNAVAILABLE"), contains("BillingClient"), any()); verify(result, never()).success(any()); } // Test launchBillingFlow not crash if `accountId` is `null` // Ideally, we should check if the `accountId` is null in the parameter; however, // since PBL 3.0, the `accountId` variable is not public. @Test public void launchBillingFlow_null_AccountId_do_not_crash() { // Fetch the sku details first and then prepare the launch billing flow call String skuId = "foo"; queryForSkus(singletonList(skuId)); HashMap arguments = new HashMap<>(); arguments.put("sku", skuId); arguments.put("accountId", null); arguments.put("obfuscatedProfileId", null); MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); // Launch the billing flow BillingResult billingResult = BillingResult.newBuilder() .setResponseCode(100) .setDebugMessage("dummy debug message") .build(); when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult); methodChannelHandler.onMethodCall(launchCall, result); // Verify we pass the arguments to the billing flow ArgumentCaptor billingFlowParamsCaptor = ArgumentCaptor.forClass(BillingFlowParams.class); verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture()); BillingFlowParams params = billingFlowParamsCaptor.getValue(); // Verify we pass the response code to result verify(result, never()).error(any(), any(), any()); verify(result, times(1)).success(fromBillingResult(billingResult)); } @Test public void launchBillingFlow_ok_null_OldSku() { // Fetch the sku details first and then prepare the launch billing flow call String skuId = "foo"; String accountId = "account"; queryForSkus(singletonList(skuId)); HashMap arguments = new HashMap<>(); arguments.put("sku", skuId); arguments.put("accountId", accountId); arguments.put("oldSku", null); MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); // Launch the billing flow BillingResult billingResult = BillingResult.newBuilder() .setResponseCode(100) .setDebugMessage("dummy debug message") .build(); when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult); methodChannelHandler.onMethodCall(launchCall, result); // Verify we pass the arguments to the billing flow ArgumentCaptor billingFlowParamsCaptor = ArgumentCaptor.forClass(BillingFlowParams.class); verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture()); BillingFlowParams params = billingFlowParamsCaptor.getValue(); // Verify we pass the response code to result verify(result, never()).error(any(), any(), any()); verify(result, times(1)).success(fromBillingResult(billingResult)); } @Test public void launchBillingFlow_ok_null_Activity() { methodChannelHandler.setActivity(null); // Fetch the sku details first and then prepare the launch billing flow call String skuId = "foo"; String accountId = "account"; queryForSkus(singletonList(skuId)); HashMap arguments = new HashMap<>(); arguments.put("sku", skuId); arguments.put("accountId", accountId); MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); methodChannelHandler.onMethodCall(launchCall, result); // Verify we pass the response code to result verify(result).error(contains("ACTIVITY_UNAVAILABLE"), contains("foreground"), any()); verify(result, never()).success(any()); } @Test public void launchBillingFlow_ok_oldSku() { // Fetch the sku details first and query the method call String skuId = "foo"; String accountId = "account"; String oldSkuId = "oldFoo"; queryForSkus(unmodifiableList(asList(skuId, oldSkuId))); HashMap arguments = new HashMap<>(); arguments.put("sku", skuId); arguments.put("accountId", accountId); arguments.put("oldSku", oldSkuId); MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); // Launch the billing flow BillingResult billingResult = BillingResult.newBuilder() .setResponseCode(100) .setDebugMessage("dummy debug message") .build(); when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult); methodChannelHandler.onMethodCall(launchCall, result); // Verify we pass the arguments to the billing flow ArgumentCaptor billingFlowParamsCaptor = ArgumentCaptor.forClass(BillingFlowParams.class); verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture()); BillingFlowParams params = billingFlowParamsCaptor.getValue(); // Verify we pass the response code to result verify(result, never()).error(any(), any(), any()); verify(result, times(1)).success(fromBillingResult(billingResult)); } @Test public void launchBillingFlow_ok_AccountId() { // Fetch the sku details first and query the method call String skuId = "foo"; String accountId = "account"; queryForSkus(singletonList(skuId)); HashMap arguments = new HashMap<>(); arguments.put("sku", skuId); arguments.put("accountId", accountId); MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); // Launch the billing flow BillingResult billingResult = BillingResult.newBuilder() .setResponseCode(100) .setDebugMessage("dummy debug message") .build(); when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult); methodChannelHandler.onMethodCall(launchCall, result); // Verify we pass the arguments to the billing flow ArgumentCaptor billingFlowParamsCaptor = ArgumentCaptor.forClass(BillingFlowParams.class); verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture()); BillingFlowParams params = billingFlowParamsCaptor.getValue(); // Verify we pass the response code to result verify(result, never()).error(any(), any(), any()); verify(result, times(1)).success(fromBillingResult(billingResult)); } @Test public void launchBillingFlow_ok_Proration() { // Fetch the sku details first and query the method call String skuId = "foo"; String oldSkuId = "oldFoo"; String purchaseToken = "purchaseTokenFoo"; String accountId = "account"; int prorationMode = BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE; queryForSkus(unmodifiableList(asList(skuId, oldSkuId))); HashMap arguments = new HashMap<>(); arguments.put("sku", skuId); arguments.put("accountId", accountId); arguments.put("oldSku", oldSkuId); arguments.put("purchaseToken", purchaseToken); arguments.put("prorationMode", prorationMode); MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); // Launch the billing flow BillingResult billingResult = BillingResult.newBuilder() .setResponseCode(100) .setDebugMessage("dummy debug message") .build(); when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult); methodChannelHandler.onMethodCall(launchCall, result); // Verify we pass the arguments to the billing flow ArgumentCaptor billingFlowParamsCaptor = ArgumentCaptor.forClass(BillingFlowParams.class); verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture()); BillingFlowParams params = billingFlowParamsCaptor.getValue(); // Verify we pass the response code to result verify(result, never()).error(any(), any(), any()); verify(result, times(1)).success(fromBillingResult(billingResult)); } @Test public void launchBillingFlow_ok_Proration_with_null_OldSku() { // Fetch the sku details first and query the method call String skuId = "foo"; String accountId = "account"; String queryOldSkuId = "oldFoo"; String oldSkuId = null; int prorationMode = BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE; queryForSkus(unmodifiableList(asList(skuId, queryOldSkuId))); HashMap arguments = new HashMap<>(); arguments.put("sku", skuId); arguments.put("accountId", accountId); arguments.put("oldSku", oldSkuId); arguments.put("prorationMode", prorationMode); MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); // Launch the billing flow BillingResult billingResult = BillingResult.newBuilder() .setResponseCode(100) .setDebugMessage("dummy debug message") .build(); when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult); methodChannelHandler.onMethodCall(launchCall, result); // Assert that we sent an error back. verify(result) .error( contains("IN_APP_PURCHASE_REQUIRE_OLD_SKU"), contains("launchBillingFlow failed because oldSku is null"), any()); verify(result, never()).success(any()); } @Test public void launchBillingFlow_ok_Full() { // Fetch the sku details first and query the method call String skuId = "foo"; String oldSkuId = "oldFoo"; String purchaseToken = "purchaseTokenFoo"; String accountId = "account"; int prorationMode = BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE; queryForSkus(unmodifiableList(asList(skuId, oldSkuId))); HashMap arguments = new HashMap<>(); arguments.put("sku", skuId); arguments.put("accountId", accountId); arguments.put("oldSku", oldSkuId); arguments.put("purchaseToken", purchaseToken); arguments.put("prorationMode", prorationMode); MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); // Launch the billing flow BillingResult billingResult = BillingResult.newBuilder() .setResponseCode(100) .setDebugMessage("dummy debug message") .build(); when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult); methodChannelHandler.onMethodCall(launchCall, result); // Verify we pass the arguments to the billing flow ArgumentCaptor billingFlowParamsCaptor = ArgumentCaptor.forClass(BillingFlowParams.class); verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture()); BillingFlowParams params = billingFlowParamsCaptor.getValue(); // Verify we pass the response code to result verify(result, never()).error(any(), any(), any()); verify(result, times(1)).success(fromBillingResult(billingResult)); } @Test public void launchBillingFlow_clientDisconnected() { // Prepare the launch call after disconnecting the client MethodCall disconnectCall = new MethodCall(END_CONNECTION, null); methodChannelHandler.onMethodCall(disconnectCall, mock(Result.class)); String skuId = "foo"; String accountId = "account"; HashMap arguments = new HashMap<>(); arguments.put("sku", skuId); arguments.put("accountId", accountId); MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); methodChannelHandler.onMethodCall(launchCall, result); // Assert that we sent an error back. verify(result).error(contains("UNAVAILABLE"), contains("BillingClient"), any()); verify(result, never()).success(any()); } @Test public void launchBillingFlow_skuNotFound() { // Try to launch the billing flow for a random sku ID establishConnectedBillingClient(null, null); String skuId = "foo"; String accountId = "account"; HashMap arguments = new HashMap<>(); arguments.put("sku", skuId); arguments.put("accountId", accountId); MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); methodChannelHandler.onMethodCall(launchCall, result); // Assert that we sent an error back. verify(result).error(contains("NOT_FOUND"), contains(skuId), any()); verify(result, never()).success(any()); } @Test public void launchBillingFlow_oldSkuNotFound() { // Try to launch the billing flow for a random sku ID establishConnectedBillingClient(null, null); String skuId = "foo"; String accountId = "account"; String oldSkuId = "oldSku"; queryForSkus(singletonList(skuId)); HashMap arguments = new HashMap<>(); arguments.put("sku", skuId); arguments.put("accountId", accountId); arguments.put("oldSku", oldSkuId); MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); methodChannelHandler.onMethodCall(launchCall, result); // Assert that we sent an error back. verify(result).error(contains("IN_APP_PURCHASE_INVALID_OLD_SKU"), contains(oldSkuId), any()); verify(result, never()).success(any()); } @Test public void queryPurchases_clientDisconnected() { // Prepare the launch call after disconnecting the client methodChannelHandler.onMethodCall(new MethodCall(END_CONNECTION, null), mock(Result.class)); HashMap arguments = new HashMap<>(); arguments.put("skuType", SkuType.INAPP); methodChannelHandler.onMethodCall(new MethodCall(QUERY_PURCHASES_ASYNC, arguments), result); // Assert that we sent an error back. verify(result).error(contains("UNAVAILABLE"), contains("BillingClient"), any()); verify(result, never()).success(any()); } @Test public void queryPurchases_returns_success() throws Exception { establishConnectedBillingClient(null, null); CountDownLatch lock = new CountDownLatch(1); doAnswer( new Answer() { public Object answer(InvocationOnMock invocation) { lock.countDown(); return null; } }) .when(result) .success(any(HashMap.class)); ArgumentCaptor purchasesResponseListenerArgumentCaptor = ArgumentCaptor.forClass(PurchasesResponseListener.class); doAnswer( new Answer() { public Object answer(InvocationOnMock invocation) { BillingResult.Builder resultBuilder = BillingResult.newBuilder() .setResponseCode(BillingClient.BillingResponseCode.OK) .setDebugMessage("hello message"); purchasesResponseListenerArgumentCaptor .getValue() .onQueryPurchasesResponse(resultBuilder.build(), new ArrayList()); return null; } }) .when(mockBillingClient) .queryPurchasesAsync( any(QueryPurchasesParams.class), purchasesResponseListenerArgumentCaptor.capture()); HashMap arguments = new HashMap<>(); arguments.put("skuType", SkuType.INAPP); methodChannelHandler.onMethodCall(new MethodCall(QUERY_PURCHASES_ASYNC, arguments), result); lock.await(5000, TimeUnit.MILLISECONDS); verify(result, never()).error(any(), any(), any()); ArgumentCaptor hashMapCaptor = ArgumentCaptor.forClass(HashMap.class); verify(result, times(1)).success(hashMapCaptor.capture()); HashMap map = hashMapCaptor.getValue(); assert (map.containsKey("responseCode")); assert (map.containsKey("billingResult")); assert (map.containsKey("purchasesList")); assert ((int) map.get("responseCode") == 0); } @Test public void queryPurchaseHistoryAsync() { // Set up an established billing client and all our mocked responses establishConnectedBillingClient(null, null); ArgumentCaptor> resultCaptor = ArgumentCaptor.forClass(HashMap.class); BillingResult billingResult = BillingResult.newBuilder() .setResponseCode(100) .setDebugMessage("dummy debug message") .build(); List purchasesList = asList(buildPurchaseHistoryRecord("foo")); HashMap arguments = new HashMap<>(); arguments.put("skuType", SkuType.INAPP); ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(PurchaseHistoryResponseListener.class); methodChannelHandler.onMethodCall( new MethodCall(QUERY_PURCHASE_HISTORY_ASYNC, arguments), result); // Verify we pass the data to result verify(mockBillingClient) .queryPurchaseHistoryAsync(any(QueryPurchaseHistoryParams.class), listenerCaptor.capture()); listenerCaptor.getValue().onPurchaseHistoryResponse(billingResult, purchasesList); verify(result).success(resultCaptor.capture()); HashMap resultData = resultCaptor.getValue(); assertEquals(fromBillingResult(billingResult), resultData.get("billingResult")); assertEquals( fromPurchaseHistoryRecordList(purchasesList), resultData.get("purchaseHistoryRecordList")); } @Test public void queryPurchaseHistoryAsync_clientDisconnected() { // Prepare the launch call after disconnecting the client methodChannelHandler.onMethodCall(new MethodCall(END_CONNECTION, null), mock(Result.class)); HashMap arguments = new HashMap<>(); arguments.put("skuType", SkuType.INAPP); methodChannelHandler.onMethodCall( new MethodCall(QUERY_PURCHASE_HISTORY_ASYNC, arguments), result); // Assert that we sent an error back. verify(result).error(contains("UNAVAILABLE"), contains("BillingClient"), any()); verify(result, never()).success(any()); } @Test public void onPurchasesUpdatedListener() { PluginPurchaseListener listener = new PluginPurchaseListener(mockMethodChannel); BillingResult billingResult = BillingResult.newBuilder() .setResponseCode(100) .setDebugMessage("dummy debug message") .build(); List purchasesList = asList(buildPurchase("foo")); ArgumentCaptor> resultCaptor = ArgumentCaptor.forClass(HashMap.class); doNothing() .when(mockMethodChannel) .invokeMethod(eq(ON_PURCHASES_UPDATED), resultCaptor.capture()); listener.onPurchasesUpdated(billingResult, purchasesList); HashMap resultData = resultCaptor.getValue(); assertEquals(fromBillingResult(billingResult), resultData.get("billingResult")); assertEquals(fromPurchasesList(purchasesList), resultData.get("purchasesList")); } @Test public void consumeAsync() { establishConnectedBillingClient(null, null); ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(BillingResult.class); BillingResult billingResult = BillingResult.newBuilder() .setResponseCode(100) .setDebugMessage("dummy debug message") .build(); HashMap arguments = new HashMap<>(); arguments.put("purchaseToken", "mockToken"); arguments.put("developerPayload", "mockPayload"); ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(ConsumeResponseListener.class); methodChannelHandler.onMethodCall(new MethodCall(CONSUME_PURCHASE_ASYNC, arguments), result); ConsumeParams params = ConsumeParams.newBuilder().setPurchaseToken("mockToken").build(); // Verify we pass the data to result verify(mockBillingClient).consumeAsync(refEq(params), listenerCaptor.capture()); listenerCaptor.getValue().onConsumeResponse(billingResult, "mockToken"); verify(result).success(resultCaptor.capture()); // Verify we pass the response code to result verify(result, never()).error(any(), any(), any()); verify(result, times(1)).success(fromBillingResult(billingResult)); } @Test public void acknowledgePurchase() { establishConnectedBillingClient(null, null); ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(BillingResult.class); BillingResult billingResult = BillingResult.newBuilder() .setResponseCode(100) .setDebugMessage("dummy debug message") .build(); HashMap arguments = new HashMap<>(); arguments.put("purchaseToken", "mockToken"); arguments.put("developerPayload", "mockPayload"); ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(AcknowledgePurchaseResponseListener.class); methodChannelHandler.onMethodCall(new MethodCall(ACKNOWLEDGE_PURCHASE, arguments), result); AcknowledgePurchaseParams params = AcknowledgePurchaseParams.newBuilder().setPurchaseToken("mockToken").build(); // Verify we pass the data to result verify(mockBillingClient).acknowledgePurchase(refEq(params), listenerCaptor.capture()); listenerCaptor.getValue().onAcknowledgePurchaseResponse(billingResult); verify(result).success(resultCaptor.capture()); // Verify we pass the response code to result verify(result, never()).error(any(), any(), any()); verify(result, times(1)).success(fromBillingResult(billingResult)); } @Test public void endConnection_if_activity_detached() { InAppPurchasePlugin plugin = new InAppPurchasePlugin(); plugin.setMethodCallHandler(methodChannelHandler); mockStartConnection(); plugin.onDetachedFromActivity(); verify(mockBillingClient).endConnection(); } @Test public void isFutureSupported_true() { mockStartConnection(); final String feature = "subscriptions"; Map arguments = new HashMap<>(); arguments.put("feature", feature); BillingResult billingResult = BillingResult.newBuilder() .setResponseCode(BillingClient.BillingResponseCode.OK) .setDebugMessage("dummy debug message") .build(); MethodCall call = new MethodCall(IS_FEATURE_SUPPORTED, arguments); when(mockBillingClient.isFeatureSupported(feature)).thenReturn(billingResult); methodChannelHandler.onMethodCall(call, result); verify(result).success(true); } @Test public void isFutureSupported_false() { mockStartConnection(); final String feature = "subscriptions"; Map arguments = new HashMap<>(); arguments.put("feature", feature); BillingResult billingResult = BillingResult.newBuilder() .setResponseCode(BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED) .setDebugMessage("dummy debug message") .build(); MethodCall call = new MethodCall(IS_FEATURE_SUPPORTED, arguments); when(mockBillingClient.isFeatureSupported(feature)).thenReturn(billingResult); methodChannelHandler.onMethodCall(call, result); verify(result).success(false); } @Test public void launchPriceChangeConfirmationFlow() { // Set up the sku details establishConnectedBillingClient(null, null); String skuId = "foo"; queryForSkus(singletonList(skuId)); BillingResult billingResult = BillingResult.newBuilder() .setResponseCode(BillingClient.BillingResponseCode.OK) .setDebugMessage("dummy debug message") .build(); // Set up the mock billing client ArgumentCaptor priceChangeConfirmationListenerArgumentCaptor = ArgumentCaptor.forClass(PriceChangeConfirmationListener.class); ArgumentCaptor priceChangeFlowParamsArgumentCaptor = ArgumentCaptor.forClass(PriceChangeFlowParams.class); doNothing() .when(mockBillingClient) .launchPriceChangeConfirmationFlow( any(), priceChangeFlowParamsArgumentCaptor.capture(), priceChangeConfirmationListenerArgumentCaptor.capture()); // Call the methodChannelHandler HashMap arguments = new HashMap<>(); arguments.put("sku", skuId); methodChannelHandler.onMethodCall( new MethodCall(LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW, arguments), result); // Verify the price change params. PriceChangeFlowParams priceChangeFlowParams = priceChangeFlowParamsArgumentCaptor.getValue(); assertEquals(skuId, priceChangeFlowParams.getSkuDetails().getSku()); // Set the response in the callback PriceChangeConfirmationListener priceChangeConfirmationListener = priceChangeConfirmationListenerArgumentCaptor.getValue(); priceChangeConfirmationListener.onPriceChangeConfirmationResult(billingResult); // Verify we pass the response to result verify(result, never()).error(any(), any(), any()); ArgumentCaptor resultCaptor = ArgumentCaptor.forClass(HashMap.class); verify(result, times(1)).success(resultCaptor.capture()); assertEquals(fromBillingResult(billingResult), resultCaptor.getValue()); } @Test public void launchPriceChangeConfirmationFlow_withoutActivity_returnsActivityUnavailableError() { // Set up the sku details establishConnectedBillingClient(null, null); String skuId = "foo"; queryForSkus(singletonList(skuId)); methodChannelHandler.setActivity(null); // Call the methodChannelHandler HashMap arguments = new HashMap<>(); arguments.put("sku", skuId); methodChannelHandler.onMethodCall( new MethodCall(LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW, arguments), result); verify(result, times(1)).error(eq("ACTIVITY_UNAVAILABLE"), any(), any()); } @Test public void launchPriceChangeConfirmationFlow_withoutSkuQuery_returnsNotFoundError() { // Set up the sku details establishConnectedBillingClient(null, null); String skuId = "foo"; // Call the methodChannelHandler HashMap arguments = new HashMap<>(); arguments.put("sku", skuId); methodChannelHandler.onMethodCall( new MethodCall(LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW, arguments), result); verify(result, times(1)).error(eq("NOT_FOUND"), contains("sku"), any()); } @Test public void launchPriceChangeConfirmationFlow_withoutBillingClient_returnsUnavailableError() { // Set up the sku details String skuId = "foo"; // Call the methodChannelHandler HashMap arguments = new HashMap<>(); arguments.put("sku", skuId); methodChannelHandler.onMethodCall( new MethodCall(LAUNCH_PRICE_CHANGE_CONFIRMATION_FLOW, arguments), result); verify(result, times(1)).error(eq("UNAVAILABLE"), contains("BillingClient"), any()); } private ArgumentCaptor mockStartConnection() { Map arguments = new HashMap<>(); arguments.put("handle", 1); MethodCall call = new MethodCall(START_CONNECTION, arguments); ArgumentCaptor captor = ArgumentCaptor.forClass(BillingClientStateListener.class); doNothing().when(mockBillingClient).startConnection(captor.capture()); methodChannelHandler.onMethodCall(call, result); return captor; } private void establishConnectedBillingClient( @Nullable Map arguments, @Nullable Result result) { if (arguments == null) { arguments = new HashMap<>(); arguments.put("handle", 1); } if (result == null) { result = mock(Result.class); } MethodCall connectCall = new MethodCall(START_CONNECTION, arguments); methodChannelHandler.onMethodCall(connectCall, result); } private void queryForSkus(List skusList) { // Set up the query method call establishConnectedBillingClient(/* arguments= */ null, /* result= */ null); HashMap arguments = new HashMap<>(); String skuType = SkuType.INAPP; arguments.put("skuType", skuType); arguments.put("skusList", skusList); MethodCall queryCall = new MethodCall(QUERY_SKU_DETAILS, arguments); // Call the method. methodChannelHandler.onMethodCall(queryCall, mock(Result.class)); // Respond to the call with a matching set of Sku details. ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(SkuDetailsResponseListener.class); verify(mockBillingClient).querySkuDetailsAsync(any(), listenerCaptor.capture()); List skuDetailsResponse = skusList.stream().map(this::buildSkuDetails).collect(toList()); BillingResult billingResult = BillingResult.newBuilder() .setResponseCode(100) .setDebugMessage("dummy debug message") .build(); listenerCaptor.getValue().onSkuDetailsResponse(billingResult, skuDetailsResponse); } private SkuDetails buildSkuDetails(String id) { String json = String.format( "{\"packageName\": \"dummyPackageName\",\"productId\":\"%s\",\"type\":\"inapp\",\"price\":\"$0.99\",\"price_amount_micros\":990000,\"price_currency_code\":\"USD\",\"title\":\"Example title\",\"description\":\"Example description.\",\"original_price\":\"$0.99\",\"original_price_micros\":990000}", id); SkuDetails details = null; try { details = new SkuDetails(json); } catch (JSONException e) { fail("buildSkuDetails failed with JSONException " + e.toString()); } return details; } private Purchase buildPurchase(String orderId) { Purchase purchase = mock(Purchase.class); when(purchase.getOrderId()).thenReturn(orderId); return purchase; } private PurchaseHistoryRecord buildPurchaseHistoryRecord(String purchaseToken) { PurchaseHistoryRecord purchase = mock(PurchaseHistoryRecord.class); when(purchase.getPurchaseToken()).thenReturn(purchaseToken); return purchase; } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java ================================================ // Copyright 2013 The Flutter 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.inapppurchase; 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.junit.Assert.fail; import androidx.annotation.NonNull; import com.android.billingclient.api.AccountIdentifiers; import com.android.billingclient.api.BillingClient; import com.android.billingclient.api.BillingResult; import com.android.billingclient.api.Purchase; import com.android.billingclient.api.PurchaseHistoryRecord; import com.android.billingclient.api.SkuDetails; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import org.json.JSONException; import org.junit.Before; import org.junit.Test; public class TranslatorTest { private static final String SKU_DETAIL_EXAMPLE_JSON = "{\"productId\":\"example\",\"type\":\"inapp\",\"price\":\"$0.99\",\"price_amount_micros\":990000,\"price_currency_code\":\"USD\",\"title\":\"Example title\",\"description\":\"Example description.\",\"original_price\":\"$0.99\",\"original_price_micros\":990000}"; private static final String PURCHASE_EXAMPLE_JSON = "{\"orderId\":\"foo\",\"packageName\":\"bar\",\"productId\":\"consumable\",\"purchaseTime\":11111111,\"purchaseState\":0,\"purchaseToken\":\"baz\",\"developerPayload\":\"dummy payload\",\"isAcknowledged\":\"true\", \"obfuscatedAccountId\":\"Account101\", \"obfuscatedProfileId\": \"Profile105\"}"; @Before public void setup() { Locale locale = new Locale("en", "us"); Locale.setDefault(locale); } @Test public void fromSkuDetail() throws JSONException { final SkuDetails expected = new SkuDetails(SKU_DETAIL_EXAMPLE_JSON); Map serialized = Translator.fromSkuDetail(expected); assertSerialized(expected, serialized); } @Test public void fromSkuDetailsList() throws JSONException { final String SKU_DETAIL_EXAMPLE_2_JSON = "{\"productId\":\"example2\",\"type\":\"inapp\",\"price\":\"$0.99\",\"price_amount_micros\":990000,\"price_currency_code\":\"USD\",\"title\":\"Example title\",\"description\":\"Example description.\",\"original_price\":\"$0.99\",\"original_price_micros\":990000}"; final List expected = Arrays.asList( new SkuDetails(SKU_DETAIL_EXAMPLE_JSON), new SkuDetails(SKU_DETAIL_EXAMPLE_2_JSON)); final List> serialized = Translator.fromSkuDetailsList(expected); assertEquals(expected.size(), serialized.size()); assertSerialized(expected.get(0), serialized.get(0)); assertSerialized(expected.get(1), serialized.get(1)); } @Test public void fromSkuDetailsList_null() { assertEquals(Collections.emptyList(), Translator.fromSkuDetailsList(null)); } @Test public void fromPurchase() throws JSONException { final Purchase expected = new Purchase(PURCHASE_EXAMPLE_JSON, "signature"); assertSerialized(expected, Translator.fromPurchase(expected)); } @Test public void fromPurchaseWithoutAccountIds() throws JSONException { final Purchase expected = new PurchaseWithoutAccountIdentifiers(PURCHASE_EXAMPLE_JSON, "signature"); Map serialized = Translator.fromPurchase(expected); assertNotNull(serialized.get("orderId")); assertNull(serialized.get("obfuscatedProfileId")); assertNull(serialized.get("obfuscatedAccountId")); } @Test public void fromPurchaseHistoryRecord() throws JSONException { final PurchaseHistoryRecord expected = new PurchaseHistoryRecord(PURCHASE_EXAMPLE_JSON, "signature"); assertSerialized(expected, Translator.fromPurchaseHistoryRecord(expected)); } @Test public void fromPurchasesHistoryRecordList() throws JSONException { final String purchase2Json = "{\"orderId\":\"foo2\",\"packageName\":\"bar\",\"productId\":\"consumable\",\"purchaseTime\":11111111,\"purchaseState\":0,\"purchaseToken\":\"baz\",\"developerPayload\":\"dummy payload\",\"isAcknowledged\":\"true\"}"; final String signature = "signature"; final List expected = Arrays.asList( new PurchaseHistoryRecord(PURCHASE_EXAMPLE_JSON, signature), new PurchaseHistoryRecord(purchase2Json, signature)); final List> serialized = Translator.fromPurchaseHistoryRecordList(expected); assertEquals(expected.size(), serialized.size()); assertSerialized(expected.get(0), serialized.get(0)); assertSerialized(expected.get(1), serialized.get(1)); } @Test public void fromPurchasesHistoryRecordList_null() { assertEquals(Collections.emptyList(), Translator.fromPurchaseHistoryRecordList(null)); } @Test public void fromPurchasesList() throws JSONException { final String purchase2Json = "{\"orderId\":\"foo2\",\"packageName\":\"bar\",\"productId\":\"consumable\",\"purchaseTime\":11111111,\"purchaseState\":0,\"purchaseToken\":\"baz\",\"developerPayload\":\"dummy payload\",\"isAcknowledged\":\"true\"}"; final String signature = "signature"; final List expected = Arrays.asList( new Purchase(PURCHASE_EXAMPLE_JSON, signature), new Purchase(purchase2Json, signature)); final List> serialized = Translator.fromPurchasesList(expected); assertEquals(expected.size(), serialized.size()); assertSerialized(expected.get(0), serialized.get(0)); assertSerialized(expected.get(1), serialized.get(1)); } @Test public void fromPurchasesList_null() { assertEquals(Collections.emptyList(), Translator.fromPurchasesList(null)); } @Test public void fromBillingResult() throws JSONException { BillingResult newBillingResult = BillingResult.newBuilder() .setDebugMessage("dummy debug message") .setResponseCode(BillingClient.BillingResponseCode.OK) .build(); Map billingResultMap = Translator.fromBillingResult(newBillingResult); assertEquals(billingResultMap.get("responseCode"), newBillingResult.getResponseCode()); assertEquals(billingResultMap.get("debugMessage"), newBillingResult.getDebugMessage()); } @Test public void fromBillingResult_debugMessageNull() throws JSONException { BillingResult newBillingResult = BillingResult.newBuilder().setResponseCode(BillingClient.BillingResponseCode.OK).build(); Map billingResultMap = Translator.fromBillingResult(newBillingResult); assertEquals(billingResultMap.get("responseCode"), newBillingResult.getResponseCode()); assertEquals(billingResultMap.get("debugMessage"), newBillingResult.getDebugMessage()); } @Test public void currencyCodeFromSymbol() { assertEquals("$", Translator.currencySymbolFromCode("USD")); try { Translator.currencySymbolFromCode("EUROPACOIN"); fail("Translator should throw an exception"); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } } private void assertSerialized(SkuDetails expected, Map serialized) { assertEquals(expected.getDescription(), serialized.get("description")); assertEquals(expected.getFreeTrialPeriod(), serialized.get("freeTrialPeriod")); assertEquals(expected.getIntroductoryPrice(), serialized.get("introductoryPrice")); assertEquals( expected.getIntroductoryPriceAmountMicros(), serialized.get("introductoryPriceAmountMicros")); assertEquals(expected.getIntroductoryPriceCycles(), serialized.get("introductoryPriceCycles")); assertEquals(expected.getIntroductoryPricePeriod(), serialized.get("introductoryPricePeriod")); assertEquals(expected.getPrice(), serialized.get("price")); assertEquals(expected.getPriceAmountMicros(), serialized.get("priceAmountMicros")); assertEquals(expected.getPriceCurrencyCode(), serialized.get("priceCurrencyCode")); assertEquals("$", serialized.get("priceCurrencySymbol")); assertEquals(expected.getSku(), serialized.get("sku")); assertEquals(expected.getSubscriptionPeriod(), serialized.get("subscriptionPeriod")); assertEquals(expected.getTitle(), serialized.get("title")); assertEquals(expected.getType(), serialized.get("type")); assertEquals(expected.getOriginalPrice(), serialized.get("originalPrice")); assertEquals( expected.getOriginalPriceAmountMicros(), serialized.get("originalPriceAmountMicros")); } private void assertSerialized(Purchase expected, Map serialized) { assertEquals(expected.getOrderId(), serialized.get("orderId")); assertEquals(expected.getPackageName(), serialized.get("packageName")); assertEquals(expected.getPurchaseTime(), serialized.get("purchaseTime")); assertEquals(expected.getPurchaseToken(), serialized.get("purchaseToken")); assertEquals(expected.getSignature(), serialized.get("signature")); assertEquals(expected.getOriginalJson(), serialized.get("originalJson")); assertEquals(expected.getSkus(), serialized.get("skus")); assertEquals(expected.getDeveloperPayload(), serialized.get("developerPayload")); assertEquals(expected.isAcknowledged(), serialized.get("isAcknowledged")); assertEquals(expected.getPurchaseState(), serialized.get("purchaseState")); assertNotNull(expected.getAccountIdentifiers().getObfuscatedAccountId()); assertEquals( expected.getAccountIdentifiers().getObfuscatedAccountId(), serialized.get("obfuscatedAccountId")); assertNotNull(expected.getAccountIdentifiers().getObfuscatedProfileId()); assertEquals( expected.getAccountIdentifiers().getObfuscatedProfileId(), serialized.get("obfuscatedProfileId")); } private void assertSerialized(PurchaseHistoryRecord expected, Map serialized) { assertEquals(expected.getPurchaseTime(), serialized.get("purchaseTime")); assertEquals(expected.getPurchaseToken(), serialized.get("purchaseToken")); assertEquals(expected.getSignature(), serialized.get("signature")); assertEquals(expected.getOriginalJson(), serialized.get("originalJson")); assertEquals(expected.getSkus(), serialized.get("skus")); assertEquals(expected.getDeveloperPayload(), serialized.get("developerPayload")); } } class PurchaseWithoutAccountIdentifiers extends Purchase { public PurchaseWithoutAccountIdentifiers(@NonNull String s, @NonNull String s1) throws JSONException { super(s, s1); } @Override public AccountIdentifiers getAccountIdentifiers() { return null; } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/build.yaml ================================================ # See https://pub.dev/packages/build_config targets: $default: builders: json_serializable: options: any_map: true create_to_json: false ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/README.md ================================================ # 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. ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } // Load the build signing secrets from a local `keystore.properties` file. // TODO(YOU): Create release keys and a `keystore.properties` file. See // `example/README.md` for more info and `keystore.example.properties` for an // example. def keystorePropertiesFile = rootProject.file("keystore.properties") def keystoreProperties = new Properties() def configured = true try { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } catch (IOException e) { configured = false logger.error('Release signing information not found.') } project.ext { // TODO(YOU): Create release keys and a `keystore.properties` file. See // `example/README.md` for more info and `keystore.example.properties` for an // example. APP_ID = configured ? keystoreProperties['appId'] : "io.flutter.plugins.inapppurchaseexample.DEFAULT_DO_NOT_USE" KEYSTORE_STORE_FILE = configured ? rootProject.file(keystoreProperties['storeFile']) : null KEYSTORE_STORE_PASSWORD = keystoreProperties['storePassword'] KEYSTORE_KEY_ALIAS = keystoreProperties['keyAlias'] KEYSTORE_KEY_PASSWORD = keystoreProperties['keyPassword'] VERSION_CODE = configured ? keystoreProperties['versionCode'].toInteger() : 1 VERSION_NAME = configured ? keystoreProperties['versionName'] : "0.0.1" } if (project.APP_ID == "io.flutter.plugins.inapppurchaseexample.DEFAULT_DO_NOT_USE") { configured = false logger.error('Unique package name not set, defaulting to "io.flutter.plugins.inapppurchaseexample.DEFAULT_DO_NOT_USE".') } // Log a final error message if we're unable to create a release key signed // build for an app configured in the Play Developer Console. Apks built in this // condition won't be able to call any of the BillingClient APIs. if (!configured) { logger.error('The app could not be configured for release signing. In app purchases will not be testable. See `example/README.md` for more info and instructions.') } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { signingConfigs { release { storeFile project.KEYSTORE_STORE_FILE storePassword project.KEYSTORE_STORE_PASSWORD keyAlias project.KEYSTORE_KEY_ALIAS keyPassword project.KEYSTORE_KEY_PASSWORD } } compileSdkVersion 31 lintOptions { disable 'InvalidPackage' } defaultConfig { applicationId project.APP_ID minSdkVersion 16 targetSdkVersion 30 versionCode project.VERSION_CODE versionName project.VERSION_NAME testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { // Google Play Billing APIs only work with apps signed for production. debug { if (configured) { signingConfig signingConfigs.release } else { signingConfig signingConfigs.debug } } release { if (configured) { signingConfig signingConfigs.release } else { signingConfig signingConfigs.debug } } } testOptions { unitTests.returnDefaultValues = true } } flutter { source '../..' } dependencies { implementation 'com.android.billingclient:billing:5.0.0' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:5.1.1' testImplementation 'org.json:json:20220924' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/android/app/src/androidTest/java/io/flutter/plugins/inapppurchaseexample/FlutterActivityTest.java ================================================ // Copyright 2013 The Flutter 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.inapppurchaseexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class FlutterActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/android/app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker ================================================ mock-maker-inline ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/android/keystore.example.properties ================================================ storePassword=??? keyPassword=??? keyAlias=??? storeFile=??? appId=io.flutter.plugins.inapppurchaseexample.DEFAULT_DO_NOT_USE versionCode=1 versionName=0.0.1 ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/integration_test/in_app_purchase_test.dart ================================================ // Copyright 2013 The Flutter 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:in_app_purchase_android/in_app_purchase_android.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:integration_test/integration_test.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('Can create InAppPurchaseAndroid instance', (WidgetTester tester) async { InAppPurchaseAndroidPlatform.registerPlatform(); final InAppPurchasePlatform androidPlatform = InAppPurchasePlatform.instance; expect(androidPlatform, isNotNull); }); } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/lib/consumable_store.dart ================================================ // Copyright 2013 The Flutter 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:shared_preferences/shared_preferences.dart'; // ignore: avoid_classes_with_only_static_members /// A store of consumable items. /// /// This is a development prototype tha stores consumables in the shared /// preferences. Do not use this in real world apps. class ConsumableStore { static const String _kPrefKey = 'consumables'; static Future _writes = Future.value(); /// Adds a consumable with ID `id` to the store. /// /// The consumable is only added after the returned Future is complete. static Future save(String id) { _writes = _writes.then((void _) => _doSave(id)); return _writes; } /// Consumes a consumable with ID `id` from the store. /// /// The consumable was only consumed after the returned Future is complete. static Future consume(String id) { _writes = _writes.then((void _) => _doConsume(id)); return _writes; } /// Returns the list of consumables from the store. static Future> load() async { return (await SharedPreferences.getInstance()).getStringList(_kPrefKey) ?? []; } static Future _doSave(String id) async { final List cached = await load(); final SharedPreferences prefs = await SharedPreferences.getInstance(); cached.add(id); await prefs.setStringList(_kPrefKey, cached); } static Future _doConsume(String id) async { final List cached = await load(); final SharedPreferences prefs = await SharedPreferences.getInstance(); cached.remove(id); await prefs.setStringList(_kPrefKey, cached); } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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: avoid_print import 'dart:async'; import 'package:flutter/material.dart'; import 'package:in_app_purchase_android/billing_client_wrappers.dart'; import 'package:in_app_purchase_android/in_app_purchase_android.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'consumable_store.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); // When using the Android plugin directly it is mandatory to register // the plugin as default instance as part of initializing the app. InAppPurchaseAndroidPlatform.registerPlatform(); runApp(_MyApp()); } // To try without auto-consume, change `true` to `false` here. const bool _kAutoConsume = true; const String _kConsumableId = 'consumable'; const String _kUpgradeId = 'upgrade'; const String _kSilverSubscriptionId = 'subscription_silver1'; const String _kGoldSubscriptionId = 'subscription_gold1'; const List _kProductIds = [ _kConsumableId, _kUpgradeId, _kSilverSubscriptionId, _kGoldSubscriptionId, ]; class _MyApp extends StatefulWidget { @override State<_MyApp> createState() => _MyAppState(); } class _MyAppState extends State<_MyApp> { final InAppPurchasePlatform _inAppPurchasePlatform = InAppPurchasePlatform.instance; late StreamSubscription> _subscription; List _notFoundIds = []; List _products = []; List _purchases = []; List _consumables = []; bool _isAvailable = false; bool _purchasePending = false; bool _loading = true; String? _queryProductError; @override void initState() { final Stream> purchaseUpdated = _inAppPurchasePlatform.purchaseStream; _subscription = purchaseUpdated.listen((List purchaseDetailsList) { _listenToPurchaseUpdated(purchaseDetailsList); }, onDone: () { _subscription.cancel(); }, onError: (Object error) { // handle error here. }); initStoreInfo(); super.initState(); } Future initStoreInfo() async { final bool isAvailable = await _inAppPurchasePlatform.isAvailable(); if (!isAvailable) { setState(() { _isAvailable = isAvailable; _products = []; _purchases = []; _notFoundIds = []; _consumables = []; _purchasePending = false; _loading = false; }); return; } final ProductDetailsResponse productDetailResponse = await _inAppPurchasePlatform.queryProductDetails(_kProductIds.toSet()); if (productDetailResponse.error != null) { setState(() { _queryProductError = productDetailResponse.error!.message; _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _purchases = []; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = []; _purchasePending = false; _loading = false; }); return; } if (productDetailResponse.productDetails.isEmpty) { setState(() { _queryProductError = null; _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _purchases = []; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = []; _purchasePending = false; _loading = false; }); return; } await _inAppPurchasePlatform.restorePurchases(); final List consumables = await ConsumableStore.load(); setState(() { _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = consumables; _purchasePending = false; _loading = false; }); } @override void dispose() { _subscription.cancel(); super.dispose(); } @override Widget build(BuildContext context) { final List stack = []; if (_queryProductError == null) { stack.add( ListView( children: [ _buildConnectionCheckTile(), _buildProductList(), _buildConsumableBox(), _FeatureCard(), ], ), ); } else { stack.add(Center( child: Text(_queryProductError!), )); } if (_purchasePending) { stack.add( // TODO(goderbauer): Make this const when that's available on stable. // ignore: prefer_const_constructors Stack( children: const [ Opacity( opacity: 0.3, child: ModalBarrier(dismissible: false, color: Colors.grey), ), Center( child: CircularProgressIndicator(), ), ], ), ); } return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('IAP Example'), ), body: Stack( children: stack, ), ), ); } Card _buildConnectionCheckTile() { if (_loading) { return const Card(child: ListTile(title: Text('Trying to connect...'))); } final Widget storeHeader = ListTile( leading: Icon(_isAvailable ? Icons.check : Icons.block, color: _isAvailable ? Colors.green : ThemeData.light().colorScheme.error), title: Text('The store is ${_isAvailable ? 'available' : 'unavailable'}.'), ); final List children = [storeHeader]; if (!_isAvailable) { children.addAll([ const Divider(), ListTile( title: Text('Not connected', style: TextStyle(color: ThemeData.light().colorScheme.error)), subtitle: const Text( 'Unable to connect to the payments processor. Has this app been configured correctly? See the example README for instructions.'), ), ]); } return Card(child: Column(children: children)); } Card _buildProductList() { if (_loading) { return const Card( child: ListTile( leading: CircularProgressIndicator(), title: Text('Fetching products...'))); } if (!_isAvailable) { return const Card(); } const ListTile productHeader = ListTile(title: Text('Products for Sale')); final List productList = []; if (_notFoundIds.isNotEmpty) { productList.add(ListTile( title: Text('[${_notFoundIds.join(", ")}] not found', style: TextStyle(color: ThemeData.light().colorScheme.error)), subtitle: const Text( 'This app needs special configuration to run. Please see example/README.md for instructions.'))); } // This loading previous purchases code is just a demo. Please do not use this as it is. // In your app you should always verify the purchase data using the `verificationData` inside the [PurchaseDetails] object before trusting it. // We recommend that you use your own server to verify the purchase data. final Map purchases = Map.fromEntries( _purchases.map((PurchaseDetails purchase) { if (purchase.pendingCompletePurchase) { _inAppPurchasePlatform.completePurchase(purchase); } return MapEntry(purchase.productID, purchase); })); productList.addAll(_products.map( (ProductDetails productDetails) { final PurchaseDetails? previousPurchase = purchases[productDetails.id]; return ListTile( title: Text( productDetails.title, ), subtitle: Text( productDetails.description, ), trailing: previousPurchase != null ? IconButton( onPressed: () { final InAppPurchaseAndroidPlatformAddition addition = InAppPurchasePlatformAddition.instance! as InAppPurchaseAndroidPlatformAddition; final SkuDetailsWrapper skuDetails = (productDetails as GooglePlayProductDetails) .skuDetails; addition .launchPriceChangeConfirmationFlow( sku: skuDetails.sku) .then((BillingResultWrapper value) => print( 'confirmationResponse: ${value.responseCode}')); }, icon: const Icon(Icons.upgrade)) : TextButton( style: TextButton.styleFrom( backgroundColor: Colors.green[800], // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: Colors.white, ), onPressed: () { // NOTE: If you are making a subscription purchase/upgrade/downgrade, we recommend you to // verify the latest status of you your subscription by using server side receipt validation // and update the UI accordingly. The subscription purchase status shown // inside the app may not be accurate. final GooglePlayPurchaseDetails? oldSubscription = _getOldSubscription( productDetails as GooglePlayProductDetails, purchases); final GooglePlayPurchaseParam purchaseParam = GooglePlayPurchaseParam( productDetails: productDetails, changeSubscriptionParam: oldSubscription != null ? ChangeSubscriptionParam( oldPurchaseDetails: oldSubscription, prorationMode: ProrationMode .immediateWithTimeProration) : null); if (productDetails.id == _kConsumableId) { _inAppPurchasePlatform.buyConsumable( purchaseParam: purchaseParam, // ignore: avoid_redundant_argument_values autoConsume: _kAutoConsume); } else { _inAppPurchasePlatform.buyNonConsumable( purchaseParam: purchaseParam); } }, child: Text(productDetails.price), )); }, )); return Card( child: Column( children: [productHeader, const Divider()] + productList)); } Card _buildConsumableBox() { if (_loading) { return const Card( child: ListTile( leading: CircularProgressIndicator(), title: Text('Fetching consumables...'))); } if (!_isAvailable || _notFoundIds.contains(_kConsumableId)) { return const Card(); } const ListTile consumableHeader = ListTile(title: Text('Purchased consumables')); final List tokens = _consumables.map((String id) { return GridTile( child: IconButton( icon: const Icon( Icons.stars, size: 42.0, color: Colors.orange, ), splashColor: Colors.yellowAccent, onPressed: () => consume(id), ), ); }).toList(); return Card( child: Column(children: [ consumableHeader, const Divider(), GridView.count( crossAxisCount: 5, shrinkWrap: true, padding: const EdgeInsets.all(16.0), children: tokens, ) ])); } Future consume(String id) async { await ConsumableStore.consume(id); final List consumables = await ConsumableStore.load(); setState(() { _consumables = consumables; }); } void showPendingUI() { setState(() { _purchasePending = true; }); } Future deliverProduct(PurchaseDetails purchaseDetails) async { // IMPORTANT!! Always verify purchase details before delivering the product. if (purchaseDetails.productID == _kConsumableId) { await ConsumableStore.save(purchaseDetails.purchaseID!); final List consumables = await ConsumableStore.load(); setState(() { _purchasePending = false; _consumables = consumables; }); } else { setState(() { _purchases.add(purchaseDetails); _purchasePending = false; }); } } void handleError(IAPError error) { setState(() { _purchasePending = false; }); } Future _verifyPurchase(PurchaseDetails purchaseDetails) { // IMPORTANT!! Always verify a purchase before delivering the product. // For the purpose of an example, we directly return true. return Future.value(true); } void _handleInvalidPurchase(PurchaseDetails purchaseDetails) { // handle invalid purchase here if _verifyPurchase` failed. } Future _listenToPurchaseUpdated( List purchaseDetailsList) async { for (final PurchaseDetails purchaseDetails in purchaseDetailsList) { if (purchaseDetails.status == PurchaseStatus.pending) { showPendingUI(); } else { if (purchaseDetails.status == PurchaseStatus.error) { handleError(purchaseDetails.error!); } else if (purchaseDetails.status == PurchaseStatus.purchased || purchaseDetails.status == PurchaseStatus.restored) { final bool valid = await _verifyPurchase(purchaseDetails); if (valid) { deliverProduct(purchaseDetails); } else { _handleInvalidPurchase(purchaseDetails); return; } } if (!_kAutoConsume && purchaseDetails.productID == _kConsumableId) { final InAppPurchaseAndroidPlatformAddition addition = InAppPurchasePlatformAddition.instance! as InAppPurchaseAndroidPlatformAddition; await addition.consumePurchase(purchaseDetails); } if (purchaseDetails.pendingCompletePurchase) { await _inAppPurchasePlatform.completePurchase(purchaseDetails); } } } } GooglePlayPurchaseDetails? _getOldSubscription( GooglePlayProductDetails productDetails, Map purchases) { // This is just to demonstrate a subscription upgrade or downgrade. // This method assumes that you have only 2 subscriptions under a group, 'subscription_silver' & 'subscription_gold'. // The 'subscription_silver' subscription can be upgraded to 'subscription_gold' and // the 'subscription_gold' subscription can be downgraded to 'subscription_silver'. // Please remember to replace the logic of finding the old subscription Id as per your app. // The old subscription is only required on Android since Apple handles this internally // by using the subscription group feature in iTunesConnect. GooglePlayPurchaseDetails? oldSubscription; if (productDetails.id == _kSilverSubscriptionId && purchases[_kGoldSubscriptionId] != null) { oldSubscription = purchases[_kGoldSubscriptionId]! as GooglePlayPurchaseDetails; } else if (productDetails.id == _kGoldSubscriptionId && purchases[_kSilverSubscriptionId] != null) { oldSubscription = purchases[_kSilverSubscriptionId]! as GooglePlayPurchaseDetails; } return oldSubscription; } } class _FeatureCard extends StatelessWidget { _FeatureCard({Key? key}) : super(key: key); final InAppPurchaseAndroidPlatformAddition addition = InAppPurchasePlatformAddition.instance! as InAppPurchaseAndroidPlatformAddition; @override Widget build(BuildContext context) { return Card( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const ListTile(title: Text('Available features')), const Divider(), for (BillingClientFeature feature in BillingClientFeature.values) _buildFeatureWidget(feature), ])); } Widget _buildFeatureWidget(BillingClientFeature feature) { return FutureBuilder( future: addition.isFeatureSupported(feature), builder: (BuildContext context, AsyncSnapshot snapshot) { Color color = Colors.grey; final bool? data = snapshot.data; if (data != null) { color = data ? Colors.green : Colors.red; } return Padding( padding: const EdgeInsets.fromLTRB(16.0, 4.0, 16.0, 4.0), child: Text( _featureToString(feature), style: TextStyle(color: color), ), ); }, ); } String _featureToString(BillingClientFeature feature) { switch (feature) { case BillingClientFeature.inAppItemsOnVR: return 'inAppItemsOnVR'; case BillingClientFeature.priceChangeConfirmation: return 'priceChangeConfirmation'; case BillingClientFeature.subscriptions: return 'subscriptions'; case BillingClientFeature.subscriptionsOnVR: return 'subscriptionsOnVR'; case BillingClientFeature.subscriptionsUpdate: return 'subscriptionsUpdate'; } } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml ================================================ name: in_app_purchase_android_example description: Demonstrates how to use the in_app_purchase_android plugin. publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter in_app_purchase_android: # When depending on this package from a real application you should use: # in_app_purchase_android: ^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: ../ in_app_purchase_platform_interface: ^1.0.0 shared_preferences: ^2.0.0 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/example/test_driver/test/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/lib/billing_client_wrappers.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'src/billing_client_wrappers/billing_client_wrapper.dart'; export 'src/billing_client_wrappers/purchase_wrapper.dart'; export 'src/billing_client_wrappers/sku_details_wrapper.dart'; ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/lib/in_app_purchase_android.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'src/in_app_purchase_android_platform.dart'; export 'src/in_app_purchase_android_platform_addition.dart'; export 'src/types/types.dart'; ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/README.md ================================================ # billing_client_wrappers This exposes a way Dart endpoints through to [Google Play Billing Library](https://developer.android.com/google/play/billing/billing_library_overview). Can be used as an alternative to [in_app_purchase](../in_app_purchase/README.md). ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart ================================================ // Copyright 2013 The Flutter 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:flutter/services.dart'; import 'package:json_annotation/json_annotation.dart'; import '../../billing_client_wrappers.dart'; import '../channel.dart'; part 'billing_client_wrapper.g.dart'; /// Method identifier for the OnPurchaseUpdated method channel method. @visibleForTesting const String kOnPurchasesUpdated = 'PurchasesUpdatedListener#onPurchasesUpdated(int, List)'; const String _kOnBillingServiceDisconnected = 'BillingClientStateListener#onBillingServiceDisconnected()'; /// Callback triggered by Play in response to purchase activity. /// /// This callback is triggered in response to all purchase activity while an /// instance of `BillingClient` is active. This includes purchases initiated by /// the app ([BillingClient.launchBillingFlow]) as well as purchases made in /// Play itself while this app is open. /// /// This does not provide any hooks for purchases made in the past. See /// [BillingClient.queryPurchases] and [BillingClient.queryPurchaseHistory]. /// /// All purchase information should also be verified manually, with your server /// if at all possible. See ["Verify a /// purchase"](https://developer.android.com/google/play/billing/billing_library_overview#Verify). /// /// Wraps a /// [`PurchasesUpdatedListener`](https://developer.android.com/reference/com/android/billingclient/api/PurchasesUpdatedListener.html). typedef PurchasesUpdatedListener = void Function( PurchasesResultWrapper purchasesResult); /// This class can be used directly instead of [InAppPurchaseConnection] to call /// Play-specific billing APIs. /// /// Wraps a /// [`com.android.billingclient.api.BillingClient`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient) /// instance. /// /// /// In general this API conforms to the Java /// `com.android.billingclient.api.BillingClient` API as much as possible, with /// some minor changes to account for language differences. Callbacks have been /// converted to futures where appropriate. class BillingClient { /// Creates a billing client. BillingClient(PurchasesUpdatedListener onPurchasesUpdated) { channel.setMethodCallHandler(callHandler); _callbacks[kOnPurchasesUpdated] = [ onPurchasesUpdated ]; } // Occasionally methods in the native layer require a Dart callback to be // triggered in response to a Java callback. For example, // [startConnection] registers an [OnBillingServiceDisconnected] callback. // This list of names to callbacks is used to trigger Dart callbacks in // response to those Java callbacks. Dart sends the Java layer a handle to the // matching callback here to remember, and then once its twin is triggered it // sends the handle back over the platform channel. We then access that handle // in this array and call it in Dart code. See also [_callHandler]. final Map> _callbacks = >{}; /// Calls /// [`BillingClient#isReady()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#isReady()) /// to get the ready status of the BillingClient instance. Future isReady() async { final bool? ready = await channel.invokeMethod('BillingClient#isReady()'); return ready ?? false; } /// Enable the [BillingClientWrapper] to handle pending purchases. /// /// **Deprecation warning:** it is no longer required to call /// [enablePendingPurchases] when initializing your application. @Deprecated( 'The requirement to call `enablePendingPurchases()` has become obsolete ' "since Google Play no longer accepts app submissions that don't support " 'pending purchases.') void enablePendingPurchases() { // No-op, until it is time to completely remove this method from the API. } /// Calls /// [`BillingClient#startConnection(BillingClientStateListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#startconnection) /// to create and connect a `BillingClient` instance. /// /// [onBillingServiceConnected] has been converted from a callback parameter /// to the Future result returned by this function. This returns the /// `BillingClient.BillingResultWrapper` describing the connection result. /// /// This triggers the creation of a new `BillingClient` instance in Java if /// one doesn't already exist. Future startConnection( {required OnBillingServiceDisconnected onBillingServiceDisconnected}) async { final List disconnectCallbacks = _callbacks[_kOnBillingServiceDisconnected] ??= []; disconnectCallbacks.add(onBillingServiceDisconnected); return BillingResultWrapper.fromJson((await channel .invokeMapMethod( 'BillingClient#startConnection(BillingClientStateListener)', { 'handle': disconnectCallbacks.length - 1, })) ?? {}); } /// Calls /// [`BillingClient#endConnection(BillingClientStateListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#endconnect /// to disconnect a `BillingClient` instance. /// /// Will trigger the [OnBillingServiceDisconnected] callback passed to [startConnection]. /// /// This triggers the destruction of the `BillingClient` instance in Java. Future endConnection() async { return channel.invokeMethod('BillingClient#endConnection()'); } /// Returns a list of [SkuDetailsWrapper]s that have [SkuDetailsWrapper.sku] /// in `skusList`, and [SkuDetailsWrapper.type] matching `skuType`. /// /// Calls through to [`BillingClient#querySkuDetailsAsync(SkuDetailsParams, /// SkuDetailsResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#querySkuDetailsAsync(com.android.billingclient.api.SkuDetailsParams,%20com.android.billingclient.api.SkuDetailsResponseListener)) /// Instead of taking a callback parameter, it returns a Future /// [SkuDetailsResponseWrapper]. It also takes the values of /// `SkuDetailsParams` as direct arguments instead of requiring it constructed /// and passed in as a class. Future querySkuDetails( {required SkuType skuType, required List skusList}) async { final Map arguments = { 'skuType': const SkuTypeConverter().toJson(skuType), 'skusList': skusList }; return SkuDetailsResponseWrapper.fromJson((await channel.invokeMapMethod< String, dynamic>( 'BillingClient#querySkuDetailsAsync(SkuDetailsParams, SkuDetailsResponseListener)', arguments)) ?? {}); } /// Attempt to launch the Play Billing Flow for a given [skuDetails]. /// /// The [skuDetails] needs to have already been fetched in a [querySkuDetails] /// call. The [accountId] is an optional hashed string associated with the user /// that's unique to your app. It's used by Google to detect unusual behavior. /// Do not pass in a cleartext [accountId], and do not use this field to store any Personally Identifiable Information (PII) /// such as emails in cleartext. Attempting to store PII in this field will result in purchases being blocked. /// Google Play recommends that you use either encryption or a one-way hash to generate an obfuscated identifier to send to Google Play. /// /// Specifies an optional [obfuscatedProfileId] that is uniquely associated with the user's profile in your app. /// Some applications allow users to have multiple profiles within a single account. Use this method to send the user's profile identifier to Google. /// Setting this field requests the user's obfuscated account id. /// /// Calling this attemps to show the Google Play purchase UI. The user is free /// to complete the transaction there. /// /// This method returns a [BillingResultWrapper] representing the initial attempt /// to show the Google Play billing flow. Actual purchase updates are /// delivered via the [PurchasesUpdatedListener]. /// /// This method calls through to /// [`BillingClient#launchBillingFlow`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#launchbillingflow). /// It constructs a /// [`BillingFlowParams`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams) /// instance by [setting the given skuDetails](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder.html#setskudetails), /// [the given accountId](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setObfuscatedAccountId(java.lang.String)) /// and the [obfuscatedProfileId] (https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setobfuscatedprofileid). /// /// When this method is called to purchase a subscription, an optional `oldSku` /// can be passed in. This will tell Google Play that rather than purchasing a new subscription, /// the user needs to upgrade/downgrade the existing subscription. /// The [oldSku](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setoldsku) and [purchaseToken] are the SKU id and purchase token that the user is upgrading or downgrading from. /// [purchaseToken] must not be `null` if [oldSku] is not `null`. /// The [prorationMode](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setreplaceskusprorationmode) is the mode of proration during subscription upgrade/downgrade. /// This value will only be effective if the `oldSku` is also set. Future launchBillingFlow( {required String sku, String? accountId, String? obfuscatedProfileId, String? oldSku, String? purchaseToken, ProrationMode? prorationMode}) async { assert(sku != null); assert((oldSku == null) == (purchaseToken == null), 'oldSku and purchaseToken must both be set, or both be null.'); final Map arguments = { 'sku': sku, 'accountId': accountId, 'obfuscatedProfileId': obfuscatedProfileId, 'oldSku': oldSku, 'purchaseToken': purchaseToken, 'prorationMode': const ProrationModeConverter().toJson(prorationMode ?? ProrationMode.unknownSubscriptionUpgradeDowngradePolicy) }; return BillingResultWrapper.fromJson( (await channel.invokeMapMethod( 'BillingClient#launchBillingFlow(Activity, BillingFlowParams)', arguments)) ?? {}); } /// Fetches recent purchases for the given [SkuType]. /// /// Unlike [queryPurchaseHistory], This does not make a network request and /// does not return items that are no longer owned. /// /// All purchase information should also be verified manually, with your /// server if at all possible. See ["Verify a /// purchase"](https://developer.android.com/google/play/billing/billing_library_overview#Verify). /// /// This wraps [`BillingClient#queryPurchases(String /// skutype)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#querypurchases). Future queryPurchases(SkuType skuType) async { assert(skuType != null); return PurchasesResultWrapper.fromJson((await channel .invokeMapMethod( 'BillingClient#queryPurchases(String)', { 'skuType': const SkuTypeConverter().toJson(skuType) })) ?? {}); } /// Fetches purchase history for the given [SkuType]. /// /// Unlike [queryPurchases], this makes a network request via Play and returns /// the most recent purchase for each [SkuDetailsWrapper] of the given /// [SkuType] even if the item is no longer owned. /// /// All purchase information should also be verified manually, with your /// server if at all possible. See ["Verify a /// purchase"](https://developer.android.com/google/play/billing/billing_library_overview#Verify). /// /// This wraps [`BillingClient#queryPurchaseHistoryAsync(String skuType, /// PurchaseHistoryResponseListener /// listener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#querypurchasehistoryasync). Future queryPurchaseHistory(SkuType skuType) async { assert(skuType != null); return PurchasesHistoryResult.fromJson((await channel.invokeMapMethod< String, dynamic>( 'BillingClient#queryPurchaseHistoryAsync(String, PurchaseHistoryResponseListener)', { 'skuType': const SkuTypeConverter().toJson(skuType) })) ?? {}); } /// Consumes a given in-app product. /// /// Consuming can only be done on an item that's owned, and as a result of consumption, the user will no longer own it. /// Consumption is done asynchronously. The method returns a Future containing a [BillingResultWrapper]. /// /// This wraps [`BillingClient#consumeAsync(String, ConsumeResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#consumeAsync(java.lang.String,%20com.android.billingclient.api.ConsumeResponseListener)) Future consumeAsync(String purchaseToken) async { assert(purchaseToken != null); return BillingResultWrapper.fromJson((await channel .invokeMapMethod( 'BillingClient#consumeAsync(String, ConsumeResponseListener)', { 'purchaseToken': purchaseToken, })) ?? {}); } /// Acknowledge an in-app purchase. /// /// The developer must acknowledge all in-app purchases after they have been granted to the user. /// If this doesn't happen within three days of the purchase, the purchase will be refunded. /// /// Consumables are already implicitly acknowledged by calls to [consumeAsync] and /// do not need to be explicitly acknowledged by using this method. /// However this method can be called for them in order to explicitly acknowledge them if desired. /// /// Be sure to only acknowledge a purchase after it has been granted to the user. /// [PurchaseWrapper.purchaseState] should be [PurchaseStateWrapper.purchased] and /// the purchase should be validated. See [Verify a purchase](https://developer.android.com/google/play/billing/billing_library_overview#Verify) on verifying purchases. /// /// Please refer to [acknowledge](https://developer.android.com/google/play/billing/billing_library_overview#acknowledge) for more /// details. /// /// This wraps [`BillingClient#acknowledgePurchase(String, AcknowledgePurchaseResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#acknowledgePurchase(com.android.billingclient.api.AcknowledgePurchaseParams,%20com.android.billingclient.api.AcknowledgePurchaseResponseListener)) Future acknowledgePurchase(String purchaseToken) async { assert(purchaseToken != null); return BillingResultWrapper.fromJson((await channel.invokeMapMethod( 'BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)', { 'purchaseToken': purchaseToken, })) ?? {}); } /// Checks if the specified feature or capability is supported by the Play Store. /// Call this to check if a [BillingClientFeature] is supported by the device. Future isFeatureSupported(BillingClientFeature feature) async { final bool? result = await channel.invokeMethod( 'BillingClient#isFeatureSupported(String)', { 'feature': const BillingClientFeatureConverter().toJson(feature), }); return result ?? false; } /// Initiates a flow to confirm the change of price for an item subscribed by the user. /// /// When the price of a user subscribed item has changed, launch this flow to take users to /// a screen with price change information. User can confirm the new price or cancel the flow. /// /// The skuDetails needs to have already been fetched in a [querySkuDetails] /// call. Future launchPriceChangeConfirmationFlow( {required String sku}) async { assert(sku != null); final Map arguments = { 'sku': sku, }; return BillingResultWrapper.fromJson((await channel.invokeMapMethod( 'BillingClient#launchPriceChangeConfirmationFlow (Activity, PriceChangeFlowParams, PriceChangeConfirmationListener)', arguments)) ?? {}); } /// The method call handler for [channel]. @visibleForTesting Future callHandler(MethodCall call) async { switch (call.method) { case kOnPurchasesUpdated: // The purchases updated listener is a singleton. assert(_callbacks[kOnPurchasesUpdated]!.length == 1); final PurchasesUpdatedListener listener = _callbacks[kOnPurchasesUpdated]!.first as PurchasesUpdatedListener; listener(PurchasesResultWrapper.fromJson( (call.arguments as Map).cast())); break; case _kOnBillingServiceDisconnected: final int handle = (call.arguments as Map)['handle']! as int; final List onDisconnected = _callbacks[_kOnBillingServiceDisconnected]! .cast(); onDisconnected[handle](); break; } } } /// Callback triggered when the [BillingClientWrapper] is disconnected. /// /// Wraps /// [`com.android.billingclient.api.BillingClientStateListener.onServiceDisconnected()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClientStateListener.html#onBillingServiceDisconnected()) /// to call back on `BillingClient` disconnect. typedef OnBillingServiceDisconnected = void Function(); /// Possible `BillingClient` response statuses. /// /// Wraps /// [`BillingClient.BillingResponse`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponse). /// See the `BillingResponse` docs for more explanation of the different /// constants. @JsonEnum(alwaysCreate: true) enum BillingResponse { // WARNING: Changes to this class need to be reflected in our generated code. // Run `flutter packages pub run build_runner watch` to rebuild and watch for // further changes. /// The request has reached the maximum timeout before Google Play responds. @JsonValue(-3) serviceTimeout, /// The requested feature is not supported by Play Store on the current device. @JsonValue(-2) featureNotSupported, /// The play Store service is not connected now - potentially transient state. @JsonValue(-1) serviceDisconnected, /// Success. @JsonValue(0) ok, /// The user pressed back or canceled a dialog. @JsonValue(1) userCanceled, /// The network connection is down. @JsonValue(2) serviceUnavailable, /// The billing API version is not supported for the type requested. @JsonValue(3) billingUnavailable, /// The requested product is not available for purchase. @JsonValue(4) itemUnavailable, /// Invalid arguments provided to the API. @JsonValue(5) developerError, /// Fatal error during the API action. @JsonValue(6) error, /// Failure to purchase since item is already owned. @JsonValue(7) itemAlreadyOwned, /// Failure to consume since item is not owned. @JsonValue(8) itemNotOwned, } /// Serializer for [BillingResponse]. /// /// Use these in `@JsonSerializable()` classes by annotating them with /// `@BillingResponseConverter()`. class BillingResponseConverter implements JsonConverter { /// Default const constructor. const BillingResponseConverter(); @override BillingResponse fromJson(int? json) { if (json == null) { return BillingResponse.error; } return $enumDecode(_$BillingResponseEnumMap, json); } @override int toJson(BillingResponse object) => _$BillingResponseEnumMap[object]!; } /// Enum representing potential [SkuDetailsWrapper.type]s. /// /// Wraps /// [`BillingClient.SkuType`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.SkuType) /// See the linked documentation for an explanation of the different constants. @JsonEnum(alwaysCreate: true) enum SkuType { // WARNING: Changes to this class need to be reflected in our generated code. // Run `flutter packages pub run build_runner watch` to rebuild and watch for // further changes. /// A one time product. Acquired in a single transaction. @JsonValue('inapp') inapp, /// A product requiring a recurring charge over time. @JsonValue('subs') subs, } /// Serializer for [SkuType]. /// /// Use these in `@JsonSerializable()` classes by annotating them with /// `@SkuTypeConverter()`. class SkuTypeConverter implements JsonConverter { /// Default const constructor. const SkuTypeConverter(); @override SkuType fromJson(String? json) { if (json == null) { return SkuType.inapp; } return $enumDecode(_$SkuTypeEnumMap, json); } @override String toJson(SkuType object) => _$SkuTypeEnumMap[object]!; } /// Enum representing the proration mode. /// /// When upgrading or downgrading a subscription, set this mode to provide details /// about the proration that will be applied when the subscription changes. /// /// Wraps [`BillingFlowParams.ProrationMode`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode) /// See the linked documentation for an explanation of the different constants. @JsonEnum(alwaysCreate: true) enum ProrationMode { // WARNING: Changes to this class need to be reflected in our generated code. // Run `flutter packages pub run build_runner watch` to rebuild and watch for // further changes. /// Unknown upgrade or downgrade policy. @JsonValue(0) unknownSubscriptionUpgradeDowngradePolicy, /// Replacement takes effect immediately, and the remaining time will be prorated /// and credited to the user. /// /// This is the current default behavior. @JsonValue(1) immediateWithTimeProration, /// Replacement takes effect immediately, and the billing cycle remains the same. /// /// The price for the remaining period will be charged. /// This option is only available for subscription upgrade. @JsonValue(2) immediateAndChargeProratedPrice, /// Replacement takes effect immediately, and the new price will be charged on next /// recurrence time. /// /// The billing cycle stays the same. @JsonValue(3) immediateWithoutProration, /// Replacement takes effect when the old plan expires, and the new price will /// be charged at the same time. @JsonValue(4) deferred, /// Replacement takes effect immediately, and the user is charged full price /// of new plan and is given a full billing cycle of subscription, plus /// remaining prorated time from the old plan. @JsonValue(5) immediateAndChargeFullPrice, } /// Serializer for [ProrationMode]. /// /// Use these in `@JsonSerializable()` classes by annotating them with /// `@ProrationModeConverter()`. class ProrationModeConverter implements JsonConverter { /// Default const constructor. const ProrationModeConverter(); @override ProrationMode fromJson(int? json) { if (json == null) { return ProrationMode.unknownSubscriptionUpgradeDowngradePolicy; } return $enumDecode(_$ProrationModeEnumMap, json); } @override int toJson(ProrationMode object) => _$ProrationModeEnumMap[object]!; } /// Features/capabilities supported by [BillingClient.isFeatureSupported()](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.FeatureType). @JsonEnum(alwaysCreate: true) enum BillingClientFeature { // WARNING: Changes to this class need to be reflected in our generated code. // Run `flutter packages pub run build_runner watch` to rebuild and watch for // further changes. // JsonValues need to match constant values defined in https://developer.android.com/reference/com/android/billingclient/api/BillingClient.FeatureType#summary /// Purchase/query for in-app items on VR. @JsonValue('inAppItemsOnVr') inAppItemsOnVR, /// Launch a price change confirmation flow. @JsonValue('priceChangeConfirmation') priceChangeConfirmation, /// Purchase/query for subscriptions. @JsonValue('subscriptions') subscriptions, /// Purchase/query for subscriptions on VR. @JsonValue('subscriptionsOnVr') subscriptionsOnVR, /// Subscriptions update/replace. @JsonValue('subscriptionsUpdate') subscriptionsUpdate } /// Serializer for [BillingClientFeature]. /// /// Use these in `@JsonSerializable()` classes by annotating them with /// `@BillingClientFeatureConverter()`. class BillingClientFeatureConverter implements JsonConverter { /// Default const constructor. const BillingClientFeatureConverter(); @override BillingClientFeature fromJson(String json) { return $enumDecode( _$BillingClientFeatureEnumMap.cast(), json); } @override String toJson(BillingClientFeature object) => _$BillingClientFeatureEnumMap[object]!; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.g.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND part of 'billing_client_wrapper.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** const _$BillingResponseEnumMap = { BillingResponse.serviceTimeout: -3, BillingResponse.featureNotSupported: -2, BillingResponse.serviceDisconnected: -1, BillingResponse.ok: 0, BillingResponse.userCanceled: 1, BillingResponse.serviceUnavailable: 2, BillingResponse.billingUnavailable: 3, BillingResponse.itemUnavailable: 4, BillingResponse.developerError: 5, BillingResponse.error: 6, BillingResponse.itemAlreadyOwned: 7, BillingResponse.itemNotOwned: 8, }; const _$SkuTypeEnumMap = { SkuType.inapp: 'inapp', SkuType.subs: 'subs', }; const _$ProrationModeEnumMap = { ProrationMode.unknownSubscriptionUpgradeDowngradePolicy: 0, ProrationMode.immediateWithTimeProration: 1, ProrationMode.immediateAndChargeProratedPrice: 2, ProrationMode.immediateWithoutProration: 3, ProrationMode.deferred: 4, ProrationMode.immediateAndChargeFullPrice: 5, }; const _$BillingClientFeatureEnumMap = { BillingClientFeature.inAppItemsOnVR: 'inAppItemsOnVr', BillingClientFeature.priceChangeConfirmation: 'priceChangeConfirmation', BillingClientFeature.subscriptions: 'subscriptions', BillingClientFeature.subscriptionsOnVR: 'subscriptionsOnVr', BillingClientFeature.subscriptionsUpdate: 'subscriptionsUpdate', }; ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:json_annotation/json_annotation.dart'; import 'billing_client_wrapper.dart'; import 'sku_details_wrapper.dart'; // WARNING: Changes to `@JsonSerializable` classes need to be reflected in the // below generated file. Run `flutter packages pub run build_runner watch` to // rebuild and watch for further changes. part 'purchase_wrapper.g.dart'; /// Data structure representing a successful purchase. /// /// All purchase information should also be verified manually, with your /// server if at all possible. See ["Verify a /// purchase"](https://developer.android.com/google/play/billing/billing_library_overview#Verify). /// /// This wraps [`com.android.billlingclient.api.Purchase`](https://developer.android.com/reference/com/android/billingclient/api/Purchase) @JsonSerializable() @PurchaseStateConverter() @immutable class PurchaseWrapper { /// Creates a purchase wrapper with the given purchase details. @visibleForTesting const PurchaseWrapper({ required this.orderId, required this.packageName, required this.purchaseTime, required this.purchaseToken, required this.signature, @Deprecated('Use skus instead') String? sku, required this.skus, required this.isAutoRenewing, required this.originalJson, this.developerPayload, required this.isAcknowledged, required this.purchaseState, this.obfuscatedAccountId, this.obfuscatedProfileId, }) : _sku = sku; /// Factory for creating a [PurchaseWrapper] from a [Map] with the purchase details. factory PurchaseWrapper.fromJson(Map map) => _$PurchaseWrapperFromJson(map); @override bool operator ==(Object other) { if (identical(other, this)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is PurchaseWrapper && other.orderId == orderId && other.packageName == packageName && other.purchaseTime == purchaseTime && other.purchaseToken == purchaseToken && other.signature == signature && other.sku == sku && other.isAutoRenewing == isAutoRenewing && other.originalJson == originalJson && other.isAcknowledged == isAcknowledged && other.purchaseState == purchaseState; } @override int get hashCode => Object.hash( orderId, packageName, purchaseTime, purchaseToken, signature, sku, isAutoRenewing, originalJson, isAcknowledged, purchaseState); /// The unique ID for this purchase. Corresponds to the Google Payments order /// ID. @JsonKey(defaultValue: '') final String orderId; /// The package name the purchase was made from. @JsonKey(defaultValue: '') final String packageName; /// When the purchase was made, as an epoch timestamp. @JsonKey(defaultValue: 0) final int purchaseTime; /// A unique ID for a given [SkuDetailsWrapper], user, and purchase. @JsonKey(defaultValue: '') final String purchaseToken; /// Signature of purchase data, signed with the developer's private key. Uses /// RSASSA-PKCS1-v1_5. @JsonKey(defaultValue: '') final String signature; /// The product ID of this purchase. @Deprecated('Use skus instead') @JsonKey(ignore: true) String get sku => _sku ?? (skus.isNotEmpty ? skus.first : ''); final String? _sku; /// The product IDs of this purchase. @JsonKey(defaultValue: []) final List skus; /// True for subscriptions that renew automatically. Does not apply to /// [SkuType.inapp] products. /// /// For [SkuType.subs] this means that the subscription is canceled when it is /// false. /// /// The value is `false` for [SkuType.inapp] products. final bool isAutoRenewing; /// Details about this purchase, in JSON. /// /// This can be used verify a purchase. See ["Verify a purchase on a /// device"](https://developer.android.com/google/play/billing/billing_library_overview#Verify-purchase-device). /// Note though that verifying a purchase locally is inherently insecure (see /// the article for more details). @JsonKey(defaultValue: '') final String originalJson; /// The payload specified by the developer when the purchase was acknowledged or consumed. /// /// The value is `null` if it wasn't specified when the purchase was acknowledged or consumed. /// The `developerPayload` is removed from [BillingClientWrapper.acknowledgePurchase], [BillingClientWrapper.consumeAsync], [InAppPurchaseConnection.completePurchase], [InAppPurchaseConnection.consumePurchase] /// after plugin version `0.5.0`. As a result, this will be `null` for new purchases that happen after updating to `0.5.0`. final String? developerPayload; /// Whether the purchase has been acknowledged. /// /// A successful purchase has to be acknowledged within 3 days after the purchase via [BillingClient.acknowledgePurchase]. /// * See also [BillingClient.acknowledgePurchase] for more details on acknowledging purchases. @JsonKey(defaultValue: false) final bool isAcknowledged; /// Determines the current state of the purchase. /// /// [BillingClient.acknowledgePurchase] should only be called when the `purchaseState` is [PurchaseStateWrapper.purchased]. /// * See also [BillingClient.acknowledgePurchase] for more details on acknowledging purchases. final PurchaseStateWrapper purchaseState; /// The obfuscatedAccountId specified when making a purchase. /// /// The [obfuscatedAccountId] can either be set in /// [PurchaseParam.applicationUserName] when using the [InAppPurchasePlatform] /// or by setting the [accountId] in [BillingClient.launchBillingFlow]. final String? obfuscatedAccountId; /// The obfuscatedProfileId can be used when there are multiple profiles /// withing one account. The obfuscatedProfileId should be specified when /// making a purchase. This property can only be set on a purchase by /// directly calling [BillingClient.launchBillingFlow] and is not available /// on the generic [InAppPurchasePlatform]. final String? obfuscatedProfileId; } /// Data structure representing a purchase history record. /// /// This class includes a subset of fields in [PurchaseWrapper]. /// /// This wraps [`com.android.billlingclient.api.PurchaseHistoryRecord`](https://developer.android.com/reference/com/android/billingclient/api/PurchaseHistoryRecord) /// /// * See also: [BillingClient.queryPurchaseHistory] for obtaining a [PurchaseHistoryRecordWrapper]. // We can optionally make [PurchaseWrapper] extend or implement [PurchaseHistoryRecordWrapper]. // For now, we keep them separated classes to be consistent with Android's BillingClient implementation. @JsonSerializable() @immutable class PurchaseHistoryRecordWrapper { /// Creates a [PurchaseHistoryRecordWrapper] with the given record details. @visibleForTesting const PurchaseHistoryRecordWrapper({ required this.purchaseTime, required this.purchaseToken, required this.signature, @Deprecated('Use skus instead') String? sku, required this.skus, required this.originalJson, required this.developerPayload, }) : _sku = sku; /// Factory for creating a [PurchaseHistoryRecordWrapper] from a [Map] with the record details. factory PurchaseHistoryRecordWrapper.fromJson(Map map) => _$PurchaseHistoryRecordWrapperFromJson(map); /// When the purchase was made, as an epoch timestamp. @JsonKey(defaultValue: 0) final int purchaseTime; /// A unique ID for a given [SkuDetailsWrapper], user, and purchase. @JsonKey(defaultValue: '') final String purchaseToken; /// Signature of purchase data, signed with the developer's private key. Uses /// RSASSA-PKCS1-v1_5. @JsonKey(defaultValue: '') final String signature; /// The product ID of this purchase. @Deprecated('Use skus instead') @JsonKey(ignore: true) String get sku => _sku ?? (skus.isNotEmpty ? skus.first : ''); final String? _sku; /// The product ID of this purchase. @JsonKey(defaultValue: []) final List skus; /// Details about this purchase, in JSON. /// /// This can be used verify a purchase. See ["Verify a purchase on a /// device"](https://developer.android.com/google/play/billing/billing_library_overview#Verify-purchase-device). /// Note though that verifying a purchase locally is inherently insecure (see /// the article for more details). @JsonKey(defaultValue: '') final String originalJson; /// The payload specified by the developer when the purchase was acknowledged or consumed. /// /// The value is `null` if it wasn't specified when the purchase was acknowledged or consumed. final String? developerPayload; @override bool operator ==(Object other) { if (identical(other, this)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is PurchaseHistoryRecordWrapper && other.purchaseTime == purchaseTime && other.purchaseToken == purchaseToken && other.signature == signature && other.sku == sku && other.originalJson == originalJson && other.developerPayload == developerPayload; } @override int get hashCode => Object.hash(purchaseTime, purchaseToken, signature, sku, originalJson, developerPayload); } /// A data struct representing the result of a transaction. /// /// Contains a potentially empty list of [PurchaseWrapper]s, a [BillingResultWrapper] /// that contains a detailed description of the status and a /// [BillingResponse] to signify the overall state of the transaction. /// /// Wraps [`com.android.billingclient.api.Purchase.PurchasesResult`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchasesResult). @JsonSerializable() @BillingResponseConverter() @immutable class PurchasesResultWrapper { /// Creates a [PurchasesResultWrapper] with the given purchase result details. const PurchasesResultWrapper( {required this.responseCode, required this.billingResult, required this.purchasesList}); /// Factory for creating a [PurchaseResultWrapper] from a [Map] with the result details. factory PurchasesResultWrapper.fromJson(Map map) => _$PurchasesResultWrapperFromJson(map); @override bool operator ==(Object other) { if (identical(other, this)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is PurchasesResultWrapper && other.responseCode == responseCode && other.purchasesList == purchasesList && other.billingResult == billingResult; } @override int get hashCode => Object.hash(billingResult, responseCode, purchasesList); /// The detailed description of the status of the operation. final BillingResultWrapper billingResult; /// The status of the operation. /// /// This can represent either the status of the "query purchase history" half /// of the operation and the "user made purchases" transaction itself. final BillingResponse responseCode; /// The list of successful purchases made in this transaction. /// /// May be empty, especially if [responseCode] is not [BillingResponse.ok]. @JsonKey(defaultValue: []) final List purchasesList; } /// A data struct representing the result of a purchase history. /// /// Contains a potentially empty list of [PurchaseHistoryRecordWrapper]s and a [BillingResultWrapper] /// that contains a detailed description of the status. @JsonSerializable() @BillingResponseConverter() @immutable class PurchasesHistoryResult { /// Creates a [PurchasesHistoryResult] with the provided history. const PurchasesHistoryResult( {required this.billingResult, required this.purchaseHistoryRecordList}); /// Factory for creating a [PurchasesHistoryResult] from a [Map] with the history result details. factory PurchasesHistoryResult.fromJson(Map map) => _$PurchasesHistoryResultFromJson(map); @override bool operator ==(Object other) { if (identical(other, this)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is PurchasesHistoryResult && other.purchaseHistoryRecordList == purchaseHistoryRecordList && other.billingResult == billingResult; } @override int get hashCode => Object.hash(billingResult, purchaseHistoryRecordList); /// The detailed description of the status of the [BillingClient.queryPurchaseHistory]. final BillingResultWrapper billingResult; /// The list of queried purchase history records. /// /// May be empty, especially if [billingResult.responseCode] is not [BillingResponse.ok]. @JsonKey(defaultValue: []) final List purchaseHistoryRecordList; } /// Possible state of a [PurchaseWrapper]. /// /// Wraps /// [`BillingClient.api.Purchase.PurchaseState`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState.html). /// * See also: [PurchaseWrapper]. @JsonEnum(alwaysCreate: true) enum PurchaseStateWrapper { /// The state is unspecified. /// /// No actions on the [PurchaseWrapper] should be performed on this state. /// This is a catch-all. It should never be returned by the Play Billing Library. @JsonValue(0) unspecified_state, /// The user has completed the purchase process. /// /// The production should be delivered and then the purchase should be acknowledged. /// * See also [BillingClient.acknowledgePurchase] for more details on acknowledging purchases. @JsonValue(1) purchased, /// The user has started the purchase process. /// /// The user should follow the instructions that were given to them by the Play /// Billing Library to complete the purchase. /// /// You can also choose to remind the user to complete the purchase if you detected a /// [PurchaseWrapper] is still in the `pending` state in the future while calling [BillingClient.queryPurchases]. @JsonValue(2) pending, } /// Serializer for [PurchaseStateWrapper]. /// /// Use these in `@JsonSerializable()` classes by annotating them with /// `@PurchaseStateConverter()`. class PurchaseStateConverter implements JsonConverter { /// Default const constructor. const PurchaseStateConverter(); @override PurchaseStateWrapper fromJson(int? json) { if (json == null) { return PurchaseStateWrapper.unspecified_state; } return $enumDecode(_$PurchaseStateWrapperEnumMap, json); } @override int toJson(PurchaseStateWrapper object) => _$PurchaseStateWrapperEnumMap[object]!; /// Converts the purchase state stored in `object` to a [PurchaseStatus]. /// /// [PurchaseStateWrapper.unspecified_state] is mapped to [PurchaseStatus.error]. PurchaseStatus toPurchaseStatus(PurchaseStateWrapper object) { switch (object) { case PurchaseStateWrapper.pending: return PurchaseStatus.pending; case PurchaseStateWrapper.purchased: return PurchaseStatus.purchased; case PurchaseStateWrapper.unspecified_state: return PurchaseStatus.error; } } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.g.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND part of 'purchase_wrapper.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** PurchaseWrapper _$PurchaseWrapperFromJson(Map json) => PurchaseWrapper( orderId: json['orderId'] as String? ?? '', packageName: json['packageName'] as String? ?? '', purchaseTime: json['purchaseTime'] as int? ?? 0, purchaseToken: json['purchaseToken'] as String? ?? '', signature: json['signature'] as String? ?? '', skus: (json['skus'] as List?)?.map((e) => e as String).toList() ?? [], isAutoRenewing: json['isAutoRenewing'] as bool, originalJson: json['originalJson'] as String? ?? '', developerPayload: json['developerPayload'] as String?, isAcknowledged: json['isAcknowledged'] as bool? ?? false, purchaseState: const PurchaseStateConverter() .fromJson(json['purchaseState'] as int?), obfuscatedAccountId: json['obfuscatedAccountId'] as String?, obfuscatedProfileId: json['obfuscatedProfileId'] as String?, ); PurchaseHistoryRecordWrapper _$PurchaseHistoryRecordWrapperFromJson(Map json) => PurchaseHistoryRecordWrapper( purchaseTime: json['purchaseTime'] as int? ?? 0, purchaseToken: json['purchaseToken'] as String? ?? '', signature: json['signature'] as String? ?? '', skus: (json['skus'] as List?)?.map((e) => e as String).toList() ?? [], originalJson: json['originalJson'] as String? ?? '', developerPayload: json['developerPayload'] as String?, ); PurchasesResultWrapper _$PurchasesResultWrapperFromJson(Map json) => PurchasesResultWrapper( responseCode: const BillingResponseConverter() .fromJson(json['responseCode'] as int?), billingResult: BillingResultWrapper.fromJson((json['billingResult'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), purchasesList: (json['purchasesList'] as List?) ?.map((e) => PurchaseWrapper.fromJson(Map.from(e as Map))) .toList() ?? [], ); PurchasesHistoryResult _$PurchasesHistoryResultFromJson(Map json) => PurchasesHistoryResult( billingResult: BillingResultWrapper.fromJson((json['billingResult'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), purchaseHistoryRecordList: (json['purchaseHistoryRecordList'] as List?) ?.map((e) => PurchaseHistoryRecordWrapper.fromJson( Map.from(e as Map))) .toList() ?? [], ); const _$PurchaseStateWrapperEnumMap = { PurchaseStateWrapper.unspecified_state: 0, PurchaseStateWrapper.purchased: 1, PurchaseStateWrapper.pending: 2, }; ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:json_annotation/json_annotation.dart'; import 'billing_client_wrapper.dart'; // WARNING: Changes to `@JsonSerializable` classes need to be reflected in the // below generated file. Run `flutter packages pub run build_runner watch` to // rebuild and watch for further changes. part 'sku_details_wrapper.g.dart'; /// The error message shown when the map represents billing result is invalid from method channel. /// /// This usually indicates a series underlining code issue in the plugin. @visibleForTesting const String kInvalidBillingResultErrorMessage = 'Invalid billing result map from method channel.'; /// Dart wrapper around [`com.android.billingclient.api.SkuDetails`](https://developer.android.com/reference/com/android/billingclient/api/SkuDetails). /// /// Contains the details of an available product in Google Play Billing. @JsonSerializable() @SkuTypeConverter() @immutable class SkuDetailsWrapper { /// Creates a [SkuDetailsWrapper] with the given purchase details. @visibleForTesting const SkuDetailsWrapper({ required this.description, required this.freeTrialPeriod, required this.introductoryPrice, @Deprecated('Use `introductoryPriceAmountMicros` parameter instead') String introductoryPriceMicros = '', this.introductoryPriceAmountMicros = 0, required this.introductoryPriceCycles, required this.introductoryPricePeriod, required this.price, required this.priceAmountMicros, required this.priceCurrencyCode, required this.priceCurrencySymbol, required this.sku, required this.subscriptionPeriod, required this.title, required this.type, required this.originalPrice, required this.originalPriceAmountMicros, }) : _introductoryPriceMicros = introductoryPriceMicros; /// Constructs an instance of this from a key value map of data. /// /// The map needs to have named string keys with values matching the names and /// types of all of the members on this class. @visibleForTesting factory SkuDetailsWrapper.fromJson(Map map) => _$SkuDetailsWrapperFromJson(map); final String _introductoryPriceMicros; /// Textual description of the product. @JsonKey(defaultValue: '') final String description; /// Trial period in ISO 8601 format. @JsonKey(defaultValue: '') final String freeTrialPeriod; /// Introductory price, only applies to [SkuType.subs]. Formatted ("$0.99"). @JsonKey(defaultValue: '') final String introductoryPrice; /// [introductoryPrice] in micro-units 990000. /// /// Returns 0 if the SKU is not a subscription or doesn't have an introductory /// period. final int introductoryPriceAmountMicros; /// String representation of [introductoryPrice] in micro-units 990000 @Deprecated('Use `introductoryPriceAmountMicros` instead.') @JsonKey(ignore: true) String get introductoryPriceMicros => _introductoryPriceMicros.isEmpty ? introductoryPriceAmountMicros.toString() : _introductoryPriceMicros; /// The number of subscription billing periods for which the user will be given the introductory price, such as 3. /// Returns 0 if the SKU is not a subscription or doesn't have an introductory period. @JsonKey(defaultValue: 0) final int introductoryPriceCycles; /// The billing period of [introductoryPrice], in ISO 8601 format. @JsonKey(defaultValue: '') final String introductoryPricePeriod; /// Formatted with currency symbol ("$0.99"). @JsonKey(defaultValue: '') final String price; /// [price] in micro-units ("990000"). @JsonKey(defaultValue: 0) final int priceAmountMicros; /// [price] ISO 4217 currency code. @JsonKey(defaultValue: '') final String priceCurrencyCode; /// [price] localized currency symbol /// For example, for the US Dollar, the symbol is "$" if the locale /// is the US, while for other locales it may be "US$". @JsonKey(defaultValue: '') final String priceCurrencySymbol; /// The product ID in Google Play Console. @JsonKey(defaultValue: '') final String sku; /// Applies to [SkuType.subs], formatted in ISO 8601. @JsonKey(defaultValue: '') final String subscriptionPeriod; /// The product's title. @JsonKey(defaultValue: '') final String title; /// The [SkuType] of the product. final SkuType type; /// The original price that the user purchased this product for. @JsonKey(defaultValue: '') final String originalPrice; /// [originalPrice] in micro-units ("990000"). @JsonKey(defaultValue: 0) final int originalPriceAmountMicros; @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) { return false; } return other is SkuDetailsWrapper && other.description == description && other.freeTrialPeriod == freeTrialPeriod && other.introductoryPrice == introductoryPrice && other.introductoryPriceAmountMicros == introductoryPriceAmountMicros && other.introductoryPriceCycles == introductoryPriceCycles && other.introductoryPricePeriod == introductoryPricePeriod && other.price == price && other.priceAmountMicros == priceAmountMicros && other.sku == sku && other.subscriptionPeriod == subscriptionPeriod && other.title == title && other.type == type && other.originalPrice == originalPrice && other.originalPriceAmountMicros == originalPriceAmountMicros; } @override int get hashCode { return Object.hash( description.hashCode, freeTrialPeriod.hashCode, introductoryPrice.hashCode, introductoryPriceAmountMicros.hashCode, introductoryPriceCycles.hashCode, introductoryPricePeriod.hashCode, price.hashCode, priceAmountMicros.hashCode, sku.hashCode, subscriptionPeriod.hashCode, title.hashCode, type.hashCode, originalPrice, originalPriceAmountMicros); } } /// Translation of [`com.android.billingclient.api.SkuDetailsResponseListener`](https://developer.android.com/reference/com/android/billingclient/api/SkuDetailsResponseListener.html). /// /// Returned by [BillingClient.querySkuDetails]. @JsonSerializable() @immutable class SkuDetailsResponseWrapper { /// Creates a [SkuDetailsResponseWrapper] with the given purchase details. @visibleForTesting const SkuDetailsResponseWrapper( {required this.billingResult, required this.skuDetailsList}); /// Constructs an instance of this from a key value map of data. /// /// The map needs to have named string keys with values matching the names and /// types of all of the members on this class. factory SkuDetailsResponseWrapper.fromJson(Map map) => _$SkuDetailsResponseWrapperFromJson(map); /// The final result of the [BillingClient.querySkuDetails] call. final BillingResultWrapper billingResult; /// A list of [SkuDetailsWrapper] matching the query to [BillingClient.querySkuDetails]. @JsonKey(defaultValue: []) final List skuDetailsList; @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) { return false; } return other is SkuDetailsResponseWrapper && other.billingResult == billingResult && other.skuDetailsList == skuDetailsList; } @override int get hashCode => Object.hash(billingResult, skuDetailsList); } /// Params containing the response code and the debug message from the Play Billing API response. @JsonSerializable() @BillingResponseConverter() @immutable class BillingResultWrapper { /// Constructs the object with [responseCode] and [debugMessage]. const BillingResultWrapper({required this.responseCode, this.debugMessage}); /// Constructs an instance of this from a key value map of data. /// /// The map needs to have named string keys with values matching the names and /// types of all of the members on this class. factory BillingResultWrapper.fromJson(Map? map) { if (map == null || map.isEmpty) { return const BillingResultWrapper( responseCode: BillingResponse.error, debugMessage: kInvalidBillingResultErrorMessage); } return _$BillingResultWrapperFromJson(map); } /// Response code returned in the Play Billing API calls. final BillingResponse responseCode; /// Debug message returned in the Play Billing API calls. /// /// Defaults to `null`. /// This message uses an en-US locale and should not be shown to users. final String? debugMessage; @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) { return false; } return other is BillingResultWrapper && other.responseCode == responseCode && other.debugMessage == debugMessage; } @override int get hashCode => Object.hash(responseCode, debugMessage); } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/sku_details_wrapper.g.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND part of 'sku_details_wrapper.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** SkuDetailsWrapper _$SkuDetailsWrapperFromJson(Map json) => SkuDetailsWrapper( description: json['description'] as String? ?? '', freeTrialPeriod: json['freeTrialPeriod'] as String? ?? '', introductoryPrice: json['introductoryPrice'] as String? ?? '', introductoryPriceAmountMicros: json['introductoryPriceAmountMicros'] as int? ?? 0, introductoryPriceCycles: json['introductoryPriceCycles'] as int? ?? 0, introductoryPricePeriod: json['introductoryPricePeriod'] as String? ?? '', price: json['price'] as String? ?? '', priceAmountMicros: json['priceAmountMicros'] as int? ?? 0, priceCurrencyCode: json['priceCurrencyCode'] as String? ?? '', priceCurrencySymbol: json['priceCurrencySymbol'] as String? ?? '', sku: json['sku'] as String? ?? '', subscriptionPeriod: json['subscriptionPeriod'] as String? ?? '', title: json['title'] as String? ?? '', type: const SkuTypeConverter().fromJson(json['type'] as String?), originalPrice: json['originalPrice'] as String? ?? '', originalPriceAmountMicros: json['originalPriceAmountMicros'] as int? ?? 0, ); SkuDetailsResponseWrapper _$SkuDetailsResponseWrapperFromJson(Map json) => SkuDetailsResponseWrapper( billingResult: BillingResultWrapper.fromJson((json['billingResult'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), skuDetailsList: (json['skuDetailsList'] as List?) ?.map((e) => SkuDetailsWrapper.fromJson( Map.from(e as Map))) .toList() ?? [], ); BillingResultWrapper _$BillingResultWrapperFromJson(Map json) => BillingResultWrapper( responseCode: const BillingResponseConverter() .fromJson(json['responseCode'] as int?), debugMessage: json['debugMessage'] as String?, ); ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/lib/src/channel.dart ================================================ // Copyright 2013 The Flutter 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'; /// Method channel for the plugin's platform<-->Dart calls. const MethodChannel channel = MethodChannel('plugins.flutter.io/in_app_purchase'); ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import '../billing_client_wrappers.dart'; import '../in_app_purchase_android.dart'; /// [IAPError.code] code for failed purchases. const String kPurchaseErrorCode = 'purchase_error'; /// [IAPError.code] code used when a consuming a purchased item fails. const String kConsumptionFailedErrorCode = 'consume_purchase_failed'; /// [IAPError.code] code used when a query for previous transaction has failed. const String kRestoredPurchaseErrorCode = 'restore_transactions_failed'; /// Indicates store front is Google Play const String kIAPSource = 'google_play'; /// An [InAppPurchasePlatform] that wraps Android BillingClient. /// /// This translates various `BillingClient` calls and responses into the /// generic plugin API. class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { InAppPurchaseAndroidPlatform._() { billingClient = BillingClient((PurchasesResultWrapper resultWrapper) async { _purchaseUpdatedController .add(await _getPurchaseDetailsFromResult(resultWrapper)); }); // Register [InAppPurchaseAndroidPlatformAddition]. InAppPurchasePlatformAddition.instance = InAppPurchaseAndroidPlatformAddition(billingClient); _readyFuture = _connect(); _purchaseUpdatedController = StreamController>.broadcast(); } /// Registers this class as the default instance of [InAppPurchasePlatform]. static void registerPlatform() { // Register the platform instance with the plugin platform // interface. InAppPurchasePlatform.instance = InAppPurchaseAndroidPlatform._(); } static late StreamController> _purchaseUpdatedController; @override Stream> get purchaseStream => _purchaseUpdatedController.stream; /// The [BillingClient] that's abstracted by [GooglePlayConnection]. /// /// This field should not be used out of test code. @visibleForTesting late final BillingClient billingClient; late Future _readyFuture; static final Set _productIdsToConsume = {}; @override Future isAvailable() async { await _readyFuture; return billingClient.isReady(); } @override Future queryProductDetails( Set identifiers) async { List responses; PlatformException? exception; try { responses = await Future.wait(>[ billingClient.querySkuDetails( skuType: SkuType.inapp, skusList: identifiers.toList()), billingClient.querySkuDetails( skuType: SkuType.subs, skusList: identifiers.toList()) ]); } on PlatformException catch (e) { exception = e; responses = [ // ignore: invalid_use_of_visible_for_testing_member SkuDetailsResponseWrapper( billingResult: BillingResultWrapper( responseCode: BillingResponse.error, debugMessage: e.code), skuDetailsList: const []), // ignore: invalid_use_of_visible_for_testing_member SkuDetailsResponseWrapper( billingResult: BillingResultWrapper( responseCode: BillingResponse.error, debugMessage: e.code), skuDetailsList: const []) ]; } final List productDetailsList = responses.expand((SkuDetailsResponseWrapper response) { return response.skuDetailsList; }).map((SkuDetailsWrapper skuDetailWrapper) { return GooglePlayProductDetails.fromSkuDetails(skuDetailWrapper); }).toList(); final Set successIDS = productDetailsList .map((ProductDetails productDetails) => productDetails.id) .toSet(); final List notFoundIDS = identifiers.difference(successIDS).toList(); return ProductDetailsResponse( productDetails: productDetailsList, notFoundIDs: notFoundIDS, error: exception == null ? null : IAPError( source: kIAPSource, code: exception.code, message: exception.message ?? '', details: exception.details)); } @override Future buyNonConsumable({required PurchaseParam purchaseParam}) async { ChangeSubscriptionParam? changeSubscriptionParam; if (purchaseParam is GooglePlayPurchaseParam) { changeSubscriptionParam = purchaseParam.changeSubscriptionParam; } final BillingResultWrapper billingResultWrapper = await billingClient.launchBillingFlow( sku: purchaseParam.productDetails.id, accountId: purchaseParam.applicationUserName, oldSku: changeSubscriptionParam?.oldPurchaseDetails.productID, purchaseToken: changeSubscriptionParam ?.oldPurchaseDetails.verificationData.serverVerificationData, prorationMode: changeSubscriptionParam?.prorationMode); return billingResultWrapper.responseCode == BillingResponse.ok; } @override Future buyConsumable( {required PurchaseParam purchaseParam, bool autoConsume = true}) { if (autoConsume) { _productIdsToConsume.add(purchaseParam.productDetails.id); } return buyNonConsumable(purchaseParam: purchaseParam); } @override Future completePurchase( PurchaseDetails purchase) async { assert( purchase is GooglePlayPurchaseDetails, 'On Android, the `purchase` should always be of type `GooglePlayPurchaseDetails`.', ); final GooglePlayPurchaseDetails googlePurchase = purchase as GooglePlayPurchaseDetails; if (googlePurchase.billingClientPurchase.isAcknowledged) { return const BillingResultWrapper(responseCode: BillingResponse.ok); } if (googlePurchase.verificationData == null) { throw ArgumentError( 'completePurchase unsuccessful. The `purchase.verificationData` is not valid'); } return billingClient .acknowledgePurchase(purchase.verificationData.serverVerificationData); } @override Future restorePurchases({ String? applicationUserName, }) async { List responses; responses = await Future.wait(>[ billingClient.queryPurchases(SkuType.inapp), billingClient.queryPurchases(SkuType.subs) ]); final Set errorCodeSet = responses .where((PurchasesResultWrapper response) => response.responseCode != BillingResponse.ok) .map((PurchasesResultWrapper response) => response.responseCode.toString()) .toSet(); final String errorMessage = errorCodeSet.isNotEmpty ? errorCodeSet.join(', ') : ''; final List pastPurchases = responses.expand((PurchasesResultWrapper response) { return response.purchasesList; }).map((PurchaseWrapper purchaseWrapper) { final GooglePlayPurchaseDetails purchaseDetails = GooglePlayPurchaseDetails.fromPurchase(purchaseWrapper); purchaseDetails.status = PurchaseStatus.restored; return purchaseDetails; }).toList(); if (errorMessage.isNotEmpty) { throw InAppPurchaseException( source: kIAPSource, code: kRestoredPurchaseErrorCode, message: errorMessage, ); } _purchaseUpdatedController.add(pastPurchases); } Future _connect() => billingClient.startConnection(onBillingServiceDisconnected: () {}); Future _maybeAutoConsumePurchase( PurchaseDetails purchaseDetails) async { if (!(purchaseDetails.status == PurchaseStatus.purchased && _productIdsToConsume.contains(purchaseDetails.productID))) { return purchaseDetails; } final BillingResultWrapper billingResult = await (InAppPurchasePlatformAddition.instance! as InAppPurchaseAndroidPlatformAddition) .consumePurchase(purchaseDetails); final BillingResponse consumedResponse = billingResult.responseCode; if (consumedResponse != BillingResponse.ok) { purchaseDetails.status = PurchaseStatus.error; purchaseDetails.error = IAPError( source: kIAPSource, code: kConsumptionFailedErrorCode, message: consumedResponse.toString(), details: billingResult.debugMessage, ); } _productIdsToConsume.remove(purchaseDetails.productID); return purchaseDetails; } Future> _getPurchaseDetailsFromResult( PurchasesResultWrapper resultWrapper) async { IAPError? error; if (resultWrapper.responseCode != BillingResponse.ok) { error = IAPError( source: kIAPSource, code: kPurchaseErrorCode, message: resultWrapper.responseCode.toString(), details: resultWrapper.billingResult.debugMessage, ); } final List> purchases = resultWrapper.purchasesList.map((PurchaseWrapper purchase) { final GooglePlayPurchaseDetails googlePlayPurchaseDetails = GooglePlayPurchaseDetails.fromPurchase(purchase)..error = error; if (resultWrapper.responseCode == BillingResponse.userCanceled) { googlePlayPurchaseDetails.status = PurchaseStatus.canceled; } return _maybeAutoConsumePurchase(googlePlayPurchaseDetails); }).toList(); if (purchases.isNotEmpty) { return Future.wait(purchases); } else { PurchaseStatus status = PurchaseStatus.error; if (resultWrapper.responseCode == BillingResponse.userCanceled) { status = PurchaseStatus.canceled; } else if (resultWrapper.responseCode == BillingResponse.ok) { status = PurchaseStatus.purchased; } return [ PurchaseDetails( purchaseID: '', productID: '', status: status, transactionDate: null, verificationData: PurchaseVerificationData( localVerificationData: '', serverVerificationData: '', source: kIAPSource)) ..error = error ]; } } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart ================================================ // Copyright 2013 The Flutter 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:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import '../billing_client_wrappers.dart'; import '../in_app_purchase_android.dart'; /// Contains InApp Purchase features that are only available on PlayStore. class InAppPurchaseAndroidPlatformAddition extends InAppPurchasePlatformAddition { /// Creates a [InAppPurchaseAndroidPlatformAddition] which uses the supplied /// `BillingClient` to provide Android specific features. InAppPurchaseAndroidPlatformAddition(this._billingClient); /// Whether pending purchase is enabled. /// /// **Deprecation warning:** it is no longer required to call /// [enablePendingPurchases] when initializing your application. From now on /// this is handled internally and the [enablePendingPurchase] property will /// always return `true`. /// // ignore: deprecated_member_use_from_same_package /// See also [enablePendingPurchases] for more on pending purchases. @Deprecated( 'The requirement to call `enablePendingPurchases()` has become obsolete ' "since Google Play no longer accepts app submissions that don't support " 'pending purchases.') static bool get enablePendingPurchase => true; /// Enable the [InAppPurchaseConnection] to handle pending purchases. /// /// **Deprecation warning:** it is no longer required to call /// [enablePendingPurchases] when initializing your application. @Deprecated( 'The requirement to call `enablePendingPurchases()` has become obsolete ' "since Google Play no longer accepts app submissions that don't support " 'pending purchases.') static void enablePendingPurchases() { // No-op, until it is time to completely remove this method from the API. } final BillingClient _billingClient; /// Mark that the user has consumed a product. /// /// You are responsible for consuming all consumable purchases once they are /// delivered. The user won't be able to buy the same product again until the /// purchase of the product is consumed. Future consumePurchase(PurchaseDetails purchase) { if (purchase.verificationData == null) { throw ArgumentError( 'consumePurchase unsuccessful. The `purchase.verificationData` is not valid'); } return _billingClient .consumeAsync(purchase.verificationData.serverVerificationData); } /// Query all previous purchases. /// /// The `applicationUserName` should match whatever was sent in the initial /// `PurchaseParam`, if anything. If no `applicationUserName` was specified in /// the initial `PurchaseParam`, use `null`. /// /// This does not return consumed products. If you want to restore unused /// consumable products, you need to persist consumable product information /// for your user on your own server. /// /// See also: /// /// * [refreshPurchaseVerificationData], for reloading failed /// [PurchaseDetails.verificationData]. Future queryPastPurchases( {String? applicationUserName}) async { List responses; PlatformException? exception; try { responses = await Future.wait(>[ _billingClient.queryPurchases(SkuType.inapp), _billingClient.queryPurchases(SkuType.subs) ]); } on PlatformException catch (e) { exception = e; responses = [ PurchasesResultWrapper( responseCode: BillingResponse.error, purchasesList: const [], billingResult: BillingResultWrapper( responseCode: BillingResponse.error, debugMessage: e.details.toString(), ), ), PurchasesResultWrapper( responseCode: BillingResponse.error, purchasesList: const [], billingResult: BillingResultWrapper( responseCode: BillingResponse.error, debugMessage: e.details.toString(), ), ) ]; } final Set errorCodeSet = responses .where((PurchasesResultWrapper response) => response.responseCode != BillingResponse.ok) .map((PurchasesResultWrapper response) => response.responseCode.toString()) .toSet(); final String errorMessage = errorCodeSet.isNotEmpty ? errorCodeSet.join(', ') : ''; final List pastPurchases = responses.expand((PurchasesResultWrapper response) { return response.purchasesList; }).map((PurchaseWrapper purchaseWrapper) { return GooglePlayPurchaseDetails.fromPurchase(purchaseWrapper); }).toList(); IAPError? error; if (exception != null) { error = IAPError( source: kIAPSource, code: exception.code, message: exception.message ?? '', details: exception.details); } else if (errorMessage.isNotEmpty) { error = IAPError( source: kIAPSource, code: kRestoredPurchaseErrorCode, message: errorMessage); } return QueryPurchaseDetailsResponse( pastPurchases: pastPurchases, error: error); } /// Checks if the specified feature or capability is supported by the Play Store. /// Call this to check if a [BillingClientFeature] is supported by the device. Future isFeatureSupported(BillingClientFeature feature) async { return _billingClient.isFeatureSupported(feature); } /// Initiates a flow to confirm the change of price for an item subscribed by the user. /// /// When the price of a user subscribed item has changed, launch this flow to take users to /// a screen with price change information. User can confirm the new price or cancel the flow. /// /// The skuDetails needs to have already been fetched in a /// [InAppPurchaseAndroidPlatform.queryProductDetails] call. Future launchPriceChangeConfirmationFlow( {required String sku}) { return _billingClient.launchPriceChangeConfirmationFlow(sku: sku); } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/lib/src/types/change_subscription_param.dart ================================================ // Copyright 2013 The Flutter 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 '../../billing_client_wrappers.dart'; import 'types.dart'; /// This parameter object for upgrading or downgrading an existing subscription. class ChangeSubscriptionParam { /// Creates a new change subscription param object with given data ChangeSubscriptionParam({ required this.oldPurchaseDetails, this.prorationMode, }); /// The purchase object of the existing subscription that the user needs to /// upgrade/downgrade from. final GooglePlayPurchaseDetails oldPurchaseDetails; /// The proration mode. /// /// This is an optional parameter that indicates how to handle the existing /// subscription when the new subscription comes into effect. final ProrationMode? prorationMode; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_product_details.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import '../../billing_client_wrappers.dart'; /// The class represents the information of a product as registered in at /// Google Play store front. class GooglePlayProductDetails extends ProductDetails { /// Creates a new Google Play specific product details object with the /// provided details. GooglePlayProductDetails({ required String id, required String title, required String description, required String price, required double rawPrice, required String currencyCode, required this.skuDetails, required String currencySymbol, }) : super( id: id, title: title, description: description, price: price, rawPrice: rawPrice, currencyCode: currencyCode, currencySymbol: currencySymbol, ); /// Generate a [GooglePlayProductDetails] object based on an Android /// [SkuDetailsWrapper] object. factory GooglePlayProductDetails.fromSkuDetails( SkuDetailsWrapper skuDetails, ) { return GooglePlayProductDetails( id: skuDetails.sku, title: skuDetails.title, description: skuDetails.description, price: skuDetails.price, rawPrice: skuDetails.priceAmountMicros / 1000000.0, currencyCode: skuDetails.priceCurrencyCode, currencySymbol: skuDetails.priceCurrencySymbol, skuDetails: skuDetails, ); } /// Points back to the [SkuDetailsWrapper] object that was used to generate /// this [GooglePlayProductDetails] object. final SkuDetailsWrapper skuDetails; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_purchase_details.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import '../../billing_client_wrappers.dart'; import '../in_app_purchase_android_platform.dart'; /// The class represents the information of a purchase made using Google Play. class GooglePlayPurchaseDetails extends PurchaseDetails { /// Creates a new Google Play specific purchase details object with the /// provided details. GooglePlayPurchaseDetails({ String? purchaseID, required String productID, required PurchaseVerificationData verificationData, required String? transactionDate, required this.billingClientPurchase, required PurchaseStatus status, }) : super( productID: productID, purchaseID: purchaseID, transactionDate: transactionDate, verificationData: verificationData, status: status, ) { pendingCompletePurchase = !billingClientPurchase.isAcknowledged; } /// Generate a [PurchaseDetails] object based on an Android [Purchase] object. factory GooglePlayPurchaseDetails.fromPurchase(PurchaseWrapper purchase) { final GooglePlayPurchaseDetails purchaseDetails = GooglePlayPurchaseDetails( purchaseID: purchase.orderId, productID: purchase.sku, verificationData: PurchaseVerificationData( localVerificationData: purchase.originalJson, serverVerificationData: purchase.purchaseToken, source: kIAPSource), transactionDate: purchase.purchaseTime.toString(), billingClientPurchase: purchase, status: const PurchaseStateConverter() .toPurchaseStatus(purchase.purchaseState), ); if (purchaseDetails.status == PurchaseStatus.error) { purchaseDetails.error = IAPError( source: kIAPSource, code: kPurchaseErrorCode, message: '', ); } return purchaseDetails; } /// Points back to the [PurchaseWrapper] which was used to generate this /// [GooglePlayPurchaseDetails] object. final PurchaseWrapper billingClientPurchase; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_purchase_param.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import '../../in_app_purchase_android.dart'; /// Google Play specific parameter object for generating a purchase. class GooglePlayPurchaseParam extends PurchaseParam { /// Creates a new [GooglePlayPurchaseParam] object with the given data. GooglePlayPurchaseParam({ required ProductDetails productDetails, String? applicationUserName, this.changeSubscriptionParam, }) : super( productDetails: productDetails, applicationUserName: applicationUserName, ); /// The 'changeSubscriptionParam' containing information for upgrading or /// downgrading an existing subscription. final ChangeSubscriptionParam? changeSubscriptionParam; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/lib/src/types/query_purchase_details_response.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'types.dart'; /// The response object for fetching the past purchases. /// /// An instance of this class is returned in [InAppPurchaseConnection.queryPastPurchases]. class QueryPurchaseDetailsResponse { /// Creates a new [QueryPurchaseDetailsResponse] object with the provider information. QueryPurchaseDetailsResponse({required this.pastPurchases, this.error}); /// A list of successfully fetched past purchases. /// /// If there are no past purchases, or there is an [error] fetching past purchases, /// this variable is an empty List. /// You should verify the purchase data using [PurchaseDetails.verificationData] before using the [PurchaseDetails] object. final List pastPurchases; /// The error when fetching past purchases. /// /// If the fetch is successful, the value is `null`. final IAPError? error; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/lib/src/types/types.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'change_subscription_param.dart'; export 'google_play_product_details.dart'; export 'google_play_purchase_details.dart'; export 'google_play_purchase_param.dart'; export 'query_purchase_details_response.dart'; ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/pubspec.yaml ================================================ 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.4+1 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: in_app_purchase platforms: android: package: io.flutter.plugins.inapppurchase pluginClass: InAppPurchasePlugin dependencies: collection: ^1.15.0 flutter: sdk: flutter in_app_purchase_platform_interface: ^1.3.0 json_annotation: ^4.6.0 dev_dependencies: build_runner: ^2.0.0 flutter_test: sdk: flutter json_serializable: ^6.3.1 mockito: ^5.1.0 test: ^1.16.0 ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart ================================================ // Copyright 2013 The Flutter 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:in_app_purchase_android/billing_client_wrappers.dart'; import 'package:in_app_purchase_android/src/channel.dart'; import '../stub_in_app_purchase_platform.dart'; import 'purchase_wrapper_test.dart'; import 'sku_details_wrapper_test.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform(); late BillingClient billingClient; setUpAll(() => _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, stubPlatform.fakeMethodCallHandler)); setUp(() { billingClient = BillingClient((PurchasesResultWrapper _) {}); stubPlatform.reset(); }); group('isReady', () { test('true', () async { stubPlatform.addResponse(name: 'BillingClient#isReady()', value: true); expect(await billingClient.isReady(), isTrue); }); test('false', () async { stubPlatform.addResponse(name: 'BillingClient#isReady()', value: false); expect(await billingClient.isReady(), isFalse); }); }); // Make sure that the enum values are supported and that the converter call // does not fail test('response states', () async { const BillingResponseConverter converter = BillingResponseConverter(); converter.fromJson(-3); converter.fromJson(-2); converter.fromJson(-1); converter.fromJson(0); converter.fromJson(1); converter.fromJson(2); converter.fromJson(3); converter.fromJson(4); converter.fromJson(5); converter.fromJson(6); converter.fromJson(7); converter.fromJson(8); }); group('startConnection', () { const String methodName = 'BillingClient#startConnection(BillingClientStateListener)'; test('returns BillingResultWrapper', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.developerError; stubPlatform.addResponse( name: methodName, value: { 'responseCode': const BillingResponseConverter().toJson(responseCode), 'debugMessage': debugMessage, }, ); const BillingResultWrapper billingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); expect( await billingClient.startConnection( onBillingServiceDisconnected: () {}), equals(billingResult)); }); test('passes handle to onBillingServiceDisconnected', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.developerError; stubPlatform.addResponse( name: methodName, value: { 'responseCode': const BillingResponseConverter().toJson(responseCode), 'debugMessage': debugMessage, }, ); await billingClient.startConnection(onBillingServiceDisconnected: () {}); final MethodCall call = stubPlatform.previousCallMatching(methodName); expect(call.arguments, equals({'handle': 0})); }); test('handles method channel returning null', () async { stubPlatform.addResponse( name: methodName, ); expect( await billingClient.startConnection( onBillingServiceDisconnected: () {}), equals(const BillingResultWrapper( responseCode: BillingResponse.error, debugMessage: kInvalidBillingResultErrorMessage))); }); }); test('endConnection', () async { const String endConnectionName = 'BillingClient#endConnection()'; expect(stubPlatform.countPreviousCalls(endConnectionName), equals(0)); stubPlatform.addResponse(name: endConnectionName); await billingClient.endConnection(); expect(stubPlatform.countPreviousCalls(endConnectionName), equals(1)); }); group('querySkuDetails', () { const String queryMethodName = 'BillingClient#querySkuDetailsAsync(SkuDetailsParams, SkuDetailsResponseListener)'; test('handles empty skuDetails', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.developerError; stubPlatform.addResponse(name: queryMethodName, value: { 'billingResult': { 'responseCode': const BillingResponseConverter().toJson(responseCode), 'debugMessage': debugMessage, }, 'skuDetailsList': >[] }); final SkuDetailsResponseWrapper response = await billingClient .querySkuDetails( skuType: SkuType.inapp, skusList: ['invalid']); const BillingResultWrapper billingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); expect(response.billingResult, equals(billingResult)); expect(response.skuDetailsList, isEmpty); }); test('returns SkuDetailsResponseWrapper', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; stubPlatform.addResponse(name: queryMethodName, value: { 'billingResult': { 'responseCode': const BillingResponseConverter().toJson(responseCode), 'debugMessage': debugMessage, }, 'skuDetailsList': >[buildSkuMap(dummySkuDetails)] }); final SkuDetailsResponseWrapper response = await billingClient .querySkuDetails( skuType: SkuType.inapp, skusList: ['invalid']); const BillingResultWrapper billingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); expect(response.billingResult, equals(billingResult)); expect(response.skuDetailsList, contains(dummySkuDetails)); }); test('handles null method channel response', () async { stubPlatform.addResponse(name: queryMethodName); final SkuDetailsResponseWrapper response = await billingClient .querySkuDetails( skuType: SkuType.inapp, skusList: ['invalid']); const BillingResultWrapper billingResult = BillingResultWrapper( responseCode: BillingResponse.error, debugMessage: kInvalidBillingResultErrorMessage); expect(response.billingResult, equals(billingResult)); expect(response.skuDetailsList, isEmpty); }); }); group('launchBillingFlow', () { const String launchMethodName = 'BillingClient#launchBillingFlow(Activity, BillingFlowParams)'; test('serializes and deserializes data', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); stubPlatform.addResponse( name: launchMethodName, value: buildBillingResultMap(expectedBillingResult), ); const SkuDetailsWrapper skuDetails = dummySkuDetails; const String accountId = 'hashedAccountId'; const String profileId = 'hashedProfileId'; expect( await billingClient.launchBillingFlow( sku: skuDetails.sku, accountId: accountId, obfuscatedProfileId: profileId), equals(expectedBillingResult)); final Map arguments = stubPlatform .previousCallMatching(launchMethodName) .arguments as Map; expect(arguments['sku'], equals(skuDetails.sku)); expect(arguments['accountId'], equals(accountId)); expect(arguments['obfuscatedProfileId'], equals(profileId)); }); test( 'Change subscription throws assertion error `oldSku` and `purchaseToken` has different nullability', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); stubPlatform.addResponse( name: launchMethodName, value: buildBillingResultMap(expectedBillingResult), ); const SkuDetailsWrapper skuDetails = dummySkuDetails; const String accountId = 'hashedAccountId'; const String profileId = 'hashedProfileId'; expect( billingClient.launchBillingFlow( sku: skuDetails.sku, accountId: accountId, obfuscatedProfileId: profileId, oldSku: dummyOldPurchase.sku), throwsAssertionError); expect( billingClient.launchBillingFlow( sku: skuDetails.sku, accountId: accountId, obfuscatedProfileId: profileId, purchaseToken: dummyOldPurchase.purchaseToken), throwsAssertionError); }); test( 'serializes and deserializes data on change subscription without proration', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); stubPlatform.addResponse( name: launchMethodName, value: buildBillingResultMap(expectedBillingResult), ); const SkuDetailsWrapper skuDetails = dummySkuDetails; const String accountId = 'hashedAccountId'; const String profileId = 'hashedProfileId'; expect( await billingClient.launchBillingFlow( sku: skuDetails.sku, accountId: accountId, obfuscatedProfileId: profileId, oldSku: dummyOldPurchase.sku, purchaseToken: dummyOldPurchase.purchaseToken), equals(expectedBillingResult)); final Map arguments = stubPlatform .previousCallMatching(launchMethodName) .arguments as Map; expect(arguments['sku'], equals(skuDetails.sku)); expect(arguments['accountId'], equals(accountId)); expect(arguments['oldSku'], equals(dummyOldPurchase.sku)); expect( arguments['purchaseToken'], equals(dummyOldPurchase.purchaseToken)); expect(arguments['obfuscatedProfileId'], equals(profileId)); }); test( 'serializes and deserializes data on change subscription with proration', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); stubPlatform.addResponse( name: launchMethodName, value: buildBillingResultMap(expectedBillingResult), ); const SkuDetailsWrapper skuDetails = dummySkuDetails; const String accountId = 'hashedAccountId'; const String profileId = 'hashedProfileId'; const ProrationMode prorationMode = ProrationMode.immediateAndChargeProratedPrice; expect( await billingClient.launchBillingFlow( sku: skuDetails.sku, accountId: accountId, obfuscatedProfileId: profileId, oldSku: dummyOldPurchase.sku, prorationMode: prorationMode, purchaseToken: dummyOldPurchase.purchaseToken), equals(expectedBillingResult)); final Map arguments = stubPlatform .previousCallMatching(launchMethodName) .arguments as Map; expect(arguments['sku'], equals(skuDetails.sku)); expect(arguments['accountId'], equals(accountId)); expect(arguments['oldSku'], equals(dummyOldPurchase.sku)); expect(arguments['obfuscatedProfileId'], equals(profileId)); expect( arguments['purchaseToken'], equals(dummyOldPurchase.purchaseToken)); expect(arguments['prorationMode'], const ProrationModeConverter().toJson(prorationMode)); }); test( 'serializes and deserializes data when using immediateAndChargeFullPrice', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); stubPlatform.addResponse( name: launchMethodName, value: buildBillingResultMap(expectedBillingResult), ); const SkuDetailsWrapper skuDetails = dummySkuDetails; const String accountId = 'hashedAccountId'; const String profileId = 'hashedProfileId'; const ProrationMode prorationMode = ProrationMode.immediateAndChargeFullPrice; expect( await billingClient.launchBillingFlow( sku: skuDetails.sku, accountId: accountId, obfuscatedProfileId: profileId, oldSku: dummyOldPurchase.sku, prorationMode: prorationMode, purchaseToken: dummyOldPurchase.purchaseToken), equals(expectedBillingResult)); final Map arguments = stubPlatform .previousCallMatching(launchMethodName) .arguments as Map; expect(arguments['sku'], equals(skuDetails.sku)); expect(arguments['accountId'], equals(accountId)); expect(arguments['oldSku'], equals(dummyOldPurchase.sku)); expect(arguments['obfuscatedProfileId'], equals(profileId)); expect( arguments['purchaseToken'], equals(dummyOldPurchase.purchaseToken)); expect(arguments['prorationMode'], const ProrationModeConverter().toJson(prorationMode)); }); test('handles null accountId', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); stubPlatform.addResponse( name: launchMethodName, value: buildBillingResultMap(expectedBillingResult), ); const SkuDetailsWrapper skuDetails = dummySkuDetails; expect(await billingClient.launchBillingFlow(sku: skuDetails.sku), equals(expectedBillingResult)); final Map arguments = stubPlatform .previousCallMatching(launchMethodName) .arguments as Map; expect(arguments['sku'], equals(skuDetails.sku)); expect(arguments['accountId'], isNull); }); test('handles method channel returning null', () async { stubPlatform.addResponse( name: launchMethodName, ); const SkuDetailsWrapper skuDetails = dummySkuDetails; expect( await billingClient.launchBillingFlow(sku: skuDetails.sku), equals(const BillingResultWrapper( responseCode: BillingResponse.error, debugMessage: kInvalidBillingResultErrorMessage))); }); }); group('queryPurchases', () { const String queryPurchasesMethodName = 'BillingClient#queryPurchases(String)'; test('serializes and deserializes data', () async { const BillingResponse expectedCode = BillingResponse.ok; final List expectedList = [ dummyPurchase ]; const String debugMessage = 'dummy message'; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); stubPlatform .addResponse(name: queryPurchasesMethodName, value: { 'billingResult': buildBillingResultMap(expectedBillingResult), 'responseCode': const BillingResponseConverter().toJson(expectedCode), 'purchasesList': expectedList .map((PurchaseWrapper purchase) => buildPurchaseMap(purchase)) .toList(), }); final PurchasesResultWrapper response = await billingClient.queryPurchases(SkuType.inapp); expect(response.billingResult, equals(expectedBillingResult)); expect(response.responseCode, equals(expectedCode)); expect(response.purchasesList, equals(expectedList)); }); test('handles empty purchases', () async { const BillingResponse expectedCode = BillingResponse.userCanceled; const String debugMessage = 'dummy message'; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); stubPlatform .addResponse(name: queryPurchasesMethodName, value: { 'billingResult': buildBillingResultMap(expectedBillingResult), 'responseCode': const BillingResponseConverter().toJson(expectedCode), 'purchasesList': [], }); final PurchasesResultWrapper response = await billingClient.queryPurchases(SkuType.inapp); expect(response.billingResult, equals(expectedBillingResult)); expect(response.responseCode, equals(expectedCode)); expect(response.purchasesList, isEmpty); }); test('handles method channel returning null', () async { stubPlatform.addResponse( name: queryPurchasesMethodName, ); final PurchasesResultWrapper response = await billingClient.queryPurchases(SkuType.inapp); expect( response.billingResult, equals(const BillingResultWrapper( responseCode: BillingResponse.error, debugMessage: kInvalidBillingResultErrorMessage))); expect(response.responseCode, BillingResponse.error); expect(response.purchasesList, isEmpty); }); }); group('queryPurchaseHistory', () { const String queryPurchaseHistoryMethodName = 'BillingClient#queryPurchaseHistoryAsync(String, PurchaseHistoryResponseListener)'; test('serializes and deserializes data', () async { const BillingResponse expectedCode = BillingResponse.ok; final List expectedList = [ dummyPurchaseHistoryRecord, ]; const String debugMessage = 'dummy message'; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); stubPlatform.addResponse( name: queryPurchaseHistoryMethodName, value: { 'billingResult': buildBillingResultMap(expectedBillingResult), 'purchaseHistoryRecordList': expectedList .map((PurchaseHistoryRecordWrapper purchaseHistoryRecord) => buildPurchaseHistoryRecordMap(purchaseHistoryRecord)) .toList(), }); final PurchasesHistoryResult response = await billingClient.queryPurchaseHistory(SkuType.inapp); expect(response.billingResult, equals(expectedBillingResult)); expect(response.purchaseHistoryRecordList, equals(expectedList)); }); test('handles empty purchases', () async { const BillingResponse expectedCode = BillingResponse.userCanceled; const String debugMessage = 'dummy message'; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); stubPlatform.addResponse( name: queryPurchaseHistoryMethodName, value: { 'billingResult': buildBillingResultMap(expectedBillingResult), 'purchaseHistoryRecordList': [], }); final PurchasesHistoryResult response = await billingClient.queryPurchaseHistory(SkuType.inapp); expect(response.billingResult, equals(expectedBillingResult)); expect(response.purchaseHistoryRecordList, isEmpty); }); test('handles method channel returning null', () async { stubPlatform.addResponse( name: queryPurchaseHistoryMethodName, ); final PurchasesHistoryResult response = await billingClient.queryPurchaseHistory(SkuType.inapp); expect( response.billingResult, equals(const BillingResultWrapper( responseCode: BillingResponse.error, debugMessage: kInvalidBillingResultErrorMessage))); expect(response.purchaseHistoryRecordList, isEmpty); }); }); group('consume purchases', () { const String consumeMethodName = 'BillingClient#consumeAsync(String, ConsumeResponseListener)'; test('consume purchase async success', () async { const BillingResponse expectedCode = BillingResponse.ok; const String debugMessage = 'dummy message'; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); stubPlatform.addResponse( name: consumeMethodName, value: buildBillingResultMap(expectedBillingResult)); final BillingResultWrapper billingResult = await billingClient.consumeAsync('dummy token'); expect(billingResult, equals(expectedBillingResult)); }); test('handles method channel returning null', () async { stubPlatform.addResponse( name: consumeMethodName, ); final BillingResultWrapper billingResult = await billingClient.consumeAsync('dummy token'); expect( billingResult, equals(const BillingResultWrapper( responseCode: BillingResponse.error, debugMessage: kInvalidBillingResultErrorMessage))); }); }); group('acknowledge purchases', () { const String acknowledgeMethodName = 'BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)'; test('acknowledge purchase success', () async { const BillingResponse expectedCode = BillingResponse.ok; const String debugMessage = 'dummy message'; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); stubPlatform.addResponse( name: acknowledgeMethodName, value: buildBillingResultMap(expectedBillingResult)); final BillingResultWrapper billingResult = await billingClient.acknowledgePurchase('dummy token'); expect(billingResult, equals(expectedBillingResult)); }); test('handles method channel returning null', () async { stubPlatform.addResponse( name: acknowledgeMethodName, ); final BillingResultWrapper billingResult = await billingClient.acknowledgePurchase('dummy token'); expect( billingResult, equals(const BillingResultWrapper( responseCode: BillingResponse.error, debugMessage: kInvalidBillingResultErrorMessage))); }); }); group('isFeatureSupported', () { const String isFeatureSupportedMethodName = 'BillingClient#isFeatureSupported(String)'; test('isFeatureSupported returns false', () async { late Map arguments; stubPlatform.addResponse( name: isFeatureSupportedMethodName, value: false, additionalStepBeforeReturn: (dynamic value) => arguments = value as Map, ); final bool isSupported = await billingClient .isFeatureSupported(BillingClientFeature.subscriptions); expect(isSupported, isFalse); expect(arguments['feature'], equals('subscriptions')); }); test('isFeatureSupported returns true', () async { late Map arguments; stubPlatform.addResponse( name: isFeatureSupportedMethodName, value: true, additionalStepBeforeReturn: (dynamic value) => arguments = value as Map, ); final bool isSupported = await billingClient .isFeatureSupported(BillingClientFeature.subscriptions); expect(isSupported, isTrue); expect(arguments['feature'], equals('subscriptions')); }); }); group('launchPriceChangeConfirmationFlow', () { const String launchPriceChangeConfirmationFlowMethodName = 'BillingClient#launchPriceChangeConfirmationFlow (Activity, PriceChangeFlowParams, PriceChangeConfirmationListener)'; const BillingResultWrapper expectedBillingResultPriceChangeConfirmation = BillingResultWrapper( responseCode: BillingResponse.ok, debugMessage: 'dummy message', ); test('serializes and deserializes data', () async { stubPlatform.addResponse( name: launchPriceChangeConfirmationFlowMethodName, value: buildBillingResultMap(expectedBillingResultPriceChangeConfirmation), ); expect( await billingClient.launchPriceChangeConfirmationFlow( sku: dummySkuDetails.sku, ), equals(expectedBillingResultPriceChangeConfirmation), ); }); test('passes sku to launchPriceChangeConfirmationFlow', () async { stubPlatform.addResponse( name: launchPriceChangeConfirmationFlowMethodName, value: buildBillingResultMap(expectedBillingResultPriceChangeConfirmation), ); await billingClient.launchPriceChangeConfirmationFlow( sku: dummySkuDetails.sku, ); final MethodCall call = stubPlatform .previousCallMatching(launchPriceChangeConfirmationFlowMethodName); expect(call.arguments, equals({'sku': dummySkuDetails.sku})); }); }); } /// 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; ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/purchase_wrapper_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:in_app_purchase_android/billing_client_wrappers.dart'; import 'package:in_app_purchase_android/in_app_purchase_android.dart'; import 'package:test/test.dart'; const PurchaseWrapper dummyPurchase = PurchaseWrapper( orderId: 'orderId', packageName: 'packageName', purchaseTime: 0, signature: 'signature', skus: ['sku'], purchaseToken: 'purchaseToken', isAutoRenewing: false, originalJson: '', developerPayload: 'dummy payload', isAcknowledged: true, purchaseState: PurchaseStateWrapper.purchased, obfuscatedAccountId: 'Account101', obfuscatedProfileId: 'Profile103', ); const PurchaseWrapper dummyUnacknowledgedPurchase = PurchaseWrapper( orderId: 'orderId', packageName: 'packageName', purchaseTime: 0, signature: 'signature', skus: ['sku'], purchaseToken: 'purchaseToken', isAutoRenewing: false, originalJson: '', developerPayload: 'dummy payload', isAcknowledged: false, purchaseState: PurchaseStateWrapper.purchased, ); const PurchaseHistoryRecordWrapper dummyPurchaseHistoryRecord = PurchaseHistoryRecordWrapper( purchaseTime: 0, signature: 'signature', skus: ['sku'], purchaseToken: 'purchaseToken', originalJson: '', developerPayload: 'dummy payload', ); const PurchaseWrapper dummyOldPurchase = PurchaseWrapper( orderId: 'oldOrderId', packageName: 'oldPackageName', purchaseTime: 0, signature: 'oldSignature', skus: ['oldSku'], purchaseToken: 'oldPurchaseToken', isAutoRenewing: false, originalJson: '', developerPayload: 'old dummy payload', isAcknowledged: true, purchaseState: PurchaseStateWrapper.purchased, ); void main() { group('PurchaseWrapper', () { test('converts from map', () { const PurchaseWrapper expected = dummyPurchase; final PurchaseWrapper parsed = PurchaseWrapper.fromJson(buildPurchaseMap(expected)); expect(parsed, equals(expected)); }); test('fromPurchase() should return correct PurchaseDetail object', () { final GooglePlayPurchaseDetails details = GooglePlayPurchaseDetails.fromPurchase(dummyPurchase); expect(details.purchaseID, dummyPurchase.orderId); expect(details.productID, dummyPurchase.sku); expect(details.transactionDate, dummyPurchase.purchaseTime.toString()); expect(details.verificationData, isNotNull); expect(details.verificationData.source, kIAPSource); expect(details.verificationData.localVerificationData, dummyPurchase.originalJson); expect(details.verificationData.serverVerificationData, dummyPurchase.purchaseToken); expect(details.billingClientPurchase, dummyPurchase); expect(details.pendingCompletePurchase, false); }); test( 'fromPurchase() should return set pendingCompletePurchase to true for unacknowledged purchase', () { final GooglePlayPurchaseDetails details = GooglePlayPurchaseDetails.fromPurchase(dummyUnacknowledgedPurchase); expect(details.purchaseID, dummyPurchase.orderId); expect(details.productID, dummyPurchase.sku); expect(details.transactionDate, dummyPurchase.purchaseTime.toString()); expect(details.verificationData, isNotNull); expect(details.verificationData.source, kIAPSource); expect(details.verificationData.localVerificationData, dummyPurchase.originalJson); expect(details.verificationData.serverVerificationData, dummyPurchase.purchaseToken); expect(details.billingClientPurchase, dummyUnacknowledgedPurchase); expect(details.pendingCompletePurchase, true); }); }); group('PurchaseHistoryRecordWrapper', () { test('converts from map', () { const PurchaseHistoryRecordWrapper expected = dummyPurchaseHistoryRecord; final PurchaseHistoryRecordWrapper parsed = PurchaseHistoryRecordWrapper.fromJson( buildPurchaseHistoryRecordMap(expected)); expect(parsed, equals(expected)); }); }); group('PurchasesResultWrapper', () { test('parsed from map', () { const BillingResponse responseCode = BillingResponse.ok; final List purchases = [ dummyPurchase, dummyPurchase ]; const String debugMessage = 'dummy Message'; const BillingResultWrapper billingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); final PurchasesResultWrapper expected = PurchasesResultWrapper( billingResult: billingResult, responseCode: responseCode, purchasesList: purchases); final PurchasesResultWrapper parsed = PurchasesResultWrapper.fromJson({ 'billingResult': buildBillingResultMap(billingResult), 'responseCode': const BillingResponseConverter().toJson(responseCode), 'purchasesList': >[ buildPurchaseMap(dummyPurchase), buildPurchaseMap(dummyPurchase) ] }); expect(parsed.billingResult, equals(expected.billingResult)); expect(parsed.responseCode, equals(expected.responseCode)); expect(parsed.purchasesList, containsAll(expected.purchasesList)); }); test('parsed from empty map', () { final PurchasesResultWrapper parsed = PurchasesResultWrapper.fromJson(const {}); expect( parsed.billingResult, equals(const BillingResultWrapper( responseCode: BillingResponse.error, debugMessage: kInvalidBillingResultErrorMessage))); expect(parsed.responseCode, BillingResponse.error); expect(parsed.purchasesList, isEmpty); }); }); group('PurchasesHistoryResult', () { test('parsed from map', () { const BillingResponse responseCode = BillingResponse.ok; final List purchaseHistoryRecordList = [ dummyPurchaseHistoryRecord, dummyPurchaseHistoryRecord ]; const String debugMessage = 'dummy Message'; const BillingResultWrapper billingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); final PurchasesHistoryResult expected = PurchasesHistoryResult( billingResult: billingResult, purchaseHistoryRecordList: purchaseHistoryRecordList); final PurchasesHistoryResult parsed = PurchasesHistoryResult.fromJson({ 'billingResult': buildBillingResultMap(billingResult), 'purchaseHistoryRecordList': >[ buildPurchaseHistoryRecordMap(dummyPurchaseHistoryRecord), buildPurchaseHistoryRecordMap(dummyPurchaseHistoryRecord) ] }); expect(parsed.billingResult, equals(billingResult)); expect(parsed.purchaseHistoryRecordList, containsAll(expected.purchaseHistoryRecordList)); }); test('parsed from empty map', () { final PurchasesHistoryResult parsed = PurchasesHistoryResult.fromJson(const {}); expect( parsed.billingResult, equals(const BillingResultWrapper( responseCode: BillingResponse.error, debugMessage: kInvalidBillingResultErrorMessage))); expect(parsed.purchaseHistoryRecordList, isEmpty); }); }); } Map buildPurchaseMap(PurchaseWrapper original) { return { 'orderId': original.orderId, 'packageName': original.packageName, 'purchaseTime': original.purchaseTime, 'signature': original.signature, 'skus': original.skus, 'purchaseToken': original.purchaseToken, 'isAutoRenewing': original.isAutoRenewing, 'originalJson': original.originalJson, 'developerPayload': original.developerPayload, 'purchaseState': const PurchaseStateConverter().toJson(original.purchaseState), 'isAcknowledged': original.isAcknowledged, 'obfuscatedAccountId': original.obfuscatedAccountId, 'obfuscatedProfileId': original.obfuscatedProfileId, }; } Map buildPurchaseHistoryRecordMap( PurchaseHistoryRecordWrapper original) { return { 'purchaseTime': original.purchaseTime, 'signature': original.signature, 'skus': original.skus, 'purchaseToken': original.purchaseToken, 'originalJson': original.originalJson, 'developerPayload': original.developerPayload, }; } Map buildBillingResultMap(BillingResultWrapper original) { return { 'responseCode': const BillingResponseConverter().toJson(original.responseCode), 'debugMessage': original.debugMessage, }; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/sku_details_wrapper_deprecated_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(mvanbeusekom): Remove this file when the deprecated // `SkuDetailsWrapper.introductoryPriceMicros` field is // removed. import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_android/billing_client_wrappers.dart'; void main() { test( 'Deprecated `introductoryPriceMicros` field reflects parameter from constructor', () { const SkuDetailsWrapper skuDetails = SkuDetailsWrapper( description: 'description', freeTrialPeriod: 'freeTrialPeriod', introductoryPrice: 'introductoryPrice', // ignore: deprecated_member_use_from_same_package introductoryPriceMicros: '990000', introductoryPriceCycles: 1, introductoryPricePeriod: 'introductoryPricePeriod', price: 'price', priceAmountMicros: 1000, priceCurrencyCode: 'priceCurrencyCode', priceCurrencySymbol: r'$', sku: 'sku', subscriptionPeriod: 'subscriptionPeriod', title: 'title', type: SkuType.inapp, originalPrice: 'originalPrice', originalPriceAmountMicros: 1000, ); expect(skuDetails, isNotNull); expect(skuDetails.introductoryPriceAmountMicros, 0); // ignore: deprecated_member_use_from_same_package expect(skuDetails.introductoryPriceMicros, '990000'); }); test( '`introductoryPriceAmoutMicros` constructor parameter is reflected by deprecated `introductoryPriceMicros` and `introductoryPriceAmountMicros` fields', () { const SkuDetailsWrapper skuDetails = SkuDetailsWrapper( description: 'description', freeTrialPeriod: 'freeTrialPeriod', introductoryPrice: 'introductoryPrice', introductoryPriceAmountMicros: 990000, introductoryPriceCycles: 1, introductoryPricePeriod: 'introductoryPricePeriod', price: 'price', priceAmountMicros: 1000, priceCurrencyCode: 'priceCurrencyCode', priceCurrencySymbol: r'$', sku: 'sku', subscriptionPeriod: 'subscriptionPeriod', title: 'title', type: SkuType.inapp, originalPrice: 'originalPrice', originalPriceAmountMicros: 1000, ); expect(skuDetails, isNotNull); expect(skuDetails.introductoryPriceAmountMicros, 990000); // ignore: deprecated_member_use_from_same_package expect(skuDetails.introductoryPriceMicros, '990000'); }); } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/sku_details_wrapper_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:in_app_purchase_android/billing_client_wrappers.dart'; import 'package:in_app_purchase_android/src/types/google_play_product_details.dart'; import 'package:test/test.dart'; const SkuDetailsWrapper dummySkuDetails = SkuDetailsWrapper( description: 'description', freeTrialPeriod: 'freeTrialPeriod', introductoryPrice: 'introductoryPrice', introductoryPriceAmountMicros: 990000, introductoryPriceCycles: 1, introductoryPricePeriod: 'introductoryPricePeriod', price: 'price', priceAmountMicros: 1000, priceCurrencyCode: 'priceCurrencyCode', priceCurrencySymbol: r'$', sku: 'sku', subscriptionPeriod: 'subscriptionPeriod', title: 'title', type: SkuType.inapp, originalPrice: 'originalPrice', originalPriceAmountMicros: 1000, ); void main() { group('SkuDetailsWrapper', () { test('converts from map', () { const SkuDetailsWrapper expected = dummySkuDetails; final SkuDetailsWrapper parsed = SkuDetailsWrapper.fromJson(buildSkuMap(expected)); expect(parsed, equals(expected)); }); }); group('SkuDetailsResponseWrapper', () { test('parsed from map', () { const BillingResponse responseCode = BillingResponse.ok; const String debugMessage = 'dummy message'; final List skusDetails = [ dummySkuDetails, dummySkuDetails ]; const BillingResultWrapper result = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); final SkuDetailsResponseWrapper expected = SkuDetailsResponseWrapper( billingResult: result, skuDetailsList: skusDetails); final SkuDetailsResponseWrapper parsed = SkuDetailsResponseWrapper.fromJson({ 'billingResult': { 'responseCode': const BillingResponseConverter().toJson(responseCode), 'debugMessage': debugMessage, }, 'skuDetailsList': >[ buildSkuMap(dummySkuDetails), buildSkuMap(dummySkuDetails) ] }); expect(parsed.billingResult, equals(expected.billingResult)); expect(parsed.skuDetailsList, containsAll(expected.skuDetailsList)); }); test('toProductDetails() should return correct Product object', () { final SkuDetailsWrapper wrapper = SkuDetailsWrapper.fromJson(buildSkuMap(dummySkuDetails)); final GooglePlayProductDetails product = GooglePlayProductDetails.fromSkuDetails(wrapper); expect(product.title, wrapper.title); expect(product.description, wrapper.description); expect(product.id, wrapper.sku); expect(product.price, wrapper.price); expect(product.skuDetails, wrapper); }); test('handles empty list of skuDetails', () { const BillingResponse responseCode = BillingResponse.error; const String debugMessage = 'dummy message'; final List skusDetails = []; const BillingResultWrapper billingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); final SkuDetailsResponseWrapper expected = SkuDetailsResponseWrapper( billingResult: billingResult, skuDetailsList: skusDetails); final SkuDetailsResponseWrapper parsed = SkuDetailsResponseWrapper.fromJson({ 'billingResult': { 'responseCode': const BillingResponseConverter().toJson(responseCode), 'debugMessage': debugMessage, }, 'skuDetailsList': const >[] }); expect(parsed.billingResult, equals(expected.billingResult)); expect(parsed.skuDetailsList, containsAll(expected.skuDetailsList)); }); test('fromJson creates an object with default values', () { final SkuDetailsResponseWrapper skuDetails = SkuDetailsResponseWrapper.fromJson(const {}); expect( skuDetails.billingResult, equals(const BillingResultWrapper( responseCode: BillingResponse.error, debugMessage: kInvalidBillingResultErrorMessage))); expect(skuDetails.skuDetailsList, isEmpty); }); }); group('BillingResultWrapper', () { test('fromJson on empty map creates an object with default values', () { final BillingResultWrapper billingResult = BillingResultWrapper.fromJson(const {}); expect(billingResult.debugMessage, kInvalidBillingResultErrorMessage); expect(billingResult.responseCode, BillingResponse.error); }); test('fromJson on null creates an object with default values', () { final BillingResultWrapper billingResult = BillingResultWrapper.fromJson(null); expect(billingResult.debugMessage, kInvalidBillingResultErrorMessage); expect(billingResult.responseCode, BillingResponse.error); }); test('operator == of SkuDetailsWrapper works fine', () { const SkuDetailsWrapper firstSkuDetailsInstance = SkuDetailsWrapper( description: 'description', freeTrialPeriod: 'freeTrialPeriod', introductoryPrice: 'introductoryPrice', introductoryPriceAmountMicros: 990000, introductoryPriceCycles: 1, introductoryPricePeriod: 'introductoryPricePeriod', price: 'price', priceAmountMicros: 1000, priceCurrencyCode: 'priceCurrencyCode', priceCurrencySymbol: r'$', sku: 'sku', subscriptionPeriod: 'subscriptionPeriod', title: 'title', type: SkuType.inapp, originalPrice: 'originalPrice', originalPriceAmountMicros: 1000, ); const SkuDetailsWrapper secondSkuDetailsInstance = SkuDetailsWrapper( description: 'description', freeTrialPeriod: 'freeTrialPeriod', introductoryPrice: 'introductoryPrice', introductoryPriceAmountMicros: 990000, introductoryPriceCycles: 1, introductoryPricePeriod: 'introductoryPricePeriod', price: 'price', priceAmountMicros: 1000, priceCurrencyCode: 'priceCurrencyCode', priceCurrencySymbol: r'$', sku: 'sku', subscriptionPeriod: 'subscriptionPeriod', title: 'title', type: SkuType.inapp, originalPrice: 'originalPrice', originalPriceAmountMicros: 1000, ); expect(firstSkuDetailsInstance == secondSkuDetailsInstance, isTrue); }); test('operator == of BillingResultWrapper works fine', () { const BillingResultWrapper firstBillingResultInstance = BillingResultWrapper( responseCode: BillingResponse.ok, debugMessage: 'debugMessage', ); const BillingResultWrapper secondBillingResultInstance = BillingResultWrapper( responseCode: BillingResponse.ok, debugMessage: 'debugMessage', ); expect(firstBillingResultInstance == secondBillingResultInstance, isTrue); }); }); } Map buildSkuMap(SkuDetailsWrapper original) { return { 'description': original.description, 'freeTrialPeriod': original.freeTrialPeriod, 'introductoryPrice': original.introductoryPrice, 'introductoryPriceAmountMicros': original.introductoryPriceAmountMicros, 'introductoryPriceCycles': original.introductoryPriceCycles, 'introductoryPricePeriod': original.introductoryPricePeriod, 'price': original.price, 'priceAmountMicros': original.priceAmountMicros, 'priceCurrencyCode': original.priceCurrencyCode, 'priceCurrencySymbol': original.priceCurrencySymbol, 'sku': original.sku, 'subscriptionPeriod': original.subscriptionPeriod, 'title': original.title, 'type': original.type.toString().substring(8), 'originalPrice': original.originalPrice, 'originalPriceAmountMicros': original.originalPriceAmountMicros, }; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart' as widgets; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_android/billing_client_wrappers.dart'; import 'package:in_app_purchase_android/in_app_purchase_android.dart'; import 'package:in_app_purchase_android/src/channel.dart'; import 'billing_client_wrappers/purchase_wrapper_test.dart'; import 'stub_in_app_purchase_platform.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform(); late InAppPurchaseAndroidPlatformAddition iapAndroidPlatformAddition; const String startConnectionCall = 'BillingClient#startConnection(BillingClientStateListener)'; const String endConnectionCall = 'BillingClient#endConnection()'; setUpAll(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, stubPlatform.fakeMethodCallHandler); }); setUp(() { widgets.WidgetsFlutterBinding.ensureInitialized(); const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); stubPlatform.addResponse( name: startConnectionCall, value: buildBillingResultMap(expectedBillingResult)); stubPlatform.addResponse(name: endConnectionCall); iapAndroidPlatformAddition = InAppPurchaseAndroidPlatformAddition(BillingClient((_) {})); }); group('consume purchases', () { const String consumeMethodName = 'BillingClient#consumeAsync(String, ConsumeResponseListener)'; test('consume purchase async success', () async { const BillingResponse expectedCode = BillingResponse.ok; const String debugMessage = 'dummy message'; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); stubPlatform.addResponse( name: consumeMethodName, value: buildBillingResultMap(expectedBillingResult), ); final BillingResultWrapper billingResultWrapper = await iapAndroidPlatformAddition.consumePurchase( GooglePlayPurchaseDetails.fromPurchase(dummyPurchase)); expect(billingResultWrapper, equals(expectedBillingResult)); }); }); group('queryPastPurchase', () { group('queryPurchaseDetails', () { const String queryMethodName = 'BillingClient#queryPurchases(String)'; test('handles error', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.developerError; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); stubPlatform .addResponse(name: queryMethodName, value: { 'billingResult': buildBillingResultMap(expectedBillingResult), 'responseCode': const BillingResponseConverter().toJson(responseCode), 'purchasesList': >[] }); final QueryPurchaseDetailsResponse response = await iapAndroidPlatformAddition.queryPastPurchases(); expect(response.pastPurchases, isEmpty); expect(response.error, isNotNull); expect( response.error!.message, BillingResponse.developerError.toString()); expect(response.error!.source, kIAPSource); }); test('returns SkuDetailsResponseWrapper', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); stubPlatform .addResponse(name: queryMethodName, value: { 'billingResult': buildBillingResultMap(expectedBillingResult), 'responseCode': const BillingResponseConverter().toJson(responseCode), 'purchasesList': >[ buildPurchaseMap(dummyPurchase), ] }); // Since queryPastPurchases makes 2 platform method calls (one for each SkuType), the result will contain 2 dummyWrapper instead // of 1. final QueryPurchaseDetailsResponse response = await iapAndroidPlatformAddition.queryPastPurchases(); expect(response.error, isNull); expect(response.pastPurchases.first.purchaseID, dummyPurchase.orderId); }); test('should store platform exception in the response', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.developerError; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); stubPlatform.addResponse( name: queryMethodName, value: { 'responseCode': const BillingResponseConverter().toJson(responseCode), 'billingResult': buildBillingResultMap(expectedBillingResult), 'purchasesList': >[] }, additionalStepBeforeReturn: (dynamic _) { throw PlatformException( code: 'error_code', message: 'error_message', details: {'info': 'error_info'}, ); }); final QueryPurchaseDetailsResponse response = await iapAndroidPlatformAddition.queryPastPurchases(); expect(response.pastPurchases, isEmpty); expect(response.error, isNotNull); expect(response.error!.code, 'error_code'); expect(response.error!.message, 'error_message'); expect( response.error!.details, {'info': 'error_info'}); }); }); }); group('isFeatureSupported', () { const String isFeatureSupportedMethodName = 'BillingClient#isFeatureSupported(String)'; test('isFeatureSupported returns false', () async { late Map arguments; stubPlatform.addResponse( name: isFeatureSupportedMethodName, value: false, additionalStepBeforeReturn: (dynamic value) => arguments = value as Map, ); final bool isSupported = await iapAndroidPlatformAddition .isFeatureSupported(BillingClientFeature.subscriptions); expect(isSupported, isFalse); expect(arguments['feature'], equals('subscriptions')); }); test('isFeatureSupported returns true', () async { late Map arguments; stubPlatform.addResponse( name: isFeatureSupportedMethodName, value: true, additionalStepBeforeReturn: (dynamic value) => arguments = value as Map, ); final bool isSupported = await iapAndroidPlatformAddition .isFeatureSupported(BillingClientFeature.subscriptions); expect(isSupported, isTrue); expect(arguments['feature'], equals('subscriptions')); }); }); group('launchPriceChangeConfirmationFlow', () { const String launchPriceChangeConfirmationFlowMethodName = 'BillingClient#launchPriceChangeConfirmationFlow (Activity, PriceChangeFlowParams, PriceChangeConfirmationListener)'; const String dummySku = 'sku'; const BillingResultWrapper expectedBillingResultPriceChangeConfirmation = BillingResultWrapper( responseCode: BillingResponse.ok, debugMessage: 'dummy message', ); test('serializes and deserializes data', () async { stubPlatform.addResponse( name: launchPriceChangeConfirmationFlowMethodName, value: buildBillingResultMap(expectedBillingResultPriceChangeConfirmation), ); expect( await iapAndroidPlatformAddition.launchPriceChangeConfirmationFlow( sku: dummySku, ), equals(expectedBillingResultPriceChangeConfirmation), ); }); test('passes sku to launchPriceChangeConfirmationFlow', () async { stubPlatform.addResponse( name: launchPriceChangeConfirmationFlowMethodName, value: buildBillingResultMap(expectedBillingResultPriceChangeConfirmation), ); await iapAndroidPlatformAddition.launchPriceChangeConfirmationFlow( sku: dummySku, ); final MethodCall call = stubPlatform .previousCallMatching(launchPriceChangeConfirmationFlowMethodName); expect(call.arguments, equals({'sku': dummySku})); }); }); } /// 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; ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart' as widgets; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_android/billing_client_wrappers.dart'; import 'package:in_app_purchase_android/in_app_purchase_android.dart'; import 'package:in_app_purchase_android/src/channel.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'billing_client_wrappers/purchase_wrapper_test.dart'; import 'billing_client_wrappers/sku_details_wrapper_test.dart'; import 'stub_in_app_purchase_platform.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform(); late InAppPurchaseAndroidPlatform iapAndroidPlatform; const String startConnectionCall = 'BillingClient#startConnection(BillingClientStateListener)'; const String endConnectionCall = 'BillingClient#endConnection()'; setUpAll(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, stubPlatform.fakeMethodCallHandler); }); setUp(() { widgets.WidgetsFlutterBinding.ensureInitialized(); const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); stubPlatform.addResponse( name: startConnectionCall, value: buildBillingResultMap(expectedBillingResult)); stubPlatform.addResponse(name: endConnectionCall); InAppPurchaseAndroidPlatform.registerPlatform(); iapAndroidPlatform = InAppPurchasePlatform.instance as InAppPurchaseAndroidPlatform; }); tearDown(() { stubPlatform.reset(); }); group('connection management', () { test('connects on initialization', () { //await iapAndroidPlatform.isAvailable(); expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(1)); }); }); group('isAvailable', () { test('true', () async { stubPlatform.addResponse(name: 'BillingClient#isReady()', value: true); expect(await iapAndroidPlatform.isAvailable(), isTrue); }); test('false', () async { stubPlatform.addResponse(name: 'BillingClient#isReady()', value: false); expect(await iapAndroidPlatform.isAvailable(), isFalse); }); }); group('querySkuDetails', () { const String queryMethodName = 'BillingClient#querySkuDetailsAsync(SkuDetailsParams, SkuDetailsResponseListener)'; test('handles empty skuDetails', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); stubPlatform.addResponse(name: queryMethodName, value: { 'billingResult': buildBillingResultMap(expectedBillingResult), 'skuDetailsList': >[], }); final ProductDetailsResponse response = await iapAndroidPlatform.queryProductDetails({''}); expect(response.productDetails, isEmpty); }); test('should get correct product details', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); stubPlatform.addResponse(name: queryMethodName, value: { 'billingResult': buildBillingResultMap(expectedBillingResult), 'skuDetailsList': >[buildSkuMap(dummySkuDetails)] }); // Since queryProductDetails makes 2 platform method calls (one for each SkuType), the result will contain 2 dummyWrapper instead // of 1. final ProductDetailsResponse response = await iapAndroidPlatform.queryProductDetails({'valid'}); expect(response.productDetails.first.title, dummySkuDetails.title); expect(response.productDetails.first.description, dummySkuDetails.description); expect(response.productDetails.first.price, dummySkuDetails.price); expect(response.productDetails.first.currencySymbol, r'$'); }); test('should get the correct notFoundIDs', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); stubPlatform.addResponse(name: queryMethodName, value: { 'billingResult': buildBillingResultMap(expectedBillingResult), 'skuDetailsList': >[buildSkuMap(dummySkuDetails)] }); // Since queryProductDetails makes 2 platform method calls (one for each SkuType), the result will contain 2 dummyWrapper instead // of 1. final ProductDetailsResponse response = await iapAndroidPlatform.queryProductDetails({'invalid'}); expect(response.notFoundIDs.first, 'invalid'); }); test( 'should have error stored in the response when platform exception is thrown', () async { const BillingResponse responseCode = BillingResponse.ok; stubPlatform.addResponse( name: queryMethodName, value: { 'responseCode': const BillingResponseConverter().toJson(responseCode), 'skuDetailsList': >[ buildSkuMap(dummySkuDetails) ] }, additionalStepBeforeReturn: (dynamic _) { throw PlatformException( code: 'error_code', message: 'error_message', details: {'info': 'error_info'}, ); }); // Since queryProductDetails makes 2 platform method calls (one for each SkuType), the result will contain 2 dummyWrapper instead // of 1. final ProductDetailsResponse response = await iapAndroidPlatform.queryProductDetails({'invalid'}); expect(response.notFoundIDs, ['invalid']); expect(response.productDetails, isEmpty); expect(response.error, isNotNull); expect(response.error!.source, kIAPSource); expect(response.error!.code, 'error_code'); expect(response.error!.message, 'error_message'); expect(response.error!.details, {'info': 'error_info'}); }); }); group('restorePurchases', () { const String queryMethodName = 'BillingClient#queryPurchases(String)'; test('handles error', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.developerError; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); stubPlatform.addResponse(name: queryMethodName, value: { 'billingResult': buildBillingResultMap(expectedBillingResult), 'responseCode': const BillingResponseConverter().toJson(responseCode), 'purchasesList': >[] }); expect( iapAndroidPlatform.restorePurchases(), throwsA( isA() .having( (InAppPurchaseException e) => e.source, 'source', kIAPSource) .having((InAppPurchaseException e) => e.code, 'code', kRestoredPurchaseErrorCode) .having((InAppPurchaseException e) => e.message, 'message', responseCode.toString()), ), ); }); test('should store platform exception in the response', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.developerError; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); stubPlatform.addResponse( name: queryMethodName, value: { 'responseCode': const BillingResponseConverter().toJson(responseCode), 'billingResult': buildBillingResultMap(expectedBillingResult), 'purchasesList': >[] }, additionalStepBeforeReturn: (dynamic _) { throw PlatformException( code: 'error_code', message: 'error_message', details: {'info': 'error_info'}, ); }); expect( iapAndroidPlatform.restorePurchases(), throwsA( isA() .having((PlatformException e) => e.code, 'code', 'error_code') .having((PlatformException e) => e.message, 'message', 'error_message') .having((PlatformException e) => e.details, 'details', {'info': 'error_info'}), ), ); }); test('returns SkuDetailsResponseWrapper', () async { final Completer> completer = Completer>(); final Stream> stream = iapAndroidPlatform.purchaseStream; late StreamSubscription> subscription; subscription = stream.listen((List purchaseDetailsList) { if (purchaseDetailsList.first.status == PurchaseStatus.restored) { completer.complete(purchaseDetailsList); subscription.cancel(); } }); const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); stubPlatform.addResponse(name: queryMethodName, value: { 'billingResult': buildBillingResultMap(expectedBillingResult), 'responseCode': const BillingResponseConverter().toJson(responseCode), 'purchasesList': >[ buildPurchaseMap(dummyPurchase), ] }); // Since queryPastPurchases makes 2 platform method calls (one for each // SkuType), the result will contain 2 dummyPurchase instances instead // of 1. await iapAndroidPlatform.restorePurchases(); final List restoredPurchases = await completer.future; expect(restoredPurchases.length, 2); for (final PurchaseDetails element in restoredPurchases) { final GooglePlayPurchaseDetails purchase = element as GooglePlayPurchaseDetails; expect(purchase.productID, dummyPurchase.sku); expect(purchase.purchaseID, dummyPurchase.orderId); expect(purchase.verificationData.localVerificationData, dummyPurchase.originalJson); expect(purchase.verificationData.serverVerificationData, dummyPurchase.purchaseToken); expect(purchase.verificationData.source, kIAPSource); expect(purchase.transactionDate, dummyPurchase.purchaseTime.toString()); expect(purchase.billingClientPurchase, dummyPurchase); expect(purchase.status, PurchaseStatus.restored); } }); }); group('make payment', () { const String launchMethodName = 'BillingClient#launchBillingFlow(Activity, BillingFlowParams)'; const String consumeMethodName = 'BillingClient#consumeAsync(String, ConsumeResponseListener)'; test('buy non consumable, serializes and deserializes data', () async { const SkuDetailsWrapper skuDetails = dummySkuDetails; const String accountId = 'hashedAccountId'; const String debugMessage = 'dummy message'; const BillingResponse sentCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: sentCode, debugMessage: debugMessage); stubPlatform.addResponse( name: launchMethodName, value: buildBillingResultMap(expectedBillingResult), additionalStepBeforeReturn: (dynamic _) { // Mock java update purchase callback. final MethodCall call = MethodCall(kOnPurchasesUpdated, { 'billingResult': buildBillingResultMap(expectedBillingResult), 'responseCode': const BillingResponseConverter().toJson(sentCode), 'purchasesList': [ { 'orderId': 'orderID1', 'skus': [skuDetails.sku], 'isAutoRenewing': false, 'packageName': 'package', 'purchaseTime': 1231231231, 'purchaseToken': 'token', 'signature': 'sign', 'originalJson': 'json', 'developerPayload': 'dummy payload', 'isAcknowledged': true, 'purchaseState': 1, } ] }); iapAndroidPlatform.billingClient.callHandler(call); }); final Completer completer = Completer(); PurchaseDetails purchaseDetails; final Stream> purchaseStream = iapAndroidPlatform.purchaseStream; late StreamSubscription> subscription; subscription = purchaseStream.listen((_) { purchaseDetails = _.first; completer.complete(purchaseDetails); subscription.cancel(); }, onDone: () {}); final GooglePlayPurchaseParam purchaseParam = GooglePlayPurchaseParam( productDetails: GooglePlayProductDetails.fromSkuDetails(skuDetails), applicationUserName: accountId); final bool launchResult = await iapAndroidPlatform.buyNonConsumable( purchaseParam: purchaseParam); final PurchaseDetails result = await completer.future; expect(launchResult, isTrue); expect(result.purchaseID, 'orderID1'); expect(result.status, PurchaseStatus.purchased); expect(result.productID, dummySkuDetails.sku); }); test('handles an error with an empty purchases list', () async { const SkuDetailsWrapper skuDetails = dummySkuDetails; const String accountId = 'hashedAccountId'; const String debugMessage = 'dummy message'; const BillingResponse sentCode = BillingResponse.error; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: sentCode, debugMessage: debugMessage); stubPlatform.addResponse( name: launchMethodName, value: buildBillingResultMap(expectedBillingResult), additionalStepBeforeReturn: (dynamic _) { // Mock java update purchase callback. final MethodCall call = MethodCall(kOnPurchasesUpdated, { 'billingResult': buildBillingResultMap(expectedBillingResult), 'responseCode': const BillingResponseConverter().toJson(sentCode), 'purchasesList': const [] }); iapAndroidPlatform.billingClient.callHandler(call); }); final Completer completer = Completer(); PurchaseDetails purchaseDetails; final Stream> purchaseStream = iapAndroidPlatform.purchaseStream; late StreamSubscription> subscription; subscription = purchaseStream.listen((_) { purchaseDetails = _.first; completer.complete(purchaseDetails); subscription.cancel(); }, onDone: () {}); final GooglePlayPurchaseParam purchaseParam = GooglePlayPurchaseParam( productDetails: GooglePlayProductDetails.fromSkuDetails(skuDetails), applicationUserName: accountId); await iapAndroidPlatform.buyNonConsumable(purchaseParam: purchaseParam); final PurchaseDetails result = await completer.future; expect(result.error, isNotNull); expect(result.error!.source, kIAPSource); expect(result.status, PurchaseStatus.error); expect(result.purchaseID, isEmpty); }); test('buy consumable with auto consume, serializes and deserializes data', () async { const SkuDetailsWrapper skuDetails = dummySkuDetails; const String accountId = 'hashedAccountId'; const String debugMessage = 'dummy message'; const BillingResponse sentCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: sentCode, debugMessage: debugMessage); stubPlatform.addResponse( name: launchMethodName, value: buildBillingResultMap(expectedBillingResult), additionalStepBeforeReturn: (dynamic _) { // Mock java update purchase callback. final MethodCall call = MethodCall(kOnPurchasesUpdated, { 'billingResult': buildBillingResultMap(expectedBillingResult), 'responseCode': const BillingResponseConverter().toJson(sentCode), 'purchasesList': [ { 'orderId': 'orderID1', 'skus': [skuDetails.sku], 'isAutoRenewing': false, 'packageName': 'package', 'purchaseTime': 1231231231, 'purchaseToken': 'token', 'signature': 'sign', 'originalJson': 'json', 'developerPayload': 'dummy payload', 'isAcknowledged': true, 'purchaseState': 1, } ] }); iapAndroidPlatform.billingClient.callHandler(call); }); final Completer consumeCompleter = Completer(); // adding call back for consume purchase const BillingResponse expectedCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResultForConsume = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); stubPlatform.addResponse( name: consumeMethodName, value: buildBillingResultMap(expectedBillingResultForConsume), additionalStepBeforeReturn: (dynamic args) { final String purchaseToken = (args as Map)['purchaseToken']! as String; consumeCompleter.complete(purchaseToken); }); final Completer completer = Completer(); PurchaseDetails purchaseDetails; final Stream> purchaseStream = iapAndroidPlatform.purchaseStream; late StreamSubscription> subscription; subscription = purchaseStream.listen((_) { purchaseDetails = _.first; completer.complete(purchaseDetails); subscription.cancel(); }, onDone: () {}); final GooglePlayPurchaseParam purchaseParam = GooglePlayPurchaseParam( productDetails: GooglePlayProductDetails.fromSkuDetails(skuDetails), applicationUserName: accountId); final bool launchResult = await iapAndroidPlatform.buyConsumable(purchaseParam: purchaseParam); // Verify that the result has succeeded final GooglePlayPurchaseDetails result = await completer.future as GooglePlayPurchaseDetails; expect(launchResult, isTrue); expect(result.billingClientPurchase, isNotNull); expect(result.billingClientPurchase.purchaseToken, await consumeCompleter.future); expect(result.status, PurchaseStatus.purchased); expect(result.error, isNull); }); test('buyNonConsumable propagates failures to launch the billing flow', () async { const String debugMessage = 'dummy message'; const BillingResponse sentCode = BillingResponse.error; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: sentCode, debugMessage: debugMessage); stubPlatform.addResponse( name: launchMethodName, value: buildBillingResultMap(expectedBillingResult)); final bool result = await iapAndroidPlatform.buyNonConsumable( purchaseParam: GooglePlayPurchaseParam( productDetails: GooglePlayProductDetails.fromSkuDetails(dummySkuDetails))); // Verify that the failure has been converted and returned expect(result, isFalse); }); test('buyConsumable propagates failures to launch the billing flow', () async { const String debugMessage = 'dummy message'; const BillingResponse sentCode = BillingResponse.developerError; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: sentCode, debugMessage: debugMessage); stubPlatform.addResponse( name: launchMethodName, value: buildBillingResultMap(expectedBillingResult), ); final bool result = await iapAndroidPlatform.buyConsumable( purchaseParam: GooglePlayPurchaseParam( productDetails: GooglePlayProductDetails.fromSkuDetails(dummySkuDetails))); // Verify that the failure has been converted and returned expect(result, isFalse); }); test('adds consumption failures to PurchaseDetails objects', () async { const SkuDetailsWrapper skuDetails = dummySkuDetails; const String accountId = 'hashedAccountId'; const String debugMessage = 'dummy message'; const BillingResponse sentCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: sentCode, debugMessage: debugMessage); stubPlatform.addResponse( name: launchMethodName, value: buildBillingResultMap(expectedBillingResult), additionalStepBeforeReturn: (dynamic _) { // Mock java update purchase callback. final MethodCall call = MethodCall(kOnPurchasesUpdated, { 'billingResult': buildBillingResultMap(expectedBillingResult), 'responseCode': const BillingResponseConverter().toJson(sentCode), 'purchasesList': [ { 'orderId': 'orderID1', 'skus': [skuDetails.sku], 'isAutoRenewing': false, 'packageName': 'package', 'purchaseTime': 1231231231, 'purchaseToken': 'token', 'signature': 'sign', 'originalJson': 'json', 'developerPayload': 'dummy payload', 'isAcknowledged': true, 'purchaseState': 1, } ] }); iapAndroidPlatform.billingClient.callHandler(call); }); final Completer consumeCompleter = Completer(); // adding call back for consume purchase const BillingResponse expectedCode = BillingResponse.error; const BillingResultWrapper expectedBillingResultForConsume = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); stubPlatform.addResponse( name: consumeMethodName, value: buildBillingResultMap(expectedBillingResultForConsume), additionalStepBeforeReturn: (dynamic args) { final String purchaseToken = (args as Map)['purchaseToken']! as String; consumeCompleter.complete(purchaseToken); }); final Completer completer = Completer(); PurchaseDetails purchaseDetails; final Stream> purchaseStream = iapAndroidPlatform.purchaseStream; late StreamSubscription> subscription; subscription = purchaseStream.listen((_) { purchaseDetails = _.first; completer.complete(purchaseDetails); subscription.cancel(); }, onDone: () {}); final GooglePlayPurchaseParam purchaseParam = GooglePlayPurchaseParam( productDetails: GooglePlayProductDetails.fromSkuDetails(skuDetails), applicationUserName: accountId); await iapAndroidPlatform.buyConsumable(purchaseParam: purchaseParam); // Verify that the result has an error for the failed consumption final GooglePlayPurchaseDetails result = await completer.future as GooglePlayPurchaseDetails; expect(result.billingClientPurchase, isNotNull); expect(result.billingClientPurchase.purchaseToken, await consumeCompleter.future); expect(result.status, PurchaseStatus.error); expect(result.error, isNotNull); expect(result.error!.code, kConsumptionFailedErrorCode); }); test( 'buy consumable without auto consume, consume api should not receive calls', () async { const SkuDetailsWrapper skuDetails = dummySkuDetails; const String accountId = 'hashedAccountId'; const String debugMessage = 'dummy message'; const BillingResponse sentCode = BillingResponse.developerError; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: sentCode, debugMessage: debugMessage); stubPlatform.addResponse( name: launchMethodName, value: buildBillingResultMap(expectedBillingResult), additionalStepBeforeReturn: (dynamic _) { // Mock java update purchase callback. final MethodCall call = MethodCall(kOnPurchasesUpdated, { 'billingResult': buildBillingResultMap(expectedBillingResult), 'responseCode': const BillingResponseConverter().toJson(sentCode), 'purchasesList': [ { 'orderId': 'orderID1', 'skus': [skuDetails.sku], 'isAutoRenewing': false, 'packageName': 'package', 'purchaseTime': 1231231231, 'purchaseToken': 'token', 'signature': 'sign', 'originalJson': 'json', 'developerPayload': 'dummy payload', 'isAcknowledged': true, 'purchaseState': 1, } ] }); iapAndroidPlatform.billingClient.callHandler(call); }); final Completer consumeCompleter = Completer(); // adding call back for consume purchase const BillingResponse expectedCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResultForConsume = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); stubPlatform.addResponse( name: consumeMethodName, value: buildBillingResultMap(expectedBillingResultForConsume), additionalStepBeforeReturn: (dynamic args) { final String purchaseToken = (args as Map)['purchaseToken']! as String; consumeCompleter.complete(purchaseToken); }); final Stream> purchaseStream = iapAndroidPlatform.purchaseStream; late StreamSubscription> subscription; subscription = purchaseStream.listen((_) { consumeCompleter.complete(null); subscription.cancel(); }, onDone: () {}); final GooglePlayPurchaseParam purchaseParam = GooglePlayPurchaseParam( productDetails: GooglePlayProductDetails.fromSkuDetails(skuDetails), applicationUserName: accountId); await iapAndroidPlatform.buyConsumable( purchaseParam: purchaseParam, autoConsume: false); expect(null, await consumeCompleter.future); }); test( 'should get canceled purchase status when response code is BillingResponse.userCanceled', () async { const SkuDetailsWrapper skuDetails = dummySkuDetails; const String accountId = 'hashedAccountId'; const String debugMessage = 'dummy message'; const BillingResponse sentCode = BillingResponse.userCanceled; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: sentCode, debugMessage: debugMessage); stubPlatform.addResponse( name: launchMethodName, value: buildBillingResultMap(expectedBillingResult), additionalStepBeforeReturn: (dynamic _) { // Mock java update purchase callback. final MethodCall call = MethodCall(kOnPurchasesUpdated, { 'billingResult': buildBillingResultMap(expectedBillingResult), 'responseCode': const BillingResponseConverter().toJson(sentCode), 'purchasesList': [ { 'orderId': 'orderID1', 'sku': skuDetails.sku, 'isAutoRenewing': false, 'packageName': 'package', 'purchaseTime': 1231231231, 'purchaseToken': 'token', 'signature': 'sign', 'originalJson': 'json', 'developerPayload': 'dummy payload', 'isAcknowledged': true, 'purchaseState': 1, } ] }); iapAndroidPlatform.billingClient.callHandler(call); }); final Completer consumeCompleter = Completer(); // adding call back for consume purchase const BillingResponse expectedCode = BillingResponse.userCanceled; const BillingResultWrapper expectedBillingResultForConsume = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); stubPlatform.addResponse( name: consumeMethodName, value: buildBillingResultMap(expectedBillingResultForConsume), additionalStepBeforeReturn: (dynamic args) { final String purchaseToken = (args as Map)['purchaseToken']! as String; consumeCompleter.complete(purchaseToken); }); final Completer completer = Completer(); PurchaseDetails purchaseDetails; final Stream> purchaseStream = iapAndroidPlatform.purchaseStream; late StreamSubscription> subscription; subscription = purchaseStream.listen((_) { purchaseDetails = _.first; completer.complete(purchaseDetails); subscription.cancel(); }, onDone: () {}); final GooglePlayPurchaseParam purchaseParam = GooglePlayPurchaseParam( productDetails: GooglePlayProductDetails.fromSkuDetails(skuDetails), applicationUserName: accountId); await iapAndroidPlatform.buyConsumable(purchaseParam: purchaseParam); // Verify that the result has an error for the failed consumption final GooglePlayPurchaseDetails result = await completer.future as GooglePlayPurchaseDetails; expect(result.status, PurchaseStatus.canceled); }); test( 'should get purchased purchase status when upgrading subscription by deferred proration mode', () async { const SkuDetailsWrapper skuDetails = dummySkuDetails; const String accountId = 'hashedAccountId'; const String debugMessage = 'dummy message'; const BillingResponse sentCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: sentCode, debugMessage: debugMessage); stubPlatform.addResponse( name: launchMethodName, value: buildBillingResultMap(expectedBillingResult), additionalStepBeforeReturn: (dynamic _) { // Mock java update purchase callback. final MethodCall call = MethodCall(kOnPurchasesUpdated, { 'billingResult': buildBillingResultMap(expectedBillingResult), 'responseCode': const BillingResponseConverter().toJson(sentCode), 'purchasesList': const [] }); iapAndroidPlatform.billingClient.callHandler(call); }); final Completer completer = Completer(); PurchaseDetails purchaseDetails; final Stream> purchaseStream = iapAndroidPlatform.purchaseStream; late StreamSubscription> subscription; subscription = purchaseStream.listen((_) { purchaseDetails = _.first; completer.complete(purchaseDetails); subscription.cancel(); }, onDone: () {}); final GooglePlayPurchaseParam purchaseParam = GooglePlayPurchaseParam( productDetails: GooglePlayProductDetails.fromSkuDetails(skuDetails), applicationUserName: accountId, changeSubscriptionParam: ChangeSubscriptionParam( oldPurchaseDetails: GooglePlayPurchaseDetails.fromPurchase( dummyUnacknowledgedPurchase), prorationMode: ProrationMode.deferred, )); await iapAndroidPlatform.buyNonConsumable(purchaseParam: purchaseParam); final PurchaseDetails result = await completer.future; expect(result.status, PurchaseStatus.purchased); }); }); group('complete purchase', () { const String completeMethodName = 'BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)'; test('complete purchase success', () async { const BillingResponse expectedCode = BillingResponse.ok; const String debugMessage = 'dummy message'; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); stubPlatform.addResponse( name: completeMethodName, value: buildBillingResultMap(expectedBillingResult), ); final PurchaseDetails purchaseDetails = GooglePlayPurchaseDetails.fromPurchase(dummyUnacknowledgedPurchase); final Completer completer = Completer(); purchaseDetails.status = PurchaseStatus.purchased; if (purchaseDetails.pendingCompletePurchase) { final BillingResultWrapper billingResultWrapper = await iapAndroidPlatform.completePurchase(purchaseDetails); expect(billingResultWrapper, equals(expectedBillingResult)); completer.complete(billingResultWrapper); } expect(await completer.future, equals(expectedBillingResult)); }); }); } /// 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; ================================================ FILE: packages/in_app_purchase/in_app_purchase_android/test/stub_in_app_purchase_platform.dart ================================================ // Copyright 2013 The Flutter 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'; typedef AdditionalSteps = void Function(dynamic args); class StubInAppPurchasePlatform { final Map _expectedCalls = {}; final Map _additionalSteps = {}; void addResponse( {required String name, dynamic value, AdditionalSteps? additionalStepBeforeReturn}) { _additionalSteps[name] = additionalStepBeforeReturn; _expectedCalls[name] = value; } final List _previousCalls = []; List get previousCalls => _previousCalls; MethodCall previousCallMatching(String name) => _previousCalls.firstWhere((MethodCall call) => call.method == name); int countPreviousCalls(String name) => _previousCalls.where((MethodCall call) => call.method == name).length; void reset() { _expectedCalls.clear(); _previousCalls.clear(); _additionalSteps.clear(); } Future fakeMethodCallHandler(MethodCall call) async { _previousCalls.add(call); if (_expectedCalls.containsKey(call.method)) { if (_additionalSteps[call.method] != null) { _additionalSteps[call.method]!(call.arguments); } return Future.sync(() => _expectedCalls[call.method]); } else { return Future.sync(() => null); } } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/AUTHORS ================================================ # 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> Maurits van Beusekom ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 1.3.2 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. * Removes unnecessary imports. ## 1.3.1 * Update to use the `verify` method introduced in plugin_platform_interface 2.1.0. ## 1.3.0 * Added new `PurchaseStatus` named `canceled` to distinguish between an error and user cancellation. ## 1.2.0 * Added `toString()` to `IAPError` ## 1.1.0 * Added `currencySymbol` in ProductDetails. ## 1.0.1 * Fixed `Restoring previous purchases` link. ## 1.0.0 * Initial open-source release. ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/LICENSE ================================================ 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. ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/README.md ================================================ # in_app_purchase_platform_interface A common platform interface for the [`in_app_purchase`][1] plugin. This interface allows platform-specific implementations of the `in_app_purchase` plugin, as well as the plugin itself, to ensure they are supporting the same interface. # Usage To implement a new platform-specific implementation of `in_app_purchase`, extend [`InAppPurchasePlatform`][2] with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `InAppPurchasePlatform` by calling `InAppPurchasePlatform.setInstance(MyPlatformInAppPurchase())`. To implement functionality that is specific to the platform and is not covered by the [`InAppPurchasePlatform`][2] idiomatic API, extend [`InAppPurchasePlatformAddition`][3] with the platform-specific functionality, and when the plugin is registered, set the addition instance by calling `InAppPurchasePlatformAddition.instance = MyPlatformInAppPurchaseAddition()`. # Note on breaking changes Strongly prefer non-breaking changes (such as adding a method to the interface) over breaking changes for this package. See https://flutter.dev/go/platform-interface-breaking-changes for a discussion on why a less-clean interface is preferable to a breaking change. [1]: ../in_app_purchase [2]: lib/in_app_purchase_platform_interface.dart [3]: lib/in_app_purchase_platform_addition.dart ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/lib/in_app_purchase_platform_interface.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'src/errors/errors.dart'; export 'src/in_app_purchase_platform.dart'; export 'src/in_app_purchase_platform_addition.dart'; export 'src/in_app_purchase_platform_addition_provider.dart'; export 'src/types/types.dart'; ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/errors/errors.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'in_app_purchase_error.dart'; export 'in_app_purchase_exception.dart'; ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/errors/in_app_purchase_error.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Captures an error from the underlying purchase platform. /// /// The error can happen during the purchase, restoring a purchase, or querying product. /// Errors from restoring a purchase are not indicative of any errors during the original purchase. /// See also: /// * [ProductDetailsResponse] for error when querying product details. /// * [PurchaseDetails] for error happened in purchase. class IAPError { /// Creates a new IAP error object with the given error details. IAPError( {required this.source, required this.code, required this.message, this.details}); /// Which source is the error on. final String source; /// The error code. final String code; /// A human-readable error message. final String message; /// Error details, possibly null. final dynamic details; @override String toString() { return 'IAPError(code: $code, source: $source, message: $message, details: $details)'; } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/errors/in_app_purchase_exception.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Thrown to indicate that an action failed while interacting with the /// in_app_purchase plugin. class InAppPurchaseException implements Exception { /// Creates a [InAppPurchaseException] with the specified source and error /// [code] and optional [message]. InAppPurchaseException({ required this.source, required this.code, this.message, }) : assert(code != null); /// An error code. final String code; /// A human-readable error message, possibly null. final String? message; /// Which source is the error on. final String source; @override String toString() => 'InAppPurchaseException($code, $message, $source)'; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform.dart ================================================ // Copyright 2013 The Flutter 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:plugin_platform_interface/plugin_platform_interface.dart'; import 'types/types.dart'; /// The interface that implementations of in_app_purchase must implement. /// /// Platform implementations should extend this class rather than implement it as `in_app_purchase` /// does not consider newly added methods to be breaking changes. Extending this class /// (using `extends`) ensures that the subclass will get the default implementation, while /// platform implementations that `implements` this interface will be broken by newly added /// [InAppPurchasePlatform] methods. abstract class InAppPurchasePlatform extends PlatformInterface { /// Constructs a InAppPurchasePlatform. InAppPurchasePlatform() : super(token: _token); static final Object _token = Object(); /// The instance of [InAppPurchasePlatform] to use. /// /// Must be set before accessing. static InAppPurchasePlatform get instance => _instance; /// Platform-specific plugins should set this with their own platform-specific /// class that extends [InAppPurchasePlatform] when they register themselves. // TODO(amirh): Extract common platform interface logic. // https://github.com/flutter/flutter/issues/43368 static set instance(InAppPurchasePlatform instance) { PlatformInterface.verify(instance, _token); _instance = instance; } // Should only be accessed after setter is called. static late InAppPurchasePlatform _instance; /// Listen to this broadcast stream to get real time update for purchases. /// /// This stream will never close as long as the app is active. /// /// Purchase updates can happen in several situations: /// * When a purchase is triggered by user in the app. /// * When a purchase is triggered by user from the platform-specific store front. /// * When a purchase is restored on the device by the user in the app. /// * If a purchase is not completed ([completePurchase] is not called on the /// purchase object) from the last app session. Purchase updates will happen /// when a new app session starts instead. /// /// IMPORTANT! You must subscribe to this stream as soon as your app launches, /// preferably before returning your main App Widget in main(). Otherwise you /// will miss purchase updated made before this stream is subscribed to. /// /// We also recommend listening to the stream with one subscription at a given /// time. If you choose to have multiple subscription at the same time, you /// should be careful at the fact that each subscription will receive all the /// events after they start to listen. Stream> get purchaseStream => throw UnimplementedError('purchaseStream has not been implemented.'); /// Returns `true` if the payment platform is ready and available. Future isAvailable() => throw UnimplementedError('isAvailable() has not been implemented.'); /// Query product details for the given set of IDs. /// /// Identifiers in the underlying payment platform, for example, [App Store /// Connect](https://appstoreconnect.apple.com/) for iOS and [Google Play /// Console](https://play.google.com/) for Android. Future queryProductDetails(Set identifiers) => throw UnimplementedError( 'queryProductDetails() had not been implemented.'); /// Buy a non consumable product or subscription. /// /// Non consumable items can only be bought once. For example, a purchase that /// unlocks a special content in your app. Subscriptions are also non /// consumable products. /// /// You always need to restore all the non consumable products for user when /// they switch their phones. /// /// This method does not return the result of the purchase. Instead, after /// triggering this method, purchase updates will be sent to /// [purchaseStream]. You should [Stream.listen] to [purchaseStream] to get /// [PurchaseDetails] objects in different [PurchaseDetails.status] and update /// your UI accordingly. When the [PurchaseDetails.status] is /// [PurchaseStatus.purchased], [PurchaseStatus.restored] or /// [PurchaseStatus.error] you should deliver the content or handle the error, /// then call [completePurchase] to finish the purchasing process. /// /// This method does return whether or not the purchase request was initially /// sent successfully. /// /// Consumable items are defined differently by the different underlying /// payment platforms, and there's no way to query for whether or not the /// [ProductDetail] is a consumable at runtime. /// /// See also: /// /// * [buyConsumable], for buying a consumable product. /// * [restorePurchases], for restoring non consumable products. /// /// Calling this method for consumable items will cause unwanted behaviors! Future buyNonConsumable({required PurchaseParam purchaseParam}) => throw UnimplementedError('buyNonConsumable() has not been implemented.'); /// Buy a consumable product. /// /// Consumable items can be "consumed" to mark that they've been used and then /// bought additional times. For example, a health potion. /// /// To restore consumable purchases across devices, you should keep track of /// those purchase on your own server and restore the purchase for your users. /// Consumed products are no longer considered to be "owned" by payment /// platforms and will not be delivered by calling [restorePurchases]. /// /// Consumable items are defined differently by the different underlying /// payment platforms, and there's no way to query for whether or not the /// [ProductDetail] is a consumable at runtime. /// /// `autoConsume` is provided as a utility and will instruct the plugin to /// automatically consume the product after a succesful purchase. /// `autoConsume` is `true` by default. /// /// This method does not return the result of the purchase. Instead, after /// triggering this method, purchase updates will be sent to /// [purchaseStream]. You should [Stream.listen] to /// [purchaseStream] to get [PurchaseDetails] objects in different /// [PurchaseDetails.status] and update your UI accordingly. When the /// [PurchaseDetails.status] is [PurchaseStatus.purchased] or /// [PurchaseStatus.error], you should deliver the content or handle the /// error, then call [completePurchase] to finish the purchasing process. /// /// This method does return whether or not the purchase request was initially /// sent succesfully. /// /// See also: /// /// * [buyNonConsumable], for buying a non consumable product or /// subscription. /// * [restorePurchases], for restoring non consumable products. /// /// Calling this method for non consumable items will cause unwanted /// behaviors! Future buyConsumable({ required PurchaseParam purchaseParam, bool autoConsume = true, }) => throw UnimplementedError('buyConsumable() has not been implemented.'); /// Mark that purchased content has been delivered to the user. /// /// You are responsible for completing every [PurchaseDetails] whose /// [PurchaseDetails.status] is [PurchaseStatus.purchased] or /// [PurchaseStatus.restored]. /// Completing a [PurchaseStatus.pending] purchase will cause an exception. /// For convenience, [PurchaseDetails.pendingCompletePurchase] indicates if a /// purchase is pending for completion. /// /// The method will throw a [PurchaseException] when the purchase could not be /// finished. Depending on the [PurchaseException.errorCode] the developer /// should try to complete the purchase via this method again, or retry the /// [completePurchase] method at a later time. If the /// [PurchaseException.errorCode] indicates you should not retry there might /// be some issue with the app's code or the configuration of the app in the /// respective store. The developer is responsible to fix this issue. The /// [PurchaseException.message] field might provide more information on what /// went wrong. Future completePurchase(PurchaseDetails purchase) => throw UnimplementedError('completePurchase() has not been implemented.'); /// Restore all previous purchases. /// /// The `applicationUserName` should match whatever was sent in the initial /// `PurchaseParam`, if anything. If no `applicationUserName` was specified in the initial /// `PurchaseParam`, use `null`. /// /// Restored purchases are delivered through the [purchaseStream] with a /// status of [PurchaseStatus.restored]. You should listen for these purchases, /// validate their receipts, deliver the content and mark the purchase complete /// by calling the [finishPurchase] method for each purchase. /// /// This does not return consumed products. If you want to restore unused /// consumable products, you need to persist consumable product information /// for your user on your own server. /// /// See also: /// /// * [refreshPurchaseVerificationData], for reloading failed /// [PurchaseDetails.verificationData]. Future restorePurchases({String? applicationUserName}) => throw UnimplementedError('restorePurchases() has not been implemented.'); } ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform_addition.dart ================================================ // Copyright 2013 The Flutter 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 '../in_app_purchase_platform_interface.dart'; // ignore: avoid_classes_with_only_static_members /// The interface that platform implementations must implement when they want to /// provide platform-specific in_app_purchase features. /// /// Platforms that wants to introduce platform-specific public APIs should create /// a class that either extend or implements [InAppPurchasePlatformAddition]. Then set /// the [InAppPurchasePlatformAddition.instance] to an instance of that class. /// /// All the APIs added by [InAppPurchasePlatformAddition] implementations will be accessed from /// [InAppPurchasePlatformAdditionProvider.getPlatformAddition] by the client APPs. /// To avoid clients directly calling [InAppPurchasePlatform] APIs, /// an [InAppPurchasePlatformAddition] implementation should not be a type of [InAppPurchasePlatform]. abstract class InAppPurchasePlatformAddition { static InAppPurchasePlatformAddition? _instance; /// The instance containing the platform-specific in_app_purchase /// functionality. /// /// Returns `null` by default. /// /// To implement additional functionality extend /// [`InAppPurchasePlatformAddition`][3] with the platform-specific /// functionality, and when the plugin is registered, set the /// `InAppPurchasePlatformAddition.instance` with the new addition /// implementation instance. /// /// Example implementation might look like this: /// ```dart /// class InAppPurchaseMyPlatformAddition extends InAppPurchasePlatformAddition { /// Future myPlatformMethod() {} /// } /// ``` /// /// The following snippet shows how to register the `InAppPurchaseMyPlatformAddition`: /// ```dart /// class InAppPurchaseMyPlatformPlugin { /// static void registerWith(Registrar registrar) { /// // Register the platform-specific implementation of the idiomatic /// // InAppPurchase API. /// InAppPurchasePlatform.instance = InAppPurchaseMyPlatformPlugin(); /// /// // Register the [InAppPurchaseMyPlatformAddition] containing the /// // platform-specific functionality. /// InAppPurchasePlatformAddition.instance = InAppPurchaseMyPlatformAddition(); /// } /// } /// ``` static InAppPurchasePlatformAddition? get instance => _instance; /// Sets the instance to a desired [InAppPurchasePlatformAddition] implementation. /// /// The `instance` should not be a type of [InAppPurchasePlatform]. static set instance(InAppPurchasePlatformAddition? instance) { assert(instance is! InAppPurchasePlatform); _instance = instance; } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform_addition_provider.dart ================================================ // Copyright 2013 The Flutter 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 'in_app_purchase_platform_addition.dart'; /// The [InAppPurchasePlatformAdditionProvider] is responsible for providing /// a platform-specific [InAppPurchasePlatformAddition]. /// /// [InAppPurchasePlatformAddition] implementation contain platform-specific /// features that are not available from the platform idiomatic /// [InAppPurchasePlatform] API. abstract class InAppPurchasePlatformAdditionProvider { /// Provides a platform-specific implementation of the [InAppPurchasePlatformAddition] /// class. T getPlatformAddition(); } ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/types/product_details.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// The class represents the information of a product. class ProductDetails { /// Creates a new product details object with the provided details. ProductDetails({ required this.id, required this.title, required this.description, required this.price, required this.rawPrice, required this.currencyCode, this.currencySymbol = '', }); /// The identifier of the product. /// /// For example, on iOS it is specified in App Store Connect; on Android, it is specified in Google Play Console. final String id; /// The title of the product. /// /// For example, on iOS it is specified in App Store Connect; on Android, it is specified in Google Play Console. final String title; /// The description of the product. /// /// For example, on iOS it is specified in App Store Connect; on Android, it is specified in Google Play Console. final String description; /// The price of the product, formatted with currency symbol ("$0.99"). /// /// For example, on iOS it is specified in App Store Connect; on Android, it is specified in Google Play Console. final String price; /// The unformatted price of the product, specified in the App Store Connect or Sku in Google Play console based on the platform. /// The currency unit for this value can be found in the [currencyCode] property. /// The value always describes full units of the currency. (e.g. 2.45 in the case of $2.45) final double rawPrice; /// The currency code for the price of the product. /// Based on the price specified in the App Store Connect or Sku in Google Play console based on the platform. final String currencyCode; /// The currency symbol for the locale, e.g. $ for US locale. /// /// When the currency symbol cannot be determined, the ISO 4217 currency code is returned. final String currencySymbol; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/types/product_details_response.dart ================================================ // Copyright 2013 The Flutter 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 '../errors/in_app_purchase_error.dart'; import 'product_details.dart'; /// The response returned by [InAppPurchasePlatform.queryProductDetails]. /// /// A list of [ProductDetails] can be obtained from the this response. class ProductDetailsResponse { /// Creates a new [ProductDetailsResponse] with the provided response details. ProductDetailsResponse( {required this.productDetails, required this.notFoundIDs, this.error}); /// Each [ProductDetails] uniquely matches one valid identifier in [identifiers] of [InAppPurchasePlatform.queryProductDetails]. final List productDetails; /// The list of identifiers that are in the `identifiers` of [InAppPurchasePlatform.queryProductDetails] but failed to be fetched. /// /// There are multiple platform-specific reasons that product information could fail to be fetched, /// ranging from products not being correctly configured in the storefront to the queried IDs not existing. final List notFoundIDs; /// A caught platform exception thrown while querying the purchases. /// /// The value is `null` if there is no error. /// /// It's possible for this to be null but for there still to be notFoundIds in cases where the request itself was a success but the /// requested IDs could not be found. final IAPError? error; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/types/purchase_details.dart ================================================ // Copyright 2013 The Flutter 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 '../errors/in_app_purchase_error.dart'; import 'purchase_status.dart'; import 'purchase_verification_data.dart'; /// Represents the transaction details of a purchase. class PurchaseDetails { /// Creates a new PurchaseDetails object with the provided data. PurchaseDetails({ this.purchaseID, required this.productID, required this.verificationData, required this.transactionDate, required this.status, }); /// A unique identifier of the purchase. final String? purchaseID; /// The product identifier of the purchase. final String productID; /// The verification data of the purchase. /// /// Use this to verify the purchase. See [PurchaseVerificationData] for /// details on how to verify purchase use this data. You should never use any /// purchase data until verified. final PurchaseVerificationData verificationData; /// The timestamp of the transaction. /// /// Milliseconds since epoch. /// /// The value is `null` if [status] is not [PurchaseStatus.purchased]. final String? transactionDate; /// The status that this [PurchaseDetails] is currently on. PurchaseStatus status; /// The error details when the [status] is [PurchaseStatus.error]. /// /// The value is `null` if [status] is not [PurchaseStatus.error]. IAPError? error; /// The developer has to call [InAppPurchasePlatform.completePurchase] if the value is `true` /// and the product has been delivered to the user. /// /// The initial value is `false`. /// * See also [InAppPurchasePlatform.completePurchase] for more details on completing purchases. bool pendingCompletePurchase = false; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/types/purchase_param.dart ================================================ // Copyright 2013 The Flutter 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 'product_details.dart'; /// The parameter object for generating a purchase. class PurchaseParam { /// Creates a new purchase parameter object with the given data. PurchaseParam({ required this.productDetails, this.applicationUserName, }); /// The product to create payment for. /// /// It has to match one of the valid [ProductDetails] objects that you get from [ProductDetailsResponse] after calling [InAppPurchasePlatform.queryProductDetails]. final ProductDetails productDetails; /// An opaque id for the user's account that's unique to your app. (Optional) /// /// Used to help the store detect irregular activity. /// Do not pass in a clear text, your developer ID, the user’s Apple ID, or the /// user's Google ID for this field. /// For example, you can use a one-way hash of the user’s account name on your server. final String? applicationUserName; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/types/purchase_status.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Status for a [PurchaseDetails]. /// /// This is the type for [PurchaseDetails.status]. enum PurchaseStatus { /// The purchase process is pending. /// /// You can update UI to let your users know the purchase is pending. pending, /// The purchase is finished and successful. /// /// Update your UI to indicate the purchase is finished and deliver the product. purchased, /// Some error occurred in the purchase. The purchasing process if aborted. error, /// The purchase has been restored to the device. /// /// You should validate the purchase and if valid deliver the content. Once the /// content has been delivered or if the receipt is invalid you should finish /// the purchase by calling the `completePurchase` method. More information on /// verifying purchases can be found [here](https://pub.dev/packages/in_app_purchase#restoring-previous-purchases). restored, /// The purchase has been canceled. /// /// Update your UI to indicate the purchase is canceled. canceled, } ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/types/purchase_verification_data.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Represents the data that is used to verify purchases. /// /// The property [source] helps you to determine the method to verify purchases. /// Different source of purchase has different methods of verifying purchases. /// /// Both platforms have 2 ways to verify purchase data. You can either choose to /// verify the data locally using [localVerificationData] or verify the data /// using your own server with [serverVerificationData]. It is preferable to /// verify purchases using a server with [serverVerificationData]. /// /// You should never use any purchase data until verified. class PurchaseVerificationData { /// Creates a [PurchaseVerificationData] object with the provided information. PurchaseVerificationData({ required this.localVerificationData, required this.serverVerificationData, required this.source, }); /// The data used for local verification. /// /// The data is formatted according to the specifications of the respective /// store. You can use the [source] field to determine the store from which /// the data originated and proces the data accordingly. final String localVerificationData; /// The data used for server verification. final String serverVerificationData; /// Indicates the source of the purchase. final String source; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/types/types.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'product_details.dart'; export 'product_details_response.dart'; export 'purchase_details.dart'; export 'purchase_param.dart'; export 'purchase_status.dart'; export 'purchase_verification_data.dart'; ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/pubspec.yaml ================================================ name: in_app_purchase_platform_interface description: A common platform interface for the in_app_purchase plugin. repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase_platform_interface issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%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: 1.3.2 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.1.0 dev_dependencies: flutter_test: sdk: flutter mockito: ^5.0.0 ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/test/in_app_purchase_platform_test.dart ================================================ // Copyright 2013 The Flutter 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:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$InAppPurchasePlatform', () { test('Cannot be implemented with `implements`', () { expect(() { InAppPurchasePlatform.instance = ImplementsInAppPurchasePlatform(); // In versions of `package:plugin_platform_interface` prior to fixing // https://github.com/flutter/flutter/issues/109339, an attempt to // implement a platform interface using `implements` would sometimes // throw a `NoSuchMethodError` and other times throw an // `AssertionError`. After the issue is fixed, an `AssertionError` will // always be thrown. For the purpose of this test, we don't really care // what exception is thrown, so just allow any exception. }, throwsA(anything)); }); test('Can be extended', () { InAppPurchasePlatform.instance = ExtendsInAppPurchasePlatform(); }); test('Can be mocked with `implements`', () { InAppPurchasePlatform.instance = MockInAppPurchasePlatform(); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of purchaseStream should throw unimplemented error', () { final ExtendsInAppPurchasePlatform inAppPurchasePlatform = ExtendsInAppPurchasePlatform(); expect( () => inAppPurchasePlatform.purchaseStream, throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of isAvailable should throw unimplemented error', () { final ExtendsInAppPurchasePlatform inAppPurchasePlatform = ExtendsInAppPurchasePlatform(); expect( () => inAppPurchasePlatform.isAvailable(), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of queryProductDetails should throw unimplemented error', () { final ExtendsInAppPurchasePlatform inAppPurchasePlatform = ExtendsInAppPurchasePlatform(); expect( () => inAppPurchasePlatform.queryProductDetails({''}), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of buyNonConsumable should throw unimplemented error', () { final ExtendsInAppPurchasePlatform inAppPurchasePlatform = ExtendsInAppPurchasePlatform(); expect( () => inAppPurchasePlatform.buyNonConsumable( purchaseParam: MockPurchaseParam(), ), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of buyConsumable should throw unimplemented error', () { final ExtendsInAppPurchasePlatform inAppPurchasePlatform = ExtendsInAppPurchasePlatform(); expect( () => inAppPurchasePlatform.buyConsumable( purchaseParam: MockPurchaseParam(), ), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of completePurchase should throw unimplemented error', () { final ExtendsInAppPurchasePlatform inAppPurchasePlatform = ExtendsInAppPurchasePlatform(); expect( () => inAppPurchasePlatform.completePurchase(MockPurchaseDetails()), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of restorePurchases should throw unimplemented error', () { final ExtendsInAppPurchasePlatform inAppPurchasePlatform = ExtendsInAppPurchasePlatform(); expect( () => inAppPurchasePlatform.restorePurchases(), throwsUnimplementedError, ); }); }); group('$InAppPurchasePlatformAddition', () { setUp(() { InAppPurchasePlatformAddition.instance = null; }); test('Default instance is null', () { expect(InAppPurchasePlatformAddition.instance, isNull); }); test('Can be implemented.', () { InAppPurchasePlatformAddition.instance = ImplementsInAppPurchasePlatformAddition(); }); test('InAppPurchasePlatformAddition Can be extended', () { InAppPurchasePlatformAddition.instance = ExtendsInAppPurchasePlatformAddition(); }); test('Can not be a `InAppPurchasePlatform`', () { expect( () => InAppPurchasePlatformAddition.instance = ExtendsInAppPurchasePlatformAdditionIsPlatformInterface(), throwsAssertionError); }); test('Provider can provide', () { ImplementsInAppPurchasePlatformAdditionProvider.register(); final ImplementsInAppPurchasePlatformAdditionProvider provider = ImplementsInAppPurchasePlatformAdditionProvider(); final InAppPurchasePlatformAddition? addition = provider.getPlatformAddition(); expect(addition.runtimeType, ExtendsInAppPurchasePlatformAddition); }); test('Provider can provide `null`', () { final ImplementsInAppPurchasePlatformAdditionProvider provider = ImplementsInAppPurchasePlatformAdditionProvider(); final InAppPurchasePlatformAddition? addition = provider.getPlatformAddition(); expect(addition, isNull); }); }); } class ImplementsInAppPurchasePlatform implements InAppPurchasePlatform { @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } class MockInAppPurchasePlatform extends Mock with // ignore: prefer_mixin MockPlatformInterfaceMixin implements InAppPurchasePlatform {} class ExtendsInAppPurchasePlatform extends InAppPurchasePlatform {} class MockPurchaseParam extends Mock implements PurchaseParam {} class MockPurchaseDetails extends Mock implements PurchaseDetails {} class ImplementsInAppPurchasePlatformAddition implements InAppPurchasePlatformAddition { @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } class ExtendsInAppPurchasePlatformAddition extends InAppPurchasePlatformAddition {} class ImplementsInAppPurchasePlatformAdditionProvider implements InAppPurchasePlatformAdditionProvider { static void register() { InAppPurchasePlatformAddition.instance = ExtendsInAppPurchasePlatformAddition(); } @override T getPlatformAddition() { return InAppPurchasePlatformAddition.instance as T; } } class ExtendsInAppPurchasePlatformAdditionIsPlatformInterface extends InAppPurchasePlatform implements ExtendsInAppPurchasePlatformAddition {} ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/test/src/errors/in_app_purchase_error_test.dart ================================================ // Copyright 2013 The Flutter 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:in_app_purchase_platform_interface/src/errors/in_app_purchase_error.dart'; void main() { test('toString: Should return a description of the error', () { final IAPError exceptionNoDetails = IAPError( code: 'error_code', message: 'dummy_message', source: 'dummy_source', ); expect(exceptionNoDetails.toString(), 'IAPError(code: error_code, source: dummy_source, message: dummy_message, details: null)'); final IAPError exceptionWithDetails = IAPError( code: 'error_code', message: 'dummy_message', source: 'dummy_source', details: 'dummy_details', ); expect(exceptionWithDetails.toString(), 'IAPError(code: error_code, source: dummy_source, message: dummy_message, details: dummy_details)'); }); } ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/test/src/errors/in_app_purchase_exception_test.dart ================================================ // Copyright 2013 The Flutter 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:in_app_purchase_platform_interface/src/errors/in_app_purchase_exception.dart'; void main() { test('toString: Should return a description of the exception', () { final InAppPurchaseException exception = InAppPurchaseException( code: 'error_code', message: 'dummy message', source: 'dummy_source', ); // Act final String actual = exception.toString(); // Assert expect(actual, 'InAppPurchaseException(error_code, dummy message, dummy_source)'); }); } ================================================ FILE: packages/in_app_purchase/in_app_purchase_platform_interface/test/src/types/product_details_test.dart ================================================ // Copyright 2013 The Flutter 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:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; void main() { group('Constructor Tests', () { test( 'fromSkProduct should correctly parse data from a SKProductWrapper instance.', () { final ProductDetails productDetails = ProductDetails( id: 'id', title: 'title', description: 'description', price: '13.37', currencyCode: 'USD', currencySymbol: r'$', rawPrice: 13.37); expect(productDetails.id, 'id'); expect(productDetails.title, 'title'); expect(productDetails.description, 'description'); expect(productDetails.rawPrice, 13.37); expect(productDetails.currencyCode, 'USD'); expect(productDetails.currencySymbol, r'$'); }); }); group('PurchaseStatus Tests', () { test('PurchaseStatus should contain 5 options', () { const List values = PurchaseStatus.values; expect(values.length, 5); }); test('PurchaseStatus enum should have items in correct index', () { const List values = PurchaseStatus.values; expect(values[0], PurchaseStatus.pending); expect(values[1], PurchaseStatus.purchased); expect(values[2], PurchaseStatus.error); expect(values[3], PurchaseStatus.restored); expect(values[4], PurchaseStatus.canceled); }); }); } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/AUTHORS ================================================ # 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> Maurits van Beusekom ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md ================================================ ## 0.3.6 * Updates minimum Flutter version to 3.3 and iOS 11. ## 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. * Fixes iOS Promotional Offers bug which prevents them from working. ## 0.3.2+2 * Updates imports for `prefer_relative_imports`. ## 0.3.2+1 * Updates minimum Flutter version to 2.10. * Replaces deprecated ThemeData.primaryColor. ## 0.3.2 * Adds the `identifier` and `type` fields to the `SKProductDiscountWrapper` to reflect the changes in the [SKProductDiscount](https://developer.apple.com/documentation/storekit/skproductdiscount?language=objc) in iOS 12.2. ## 0.3.1+1 * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 0.3.1 * Adds ability to purchase more than one of a product. ## 0.3.0+10 * Ignores deprecation warnings for upcoming styleFrom button API changes. ## 0.3.0+9 * Updates references to the obsolete master branch. ## 0.3.0+8 * Fixes a memory leak on iOS. ## 0.3.0+7 * Minor fixes for new analysis options. ## 0.3.0+6 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.3.0+5 * Migrates from `ui.hash*` to `Object.hash*`. ## 0.3.0+4 * Ensures that `NSError` instances with an unexpected value for the `userInfo` field don't crash the app, but send an explanatory message instead. ## 0.3.0+3 * Implements transaction caching for StoreKit ensuring transactions are delivered to the Flutter client. ## 0.3.0+2 * Internal code cleanup for stricter analysis options. ## 0.3.0+1 * Removes dependency on `meta`. ## 0.3.0 * **BREAKING CHANGE:** `InAppPurchaseStoreKitPlatform.restorePurchase()` emits an empty instance of `List` when there were no transactions to restore, indicating that the restore procedure has finished. ## 0.2.1 * Renames `in_app_purchase_ios` to `in_app_purchase_storekit` to facilitate future macOS support. ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/LICENSE ================================================ 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. ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/README.md ================================================ # in\_app\_purchase\_storekit The iOS and macOS implementation of [`in_app_purchase`][1]. ## Usage This package has been [endorsed][2], meaning that you only need to add `in_app_purchase` as a dependency in your `pubspec.yaml`. This package will be automatically included in your app when you do. If you wish to use this package only, you can [add `in_app_purchase_storekit` directly][3]. ## Contributing This plugin uses [json_serializable](https://pub.dev/packages/json_serializable) for the many data structs passed between the underlying platform layers and Dart. After editing any of the serialized data structs, rebuild the serializers by running `flutter packages pub run build_runner build --delete-conflicting-outputs`. `flutter packages pub run build_runner watch --delete-conflicting-outputs` will watch the filesystem for changes. If you would like to contribute to the plugin, check out our [contribution guide](https://github.com/flutter/plugins/blob/main/CONTRIBUTING.md). [1]: ../in_app_purchase [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin [3]: https://pub.dev/packages/in_app_purchase_storekit/install ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/build.yaml ================================================ # See https://pub.dev/packages/build_config targets: $default: builders: json_serializable: options: any_map: true create_to_json: false ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import #import NS_ASSUME_NONNULL_BEGIN @interface FIAObjectTranslator : NSObject // Converts an instance of SKProduct into a dictionary. + (NSDictionary *)getMapFromSKProduct:(SKProduct *)product; // Converts an instance of SKProductSubscriptionPeriod into a dictionary. + (NSDictionary *)getMapFromSKProductSubscriptionPeriod:(SKProductSubscriptionPeriod *)period API_AVAILABLE(ios(11.2)); // Converts an instance of SKProductDiscount into a dictionary. + (NSDictionary *)getMapFromSKProductDiscount:(SKProductDiscount *)discount API_AVAILABLE(ios(11.2)); // Converts an array of SKProductDiscount instances into an array of dictionaries. + (nonnull NSArray *)getMapArrayFromSKProductDiscounts: (nonnull NSArray *)productDiscounts API_AVAILABLE(ios(12.2)); // Converts an instance of SKProductsResponse into a dictionary. + (NSDictionary *)getMapFromSKProductsResponse:(SKProductsResponse *)productResponse; // Converts an instance of SKPayment into a dictionary. + (NSDictionary *)getMapFromSKPayment:(SKPayment *)payment; // Converts an instance of NSLocale into a dictionary. + (NSDictionary *)getMapFromNSLocale:(NSLocale *)locale; // Creates an instance of the SKMutablePayment class based on the supplied dictionary. + (SKMutablePayment *)getSKMutablePaymentFromMap:(NSDictionary *)map; // Converts an instance of SKPaymentTransaction into a dictionary. + (NSDictionary *)getMapFromSKPaymentTransaction:(SKPaymentTransaction *)transaction; // Converts an instance of NSError into a dictionary. + (NSDictionary *)getMapFromNSError:(NSError *)error; // Converts an instance of SKStorefront into a dictionary. + (NSDictionary *)getMapFromSKStorefront:(SKStorefront *)storefront API_AVAILABLE(ios(13), macos(10.15), watchos(6.2)); // Converts the supplied instances of SKStorefront and SKPaymentTransaction into a dictionary. + (NSDictionary *)getMapFromSKStorefront:(SKStorefront *)storefront andSKPaymentTransaction:(SKPaymentTransaction *)transaction API_AVAILABLE(ios(13), macos(10.15), watchos(6.2)); // Creates an instance of the SKPaymentDiscount class based on the supplied dictionary. + (nullable SKPaymentDiscount *)getSKPaymentDiscountFromMap:(NSDictionary *)map withError:(NSString *_Nullable *_Nullable)error API_AVAILABLE(ios(12.2)); @end ; NS_ASSUME_NONNULL_END ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "FIAObjectTranslator.h" #pragma mark - SKProduct Coders @implementation FIAObjectTranslator + (NSDictionary *)getMapFromSKProduct:(SKProduct *)product { if (!product) { return nil; } NSMutableDictionary *map = [[NSMutableDictionary alloc] initWithDictionary:@{ @"localizedDescription" : product.localizedDescription ?: [NSNull null], @"localizedTitle" : product.localizedTitle ?: [NSNull null], @"productIdentifier" : product.productIdentifier ?: [NSNull null], @"price" : product.price.description ?: [NSNull null] }]; // TODO(cyanglaz): NSLocale is a complex object, want to see the actual need of getting this // expanded to a map. Matching android to only get the currencySymbol for now. // https://github.com/flutter/flutter/issues/26610 [map setObject:[FIAObjectTranslator getMapFromNSLocale:product.priceLocale] ?: [NSNull null] forKey:@"priceLocale"]; if (@available(iOS 11.2, *)) { [map setObject:[FIAObjectTranslator getMapFromSKProductSubscriptionPeriod:product.subscriptionPeriod] ?: [NSNull null] forKey:@"subscriptionPeriod"]; } if (@available(iOS 11.2, *)) { [map setObject:[FIAObjectTranslator getMapFromSKProductDiscount:product.introductoryPrice] ?: [NSNull null] forKey:@"introductoryPrice"]; } if (@available(iOS 12.2, *)) { [map setObject:[FIAObjectTranslator getMapArrayFromSKProductDiscounts:product.discounts] forKey:@"discounts"]; } if (@available(iOS 12.0, *)) { [map setObject:product.subscriptionGroupIdentifier ?: [NSNull null] forKey:@"subscriptionGroupIdentifier"]; } return map; } + (NSDictionary *)getMapFromSKProductSubscriptionPeriod:(SKProductSubscriptionPeriod *)period { if (!period) { return nil; } return @{@"numberOfUnits" : @(period.numberOfUnits), @"unit" : @(period.unit)}; } + (nonnull NSArray *)getMapArrayFromSKProductDiscounts: (nonnull NSArray *)productDiscounts { NSMutableArray *discountsMapArray = [[NSMutableArray alloc] init]; for (SKProductDiscount *productDiscount in productDiscounts) { [discountsMapArray addObject:[FIAObjectTranslator getMapFromSKProductDiscount:productDiscount]]; } return discountsMapArray; } + (NSDictionary *)getMapFromSKProductDiscount:(SKProductDiscount *)discount { if (!discount) { return nil; } NSMutableDictionary *map = [[NSMutableDictionary alloc] initWithDictionary:@{ @"price" : discount.price.description ?: [NSNull null], @"numberOfPeriods" : @(discount.numberOfPeriods), @"subscriptionPeriod" : [FIAObjectTranslator getMapFromSKProductSubscriptionPeriod:discount.subscriptionPeriod] ?: [NSNull null], @"paymentMode" : @(discount.paymentMode), }]; if (@available(iOS 12.2, *)) { [map setObject:discount.identifier ?: [NSNull null] forKey:@"identifier"]; [map setObject:@(discount.type) forKey:@"type"]; } // TODO(cyanglaz): NSLocale is a complex object, want to see the actual need of getting this // expanded to a map. Matching android to only get the currencySymbol for now. // https://github.com/flutter/flutter/issues/26610 [map setObject:[FIAObjectTranslator getMapFromNSLocale:discount.priceLocale] ?: [NSNull null] forKey:@"priceLocale"]; return map; } + (NSDictionary *)getMapFromSKProductsResponse:(SKProductsResponse *)productResponse { if (!productResponse) { return nil; } NSMutableArray *productsMapArray = [NSMutableArray new]; for (SKProduct *product in productResponse.products) { [productsMapArray addObject:[FIAObjectTranslator getMapFromSKProduct:product]]; } return @{ @"products" : productsMapArray, @"invalidProductIdentifiers" : productResponse.invalidProductIdentifiers ?: @[] }; } + (NSDictionary *)getMapFromSKPayment:(SKPayment *)payment { if (!payment) { return nil; } NSMutableDictionary *map = [[NSMutableDictionary alloc] initWithDictionary:@{ @"productIdentifier" : payment.productIdentifier ?: [NSNull null], @"requestData" : payment.requestData ? [[NSString alloc] initWithData:payment.requestData encoding:NSUTF8StringEncoding] : [NSNull null], @"quantity" : @(payment.quantity), @"applicationUsername" : payment.applicationUsername ?: [NSNull null] }]; [map setObject:@(payment.simulatesAskToBuyInSandbox) forKey:@"simulatesAskToBuyInSandbox"]; return map; } + (NSDictionary *)getMapFromNSLocale:(NSLocale *)locale { if (!locale) { return nil; } NSMutableDictionary *map = [[NSMutableDictionary alloc] init]; [map setObject:[locale objectForKey:NSLocaleCurrencySymbol] ?: [NSNull null] forKey:@"currencySymbol"]; [map setObject:[locale objectForKey:NSLocaleCurrencyCode] ?: [NSNull null] forKey:@"currencyCode"]; [map setObject:[locale objectForKey:NSLocaleCountryCode] ?: [NSNull null] forKey:@"countryCode"]; return map; } + (SKMutablePayment *)getSKMutablePaymentFromMap:(NSDictionary *)map { if (!map) { return nil; } SKMutablePayment *payment = [[SKMutablePayment alloc] init]; payment.productIdentifier = map[@"productIdentifier"]; NSString *utf8String = map[@"requestData"]; payment.requestData = [utf8String dataUsingEncoding:NSUTF8StringEncoding]; payment.quantity = [map[@"quantity"] integerValue]; payment.applicationUsername = map[@"applicationUsername"]; payment.simulatesAskToBuyInSandbox = [map[@"simulatesAskToBuyInSandbox"] boolValue]; return payment; } + (NSDictionary *)getMapFromSKPaymentTransaction:(SKPaymentTransaction *)transaction { if (!transaction) { return nil; } NSMutableDictionary *map = [[NSMutableDictionary alloc] initWithDictionary:@{ @"error" : [FIAObjectTranslator getMapFromNSError:transaction.error] ?: [NSNull null], @"payment" : transaction.payment ? [FIAObjectTranslator getMapFromSKPayment:transaction.payment] : [NSNull null], @"originalTransaction" : transaction.originalTransaction ? [FIAObjectTranslator getMapFromSKPaymentTransaction:transaction.originalTransaction] : [NSNull null], @"transactionTimeStamp" : transaction.transactionDate ? @(transaction.transactionDate.timeIntervalSince1970) : [NSNull null], @"transactionIdentifier" : transaction.transactionIdentifier ?: [NSNull null], @"transactionState" : @(transaction.transactionState) }]; return map; } + (NSDictionary *)getMapFromNSError:(NSError *)error { if (!error) { return nil; } NSMutableDictionary *userInfo = [NSMutableDictionary new]; for (NSErrorUserInfoKey key in error.userInfo) { id value = error.userInfo[key]; userInfo[key] = [FIAObjectTranslator encodeNSErrorUserInfo:value]; } return @{@"code" : @(error.code), @"domain" : error.domain ?: @"", @"userInfo" : userInfo}; } + (id)encodeNSErrorUserInfo:(id)value { if ([value isKindOfClass:[NSError class]]) { return [FIAObjectTranslator getMapFromNSError:value]; } else if ([value isKindOfClass:[NSURL class]]) { return [value absoluteString]; } else if ([value isKindOfClass:[NSNumber class]]) { return value; } else if ([value isKindOfClass:[NSString class]]) { return value; } else if ([value isKindOfClass:[NSArray class]]) { NSMutableArray *errors = [[NSMutableArray alloc] init]; for (id error in value) { [errors addObject:[FIAObjectTranslator encodeNSErrorUserInfo:error]]; } return errors; } else { return [NSString stringWithFormat: @"Unable to encode native userInfo object of type %@ to map. Please submit an issue at " @"https://github.com/flutter/flutter/issues/new with the title " @"\"[in_app_purchase_storekit] " @"Unable to encode userInfo of type %@\" and add reproduction steps and the error " @"details in " @"the description field.", [value class], [value class]]; } } + (NSDictionary *)getMapFromSKStorefront:(SKStorefront *)storefront { if (!storefront) { return nil; } NSMutableDictionary *map = [[NSMutableDictionary alloc] initWithDictionary:@{ @"countryCode" : storefront.countryCode, @"identifier" : storefront.identifier }]; return map; } + (NSDictionary *)getMapFromSKStorefront:(SKStorefront *)storefront andSKPaymentTransaction:(SKPaymentTransaction *)transaction { if (!storefront || !transaction) { return nil; } NSMutableDictionary *map = [[NSMutableDictionary alloc] initWithDictionary:@{ @"storefront" : [FIAObjectTranslator getMapFromSKStorefront:storefront], @"transaction" : [FIAObjectTranslator getMapFromSKPaymentTransaction:transaction] }]; return map; } + (SKPaymentDiscount *)getSKPaymentDiscountFromMap:(NSDictionary *)map withError:(NSString **)error { if (!map || map.count <= 0) { return nil; } NSString *identifier = map[@"identifier"]; NSString *keyIdentifier = map[@"keyIdentifier"]; NSString *nonce = map[@"nonce"]; NSString *signature = map[@"signature"]; NSNumber *timestamp = map[@"timestamp"]; if (!identifier || ![identifier isKindOfClass:NSString.class] || [identifier isEqualToString:@""]) { if (error) { *error = @"When specifying a payment discount the 'identifier' field is mandatory."; } return nil; } if (!keyIdentifier || ![keyIdentifier isKindOfClass:NSString.class] || [keyIdentifier isEqualToString:@""]) { if (error) { *error = @"When specifying a payment discount the 'keyIdentifier' field is mandatory."; } return nil; } if (!nonce || ![nonce isKindOfClass:NSString.class] || [nonce isEqualToString:@""]) { if (error) { *error = @"When specifying a payment discount the 'nonce' field is mandatory."; } return nil; } if (!signature || ![signature isKindOfClass:NSString.class] || [signature isEqualToString:@""]) { if (error) { *error = @"When specifying a payment discount the 'signature' field is mandatory."; } return nil; } if (!timestamp || ![timestamp isKindOfClass:NSNumber.class] || [timestamp longLongValue] <= 0) { if (error) { *error = @"When specifying a payment discount the 'timestamp' field is mandatory."; } return nil; } SKPaymentDiscount *discount = [[SKPaymentDiscount alloc] initWithIdentifier:identifier keyIdentifier:keyIdentifier nonce:[[NSUUID alloc] initWithUUIDString:nonce] signature:signature timestamp:timestamp]; return discount; } @end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #if TARGET_OS_OSX #import #else #import #endif #import #import NS_ASSUME_NONNULL_BEGIN API_AVAILABLE(ios(13)) API_UNAVAILABLE(tvos, macos, watchos) @interface FIAPPaymentQueueDelegate : NSObject - (id)initWithMethodChannel:(FlutterMethodChannel *)methodChannel; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.m ================================================ // Copyright 2013 The Flutter 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 "FIAPPaymentQueueDelegate.h" #import "FIAObjectTranslator.h" @interface FIAPPaymentQueueDelegate () @property(strong, nonatomic, readonly) FlutterMethodChannel *callbackChannel; @end @implementation FIAPPaymentQueueDelegate - (id)initWithMethodChannel:(FlutterMethodChannel *)methodChannel { self = [super init]; if (self) { _callbackChannel = methodChannel; } return self; } - (BOOL)paymentQueue:(SKPaymentQueue *)paymentQueue shouldContinueTransaction:(SKPaymentTransaction *)transaction inStorefront:(SKStorefront *)newStorefront { // Default return value for this method is true (see // https://developer.apple.com/documentation/storekit/skpaymentqueuedelegate/3521328-paymentqueueshouldshowpriceconse?language=objc) __block BOOL shouldContinue = YES; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [self.callbackChannel invokeMethod:@"shouldContinueTransaction" arguments:[FIAObjectTranslator getMapFromSKStorefront:newStorefront andSKPaymentTransaction:transaction] result:^(id _Nullable result) { // When result is a valid instance of NSNumber use it to determine // if the transaction should continue. Otherwise use the default // value. if (result && [result isKindOfClass:[NSNumber class]]) { shouldContinue = [(NSNumber *)result boolValue]; } dispatch_semaphore_signal(semaphore); }]; // The client should respond within 1 second otherwise continue // with default value. dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC)); return shouldContinue; } #if TARGET_OS_IOS - (BOOL)paymentQueueShouldShowPriceConsent:(SKPaymentQueue *)paymentQueue { // Default return value for this method is true (see // https://developer.apple.com/documentation/storekit/skpaymentqueuedelegate/3521328-paymentqueueshouldshowpriceconse?language=objc) __block BOOL shouldShowPriceConsent = YES; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [self.callbackChannel invokeMethod:@"shouldShowPriceConsent" arguments:nil result:^(id _Nullable result) { // When result is a valid instance of NSNumber use it to determine // if the transaction should continue. Otherwise use the default // value. if (result && [result isKindOfClass:[NSNumber class]]) { shouldShowPriceConsent = [(NSNumber *)result boolValue]; } dispatch_semaphore_signal(semaphore); }]; // The client should respond within 1 second otherwise continue // with default value. dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC)); return shouldShowPriceConsent; } #endif @end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import NS_ASSUME_NONNULL_BEGIN @class FlutterError; @interface FIAPReceiptManager : NSObject - (nullable NSString *)retrieveReceiptWithError:(FlutterError *_Nullable *_Nullable)error; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m ================================================ // Copyright 2013 The Flutter 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 "FIAPReceiptManager.h" #if TARGET_OS_OSX #import #else #import #endif #import "FIAObjectTranslator.h" @interface FIAPReceiptManager () // Gets the receipt file data from the location of the url. Can be nil if // there is an error. This interface is defined so it can be stubbed for testing. - (NSData *)getReceiptData:(NSURL *)url error:(NSError **)error; @end @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) { if (flutterError) { NSDictionary *errorMap = [FIAObjectTranslator getMapFromNSError:receiptError]; *flutterError = [FlutterError errorWithCode:errorMap[@"code"] message:errorMap[@"domain"] details:errorMap[@"userInfo"]]; } return nil; } return [receipt base64EncodedStringWithOptions:kNilOptions]; } - (NSData *)getReceiptData:(NSURL *)url error:(NSError **)error { return [NSData dataWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:error]; } @end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import #import NS_ASSUME_NONNULL_BEGIN typedef void (^ProductRequestCompletion)(SKProductsResponse *_Nullable response, NSError *_Nullable errror); @interface FIAPRequestHandler : NSObject - (instancetype)initWithRequest:(SKRequest *)request; - (void)startProductRequestWithCompletionHandler:(ProductRequestCompletion)completion; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m ================================================ // Copyright 2013 The Flutter 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 "FIAPRequestHandler.h" #import #pragma mark - Main Handler @interface FIAPRequestHandler () @property(copy, nonatomic) ProductRequestCompletion completion; @property(strong, nonatomic) SKRequest *request; @end @implementation FIAPRequestHandler - (instancetype)initWithRequest:(SKRequest *)request { self = [super init]; if (self) { self.request = request; request.delegate = self; } return self; } - (void)startProductRequestWithCompletionHandler:(ProductRequestCompletion)completion { self.completion = completion; [self.request start]; } - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { if (self.completion) { self.completion(response, nil); // set the completion to nil here so self.completion won't be triggered again in // requestDidFinish for SKProductRequest. self.completion = nil; } } - (void)requestDidFinish:(SKRequest *)request { if (self.completion) { self.completion(nil, nil); } } - (void)request:(SKRequest *)request didFailWithError:(NSError *)error { if (self.completion) { self.completion(nil, error); } } @end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h ================================================ // Copyright 2013 The Flutter 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 "FIATransactionCache.h" @class SKPaymentTransaction; NS_ASSUME_NONNULL_BEGIN typedef void (^TransactionsUpdated)(NSArray *transactions); typedef void (^TransactionsRemoved)(NSArray *transactions); typedef void (^RestoreTransactionFailed)(NSError *error); typedef void (^RestoreCompletedTransactionsFinished)(void); typedef BOOL (^ShouldAddStorePayment)(SKPayment *payment, SKProduct *product); typedef void (^UpdatedDownloads)(NSArray *downloads); @interface FIAPaymentQueueHandler : NSObject @property(NS_NONATOMIC_IOSONLY, weak, nullable) id delegate API_AVAILABLE( ios(13.0), macos(10.15), watchos(6.2)); /// Creates a new FIAPaymentQueueHandler initialized with an empty /// FIATransactionCache. /// /// @param queue The SKPaymentQueue instance connected to the App Store and /// responsible for processing transactions. /// @param transactionsUpdated Callback method that is called each time the App /// Store indicates transactions are updated. /// @param transactionsRemoved Callback method that is called each time the App /// Store indicates transactions are removed. /// @param restoreTransactionFailed Callback method that is called each time /// the App Store indicates transactions failed /// to restore. /// @param restoreCompletedTransactionsFinished Callback method that is called /// each time the App Store /// indicates restoring of /// transactions has finished. /// @param shouldAddStorePayment Callback method that is called each time an /// in-app purchase has been initiated from the /// App Store. /// @param updatedDownloads Callback method that is called each time the App /// Store indicates downloads are updated. - (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed restoreCompletedTransactionsFinished: (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment updatedDownloads:(nullable UpdatedDownloads)updatedDownloads DEPRECATED_MSG_ATTRIBUTE( "Use the " "'initWithQueue:transactionsUpdated:transactionsRemoved:restoreTransactionsFinished:" "shouldAddStorePayment:updatedDownloads:transactionCache:' message instead."); /// Creates a new FIAPaymentQueueHandler. /// /// The "transactionsUpdated", "transactionsRemoved" and "updatedDownloads" /// callbacks are only called while actively observing transactions. To start /// observing transactions send the "startObservingPaymentQueue" message. /// Sending the "stopObservingPaymentQueue" message will stop actively /// observing transactions. When transactions are not observed they are cached /// to the "transactionCache" and will be delivered via the /// "transactionsUpdated", "transactionsRemoved" and "updatedDownloads" /// callbacks as soon as the "startObservingPaymentQueue" message arrives. /// /// Note: cached transactions that are not processed when the application is /// killed will be delivered again by the App Store as soon as the application /// starts again. /// /// @param queue The SKPaymentQueue instance connected to the App Store and /// responsible for processing transactions. /// @param transactionsUpdated Callback method that is called each time the App /// Store indicates transactions are updated. /// @param transactionsRemoved Callback method that is called each time the App /// Store indicates transactions are removed. /// @param restoreTransactionFailed Callback method that is called each time /// the App Store indicates transactions failed /// to restore. /// @param restoreCompletedTransactionsFinished Callback method that is called /// each time the App Store /// indicates restoring of /// transactions has finished. /// @param shouldAddStorePayment Callback method that is called each time an /// in-app purchase has been initiated from the /// App Store. /// @param updatedDownloads Callback method that is called each time the App /// Store indicates downloads are updated. /// @param transactionCache An empty [FIATransactionCache] instance that is /// responsible for keeping track of transactions that /// arrive when not actively observing transactions. - (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed restoreCompletedTransactionsFinished: (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment updatedDownloads:(nullable UpdatedDownloads)updatedDownloads transactionCache:(nonnull FIATransactionCache *)transactionCache; // Can throw exceptions if the transaction type is purchasing, should always used in a @try block. - (void)finishTransaction:(nonnull SKPaymentTransaction *)transaction; - (void)restoreTransactions:(nullable NSString *)applicationName; - (void)presentCodeRedemptionSheet API_UNAVAILABLE(tvos, macos, watchos); - (NSArray *)getUnfinishedTransactions; // This method needs to be called before any other methods. - (void)startObservingPaymentQueue; // Call this method when the Flutter app is no longer listening - (void)stopObservingPaymentQueue; // Appends a payment to the SKPaymentQueue. // // @param payment Payment object to be added to the payment queue. // @return whether "addPayment" was successful. - (BOOL)addPayment:(SKPayment *)payment; // Displays the price consent sheet. // // The price consent sheet is only displayed when the following // is true: // - You have increased the price of the subscription in App Store Connect. // - The subscriber has not yet responded to a price consent query. // Otherwise the method has no effect. - (void)showPriceConsentIfNeeded API_AVAILABLE(ios(13.4))API_UNAVAILABLE(tvos, macos, watchos); @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m ================================================ // Copyright 2013 The Flutter 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 "FIAPaymentQueueHandler.h" #import "FIAPPaymentQueueDelegate.h" #import "FIATransactionCache.h" @interface FIAPaymentQueueHandler () /// The SKPaymentQueue instance connected to the App Store and responsible for processing /// transactions. @property(strong, nonatomic) SKPaymentQueue *queue; /// Callback method that is called each time the App Store indicates transactions are updated. @property(nullable, copy, nonatomic) TransactionsUpdated transactionsUpdated; /// Callback method that is called each time the App Store indicates transactions are removed. @property(nullable, copy, nonatomic) TransactionsRemoved transactionsRemoved; /// Callback method that is called each time the App Store indicates transactions failed to restore. @property(nullable, copy, nonatomic) RestoreTransactionFailed restoreTransactionFailed; /// Callback method that is called each time the App Store indicates restoring of transactions has /// finished. @property(nullable, copy, nonatomic) RestoreCompletedTransactionsFinished paymentQueueRestoreCompletedTransactionsFinished; /// Callback method that is called each time an in-app purchase has been initiated from the App /// Store. @property(nullable, copy, nonatomic) ShouldAddStorePayment shouldAddStorePayment; /// Callback method that is called each time the App Store indicates downloads are updated. @property(nullable, copy, nonatomic) UpdatedDownloads updatedDownloads; /// The transaction cache responsible for caching transactions. /// /// Keeps track of transactions that arrive when the Flutter client is not /// actively observing for transactions. @property(strong, nonatomic, nonnull) FIATransactionCache *transactionCache; /// Indicates if the Flutter client is observing transactions. /// /// When the client is not observing, transactions are cached and send to the /// client as soon as it starts observing. The Flutter client can start /// observing by sending a startObservingPaymentQueue message and stop by /// sending a stopObservingPaymentQueue message. @property(atomic, assign, readwrite, getter=isObservingTransactions) BOOL observingTransactions; @end @implementation FIAPaymentQueueHandler - (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed restoreCompletedTransactionsFinished: (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment updatedDownloads:(nullable UpdatedDownloads)updatedDownloads { return [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:transactionsUpdated transactionRemoved:transactionsRemoved restoreTransactionFailed:restoreTransactionFailed restoreCompletedTransactionsFinished:restoreCompletedTransactionsFinished shouldAddStorePayment:shouldAddStorePayment updatedDownloads:updatedDownloads transactionCache:[[FIATransactionCache alloc] init]]; } - (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed restoreCompletedTransactionsFinished: (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment updatedDownloads:(nullable UpdatedDownloads)updatedDownloads transactionCache:(nonnull FIATransactionCache *)transactionCache { self = [super init]; if (self) { _queue = queue; _transactionsUpdated = transactionsUpdated; _transactionsRemoved = transactionsRemoved; _restoreTransactionFailed = restoreTransactionFailed; _paymentQueueRestoreCompletedTransactionsFinished = restoreCompletedTransactionsFinished; _shouldAddStorePayment = shouldAddStorePayment; _updatedDownloads = updatedDownloads; _transactionCache = transactionCache; [_queue addTransactionObserver:self]; if (@available(iOS 13.0, macOS 10.15, *)) { queue.delegate = self.delegate; } } return self; } - (void)startObservingPaymentQueue { self.observingTransactions = YES; [self processCachedTransactions]; } - (void)stopObservingPaymentQueue { // When the client stops observing transaction, the transaction observer is // not removed from the SKPaymentQueue. The FIAPaymentQueueHandler will cache // trasnactions in memory when the client is not observing, allowing the app // to process these transactions if it starts observing again during the same // lifetime of the app. // // If the app is killed, cached transactions will be removed from memory; // however, the App Store will re-deliver the transactions as soon as the app // is started again, since the cached transactions have not been acknowledged // by the client (by sending the `finishTransaction` message). self.observingTransactions = NO; } - (void)processCachedTransactions { NSArray *cachedObjects = [self.transactionCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]; if (cachedObjects.count != 0) { self.transactionsUpdated(cachedObjects); } cachedObjects = [self.transactionCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]; if (cachedObjects.count != 0) { self.updatedDownloads(cachedObjects); } cachedObjects = [self.transactionCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]; if (cachedObjects.count != 0) { self.transactionsRemoved(cachedObjects); } [self.transactionCache clear]; } - (BOOL)addPayment:(SKPayment *)payment { for (SKPaymentTransaction *transaction in self.queue.transactions) { if ([transaction.payment.productIdentifier isEqualToString:payment.productIdentifier]) { return NO; } } [self.queue addPayment:payment]; return YES; } - (void)finishTransaction:(SKPaymentTransaction *)transaction { [self.queue finishTransaction:transaction]; } - (void)restoreTransactions:(nullable NSString *)applicationName { if (applicationName) { [self.queue restoreCompletedTransactionsWithApplicationUsername:applicationName]; } else { [self.queue restoreCompletedTransactions]; } } #if TARGET_OS_IOS - (void)presentCodeRedemptionSheet { if (@available(iOS 14, *)) { [self.queue presentCodeRedemptionSheet]; } else { NSLog(@"presentCodeRedemptionSheet is only available on iOS 14 or newer"); } } #endif #if TARGET_OS_IOS - (void)showPriceConsentIfNeeded { [self.queue showPriceConsentIfNeeded]; } #endif #pragma mark - observing // Sent when the transaction array has changed (additions or state changes). Client should check // state of transactions and finish as appropriate. - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { if (!self.observingTransactions) { [_transactionCache addObjects:transactions forKey:TransactionCacheKeyUpdatedTransactions]; return; } // notify dart through callbacks. self.transactionsUpdated(transactions); } // Sent when transactions are removed from the queue (via finishTransaction:). - (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions { if (!self.observingTransactions) { [_transactionCache addObjects:transactions forKey:TransactionCacheKeyRemovedTransactions]; return; } self.transactionsRemoved(transactions); } // Sent when an error is encountered while adding transactions from the user's purchase history back // to the queue. - (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error { self.restoreTransactionFailed(error); } // Sent when all transactions from the user's purchase history have successfully been added back to // the queue. - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue { self.paymentQueueRestoreCompletedTransactionsFinished(); } // Sent when the download state has changed. - (void)paymentQueue:(SKPaymentQueue *)queue updatedDownloads:(NSArray *)downloads { if (!self.observingTransactions) { [_transactionCache addObjects:downloads forKey:TransactionCacheKeyUpdatedDownloads]; return; } self.updatedDownloads(downloads); } // Sent when a user initiates an IAP buy from the App Store - (BOOL)paymentQueue:(SKPaymentQueue *)queue shouldAddStorePayment:(SKPayment *)payment forProduct:(SKProduct *)product { return (self.shouldAddStorePayment(payment, product)); } - (NSArray *)getUnfinishedTransactions { return self.queue.transactions; } @end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIATransactionCache.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSUInteger, TransactionCacheKey) { TransactionCacheKeyUpdatedDownloads, TransactionCacheKeyUpdatedTransactions, TransactionCacheKeyRemovedTransactions }; @interface FIATransactionCache : NSObject /// Adds objects to the transaction cache. /// /// If the cache already contains an array of objects on the specified key, the supplied /// array will be appended to the existing array. - (void)addObjects:(NSArray *)objects forKey:(TransactionCacheKey)key; /// Gets the array of objects stored at the given key. /// /// If there are no objects associated with the given key nil is returned. - (NSArray *)getObjectsForKey:(TransactionCacheKey)key; /// Removes all objects from the transaction cache. - (void)clear; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIATransactionCache.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "FIATransactionCache.h" @interface FIATransactionCache () /// A NSMutableDictionary storing the objects that are cached. @property(nonatomic, strong, nonnull) NSMutableDictionary *cache; @end @implementation FIATransactionCache - (instancetype)init { self = [super init]; if (self) { self.cache = [[NSMutableDictionary alloc] init]; } return self; } - (void)addObjects:(NSArray *)objects forKey:(TransactionCacheKey)key { NSArray *cachedObjects = self.cache[@(key)]; self.cache[@(key)] = cachedObjects ? [cachedObjects arrayByAddingObjectsFromArray:objects] : objects; } - (NSArray *)getObjectsForKey:(TransactionCacheKey)key { return self.cache[@(key)]; } - (void)clear { [self.cache removeAllObjects]; } @end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #if TARGET_OS_OSX #import #else #import #endif @class FIAPaymentQueueHandler; @class FIAPReceiptManager; @interface InAppPurchasePlugin : NSObject @property(strong, nonatomic) FIAPaymentQueueHandler *paymentQueueHandler; - (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_UNAVAILABLE; @end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "InAppPurchasePlugin.h" #import #import "FIAObjectTranslator.h" #import "FIAPPaymentQueueDelegate.h" #import "FIAPReceiptManager.h" #import "FIAPRequestHandler.h" #import "FIAPaymentQueueHandler.h" @interface InAppPurchasePlugin () // Holding strong references to FIAPRequestHandlers. Remove the handlers from the set after // the request is finished. @property(strong, nonatomic, readonly) NSMutableSet *requestHandlers; // After querying the product, the available products will be saved in the map to be used // for purchase. @property(strong, nonatomic, readonly) NSMutableDictionary *productsCache; // Callback channel to dart used for when a function from the transaction observer is triggered. @property(strong, nonatomic, readonly) FlutterMethodChannel *transactionObserverCallbackChannel; // Callback channel to dart used for when a function from the payment queue delegate is triggered. @property(strong, nonatomic, readonly) FlutterMethodChannel *paymentQueueDelegateCallbackChannel; @property(strong, nonatomic, readonly) NSObject *registrar; @property(strong, nonatomic, readonly) FIAPReceiptManager *receiptManager; @property(strong, nonatomic, readonly) FIAPPaymentQueueDelegate *paymentQueueDelegate API_AVAILABLE(ios(13)) API_UNAVAILABLE(tvos, macos, watchos); @end @implementation InAppPurchasePlugin + (void)registerWithRegistrar:(NSObject *)registrar { FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/in_app_purchase" binaryMessenger:[registrar messenger]]; InAppPurchasePlugin *instance = [[InAppPurchasePlugin alloc] initWithRegistrar:registrar]; [registrar addMethodCallDelegate:instance channel:channel]; } - (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager { self = [super init]; _receiptManager = receiptManager; _requestHandlers = [NSMutableSet new]; _productsCache = [NSMutableDictionary new]; return self; } - (instancetype)initWithRegistrar:(NSObject *)registrar { self = [self initWithReceiptManager:[FIAPReceiptManager new]]; _registrar = registrar; __weak typeof(self) weakSelf = self; _paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueue defaultQueue] transactionsUpdated:^(NSArray *_Nonnull transactions) { [weakSelf handleTransactionsUpdated:transactions]; } transactionRemoved:^(NSArray *_Nonnull transactions) { [weakSelf handleTransactionsRemoved:transactions]; } restoreTransactionFailed:^(NSError *_Nonnull error) { [weakSelf handleTransactionRestoreFailed:error]; } restoreCompletedTransactionsFinished:^{ [weakSelf restoreCompletedTransactionsFinished]; } shouldAddStorePayment:^BOOL(SKPayment *payment, SKProduct *product) { return [weakSelf shouldAddStorePayment:payment product:product]; } updatedDownloads:^void(NSArray *_Nonnull downloads) { [weakSelf updatedDownloads:downloads]; } transactionCache:[[FIATransactionCache alloc] init]]; _transactionObserverCallbackChannel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/in_app_purchase" binaryMessenger:[registrar messenger]]; return self; } - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if ([@"-[SKPaymentQueue canMakePayments:]" isEqualToString:call.method]) { [self canMakePayments:result]; } else if ([@"-[SKPaymentQueue transactions]" isEqualToString:call.method]) { [self getPendingTransactions:result]; } else if ([@"-[InAppPurchasePlugin startProductRequest:result:]" isEqualToString:call.method]) { [self handleProductRequestMethodCall:call result:result]; } else if ([@"-[InAppPurchasePlugin addPayment:result:]" isEqualToString:call.method]) { [self addPayment:call result:result]; } else if ([@"-[InAppPurchasePlugin finishTransaction:result:]" isEqualToString:call.method]) { [self finishTransaction:call result:result]; } else if ([@"-[InAppPurchasePlugin restoreTransactions:result:]" isEqualToString:call.method]) { [self restoreTransactions:call result:result]; #if TARGET_OS_IOS } else if ([@"-[InAppPurchasePlugin presentCodeRedemptionSheet:result:]" isEqualToString:call.method]) { [self presentCodeRedemptionSheet:call result:result]; #endif } else if ([@"-[InAppPurchasePlugin retrieveReceiptData:result:]" isEqualToString:call.method]) { [self retrieveReceiptData:call result:result]; } else if ([@"-[InAppPurchasePlugin refreshReceipt:result:]" isEqualToString:call.method]) { [self refreshReceipt:call result:result]; } else if ([@"-[SKPaymentQueue startObservingTransactionQueue]" isEqualToString:call.method]) { [self startObservingPaymentQueue:result]; } else if ([@"-[SKPaymentQueue stopObservingTransactionQueue]" isEqualToString:call.method]) { [self stopObservingPaymentQueue:result]; #if TARGET_OS_IOS } else if ([@"-[SKPaymentQueue registerDelegate]" isEqualToString:call.method]) { [self registerPaymentQueueDelegate:result]; #endif } else if ([@"-[SKPaymentQueue removeDelegate]" isEqualToString:call.method]) { [self removePaymentQueueDelegate:result]; #if TARGET_OS_IOS } else if ([@"-[SKPaymentQueue showPriceConsentIfNeeded]" isEqualToString:call.method]) { [self showPriceConsentIfNeeded:result]; #endif } else { result(FlutterMethodNotImplemented); } } - (void)canMakePayments:(FlutterResult)result { result(@([SKPaymentQueue canMakePayments])); } - (void)getPendingTransactions:(FlutterResult)result { NSArray *transactions = [self.paymentQueueHandler getUnfinishedTransactions]; NSMutableArray *transactionMaps = [[NSMutableArray alloc] init]; for (SKPaymentTransaction *transaction in transactions) { [transactionMaps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:transaction]]; } result(transactionMaps); } - (void)handleProductRequestMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if (![call.arguments isKindOfClass:[NSArray class]]) { result([FlutterError errorWithCode:@"storekit_invalid_argument" message:@"Argument type of startRequest is not array" details:call.arguments]); return; } NSArray *productIdentifiers = (NSArray *)call.arguments; SKProductsRequest *request = [self getProductRequestWithIdentifiers:[NSSet setWithArray:productIdentifiers]]; FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request]; [self.requestHandlers addObject:handler]; __weak typeof(self) weakSelf = self; [handler startProductRequestWithCompletionHandler:^(SKProductsResponse *_Nullable response, NSError *_Nullable error) { if (error) { result([FlutterError errorWithCode:@"storekit_getproductrequest_platform_error" message:error.localizedDescription details:error.description]); return; } if (!response) { result([FlutterError errorWithCode:@"storekit_platform_no_response" message:@"Failed to get SKProductResponse in startRequest " @"call. Error occured on iOS platform" details:call.arguments]); return; } for (SKProduct *product in response.products) { [self.productsCache setObject:product forKey:product.productIdentifier]; } result([FIAObjectTranslator getMapFromSKProductsResponse:response]); [weakSelf.requestHandlers removeObject:handler]; }]; } - (void)addPayment:(FlutterMethodCall *)call result:(FlutterResult)result { if (![call.arguments isKindOfClass:[NSDictionary class]]) { result([FlutterError errorWithCode:@"storekit_invalid_argument" message:@"Argument type of addPayment is not a Dictionary" details:call.arguments]); return; } NSDictionary *paymentMap = (NSDictionary *)call.arguments; NSString *productID = [paymentMap objectForKey:@"productIdentifier"]; // When a product is already fetched, we create a payment object with // the product to process the payment. SKProduct *product = [self getProduct:productID]; if (!product) { result([FlutterError errorWithCode:@"storekit_invalid_payment_object" message: @"You have requested a payment for an invalid product. Either the " @"`productIdentifier` of the payment is not valid or the product has not been " @"fetched before adding the payment to the payment queue." details:call.arguments]); return; } SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product]; payment.applicationUsername = [paymentMap objectForKey:@"applicationUsername"]; NSNumber *quantity = [paymentMap objectForKey:@"quantity"]; payment.quantity = (quantity != nil) ? quantity.integerValue : 1; NSNumber *simulatesAskToBuyInSandbox = [paymentMap objectForKey:@"simulatesAskToBuyInSandbox"]; payment.simulatesAskToBuyInSandbox = (id)simulatesAskToBuyInSandbox == (id)[NSNull null] ? NO : [simulatesAskToBuyInSandbox boolValue]; if (@available(iOS 12.2, *)) { NSDictionary *paymentDiscountMap = [self getNonNullValueFromDictionary:paymentMap forKey:@"paymentDiscount"]; NSString *error = nil; SKPaymentDiscount *paymentDiscount = [FIAObjectTranslator getSKPaymentDiscountFromMap:paymentDiscountMap withError:&error]; if (error) { result([FlutterError errorWithCode:@"storekit_invalid_payment_discount_object" message:[NSString stringWithFormat:@"You have requested a payment and specified a " @"payment discount with invalid properties. %@", error] details:call.arguments]); return; } payment.paymentDiscount = paymentDiscount; } if (![self.paymentQueueHandler addPayment:payment]) { result([FlutterError errorWithCode:@"storekit_duplicate_product_object" message:@"There is a pending transaction for the same product identifier. Please " @"either wait for it to be finished or finish it manually using " @"`completePurchase` to avoid edge cases." details:call.arguments]); return; } result(nil); } - (void)finishTransaction:(FlutterMethodCall *)call result:(FlutterResult)result { if (![call.arguments isKindOfClass:[NSDictionary class]]) { result([FlutterError errorWithCode:@"storekit_invalid_argument" message:@"Argument type of finishTransaction is not a Dictionary" details:call.arguments]); return; } NSDictionary *paymentMap = (NSDictionary *)call.arguments; NSString *transactionIdentifier = [paymentMap objectForKey:@"transactionIdentifier"]; NSString *productIdentifier = [paymentMap objectForKey:@"productIdentifier"]; NSArray *pendingTransactions = [self.paymentQueueHandler getUnfinishedTransactions]; for (SKPaymentTransaction *transaction in pendingTransactions) { // If the user cancels the purchase dialog we won't have a transactionIdentifier. // So if it is null AND a transaction in the pendingTransactions list has // also a null transactionIdentifier we check for equal product identifiers. if ([transaction.transactionIdentifier isEqualToString:transactionIdentifier] || ([transactionIdentifier isEqual:[NSNull null]] && transaction.transactionIdentifier == nil && [transaction.payment.productIdentifier isEqualToString:productIdentifier])) { @try { [self.paymentQueueHandler finishTransaction:transaction]; } @catch (NSException *e) { result([FlutterError errorWithCode:@"storekit_finish_transaction_exception" message:e.name details:e.description]); return; } } } result(nil); } - (void)restoreTransactions:(FlutterMethodCall *)call result:(FlutterResult)result { if (call.arguments && ![call.arguments isKindOfClass:[NSString class]]) { result([FlutterError errorWithCode:@"storekit_invalid_argument" message:@"Argument is not nil and the type of finishTransaction is not a string." details:call.arguments]); return; } [self.paymentQueueHandler restoreTransactions:call.arguments]; result(nil); } #if TARGET_OS_IOS - (void)presentCodeRedemptionSheet:(FlutterMethodCall *)call result:(FlutterResult)result { [self.paymentQueueHandler presentCodeRedemptionSheet]; result(nil); } #endif - (void)retrieveReceiptData:(FlutterMethodCall *)call result:(FlutterResult)result { FlutterError *error = nil; NSString *receiptData = [self.receiptManager retrieveReceiptWithError:&error]; if (error) { result(error); return; } result(receiptData); } - (void)refreshReceipt:(FlutterMethodCall *)call result:(FlutterResult)result { NSDictionary *arguments = call.arguments; SKReceiptRefreshRequest *request; if (arguments) { if (![arguments isKindOfClass:[NSDictionary class]]) { result([FlutterError errorWithCode:@"storekit_invalid_argument" message:@"Argument type of startRequest is not array" details:call.arguments]); return; } NSMutableDictionary *properties = [NSMutableDictionary new]; properties[SKReceiptPropertyIsExpired] = arguments[@"isExpired"]; properties[SKReceiptPropertyIsRevoked] = arguments[@"isRevoked"]; properties[SKReceiptPropertyIsVolumePurchase] = arguments[@"isVolumePurchase"]; request = [self getRefreshReceiptRequest:properties]; } else { request = [self getRefreshReceiptRequest:nil]; } FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request]; [self.requestHandlers addObject:handler]; __weak typeof(self) weakSelf = self; [handler startProductRequestWithCompletionHandler:^(SKProductsResponse *_Nullable response, NSError *_Nullable error) { if (error) { result([FlutterError errorWithCode:@"storekit_refreshreceiptrequest_platform_error" message:error.localizedDescription details:error.description]); return; } result(nil); [weakSelf.requestHandlers removeObject:handler]; }]; } - (void)startObservingPaymentQueue:(FlutterResult)result { [_paymentQueueHandler startObservingPaymentQueue]; result(nil); } - (void)stopObservingPaymentQueue:(FlutterResult)result { [_paymentQueueHandler stopObservingPaymentQueue]; result(nil); } #if TARGET_OS_IOS - (void)registerPaymentQueueDelegate:(FlutterResult)result { if (@available(iOS 13.0, *)) { _paymentQueueDelegateCallbackChannel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/in_app_purchase_payment_queue_delegate" binaryMessenger:[_registrar messenger]]; _paymentQueueDelegate = [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:_paymentQueueDelegateCallbackChannel]; _paymentQueueHandler.delegate = _paymentQueueDelegate; } result(nil); } #endif - (void)removePaymentQueueDelegate:(FlutterResult)result { if (@available(iOS 13.0, *)) { _paymentQueueHandler.delegate = nil; } _paymentQueueDelegate = nil; _paymentQueueDelegateCallbackChannel = nil; result(nil); } #if TARGET_OS_IOS - (void)showPriceConsentIfNeeded:(FlutterResult)result { if (@available(iOS 13.4, *)) { [_paymentQueueHandler showPriceConsentIfNeeded]; } result(nil); } #endif - (id)getNonNullValueFromDictionary:(NSDictionary *)dictionary forKey:(NSString *)key { id value = dictionary[key]; return [value isKindOfClass:[NSNull class]] ? nil : value; } #pragma mark - transaction observer: - (void)handleTransactionsUpdated:(NSArray *)transactions { NSMutableArray *maps = [NSMutableArray new]; for (SKPaymentTransaction *transaction in transactions) { [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:transaction]]; } [self.transactionObserverCallbackChannel invokeMethod:@"updatedTransactions" arguments:maps]; } - (void)handleTransactionsRemoved:(NSArray *)transactions { NSMutableArray *maps = [NSMutableArray new]; for (SKPaymentTransaction *transaction in transactions) { [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:transaction]]; } [self.transactionObserverCallbackChannel invokeMethod:@"removedTransactions" arguments:maps]; } - (void)handleTransactionRestoreFailed:(NSError *)error { [self.transactionObserverCallbackChannel invokeMethod:@"restoreCompletedTransactionsFailed" arguments:[FIAObjectTranslator getMapFromNSError:error]]; } - (void)restoreCompletedTransactionsFinished { [self.transactionObserverCallbackChannel invokeMethod:@"paymentQueueRestoreCompletedTransactionsFinished" arguments:nil]; } - (void)updatedDownloads:(NSArray *)downloads { NSLog(@"Received an updatedDownloads callback, but downloads are not supported."); } - (BOOL)shouldAddStorePayment:(SKPayment *)payment product:(SKProduct *)product { // We always return NO here. And we send the message to dart to process the payment; and we will // have a interception method that deciding if the payment should be processed (implemented by the // programmer). [self.productsCache setObject:product forKey:product.productIdentifier]; [self.transactionObserverCallbackChannel invokeMethod:@"shouldAddStorePayment" arguments:@{ @"payment" : [FIAObjectTranslator getMapFromSKPayment:payment], @"product" : [FIAObjectTranslator getMapFromSKProduct:product] }]; return NO; } #pragma mark - dependency injection (for unit testing) - (SKProductsRequest *)getProductRequestWithIdentifiers:(NSSet *)identifiers { return [[SKProductsRequest alloc] initWithProductIdentifiers:identifiers]; } - (SKProduct *)getProduct:(NSString *)productID { return [self.productsCache objectForKey:productID]; } - (SKReceiptRefreshRequest *)getRefreshReceiptRequest:(NSDictionary *)properties { return [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:properties]; } @end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| s.name = 'in_app_purchase_storekit' s.version = '0.0.1' s.summary = 'Flutter In App Purchase iOS and macOS' s.description = <<-DESC A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store. 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/in_app_purchase/in_app_purchase_storekit' } # TODO(mvanbeusekom): update URL when in_app_purchase_storekit package is published. # Updating it before the package is published will cause a lint error and block the tree. s.documentation_url = 'https://pub.dev/packages/in_app_purchase' s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.ios.dependency 'Flutter' s.osx.dependency 'FlutterMacOS' s.ios.deployment_target = '11.0' s.osx.deployment_target = '10.15' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/README.md ================================================ # 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. ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/integration_test/in_app_purchase_test.dart ================================================ // Copyright 2013 The Flutter 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:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; import 'package:integration_test/integration_test.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('Can create InAppPurchaseStoreKit instance', (WidgetTester tester) async { InAppPurchaseStoreKitPlatform.registerPlatform(); final InAppPurchasePlatform androidPlatform = InAppPurchasePlatform.instance; expect(androidPlatform, isNotNull); }); } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 11.0 ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Flutter/Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Flutter/Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths # Matches in_app_purchase test_spec dependency. pod 'OCMock', '~> 3.6' end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "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" } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit ================================================ { "identifier" : "6073E9A3", "nonRenewingSubscriptions" : [ ], "products" : [ { "displayPrice" : "0.99", "familyShareable" : false, "internalID" : "AE10D05D", "localizations" : [ { "description" : "A consumable product.", "displayName" : "Consumable", "locale" : "en_US" } ], "productID" : "consumable", "referenceName" : "consumable", "type" : "Consumable" }, { "displayPrice" : "10.99", "familyShareable" : false, "internalID" : "FABCF067", "localizations" : [ { "description" : "An non-consumable product.", "displayName" : "Upgrade", "locale" : "en_US" } ], "productID" : "upgrade", "referenceName" : "upgrade", "type" : "NonConsumable" } ], "settings" : { }, "subscriptionGroups" : [ { "id" : "D0FEE8D8", "localizations" : [ ], "name" : "Example Subscriptions", "subscriptions" : [ { "adHocOffers" : [ ], "displayPrice" : "4.99", "familyShareable" : false, "groupNumber" : 1, "internalID" : "922EB597", "introductoryOffer" : null, "localizations" : [ { "description" : "A lower level subscription.", "displayName" : "Subscription Silver", "locale" : "en_US" } ], "productID" : "subscription_silver", "recurringSubscriptionPeriod" : "P1W", "referenceName" : "subscription_silver", "subscriptionGroupID" : "D0FEE8D8", "type" : "RecurringSubscription" }, { "adHocOffers" : [ ], "displayPrice" : "5.99", "familyShareable" : false, "groupNumber" : 2, "internalID" : "0BC7FF5E", "introductoryOffer" : null, "localizations" : [ { "description" : "A higher level subscription.", "displayName" : "Subscription Gold", "locale" : "en_US" } ], "productID" : "subscription_gold", "recurringSubscriptionPeriod" : "P1M", "referenceName" : "subscription_gold", "subscriptionGroupID" : "D0FEE8D8", "type" : "RecurringSubscription" } ] } ], "version" : { "major" : 1, "minor" : 1 } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName in_app_purchase_example CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 0FFCF66105590202CD84C7AA /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1630769A874F9381BC761FE1 /* libPods-Runner.a */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 688DE35121F2A5A100EA2684 /* TranslatorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 688DE35021F2A5A100EA2684 /* TranslatorTests.m */; }; 6896B34621E9363700D37AEF /* ProductRequestHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6896B34521E9363700D37AEF /* ProductRequestHandlerTests.m */; }; 6896B34C21EEB4B800D37AEF /* Stubs.m in Sources */ = {isa = PBXBuildFile; fileRef = 6896B34B21EEB4B800D37AEF /* Stubs.m */; }; 7E34217B7715B1918134647A /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 18D02AB334F1C07BB9A4374A /* libPods-RunnerTests.a */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; A5279298219369C600FF69E6 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5279297219369C600FF69E6 /* StoreKit.framework */; }; A59001A721E69658004A3E5E /* InAppPurchasePluginTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A59001A621E69658004A3E5E /* InAppPurchasePluginTests.m */; }; F67646F82681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */; }; F6995BDD27CF73000050EA78 /* FIATransactionCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F6995BDC27CF73000050EA78 /* FIATransactionCacheTests.m */; }; F78AF3142342BC89008449C7 /* PaymentQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F78AF3132342BC89008449C7 /* PaymentQueueTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ A59001A921E69658004A3E5E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 10B860DFD91A1DF639D7BE1D /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 1630769A874F9381BC761FE1 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 18D02AB334F1C07BB9A4374A /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 2550EB3A5A3E749A54ADCA2D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 688DE35021F2A5A100EA2684 /* TranslatorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TranslatorTests.m; sourceTree = ""; }; 6896B34521E9363700D37AEF /* ProductRequestHandlerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ProductRequestHandlerTests.m; sourceTree = ""; }; 6896B34A21EEB4B800D37AEF /* Stubs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Stubs.h; sourceTree = ""; }; 6896B34B21EEB4B800D37AEF /* Stubs.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Stubs.m; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9D681E092EB0D20D652F69FC /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; A5279297219369C600FF69E6 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; A59001A421E69658004A3E5E /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A59001A621E69658004A3E5E /* InAppPurchasePluginTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InAppPurchasePluginTests.m; sourceTree = ""; }; A59001A821E69658004A3E5E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E4F9651425A612301059769C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIAPPaymentQueueDeleteTests.m; sourceTree = ""; }; F6995BDC27CF73000050EA78 /* FIATransactionCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIATransactionCacheTests.m; sourceTree = ""; }; F6E5D5F926131C4800C68BED /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Configuration.storekit; sourceTree = ""; }; F78AF3132342BC89008449C7 /* PaymentQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PaymentQueueTests.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( A5279298219369C600FF69E6 /* StoreKit.framework in Frameworks */, 0FFCF66105590202CD84C7AA /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; A59001A121E69658004A3E5E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 7E34217B7715B1918134647A /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 0B4403AC68C3196AECF5EF89 /* Pods */ = { isa = PBXGroup; children = ( E4F9651425A612301059769C /* Pods-Runner.debug.xcconfig */, 2550EB3A5A3E749A54ADCA2D /* Pods-Runner.release.xcconfig */, 9D681E092EB0D20D652F69FC /* Pods-RunnerTests.debug.xcconfig */, 10B860DFD91A1DF639D7BE1D /* Pods-RunnerTests.release.xcconfig */, ); path = Pods; sourceTree = ""; }; 334733E826680E5900DCC49E /* Temp */ = { isa = PBXGroup; children = ( ); path = Temp; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 334733E826680E5900DCC49E /* Temp */, 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, A59001A521E69658004A3E5E /* RunnerTests */, 97C146EF1CF9000F007C117D /* Products */, E4DB99639FAD8ADED6B572FC /* Frameworks */, 0B4403AC68C3196AECF5EF89 /* Pods */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, A59001A421E69658004A3E5E /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, F6E5D5F926131C4800C68BED /* Configuration.storekit */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; A59001A521E69658004A3E5E /* RunnerTests */ = { isa = PBXGroup; children = ( A59001A821E69658004A3E5E /* Info.plist */, 6896B34A21EEB4B800D37AEF /* Stubs.h */, 6896B34B21EEB4B800D37AEF /* Stubs.m */, A59001A621E69658004A3E5E /* InAppPurchasePluginTests.m */, 6896B34521E9363700D37AEF /* ProductRequestHandlerTests.m */, F78AF3132342BC89008449C7 /* PaymentQueueTests.m */, 688DE35021F2A5A100EA2684 /* TranslatorTests.m */, F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */, F6995BDC27CF73000050EA78 /* FIATransactionCacheTests.m */, ); path = RunnerTests; sourceTree = ""; }; E4DB99639FAD8ADED6B572FC /* Frameworks */ = { isa = PBXGroup; children = ( A5279297219369C600FF69E6 /* StoreKit.framework */, 1630769A874F9381BC761FE1 /* libPods-Runner.a */, 18D02AB334F1C07BB9A4374A /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( EDD921296E29F853F7B69716 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; A59001A321E69658004A3E5E /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = A59001AD21E69658004A3E5E /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( 95C7A5986B77A8DF76F6DF3A /* [CP] Check Pods Manifest.lock */, A59001A021E69658004A3E5E /* Sources */, A59001A121E69658004A3E5E /* Frameworks */, A59001A221E69658004A3E5E /* Resources */, ); buildRules = ( ); dependencies = ( A59001AA21E69658004A3E5E /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = A59001A421E69658004A3E5E /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { DefaultBuildSystemTypeForWorkspace = Original; LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; SystemCapabilities = { com.apple.InAppPurchase = { enabled = 1; }; }; }; A59001A321E69658004A3E5E = { CreatedOnToolsVersion = 10.0; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, A59001A321E69658004A3E5E /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; A59001A221E69658004A3E5E /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 95C7A5986B77A8DF76F6DF3A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 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"; }; EDD921296E29F853F7B69716 /* [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", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; A59001A021E69658004A3E5E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F78AF3142342BC89008449C7 /* PaymentQueueTests.m in Sources */, F67646F82681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m in Sources */, 6896B34621E9363700D37AEF /* ProductRequestHandlerTests.m in Sources */, 688DE35121F2A5A100EA2684 /* TranslatorTests.m in Sources */, F6995BDD27CF73000050EA78 /* FIATransactionCacheTests.m in Sources */, A59001A721E69658004A3E5E /* InAppPurchasePluginTests.m in Sources */, 6896B34C21EEB4B800D37AEF /* Stubs.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ A59001AA21E69658004A3E5E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = A59001A921E69658004A3E5E /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.inAppPurchaseExample; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.inAppPurchaseExample; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; A59001AB21E69658004A3E5E /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9D681E092EB0D20D652F69FC /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Debug; }; A59001AC21E69658004A3E5E /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 10B860DFD91A1DF639D7BE1D /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; A59001AD21E69658004A3E5E /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( A59001AB21E69658004A3E5E /* Debug */, A59001AC21E69658004A3E5E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/lib/consumable_store.dart ================================================ // Copyright 2013 The Flutter 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:shared_preferences/shared_preferences.dart'; // ignore: avoid_classes_with_only_static_members /// A store of consumable items. /// /// This is a development prototype that stores consumables in the shared /// preferences. Do not use this in real world apps. class ConsumableStore { static const String _kPrefKey = 'consumables'; static Future _writes = Future.value(); /// Adds a consumable with ID `id` to the store. /// /// The consumable is only added after the returned Future is complete. static Future save(String id) { _writes = _writes.then((void _) => _doSave(id)); return _writes; } /// Consumes a consumable with ID `id` from the store. /// /// The consumable was only consumed after the returned Future is complete. static Future consume(String id) { _writes = _writes.then((void _) => _doConsume(id)); return _writes; } /// Returns the list of consumables from the store. static Future> load() async { return (await SharedPreferences.getInstance()).getStringList(_kPrefKey) ?? []; } static Future _doSave(String id) async { final List cached = await load(); final SharedPreferences prefs = await SharedPreferences.getInstance(); cached.add(id); await prefs.setStringList(_kPrefKey, cached); } static Future _doConsume(String id) async { final List cached = await load(); final SharedPreferences prefs = await SharedPreferences.getInstance(); cached.remove(id); await prefs.setStringList(_kPrefKey, cached); } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/lib/example_payment_queue_delegate.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; /// Example implementation of the /// [`SKPaymentQueueDelegate`](https://developer.apple.com/documentation/storekit/skpaymentqueuedelegate?language=objc). /// /// The payment queue delegate can be implementated to provide information /// needed to complete transactions. class ExamplePaymentQueueDelegate implements SKPaymentQueueDelegateWrapper { @override bool shouldContinueTransaction( SKPaymentTransactionWrapper transaction, SKStorefrontWrapper storefront) { return true; } @override bool shouldShowPriceConsent() { return false; } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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/material.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; import 'consumable_store.dart'; import 'example_payment_queue_delegate.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); // When using the Android plugin directly it is mandatory to register // the plugin as default instance as part of initializing the app. InAppPurchaseStoreKitPlatform.registerPlatform(); runApp(_MyApp()); } const String _kConsumableId = 'consumable'; const String _kUpgradeId = 'upgrade'; const String _kSilverSubscriptionId = 'subscription_silver'; const String _kGoldSubscriptionId = 'subscription_gold'; const List _kProductIds = [ _kConsumableId, _kUpgradeId, _kSilverSubscriptionId, _kGoldSubscriptionId, ]; class _MyApp extends StatefulWidget { @override State<_MyApp> createState() => _MyAppState(); } class _MyAppState extends State<_MyApp> { final InAppPurchaseStoreKitPlatform _iapStoreKitPlatform = InAppPurchasePlatform.instance as InAppPurchaseStoreKitPlatform; final InAppPurchaseStoreKitPlatformAddition _iapStoreKitPlatformAddition = InAppPurchasePlatformAddition.instance! as InAppPurchaseStoreKitPlatformAddition; late StreamSubscription> _subscription; List _notFoundIds = []; List _products = []; List _purchases = []; List _consumables = []; bool _isAvailable = false; bool _purchasePending = false; bool _loading = true; String? _queryProductError; @override void initState() { final Stream> purchaseUpdated = _iapStoreKitPlatform.purchaseStream; _subscription = purchaseUpdated.listen((List purchaseDetailsList) { _listenToPurchaseUpdated(purchaseDetailsList); }, onDone: () { _subscription.cancel(); }, onError: (Object error) { // handle error here. }); // Register the example payment queue delegate _iapStoreKitPlatformAddition.setDelegate(ExamplePaymentQueueDelegate()); initStoreInfo(); super.initState(); } Future initStoreInfo() async { final bool isAvailable = await _iapStoreKitPlatform.isAvailable(); if (!isAvailable) { setState(() { _isAvailable = isAvailable; _products = []; _purchases = []; _notFoundIds = []; _consumables = []; _purchasePending = false; _loading = false; }); return; } final ProductDetailsResponse productDetailResponse = await _iapStoreKitPlatform.queryProductDetails(_kProductIds.toSet()); if (productDetailResponse.error != null) { setState(() { _queryProductError = productDetailResponse.error!.message; _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _purchases = []; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = []; _purchasePending = false; _loading = false; }); return; } if (productDetailResponse.productDetails.isEmpty) { setState(() { _queryProductError = null; _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _purchases = []; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = []; _purchasePending = false; _loading = false; }); return; } final List consumables = await ConsumableStore.load(); setState(() { _isAvailable = isAvailable; _products = productDetailResponse.productDetails; _notFoundIds = productDetailResponse.notFoundIDs; _consumables = consumables; _purchasePending = false; _loading = false; }); } @override void dispose() { _subscription.cancel(); super.dispose(); } @override Widget build(BuildContext context) { final List stack = []; if (_queryProductError == null) { stack.add( ListView( children: [ _buildConnectionCheckTile(), _buildProductList(), _buildConsumableBox(), _buildRestoreButton(), ], ), ); } else { stack.add(Center( child: Text(_queryProductError!), )); } if (_purchasePending) { stack.add( // TODO(goderbauer): Make this const when that's available on stable. // ignore: prefer_const_constructors Stack( children: const [ Opacity( opacity: 0.3, child: ModalBarrier(dismissible: false, color: Colors.grey), ), Center( child: CircularProgressIndicator(), ), ], ), ); } return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('IAP Example'), ), body: Stack( children: stack, ), ), ); } Card _buildConnectionCheckTile() { if (_loading) { return const Card(child: ListTile(title: Text('Trying to connect...'))); } final Widget storeHeader = ListTile( leading: Icon(_isAvailable ? Icons.check : Icons.block, color: _isAvailable ? Colors.green : ThemeData.light().colorScheme.error), title: Text('The store is ${_isAvailable ? 'available' : 'unavailable'}.'), ); final List children = [storeHeader]; if (!_isAvailable) { children.addAll([ const Divider(), ListTile( title: Text('Not connected', style: TextStyle(color: ThemeData.light().colorScheme.error)), subtitle: const Text( 'Unable to connect to the payments processor. Has this app been configured correctly? See the example README for instructions.'), ), ]); } return Card(child: Column(children: children)); } Card _buildProductList() { if (_loading) { return const Card( child: ListTile( leading: CircularProgressIndicator(), title: Text('Fetching products...'))); } if (!_isAvailable) { return const Card(); } const ListTile productHeader = ListTile(title: Text('Products for Sale')); final List productList = []; if (_notFoundIds.isNotEmpty) { productList.add(ListTile( title: Text('[${_notFoundIds.join(", ")}] not found', style: TextStyle(color: ThemeData.light().colorScheme.error)), subtitle: const Text( 'This app needs special configuration to run. Please see example/README.md for instructions.'))); } // This loading previous purchases code is just a demo. Please do not use this as it is. // In your app you should always verify the purchase data using the `verificationData` inside the [PurchaseDetails] object before trusting it. // We recommend that you use your own server to verify the purchase data. final Map purchases = Map.fromEntries( _purchases.map((PurchaseDetails purchase) { if (purchase.pendingCompletePurchase) { _iapStoreKitPlatform.completePurchase(purchase); } return MapEntry(purchase.productID, purchase); })); productList.addAll(_products.map( (ProductDetails productDetails) { final PurchaseDetails? previousPurchase = purchases[productDetails.id]; return ListTile( title: Text( productDetails.title, ), subtitle: Text( productDetails.description, ), trailing: previousPurchase != null ? IconButton( onPressed: () { _iapStoreKitPlatformAddition.showPriceConsentIfNeeded(); }, icon: const Icon(Icons.upgrade)) : TextButton( style: TextButton.styleFrom( backgroundColor: Colors.green[800], // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: Colors.white, ), onPressed: () { final PurchaseParam purchaseParam = PurchaseParam( productDetails: productDetails, ); if (productDetails.id == _kConsumableId) { _iapStoreKitPlatform.buyConsumable( purchaseParam: purchaseParam); } else { _iapStoreKitPlatform.buyNonConsumable( purchaseParam: purchaseParam); } }, child: Text(productDetails.price), )); }, )); return Card( child: Column( children: [productHeader, const Divider()] + productList)); } Card _buildConsumableBox() { if (_loading) { return const Card( child: ListTile( leading: CircularProgressIndicator(), title: Text('Fetching consumables...'))); } if (!_isAvailable || _notFoundIds.contains(_kConsumableId)) { return const Card(); } const ListTile consumableHeader = ListTile(title: Text('Purchased consumables')); final List tokens = _consumables.map((String id) { return GridTile( child: IconButton( icon: const Icon( Icons.stars, size: 42.0, color: Colors.orange, ), splashColor: Colors.yellowAccent, onPressed: () => consume(id), ), ); }).toList(); return Card( child: Column(children: [ consumableHeader, const Divider(), GridView.count( crossAxisCount: 5, shrinkWrap: true, padding: const EdgeInsets.all(16.0), children: tokens, ) ])); } Widget _buildRestoreButton() { if (_loading) { return Container(); } return Padding( padding: const EdgeInsets.all(4.0), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( style: TextButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.primary, // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724 // ignore: deprecated_member_use primary: Colors.white, ), onPressed: () => _iapStoreKitPlatform.restorePurchases(), child: const Text('Restore purchases'), ), ], ), ); } Future consume(String id) async { await ConsumableStore.consume(id); final List consumables = await ConsumableStore.load(); setState(() { _consumables = consumables; }); } void showPendingUI() { setState(() { _purchasePending = true; }); } Future deliverProduct(PurchaseDetails purchaseDetails) async { // IMPORTANT!! Always verify purchase details before delivering the product. if (purchaseDetails.productID == _kConsumableId) { await ConsumableStore.save(purchaseDetails.purchaseID!); final List consumables = await ConsumableStore.load(); setState(() { _purchasePending = false; _consumables = consumables; }); } else { setState(() { _purchases.add(purchaseDetails); _purchasePending = false; }); } } void handleError(IAPError error) { setState(() { _purchasePending = false; }); } Future _verifyPurchase(PurchaseDetails purchaseDetails) { // IMPORTANT!! Always verify a purchase before delivering the product. // For the purpose of an example, we directly return true. return Future.value(true); } void _handleInvalidPurchase(PurchaseDetails purchaseDetails) { // handle invalid purchase here if _verifyPurchase` failed. } void _listenToPurchaseUpdated(List purchaseDetailsList) { purchaseDetailsList.forEach(_handleReportedPurchaseState); } Future _handleReportedPurchaseState( PurchaseDetails purchaseDetails) async { if (purchaseDetails.status == PurchaseStatus.pending) { showPendingUI(); } else { if (purchaseDetails.status == PurchaseStatus.error) { handleError(purchaseDetails.error!); } else if (purchaseDetails.status == PurchaseStatus.purchased || purchaseDetails.status == PurchaseStatus.restored) { final bool valid = await _verifyPurchase(purchaseDetails); if (valid) { await deliverProduct(purchaseDetails); } else { _handleInvalidPurchase(purchaseDetails); return; } } if (purchaseDetails.pendingCompletePurchase) { await _iapStoreKitPlatform.completePurchase(purchaseDetails); } } } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/macos/Flutter/Flutter-Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/macos/Flutter/Flutter-Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/macos/Podfile ================================================ platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_macos_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths pod 'OCMock', '~> 3.6' end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) end end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner/AppDelegate.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner/Base.lproj/MainMenu.xib ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner/Configs/AppInfo.xcconfig ================================================ // Application-level settings for the Runner target. // // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the // future. If not, the values below would default to using the project name when this becomes a // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. PRODUCT_NAME = example // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.example // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2022 dev.flutter.plugins. All rights reserved. ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner/DebugProfile.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner/MainFlutterWindow.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { let flutterViewController = FlutterViewController.init() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 51; objects = { /* Begin PBXAggregateTarget section */ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { isa = PBXAggregateTarget; buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; buildPhases = ( 33CC111E2044C6BF0003C045 /* ShellScript */, ); dependencies = ( ); name = "Flutter Assemble"; productName = FLX; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; A2C6CD5797E6A6721FDBCA1C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 36DEEA66738F64D983F76848 /* Pods_Runner.framework */; }; C51E64432925727D7AC7BBFF /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE8A421F08C80BE6E90142D5 /* Pods_RunnerTests.framework */; }; F79BDC102905FBE300E3999D /* FIAPPaymentQueueDeleteTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F79BDC0F2905FBE300E3999D /* FIAPPaymentQueueDeleteTests.m */; }; F79BDC122905FBF700E3999D /* FIATransactionCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F79BDC112905FBF700E3999D /* FIATransactionCacheTests.m */; }; F79BDC142905FBFE00E3999D /* InAppPurchasePluginTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F79BDC132905FBFE00E3999D /* InAppPurchasePluginTests.m */; }; F79BDC182905FC1800E3999D /* PaymentQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F79BDC172905FC1800E3999D /* PaymentQueueTests.m */; }; F79BDC1A2905FC1F00E3999D /* ProductRequestHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F79BDC192905FC1F00E3999D /* ProductRequestHandlerTests.m */; }; F79BDC1C2905FC3200E3999D /* Stubs.m in Sources */ = {isa = PBXBuildFile; fileRef = F79BDC1B2905FC3200E3999D /* Stubs.m */; }; F79BDC1E2905FC3900E3999D /* TranslatorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F79BDC1D2905FC3900E3999D /* TranslatorTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC111A2044C6BA0003C045; remoteInfo = FLX; }; F700DD0628E652A10004836B /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC10EC2044A3C60003C045; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 33CC110E2044A8840003C045 /* Bundle Framework */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Bundle Framework"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 36DEEA66738F64D983F76848 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4E423AE82F466005587C3567 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 5E5D46173E3025B0DB32A1BE /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 5EBC5A8BA44B08330BA605AB /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 62F1680C5AE033907C1DF7AB /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; 9A4FEABF1DEF0D106FEB7974 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; B6C8FD76BB3278AA51FED870 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; EE8A421F08C80BE6E90142D5 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F700DD0228E652A10004836B /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F79BDC0F2905FBE300E3999D /* FIAPPaymentQueueDeleteTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIAPPaymentQueueDeleteTests.m; path = ../../shared/RunnerTests/FIAPPaymentQueueDeleteTests.m; sourceTree = ""; }; F79BDC112905FBF700E3999D /* FIATransactionCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIATransactionCacheTests.m; path = ../../shared/RunnerTests/FIATransactionCacheTests.m; sourceTree = ""; }; F79BDC132905FBFE00E3999D /* InAppPurchasePluginTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = InAppPurchasePluginTests.m; path = ../../shared/RunnerTests/InAppPurchasePluginTests.m; sourceTree = ""; }; F79BDC152905FC0500E3999D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../../shared/RunnerTests/Info.plist; sourceTree = ""; }; F79BDC172905FC1800E3999D /* PaymentQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PaymentQueueTests.m; path = ../../shared/RunnerTests/PaymentQueueTests.m; sourceTree = ""; }; F79BDC192905FC1F00E3999D /* ProductRequestHandlerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProductRequestHandlerTests.m; path = ../../shared/RunnerTests/ProductRequestHandlerTests.m; sourceTree = ""; }; F79BDC1B2905FC3200E3999D /* Stubs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Stubs.m; path = ../../shared/RunnerTests/Stubs.m; sourceTree = ""; }; F79BDC1D2905FC3900E3999D /* TranslatorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TranslatorTests.m; path = ../../shared/RunnerTests/TranslatorTests.m; sourceTree = ""; }; F79BDC1F2906023C00E3999D /* Stubs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Stubs.h; path = ../../shared/RunnerTests/Stubs.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( A2C6CD5797E6A6721FDBCA1C /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; F700DCFF28E652A10004836B /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( C51E64432925727D7AC7BBFF /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 09D47623A8E19B84FF0453EE /* Pods */ = { isa = PBXGroup; children = ( B6C8FD76BB3278AA51FED870 /* Pods-Runner.debug.xcconfig */, 9A4FEABF1DEF0D106FEB7974 /* Pods-Runner.release.xcconfig */, 62F1680C5AE033907C1DF7AB /* Pods-Runner.profile.xcconfig */, 5E5D46173E3025B0DB32A1BE /* Pods-RunnerTests.debug.xcconfig */, 5EBC5A8BA44B08330BA605AB /* Pods-RunnerTests.release.xcconfig */, 4E423AE82F466005587C3567 /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( 33E5194F232828860026EE4D /* AppInfo.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, ); path = Configs; sourceTree = ""; }; 33CC10E42044A3C60003C045 = { isa = PBXGroup; children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, F700DD0328E652A10004836B /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, 09D47623A8E19B84FF0453EE /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* example.app */, F700DD0228E652A10004836B /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 33CC11242044D66E0003C045 /* Resources */ = { isa = PBXGroup; children = ( 33CC10F22044A3C60003C045 /* Assets.xcassets */, 33CC10F42044A3C60003C045 /* MainMenu.xib */, 33CC10F72044A3C60003C045 /* Info.plist */, ); name = Resources; path = ..; sourceTree = ""; }; 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, ); path = Flutter; sourceTree = ""; }; 33FAB671232836740065AC1E /* Runner */ = { isa = PBXGroup; children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, 33CC11242044D66E0003C045 /* Resources */, 33BA886A226E78AF003329D5 /* Configs */, ); path = Runner; sourceTree = ""; }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( 36DEEA66738F64D983F76848 /* Pods_Runner.framework */, EE8A421F08C80BE6E90142D5 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; }; F700DD0328E652A10004836B /* RunnerTests */ = { isa = PBXGroup; children = ( F79BDC0F2905FBE300E3999D /* FIAPPaymentQueueDeleteTests.m */, F79BDC132905FBFE00E3999D /* InAppPurchasePluginTests.m */, F79BDC172905FC1800E3999D /* PaymentQueueTests.m */, F79BDC1F2906023C00E3999D /* Stubs.h */, F79BDC152905FC0500E3999D /* Info.plist */, F79BDC1B2905FC3200E3999D /* Stubs.m */, F79BDC1D2905FC3900E3999D /* TranslatorTests.m */, F79BDC192905FC1F00E3999D /* ProductRequestHandlerTests.m */, F79BDC112905FBF700E3999D /* FIATransactionCacheTests.m */, ); path = RunnerTests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 377E3E3C5CA24E98C4B6A4BB /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, 23A80E9A6DAA80757416464A /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* example.app */; productType = "com.apple.product-type.application"; }; F700DD0128E652A10004836B /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = F700DD0B28E652A10004836B /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( 959FA4942EA5DA018C52D3DA /* [CP] Check Pods Manifest.lock */, F700DCFE28E652A10004836B /* Sources */, F700DCFF28E652A10004836B /* Frameworks */, F700DD0028E652A10004836B /* Resources */, 1FAA0D39365CA43DED71E657 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( F700DD0728E652A10004836B /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = F700DD0228E652A10004836B /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1400; LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; }; }; }; 33CC111A2044C6BA0003C045 = { CreatedOnToolsVersion = 9.2; ProvisioningStyle = Manual; }; F700DD0128E652A10004836B = { CreatedOnToolsVersion = 14.0.1; LastSwiftMigration = 1400; TestTargetID = 33CC10EC2044A3C60003C045; }; }; }; buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 33CC10E42044A3C60003C045; productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, F700DD0128E652A10004836B /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; F700DD0028E652A10004836B /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 1FAA0D39365CA43DED71E657 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-RunnerTests/Pods-RunnerTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-RunnerTests/Pods-RunnerTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RunnerTests/Pods-RunnerTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 23A80E9A6DAA80757416464A /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); 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 = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( Flutter/ephemeral/FlutterInputs.xcfilelist, ); inputPaths = ( Flutter/ephemeral/tripwire, ); outputFileListPaths = ( Flutter/ephemeral/FlutterOutputs.xcfilelist, ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; 377E3E3C5CA24E98C4B6A4BB /* [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", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 959FA4942EA5DA018C52D3DA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F700DCFE28E652A10004836B /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F79BDC1A2905FC1F00E3999D /* ProductRequestHandlerTests.m in Sources */, F79BDC1E2905FC3900E3999D /* TranslatorTests.m in Sources */, F79BDC182905FC1800E3999D /* PaymentQueueTests.m in Sources */, F79BDC1C2905FC3200E3999D /* Stubs.m in Sources */, F79BDC102905FBE300E3999D /* FIAPPaymentQueueDeleteTests.m in Sources */, F79BDC142905FBFE00E3999D /* InAppPurchasePluginTests.m in Sources */, F79BDC122905FBF700E3999D /* FIATransactionCacheTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; }; F700DD0728E652A10004836B /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC10EC2044A3C60003C045 /* Runner */; targetProxy = F700DD0628E652A10004836B /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 33CC10F52044A3C60003C045 /* Base */, ); name = MainMenu.xib; path = Runner; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Profile; }; 338D0CEA231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Profile; }; 338D0CEB231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Profile; }; 33CC10F92044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 33CC10FA2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; 33CC10FC2044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; 33CC10FD2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Release; }; 33CC111C2044C6BA0003C045 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 33CC111D2044C6BA0003C045 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; F700DD0828E652A10004836B /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5E5D46173E3025B0DB32A1BE /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/in_app_purchase_storekit/in_app_purchase_storekit.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/integration_test/integration_test.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/shared_preferences_macos/shared_preferences_macos.modulemap\""; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; }; name = Debug; }; F700DD0928E652A10004836B /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5EBC5A8BA44B08330BA605AB /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/in_app_purchase_storekit/in_app_purchase_storekit.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/integration_test/integration_test.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/shared_preferences_macos/shared_preferences_macos.modulemap\""; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; }; name = Release; }; F700DD0A28E652A10004836B /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 4E423AE82F466005587C3567 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D COCOAPODS -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/in_app_purchase_storekit/in_app_purchase_storekit.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/integration_test/integration_test.modulemap\" -Xcc -fmodule-map-file=\"${PODS_ROOT}/Headers/Public/shared_preferences_macos/shared_preferences_macos.modulemap\""; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/example"; }; name = Profile; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10F92044A3C60003C045 /* Debug */, 33CC10FA2044A3C60003C045 /* Release */, 338D0CE9231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10FC2044A3C60003C045 /* Debug */, 33CC10FD2044A3C60003C045 /* Release */, 338D0CEA231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC111C2044C6BA0003C045 /* Debug */, 33CC111D2044C6BA0003C045 /* Release */, 338D0CEB231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F700DD0B28E652A10004836B /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( F700DD0828E652A10004836B /* Debug */, F700DD0928E652A10004836B /* Release */, F700DD0A28E652A10004836B /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/pubspec.yaml ================================================ name: in_app_purchase_storekit_example description: Demonstrates how to use the in_app_purchase_storekit plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter in_app_purchase_platform_interface: ^1.0.0 in_app_purchase_storekit: # When depending on this package from a real application you should use: # in_app_purchase_storekit: ^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: ^2.0.0 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/FIAPPaymentQueueDeleteTests.m ================================================ // Copyright 2013 The Flutter 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 "FIAObjectTranslator.h" #import "FIAPaymentQueueHandler.h" #import "Stubs.h" @import in_app_purchase_storekit; API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(tvos, macos, watchos) @interface FIAPPaymentQueueDelegateTests : XCTestCase @property(strong, nonatomic) FlutterMethodChannel *channel; @property(strong, nonatomic) SKPaymentTransaction *transaction; @property(strong, nonatomic) SKStorefront *storefront; @end @implementation FIAPPaymentQueueDelegateTests - (void)setUp { self.channel = OCMClassMock(FlutterMethodChannel.class); NSDictionary *transactionMap = @{ @"transactionIdentifier" : [NSNull null], @"transactionState" : @(SKPaymentTransactionStatePurchasing), @"payment" : [NSNull null], @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" code:123 userInfo:@{}]], @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), @"originalTransaction" : [NSNull null], }; self.transaction = [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; NSDictionary *storefrontMap = @{ @"countryCode" : @"USA", @"identifier" : @"unique_identifier", }; self.storefront = [[SKStorefrontStub alloc] initWithMap:storefrontMap]; } - (void)tearDown { self.channel = nil; } - (void)testShouldContinueTransaction { if (@available(iOS 13.0, *)) { FIAPPaymentQueueDelegate *delegate = [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel]; OCMStub([self.channel invokeMethod:@"shouldContinueTransaction" arguments:[FIAObjectTranslator getMapFromSKStorefront:self.storefront andSKPaymentTransaction:self.transaction] result:([OCMArg invokeBlockWithArgs:[NSNumber numberWithBool:NO], nil])]); BOOL shouldContinue = [delegate paymentQueue:OCMClassMock(SKPaymentQueue.class) shouldContinueTransaction:self.transaction inStorefront:self.storefront]; XCTAssertFalse(shouldContinue); } } - (void)testShouldContinueTransaction_should_default_to_yes { if (@available(iOS 13.0, *)) { FIAPPaymentQueueDelegate *delegate = [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel]; OCMStub([self.channel invokeMethod:@"shouldContinueTransaction" arguments:[FIAObjectTranslator getMapFromSKStorefront:self.storefront andSKPaymentTransaction:self.transaction] result:[OCMArg any]]); BOOL shouldContinue = [delegate paymentQueue:OCMClassMock(SKPaymentQueue.class) shouldContinueTransaction:self.transaction inStorefront:self.storefront]; XCTAssertTrue(shouldContinue); } } #if TARGET_OS_IOS - (void)testShouldShowPriceConsentIfNeeded { if (@available(iOS 13.4, *)) { FIAPPaymentQueueDelegate *delegate = [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel]; OCMStub([self.channel invokeMethod:@"shouldShowPriceConsent" arguments:nil result:([OCMArg invokeBlockWithArgs:[NSNumber numberWithBool:NO], nil])]); BOOL shouldShow = [delegate paymentQueueShouldShowPriceConsent:OCMClassMock(SKPaymentQueue.class)]; XCTAssertFalse(shouldShow); } } #endif #if TARGET_OS_IOS - (void)testShouldShowPriceConsentIfNeeded_should_default_to_yes { if (@available(iOS 13.4, *)) { FIAPPaymentQueueDelegate *delegate = [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel]; OCMStub([self.channel invokeMethod:@"shouldShowPriceConsent" arguments:nil result:[OCMArg any]]); BOOL shouldShow = [delegate paymentQueueShouldShowPriceConsent:OCMClassMock(SKPaymentQueue.class)]; XCTAssertTrue(shouldShow); } } #endif @end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/FIATransactionCacheTests.m ================================================ // Copyright 2013 The Flutter 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 in_app_purchase_storekit; @interface FIATransactionCacheTests : XCTestCase @end @implementation FIATransactionCacheTests - (void)testAddObjectsForNewKey { NSArray *dummyArray = @[ @1, @2, @3 ]; FIATransactionCache *cache = [[FIATransactionCache alloc] init]; [cache addObjects:dummyArray forKey:TransactionCacheKeyUpdatedTransactions]; XCTAssertEqual(dummyArray, [cache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]); } - (void)testAddObjectsForExistingKey { NSArray *dummyArray = @[ @1, @2, @3 ]; FIATransactionCache *cache = [[FIATransactionCache alloc] init]; [cache addObjects:dummyArray forKey:TransactionCacheKeyUpdatedTransactions]; XCTAssertEqual(dummyArray, [cache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]); [cache addObjects:@[ @4, @5, @6 ] forKey:TransactionCacheKeyUpdatedTransactions]; NSArray *expected = @[ @1, @2, @3, @4, @5, @6 ]; XCTAssertEqualObjects(expected, [cache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]); } - (void)testGetObjectsForNonExistingKey { FIATransactionCache *cache = [[FIATransactionCache alloc] init]; XCTAssertNil([cache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]); } - (void)testClear { NSArray *fakeUpdatedTransactions = @[ @1, @2, @3 ]; NSArray *fakeRemovedTransactions = @[ @"Remove 1", @"Remove 2", @"Remove 3" ]; NSArray *fakeUpdatedDownloads = @[ @"Download 1", @"Download 2" ]; FIATransactionCache *cache = [[FIATransactionCache alloc] init]; [cache addObjects:fakeUpdatedTransactions forKey:TransactionCacheKeyUpdatedTransactions]; [cache addObjects:fakeRemovedTransactions forKey:TransactionCacheKeyRemovedTransactions]; [cache addObjects:fakeUpdatedDownloads forKey:TransactionCacheKeyUpdatedDownloads]; XCTAssertEqual(fakeUpdatedTransactions, [cache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]); XCTAssertEqual(fakeRemovedTransactions, [cache getObjectsForKey:TransactionCacheKeyRemovedTransactions]); XCTAssertEqual(fakeUpdatedDownloads, [cache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]); [cache clear]; XCTAssertNil([cache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]); XCTAssertNil([cache getObjectsForKey:TransactionCacheKeyRemovedTransactions]); XCTAssertNil([cache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]); } @end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m ================================================ // Copyright 2013 The Flutter 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 "FIAPaymentQueueHandler.h" #import "Stubs.h" @import in_app_purchase_storekit; @interface InAppPurchasePluginTest : XCTestCase @property(strong, nonatomic) FIAPReceiptManagerStub *receiptManagerStub; @property(strong, nonatomic) InAppPurchasePlugin *plugin; @end @implementation InAppPurchasePluginTest - (void)setUp { self.receiptManagerStub = [FIAPReceiptManagerStub new]; self.plugin = [[InAppPurchasePluginStub alloc] initWithReceiptManager:self.receiptManagerStub]; } - (void)tearDown { } - (void)testInvalidMethodCall { XCTestExpectation *expectation = [self expectationWithDescription:@"expect result to be not implemented"]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"invalid" arguments:NULL]; __block id result; [self.plugin handleMethodCall:call result:^(id r) { [expectation fulfill]; result = r; }]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertEqual(result, FlutterMethodNotImplemented); } - (void)testCanMakePayments { XCTestExpectation *expectation = [self expectationWithDescription:@"expect result to be YES"]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue canMakePayments:]" arguments:NULL]; __block id result; [self.plugin handleMethodCall:call result:^(id r) { [expectation fulfill]; result = r; }]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertEqual(result, @YES); } - (void)testGetProductResponse { XCTestExpectation *expectation = [self expectationWithDescription:@"expect response contains 1 item"]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin startProductRequest:result:]" arguments:@[ @"123" ]]; __block id result; [self.plugin handleMethodCall:call result:^(id r) { [expectation fulfill]; result = r; }]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssert([result isKindOfClass:[NSDictionary class]]); NSArray *resultArray = [result objectForKey:@"products"]; XCTAssertEqual(resultArray.count, 1); XCTAssertTrue([resultArray.firstObject[@"productIdentifier"] isEqualToString:@"123"]); } - (void)testAddPaymentShouldReturnFlutterErrorWhenArgumentsAreInvalid { XCTestExpectation *expectation = [self expectationWithDescription: @"Result should contain a FlutterError when invalid parameters are passed in."]; NSString *argument = @"Invalid argument"; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin addPayment:result:]" arguments:argument]; [self.plugin handleMethodCall:call result:^(id _Nullable result) { FlutterError *error = result; XCTAssertEqualObjects(@"storekit_invalid_argument", error.code); XCTAssertEqualObjects(@"Argument type of addPayment is not a Dictionary", error.message); XCTAssertEqualObjects(argument, error.details); [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:5]; } - (void)testAddPaymentShouldReturnFlutterErrorWhenPaymentFails { NSDictionary *arguments = @{ @"productIdentifier" : @"123", @"quantity" : @(1), @"simulatesAskToBuyInSandbox" : @YES, }; XCTestExpectation *expectation = [self expectationWithDescription:@"Result should return failed state."]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin addPayment:result:]" arguments:arguments]; FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(NO); self.plugin.paymentQueueHandler = mockHandler; [self.plugin handleMethodCall:call result:^(id _Nullable result) { FlutterError *error = result; XCTAssertEqualObjects(@"storekit_duplicate_product_object", error.code); XCTAssertEqualObjects( @"There is a pending transaction for the same product identifier. " @"Please either wait for it to be finished or finish it manually " @"using `completePurchase` to avoid edge cases.", error.message); XCTAssertEqualObjects(arguments, error.details); [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:5]; OCMVerify(times(1), [mockHandler addPayment:[OCMArg any]]); } - (void)testAddPaymentSuccessWithoutPaymentDiscount { XCTestExpectation *expectation = [self expectationWithDescription:@"Result should return success state"]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin addPayment:result:]" arguments:@{ @"productIdentifier" : @"123", @"quantity" : @(1), @"simulatesAskToBuyInSandbox" : @YES, }]; FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(YES); self.plugin.paymentQueueHandler = mockHandler; [self.plugin handleMethodCall:call result:^(id _Nullable result) { XCTAssertNil(result); [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:5]; } - (void)testAddPaymentSuccessWithPaymentDiscount { XCTestExpectation *expectation = [self expectationWithDescription:@"Result should return success state"]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin addPayment:result:]" arguments:@{ @"productIdentifier" : @"123", @"quantity" : @(1), @"simulatesAskToBuyInSandbox" : @YES, @"paymentDiscount" : @{ @"identifier" : @"test_identifier", @"keyIdentifier" : @"test_key_identifier", @"nonce" : @"4a11a9cc-3bc3-11ec-8d3d-0242ac130003", @"signature" : @"test_signature", @"timestamp" : @(1635847102), } }]; FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(YES); self.plugin.paymentQueueHandler = mockHandler; [self.plugin handleMethodCall:call result:^(id _Nullable result) { XCTAssertNil(result); [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:5]; OCMVerify( times(1), [mockHandler addPayment:[OCMArg checkWithBlock:^BOOL(id obj) { SKPayment *payment = obj; if (@available(iOS 12.2, *)) { SKPaymentDiscount *discount = payment.paymentDiscount; return [discount.identifier isEqual:@"test_identifier"] && [discount.keyIdentifier isEqual:@"test_key_identifier"] && [discount.nonce isEqual:[[NSUUID alloc] initWithUUIDString:@"4a11a9cc-3bc3-11ec-8d3d-0242ac130003"]] && [discount.signature isEqual:@"test_signature"] && [discount.timestamp isEqual:@(1635847102)]; } return YES; }]]); } - (void)testAddPaymentFailureWithInvalidPaymentDiscount { // Support for payment discount is only available on iOS 12.2 and higher. if (@available(iOS 12.2, *)) { XCTestExpectation *expectation = [self expectationWithDescription:@"Result should return success state"]; NSDictionary *arguments = @{ @"productIdentifier" : @"123", @"quantity" : @(1), @"simulatesAskToBuyInSandbox" : @YES, @"paymentDiscount" : @{ @"keyIdentifier" : @"test_key_identifier", @"nonce" : @"4a11a9cc-3bc3-11ec-8d3d-0242ac130003", @"signature" : @"test_signature", @"timestamp" : @(1635847102), } }; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin addPayment:result:]" arguments:arguments]; FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); id translator = OCMClassMock(FIAObjectTranslator.class); NSString *error = @"Some error occurred"; OCMStub(ClassMethod([translator getSKPaymentDiscountFromMap:[OCMArg any] withError:(NSString __autoreleasing **)[OCMArg setTo:error]])) .andReturn(nil); self.plugin.paymentQueueHandler = mockHandler; [self.plugin handleMethodCall:call result:^(id _Nullable result) { FlutterError *error = result; XCTAssertEqualObjects(@"storekit_invalid_payment_discount_object", error.code); XCTAssertEqualObjects( @"You have requested a payment and specified a " @"payment discount with invalid properties. Some error occurred", error.message); XCTAssertEqualObjects(arguments, error.details); [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:5]; OCMVerify(never(), [mockHandler addPayment:[OCMArg any]]); } } - (void)testAddPaymentWithNullSandboxArgument { XCTestExpectation *expectation = [self expectationWithDescription:@"result should return success state"]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin addPayment:result:]" arguments:@{ @"productIdentifier" : @"123", @"quantity" : @(1), @"simulatesAskToBuyInSandbox" : [NSNull null], }]; FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(YES); self.plugin.paymentQueueHandler = mockHandler; [self.plugin handleMethodCall:call result:^(id _Nullable result) { XCTAssertNil(result); [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:5]; OCMVerify(times(1), [mockHandler addPayment:[OCMArg checkWithBlock:^BOOL(id obj) { SKPayment *payment = obj; return !payment.simulatesAskToBuyInSandbox; }]]); } - (void)testRestoreTransactions { XCTestExpectation *expectation = [self expectationWithDescription:@"result successfully restore transactions"]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin restoreTransactions:result:]" arguments:nil]; SKPaymentQueueStub *queue = [SKPaymentQueueStub new]; queue.testState = SKPaymentTransactionStatePurchased; __block BOOL callbackInvoked = NO; self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:^(NSArray *_Nonnull transactions) { } transactionRemoved:nil restoreTransactionFailed:nil restoreCompletedTransactionsFinished:^() { callbackInvoked = YES; [expectation fulfill]; } shouldAddStorePayment:nil updatedDownloads:nil transactionCache:OCMClassMock(FIATransactionCache.class)]; [queue addTransactionObserver:self.plugin.paymentQueueHandler]; [self.plugin handleMethodCall:call result:^(id r){ }]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertTrue(callbackInvoked); } - (void)testRetrieveReceiptDataSuccess { XCTestExpectation *expectation = [self expectationWithDescription:@"receipt data retrieved"]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin retrieveReceiptData:result:]" arguments:nil]; __block NSDictionary *result; [self.plugin handleMethodCall:call result:^(id r) { result = r; [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertNotNil(result); 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 methodCallWithMethodName:@"-[InAppPurchasePlugin retrieveReceiptData:result:]" arguments:nil]; __block NSDictionary *result; self.receiptManagerStub.returnError = YES; [self.plugin handleMethodCall:call result:^(id r) { result = r; [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertNotNil(result); XCTAssert([result isKindOfClass:[FlutterError class]]); NSDictionary *details = ((FlutterError *)result).details; XCTAssertNotNil(details[@"error"]); NSNumber *errorCode = (NSNumber *)details[@"error"][@"code"]; XCTAssertEqual(errorCode, [NSNumber numberWithInteger:99]); } - (void)testRefreshReceiptRequest { XCTestExpectation *expectation = [self expectationWithDescription:@"expect success"]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin refreshReceipt:result:]" arguments:nil]; __block BOOL result = NO; [self.plugin handleMethodCall:call result:^(id r) { result = YES; [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertTrue(result); } - (void)testPresentCodeRedemptionSheet { XCTestExpectation *expectation = [self expectationWithDescription:@"expect successfully present Code Redemption Sheet"]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin presentCodeRedemptionSheet:result:]" arguments:nil]; __block BOOL callbackInvoked = NO; [self.plugin handleMethodCall:call result:^(id r) { callbackInvoked = YES; [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertTrue(callbackInvoked); } - (void)testGetPendingTransactions { XCTestExpectation *expectation = [self expectationWithDescription:@"expect success"]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue transactions]" arguments:nil]; SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class); NSDictionary *transactionMap = @{ @"transactionIdentifier" : [NSNull null], @"transactionState" : @(SKPaymentTransactionStatePurchasing), @"payment" : [NSNull null], @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" code:123 userInfo:@{}]], @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), @"originalTransaction" : [NSNull null], }; OCMStub(mockQueue.transactions).andReturn(@[ [[SKPaymentTransactionStub alloc] initWithMap:transactionMap] ]); __block NSArray *resultArray; self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:mockQueue transactionsUpdated:nil transactionRemoved:nil restoreTransactionFailed:nil restoreCompletedTransactionsFinished:nil shouldAddStorePayment:nil updatedDownloads:nil transactionCache:OCMClassMock(FIATransactionCache.class)]; [self.plugin handleMethodCall:call result:^(id r) { resultArray = r; [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertEqualObjects(resultArray, @[ transactionMap ]); } - (void)testStartObservingPaymentQueue { XCTestExpectation *expectation = [self expectationWithDescription:@"Should return success result"]; FlutterMethodCall *startCall = [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue startObservingTransactionQueue]" arguments:nil]; FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]); self.plugin.paymentQueueHandler = mockHandler; [self.plugin handleMethodCall:startCall result:^(id _Nullable result) { XCTAssertNil(result); [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:5]; OCMVerify(times(1), [mockHandler startObservingPaymentQueue]); } - (void)testStopObservingPaymentQueue { XCTestExpectation *expectation = [self expectationWithDescription:@"Should return success result"]; FlutterMethodCall *stopCall = [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue stopObservingTransactionQueue]" arguments:nil]; FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]); self.plugin.paymentQueueHandler = mockHandler; [self.plugin handleMethodCall:stopCall result:^(id _Nullable result) { XCTAssertNil(result); [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:5]; OCMVerify(times(1), [mockHandler stopObservingPaymentQueue]); } #if TARGET_OS_IOS - (void)testRegisterPaymentQueueDelegate { if (@available(iOS 13, *)) { FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue registerDelegate]" arguments:nil]; self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueueStub new] transactionsUpdated:nil transactionRemoved:nil restoreTransactionFailed:nil restoreCompletedTransactionsFinished:nil shouldAddStorePayment:nil updatedDownloads:nil transactionCache:OCMClassMock(FIATransactionCache.class)]; // Verify the delegate is nil before we register one. XCTAssertNil(self.plugin.paymentQueueHandler.delegate); [self.plugin handleMethodCall:call result:^(id r){ }]; // Verify the delegate is not nil after we registered one. XCTAssertNotNil(self.plugin.paymentQueueHandler.delegate); } } #endif - (void)testRemovePaymentQueueDelegate { if (@available(iOS 13, *)) { FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue removeDelegate]" arguments:nil]; self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueueStub new] transactionsUpdated:nil transactionRemoved:nil restoreTransactionFailed:nil restoreCompletedTransactionsFinished:nil shouldAddStorePayment:nil updatedDownloads:nil transactionCache:OCMClassMock(FIATransactionCache.class)]; self.plugin.paymentQueueHandler.delegate = OCMProtocolMock(@protocol(SKPaymentQueueDelegate)); // Verify the delegate is not nil before removing it. XCTAssertNotNil(self.plugin.paymentQueueHandler.delegate); [self.plugin handleMethodCall:call result:^(id r){ }]; // Verify the delegate is nill after removing it. XCTAssertNil(self.plugin.paymentQueueHandler.delegate); } } #if TARGET_OS_IOS - (void)testShowPriceConsentIfNeeded { FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue showPriceConsentIfNeeded]" arguments:nil]; FIAPaymentQueueHandler *mockQueueHandler = OCMClassMock(FIAPaymentQueueHandler.class); self.plugin.paymentQueueHandler = mockQueueHandler; [self.plugin handleMethodCall:call result:^(id r){ }]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpartial-availability" if (@available(iOS 13.4, *)) { OCMVerify(times(1), [mockQueueHandler showPriceConsentIfNeeded]); } else { OCMVerify(never(), [mockQueueHandler showPriceConsentIfNeeded]); } #pragma clang diagnostic pop } #endif @end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/PaymentQueueTests.m ================================================ // Copyright 2013 The Flutter 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 "Stubs.h" @import in_app_purchase_storekit; @interface PaymentQueueTest : XCTestCase @property(strong, nonatomic) NSDictionary *periodMap; @property(strong, nonatomic) NSDictionary *discountMap; @property(strong, nonatomic) NSDictionary *productMap; @property(strong, nonatomic) NSDictionary *productResponseMap; @end @implementation PaymentQueueTest - (void)setUp { self.periodMap = @{@"numberOfUnits" : @(0), @"unit" : @(0)}; self.discountMap = @{ @"price" : @1.0, @"currencyCode" : @"USD", @"numberOfPeriods" : @1, @"subscriptionPeriod" : self.periodMap, @"paymentMode" : @1 }; self.productMap = @{ @"price" : @1.0, @"currencyCode" : @"USD", @"productIdentifier" : @"123", @"localizedTitle" : @"title", @"localizedDescription" : @"des", @"subscriptionPeriod" : self.periodMap, @"introductoryPrice" : self.discountMap, @"subscriptionGroupIdentifier" : @"com.group" }; self.productResponseMap = @{@"products" : @[ self.productMap ], @"invalidProductIdentifiers" : [NSNull null]}; } - (void)testTransactionPurchased { XCTestExpectation *expectation = [self expectationWithDescription:@"expect to get purchased transcation."]; SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStatePurchased; __block SKPaymentTransactionStub *tran; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:^(NSArray *_Nonnull transactions) { SKPaymentTransaction *transaction = transactions[0]; tran = (SKPaymentTransactionStub *)transaction; [expectation fulfill]; } transactionRemoved:nil restoreTransactionFailed:nil restoreCompletedTransactionsFinished:nil shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) { return YES; } updatedDownloads:nil transactionCache:OCMClassMock(FIATransactionCache.class)]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; [handler startObservingPaymentQueue]; [handler addPayment:payment]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertEqual(tran.transactionState, SKPaymentTransactionStatePurchased); XCTAssertEqual(tran.transactionIdentifier, @"fakeID"); } - (void)testTransactionFailed { XCTestExpectation *expectation = [self expectationWithDescription:@"expect to get failed transcation."]; SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStateFailed; __block SKPaymentTransactionStub *tran; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:^(NSArray *_Nonnull transactions) { SKPaymentTransaction *transaction = transactions[0]; tran = (SKPaymentTransactionStub *)transaction; [expectation fulfill]; } transactionRemoved:nil restoreTransactionFailed:nil restoreCompletedTransactionsFinished:nil shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) { return YES; } updatedDownloads:nil transactionCache:OCMClassMock(FIATransactionCache.class)]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; [handler startObservingPaymentQueue]; [handler addPayment:payment]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertEqual(tran.transactionState, SKPaymentTransactionStateFailed); XCTAssertEqual(tran.transactionIdentifier, nil); } - (void)testTransactionRestored { XCTestExpectation *expectation = [self expectationWithDescription:@"expect to get restored transcation."]; SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStateRestored; __block SKPaymentTransactionStub *tran; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:^(NSArray *_Nonnull transactions) { SKPaymentTransaction *transaction = transactions[0]; tran = (SKPaymentTransactionStub *)transaction; [expectation fulfill]; } transactionRemoved:nil restoreTransactionFailed:nil restoreCompletedTransactionsFinished:nil shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) { return YES; } updatedDownloads:nil transactionCache:OCMClassMock(FIATransactionCache.class)]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; [handler startObservingPaymentQueue]; [handler addPayment:payment]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertEqual(tran.transactionState, SKPaymentTransactionStateRestored); XCTAssertEqual(tran.transactionIdentifier, @"fakeID"); } - (void)testTransactionPurchasing { XCTestExpectation *expectation = [self expectationWithDescription:@"expect to get purchasing transcation."]; SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStatePurchasing; __block SKPaymentTransactionStub *tran; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:^(NSArray *_Nonnull transactions) { SKPaymentTransaction *transaction = transactions[0]; tran = (SKPaymentTransactionStub *)transaction; [expectation fulfill]; } transactionRemoved:nil restoreTransactionFailed:nil restoreCompletedTransactionsFinished:nil shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) { return YES; } updatedDownloads:nil transactionCache:OCMClassMock(FIATransactionCache.class)]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; [handler startObservingPaymentQueue]; [handler addPayment:payment]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertEqual(tran.transactionState, SKPaymentTransactionStatePurchasing); XCTAssertEqual(tran.transactionIdentifier, nil); } - (void)testTransactionDeferred { XCTestExpectation *expectation = [self expectationWithDescription:@"expect to get deffered transcation."]; SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStateDeferred; __block SKPaymentTransactionStub *tran; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:^(NSArray *_Nonnull transactions) { SKPaymentTransaction *transaction = transactions[0]; tran = (SKPaymentTransactionStub *)transaction; [expectation fulfill]; } transactionRemoved:nil restoreTransactionFailed:nil restoreCompletedTransactionsFinished:nil shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) { return YES; } updatedDownloads:nil transactionCache:OCMClassMock(FIATransactionCache.class)]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; [handler startObservingPaymentQueue]; [handler addPayment:payment]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertEqual(tran.transactionState, SKPaymentTransactionStateDeferred); XCTAssertEqual(tran.transactionIdentifier, nil); } - (void)testFinishTransaction { XCTestExpectation *expectation = [self expectationWithDescription:@"handler.transactions should be empty."]; SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStateDeferred; __block FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:^(NSArray *_Nonnull transactions) { XCTAssertEqual(transactions.count, 1); SKPaymentTransaction *transaction = transactions[0]; [handler finishTransaction:transaction]; } transactionRemoved:^(NSArray *_Nonnull transactions) { XCTAssertEqual(transactions.count, 1); [expectation fulfill]; } restoreTransactionFailed:nil restoreCompletedTransactionsFinished:nil shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) { return YES; } updatedDownloads:nil transactionCache:OCMClassMock(FIATransactionCache.class)]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; [handler startObservingPaymentQueue]; [handler addPayment:payment]; [self waitForExpectations:@[ expectation ] timeout:5]; } - (void)testStartObservingPaymentQueueShouldNotProcessTransactionsWhenCacheIsEmpty { FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class); FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:[[SKPaymentQueueStub alloc] init] transactionsUpdated:^(NSArray *_Nonnull transactions) { XCTFail("transactionsUpdated callback should not be called when cache is empty."); } transactionRemoved:^(NSArray *_Nonnull transactions) { XCTFail("transactionRemoved callback should not be called when cache is empty."); } restoreTransactionFailed:nil restoreCompletedTransactionsFinished:nil shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) { return YES; } updatedDownloads:^(NSArray *_Nonnull downloads) { XCTFail("updatedDownloads callback should not be called when cache is empty."); } transactionCache:mockCache]; [handler startObservingPaymentQueue]; OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]); OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]); OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]); } - (void) testStartObservingPaymentQueueShouldNotProcessTransactionsWhenCacheContainsEmptyTransactionArrays { FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class); FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:[[SKPaymentQueueStub alloc] init] transactionsUpdated:^(NSArray *_Nonnull transactions) { XCTFail("transactionsUpdated callback should not be called when cache is empty."); } transactionRemoved:^(NSArray *_Nonnull transactions) { XCTFail("transactionRemoved callback should not be called when cache is empty."); } restoreTransactionFailed:nil restoreCompletedTransactionsFinished:nil shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) { return YES; } updatedDownloads:^(NSArray *_Nonnull downloads) { XCTFail("updatedDownloads callback should not be called when cache is empty."); } transactionCache:mockCache]; OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]).andReturn(@[]); OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]).andReturn(@[]); OCMStub([mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]).andReturn(@[]); [handler startObservingPaymentQueue]; OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]); OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]); OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]); } - (void)testStartObservingPaymentQueueShouldProcessTransactionsForItemsInCache { XCTestExpectation *updateTransactionsExpectation = [self expectationWithDescription: @"transactionsUpdated callback should be called with one transaction."]; XCTestExpectation *removeTransactionsExpectation = [self expectationWithDescription: @"transactionsRemoved callback should be called with one transaction."]; XCTestExpectation *updateDownloadsExpectation = [self expectationWithDescription: @"downloadsUpdated callback should be called with one transaction."]; SKPaymentTransaction *mockTransaction = OCMClassMock(SKPaymentTransaction.class); SKDownload *mockDownload = OCMClassMock(SKDownload.class); FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class); FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:[[SKPaymentQueueStub alloc] init] transactionsUpdated:^(NSArray *_Nonnull transactions) { XCTAssertEqualObjects(transactions, @[ mockTransaction ]); [updateTransactionsExpectation fulfill]; } transactionRemoved:^(NSArray *_Nonnull transactions) { XCTAssertEqualObjects(transactions, @[ mockTransaction ]); [removeTransactionsExpectation fulfill]; } restoreTransactionFailed:nil restoreCompletedTransactionsFinished:nil shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) { return YES; } updatedDownloads:^(NSArray *_Nonnull downloads) { XCTAssertEqualObjects(downloads, @[ mockDownload ]); [updateDownloadsExpectation fulfill]; } transactionCache:mockCache]; OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]).andReturn(@[ mockTransaction ]); OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]).andReturn(@[ mockDownload ]); OCMStub([mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]).andReturn(@[ mockTransaction ]); [handler startObservingPaymentQueue]; [self waitForExpectations:@[ updateTransactionsExpectation, removeTransactionsExpectation, updateDownloadsExpectation ] timeout:5]; OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]); OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]); OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]); OCMVerify(times(1), [mockCache clear]); } - (void)testTransactionsShouldBeCachedWhenNotObserving { SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class); FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:^(NSArray *_Nonnull transactions) { XCTFail("transactionsUpdated callback should not be called when cache is empty."); } transactionRemoved:^(NSArray *_Nonnull transactions) { XCTFail("transactionRemoved callback should not be called when cache is empty."); } restoreTransactionFailed:nil restoreCompletedTransactionsFinished:nil shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) { return YES; } updatedDownloads:^(NSArray *_Nonnull downloads) { XCTFail("updatedDownloads callback should not be called when cache is empty."); } transactionCache:mockCache]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; [handler addPayment:payment]; OCMVerify(times(1), [mockCache addObjects:[OCMArg any] forKey:TransactionCacheKeyUpdatedTransactions]); OCMVerify(never(), [mockCache addObjects:[OCMArg any] forKey:TransactionCacheKeyUpdatedDownloads]); OCMVerify(never(), [mockCache addObjects:[OCMArg any] forKey:TransactionCacheKeyRemovedTransactions]); } - (void)testTransactionsShouldNotBeCachedWhenObserving { XCTestExpectation *updateTransactionsExpectation = [self expectationWithDescription: @"transactionsUpdated callback should be called with one transaction."]; XCTestExpectation *removeTransactionsExpectation = [self expectationWithDescription: @"transactionsRemoved callback should be called with one transaction."]; XCTestExpectation *updateDownloadsExpectation = [self expectationWithDescription: @"downloadsUpdated callback should be called with one transaction."]; SKPaymentTransaction *mockTransaction = OCMClassMock(SKPaymentTransaction.class); SKDownload *mockDownload = OCMClassMock(SKDownload.class); SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStatePurchased; FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class); FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:^(NSArray *_Nonnull transactions) { XCTAssertEqualObjects(transactions, @[ mockTransaction ]); [updateTransactionsExpectation fulfill]; } transactionRemoved:^(NSArray *_Nonnull transactions) { XCTAssertEqualObjects(transactions, @[ mockTransaction ]); [removeTransactionsExpectation fulfill]; } restoreTransactionFailed:nil restoreCompletedTransactionsFinished:nil shouldAddStorePayment:^BOOL(SKPayment *_Nonnull payment, SKProduct *_Nonnull product) { return YES; } updatedDownloads:^(NSArray *_Nonnull downloads) { XCTAssertEqualObjects(downloads, @[ mockDownload ]); [updateDownloadsExpectation fulfill]; } transactionCache:mockCache]; [handler startObservingPaymentQueue]; [handler paymentQueue:queue updatedTransactions:@[ mockTransaction ]]; [handler paymentQueue:queue removedTransactions:@[ mockTransaction ]]; [handler paymentQueue:queue updatedDownloads:@[ mockDownload ]]; [self waitForExpectations:@[ updateTransactionsExpectation, removeTransactionsExpectation, updateDownloadsExpectation ] timeout:5]; OCMVerify(never(), [mockCache addObjects:[OCMArg any] forKey:TransactionCacheKeyUpdatedTransactions]); OCMVerify(never(), [mockCache addObjects:[OCMArg any] forKey:TransactionCacheKeyUpdatedDownloads]); OCMVerify(never(), [mockCache addObjects:[OCMArg any] forKey:TransactionCacheKeyRemovedTransactions]); } @end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/ProductRequestHandlerTests.m ================================================ // Copyright 2013 The Flutter 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 "Stubs.h" @import in_app_purchase_storekit; #pragma tests start here @interface RequestHandlerTest : XCTestCase @end @implementation RequestHandlerTest - (void)testRequestHandlerWithProductRequestSuccess { SKProductRequestStub *request = [[SKProductRequestStub alloc] initWithProductIdentifiers:[NSSet setWithArray:@[ @"123" ]]]; FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect to get response with 1 product"]; __block SKProductsResponse *response; [handler startProductRequestWithCompletionHandler:^(SKProductsResponse *_Nullable r, NSError *error) { response = r; [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertNotNil(response); XCTAssertEqual(response.products.count, 1); SKProduct *product = response.products.firstObject; XCTAssertTrue([product.productIdentifier isEqualToString:@"123"]); } - (void)testRequestHandlerWithProductRequestFailure { SKProductRequestStub *request = [[SKProductRequestStub alloc] initWithFailureError:[NSError errorWithDomain:@"test" code:123 userInfo:@{}]]; FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect to get response with 1 product"]; __block NSError *error; __block SKProductsResponse *response; [handler startProductRequestWithCompletionHandler:^(SKProductsResponse *_Nullable r, NSError *e) { error = e; response = r; [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertNotNil(error); XCTAssertEqual(error.domain, @"test"); XCTAssertNil(response); } - (void)testRequestHandlerWithRefreshReceiptSuccess { SKReceiptRefreshRequestStub *request = [[SKReceiptRefreshRequestStub alloc] initWithReceiptProperties:nil]; FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect no error"]; __block NSError *e; [handler startProductRequestWithCompletionHandler:^(SKProductsResponse *_Nullable r, NSError *error) { e = error; [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertNil(e); } - (void)testRequestHandlerWithRefreshReceiptFailure { SKReceiptRefreshRequestStub *request = [[SKReceiptRefreshRequestStub alloc] initWithFailureError:[NSError errorWithDomain:@"test" code:123 userInfo:@{}]]; FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request]; XCTestExpectation *expectation = [self expectationWithDescription:@"expect error"]; __block NSError *error; __block SKProductsResponse *response; [handler startProductRequestWithCompletionHandler:^(SKProductsResponse *_Nullable r, NSError *e) { error = e; response = r; [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertNotNil(error); XCTAssertEqual(error.domain, @"test"); XCTAssertNil(response); } @end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h ================================================ // Copyright 2013 The Flutter 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 in_app_purchase_storekit; NS_ASSUME_NONNULL_BEGIN API_AVAILABLE(ios(11.2), macos(10.13.2)) @interface SKProductSubscriptionPeriodStub : SKProductSubscriptionPeriod - (instancetype)initWithMap:(NSDictionary *)map; @end API_AVAILABLE(ios(11.2), macos(10.13.2)) @interface SKProductDiscountStub : SKProductDiscount - (instancetype)initWithMap:(NSDictionary *)map; @end @interface SKProductStub : SKProduct - (instancetype)initWithMap:(NSDictionary *)map; @end @interface SKProductRequestStub : SKProductsRequest - (instancetype)initWithProductIdentifiers:(NSSet *)productIdentifiers; - (instancetype)initWithFailureError:(NSError *)error; @end @interface SKProductsResponseStub : SKProductsResponse - (instancetype)initWithMap:(NSDictionary *)map; @end @interface InAppPurchasePluginStub : InAppPurchasePlugin @end @interface SKPaymentQueueStub : SKPaymentQueue @property(assign, nonatomic) SKPaymentTransactionState testState; @property(strong, nonatomic, nullable) id observer; @end @interface SKPaymentTransactionStub : SKPaymentTransaction - (instancetype)initWithMap:(NSDictionary *)map; - (instancetype)initWithState:(SKPaymentTransactionState)state; - (instancetype)initWithState:(SKPaymentTransactionState)state payment:(SKPayment *)payment; @end @interface SKMutablePaymentStub : SKMutablePayment - (instancetype)initWithMap:(NSDictionary *)map; @end @interface NSErrorStub : NSError - (instancetype)initWithMap:(NSDictionary *)map; @end @interface FIAPReceiptManagerStub : FIAPReceiptManager // Indicates whether getReceiptData of this stub is going to return an error. // Setting this to true will let getReceiptData give a basic NSError and return nil. @property(assign, nonatomic) BOOL returnError; @end @interface SKReceiptRefreshRequestStub : SKReceiptRefreshRequest - (instancetype)initWithFailureError:(NSError *)error; @end API_AVAILABLE(ios(13.0), macos(10.15)) @interface SKStorefrontStub : SKStorefront - (instancetype)initWithMap:(NSDictionary *)map; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m ================================================ // Copyright 2013 The Flutter 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 "Stubs.h" @implementation SKProductSubscriptionPeriodStub - (instancetype)initWithMap:(NSDictionary *)map { self = [super init]; if (self) { [self setValue:map[@"numberOfUnits"] ?: @(0) forKey:@"numberOfUnits"]; [self setValue:map[@"unit"] ?: @(0) forKey:@"unit"]; } return self; } @end @implementation SKProductDiscountStub - (instancetype)initWithMap:(NSDictionary *)map { self = [super init]; if (self) { [self setValue:[[NSDecimalNumber alloc] initWithString:map[@"price"]] ?: [NSNull null] forKey:@"price"]; NSLocale *locale = NSLocale.systemLocale; [self setValue:locale ?: [NSNull null] forKey:@"priceLocale"]; [self setValue:map[@"numberOfPeriods"] ?: @(0) forKey:@"numberOfPeriods"]; SKProductSubscriptionPeriodStub *subscriptionPeriodSub = [[SKProductSubscriptionPeriodStub alloc] initWithMap:map[@"subscriptionPeriod"]]; [self setValue:subscriptionPeriodSub forKey:@"subscriptionPeriod"]; [self setValue:map[@"paymentMode"] ?: @(0) forKey:@"paymentMode"]; if (@available(iOS 12.2, *)) { [self setValue:map[@"identifier"] ?: [NSNull null] forKey:@"identifier"]; [self setValue:map[@"type"] ?: @(0) forKey:@"type"]; } } return self; } @end @implementation SKProductStub - (instancetype)initWithMap:(NSDictionary *)map { self = [super init]; if (self) { [self setValue:map[@"productIdentifier"] ?: [NSNull null] forKey:@"productIdentifier"]; [self setValue:map[@"localizedDescription"] ?: [NSNull null] forKey:@"localizedDescription"]; [self setValue:map[@"localizedTitle"] ?: [NSNull null] forKey:@"localizedTitle"]; [self setValue:map[@"downloadable"] ?: @NO forKey:@"downloadable"]; [self setValue:[[NSDecimalNumber alloc] initWithString:map[@"price"]] ?: [NSNull null] forKey:@"price"]; NSLocale *locale = NSLocale.systemLocale; [self setValue:locale ?: [NSNull null] forKey:@"priceLocale"]; [self setValue:map[@"downloadContentLengths"] ?: @(0) forKey:@"downloadContentLengths"]; if (@available(iOS 11.2, *)) { SKProductSubscriptionPeriodStub *period = [[SKProductSubscriptionPeriodStub alloc] initWithMap:map[@"subscriptionPeriod"]]; [self setValue:period ?: [NSNull null] forKey:@"subscriptionPeriod"]; SKProductDiscountStub *discount = [[SKProductDiscountStub alloc] initWithMap:map[@"introductoryPrice"]]; [self setValue:discount ?: [NSNull null] forKey:@"introductoryPrice"]; [self setValue:map[@"subscriptionGroupIdentifier"] ?: [NSNull null] forKey:@"subscriptionGroupIdentifier"]; } if (@available(iOS 12.2, *)) { NSMutableArray *discounts = [[NSMutableArray alloc] init]; for (NSDictionary *discountMap in map[@"discounts"]) { [discounts addObject:[[SKProductDiscountStub alloc] initWithMap:discountMap]]; } [self setValue:discounts forKey:@"discounts"]; } } return self; } - (instancetype)initWithProductID:(NSString *)productIdentifier { self = [super init]; if (self) { [self setValue:productIdentifier forKey:@"productIdentifier"]; } return self; } @end @interface SKProductRequestStub () @property(strong, nonatomic) NSSet *identifers; @property(strong, nonatomic) NSError *error; @end @implementation SKProductRequestStub - (instancetype)initWithProductIdentifiers:(NSSet *)productIdentifiers { self = [super initWithProductIdentifiers:productIdentifiers]; self.identifers = productIdentifiers; return self; } - (instancetype)initWithFailureError:(NSError *)error { self = [super init]; self.error = error; return self; } - (void)start { NSMutableArray *productArray = [NSMutableArray new]; for (NSString *identifier in self.identifers) { [productArray addObject:@{@"productIdentifier" : identifier}]; } SKProductsResponseStub *response = [[SKProductsResponseStub alloc] initWithMap:@{@"products" : productArray}]; if (self.error) { [self.delegate request:self didFailWithError:self.error]; } else { [self.delegate productsRequest:self didReceiveResponse:response]; } } @end @implementation SKProductsResponseStub - (instancetype)initWithMap:(NSDictionary *)map { self = [super init]; if (self) { NSMutableArray *products = [NSMutableArray new]; for (NSDictionary *productMap in map[@"products"]) { SKProductStub *product = [[SKProductStub alloc] initWithMap:productMap]; [products addObject:product]; } [self setValue:products forKey:@"products"]; } return self; } @end @interface InAppPurchasePluginStub () @end @implementation InAppPurchasePluginStub - (SKProductRequestStub *)getProductRequestWithIdentifiers:(NSSet *)identifiers { return [[SKProductRequestStub alloc] initWithProductIdentifiers:identifiers]; } - (SKProduct *)getProduct:(NSString *)productID { return [[SKProductStub alloc] initWithProductID:productID]; } - (SKReceiptRefreshRequestStub *)getRefreshReceiptRequest:(NSDictionary *)properties { return [[SKReceiptRefreshRequestStub alloc] initWithReceiptProperties:properties]; } @end @interface SKPaymentQueueStub () @end @implementation SKPaymentQueueStub - (void)addTransactionObserver:(id)observer { self.observer = observer; } - (void)removeTransactionObserver:(id)observer { self.observer = nil; } - (void)addPayment:(SKPayment *)payment { SKPaymentTransactionStub *transaction = [[SKPaymentTransactionStub alloc] initWithState:self.testState payment:payment]; [self.observer paymentQueue:self updatedTransactions:@[ transaction ]]; } - (void)restoreCompletedTransactions { if ([self.observer respondsToSelector:@selector(paymentQueueRestoreCompletedTransactionsFinished:)]) { [self.observer paymentQueueRestoreCompletedTransactionsFinished:self]; } } - (void)finishTransaction:(SKPaymentTransaction *)transaction { if ([self.observer respondsToSelector:@selector(paymentQueue:removedTransactions:)]) { [self.observer paymentQueue:self removedTransactions:@[ transaction ]]; } } @end @implementation SKPaymentTransactionStub { SKPayment *_payment; } - (instancetype)initWithID:(NSString *)identifier { self = [super init]; if (self) { [self setValue:identifier forKey:@"transactionIdentifier"]; } return self; } - (instancetype)initWithMap:(NSDictionary *)map { self = [super init]; if (self) { [self setValue:map[@"transactionIdentifier"] forKey:@"transactionIdentifier"]; [self setValue:map[@"transactionState"] forKey:@"transactionState"]; if (![map[@"originalTransaction"] isKindOfClass:[NSNull class]] && map[@"originalTransaction"]) { [self setValue:[[SKPaymentTransactionStub alloc] initWithMap:map[@"originalTransaction"]] forKey:@"originalTransaction"]; } [self setValue:map[@"error"] ? [[NSErrorStub alloc] initWithMap:map[@"error"]] : [NSNull null] forKey:@"error"]; [self setValue:[NSDate dateWithTimeIntervalSince1970:[map[@"transactionTimeStamp"] doubleValue]] forKey:@"transactionDate"]; } return self; } - (instancetype)initWithState:(SKPaymentTransactionState)state { self = [super init]; if (self) { // Only purchased and restored transactions have transactionIdentifier: // https://developer.apple.com/documentation/storekit/skpaymenttransaction/1411288-transactionidentifier?language=objc if (state == SKPaymentTransactionStatePurchased || state == SKPaymentTransactionStateRestored) { [self setValue:@"fakeID" forKey:@"transactionIdentifier"]; } [self setValue:@(state) forKey:@"transactionState"]; } return self; } - (instancetype)initWithState:(SKPaymentTransactionState)state payment:(SKPayment *)payment { self = [super init]; if (self) { // Only purchased and restored transactions have transactionIdentifier: // https://developer.apple.com/documentation/storekit/skpaymenttransaction/1411288-transactionidentifier?language=objc if (state == SKPaymentTransactionStatePurchased || state == SKPaymentTransactionStateRestored) { [self setValue:@"fakeID" forKey:@"transactionIdentifier"]; } [self setValue:@(state) forKey:@"transactionState"]; _payment = payment; } return self; } - (SKPayment *)payment { return _payment; } @end @implementation NSErrorStub - (instancetype)initWithMap:(NSDictionary *)map { return [self initWithDomain:[map objectForKey:@"domain"] code:[[map objectForKey:@"code"] integerValue] userInfo:[map objectForKey:@"userInfo"]]; } @end @implementation FIAPReceiptManagerStub : FIAPReceiptManager - (NSData *)getReceiptData:(NSURL *)url error:(NSError **)error { if (self.returnError) { *error = [NSError errorWithDomain:@"test" code:1 userInfo:@{ @"name" : @"test", @"houseNr" : @5, @"error" : [[NSError alloc] initWithDomain:@"internalTestDomain" code:99 userInfo:nil] }]; return nil; } NSString *originalString = [NSString stringWithFormat:@"test"]; return [[NSData alloc] initWithBase64EncodedString:originalString options:kNilOptions]; } @end @implementation SKReceiptRefreshRequestStub { NSError *_error; } - (instancetype)initWithReceiptProperties:(NSDictionary *)properties { self = [super initWithReceiptProperties:properties]; return self; } - (instancetype)initWithFailureError:(NSError *)error { self = [super init]; _error = error; return self; } - (void)start { if (_error) { [self.delegate request:self didFailWithError:_error]; } else { [self.delegate requestDidFinish:self]; } } @end @implementation SKStorefrontStub - (instancetype)initWithMap:(NSDictionary *)map { self = [super init]; if (self) { // Set stub values [self setValue:map[@"countryCode"] forKey:@"countryCode"]; [self setValue:map[@"identifier"] forKey:@"identifier"]; } return self; } @end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/TranslatorTests.m ================================================ // Copyright 2013 The Flutter 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 "Stubs.h" @import in_app_purchase_storekit; @interface TranslatorTest : XCTestCase @property(strong, nonatomic) NSDictionary *periodMap; @property(strong, nonatomic) NSMutableDictionary *discountMap; @property(strong, nonatomic) NSMutableDictionary *discountMissingIdentifierMap; @property(strong, nonatomic) NSMutableDictionary *productMap; @property(strong, nonatomic) NSDictionary *productResponseMap; @property(strong, nonatomic) NSDictionary *paymentMap; @property(copy, nonatomic) NSDictionary *paymentDiscountMap; @property(strong, nonatomic) NSDictionary *transactionMap; @property(strong, nonatomic) NSDictionary *errorMap; @property(strong, nonatomic) NSDictionary *localeMap; @property(strong, nonatomic) NSDictionary *storefrontMap; @property(strong, nonatomic) NSDictionary *storefrontAndPaymentTransactionMap; @end @implementation TranslatorTest - (void)setUp { self.periodMap = @{@"numberOfUnits" : @(0), @"unit" : @(0)}; self.discountMap = [[NSMutableDictionary alloc] initWithDictionary:@{ @"price" : @"1", @"priceLocale" : [FIAObjectTranslator getMapFromNSLocale:NSLocale.systemLocale], @"numberOfPeriods" : @1, @"subscriptionPeriod" : self.periodMap, @"paymentMode" : @1, }]; if (@available(iOS 12.2, *)) { self.discountMap[@"identifier"] = @"test offer id"; self.discountMap[@"type"] = @(SKProductDiscountTypeIntroductory); } self.discountMissingIdentifierMap = [[NSMutableDictionary alloc] initWithDictionary:@{ @"price" : @"1", @"priceLocale" : [FIAObjectTranslator getMapFromNSLocale:NSLocale.systemLocale], @"numberOfPeriods" : @1, @"subscriptionPeriod" : self.periodMap, @"paymentMode" : @1, @"identifier" : [NSNull null], @"type" : @0, }]; self.productMap = [[NSMutableDictionary alloc] initWithDictionary:@{ @"price" : @"1", @"priceLocale" : [FIAObjectTranslator getMapFromNSLocale:NSLocale.systemLocale], @"productIdentifier" : @"123", @"localizedTitle" : @"title", @"localizedDescription" : @"des", }]; if (@available(iOS 11.2, *)) { self.productMap[@"subscriptionPeriod"] = self.periodMap; self.productMap[@"introductoryPrice"] = self.discountMap; } if (@available(iOS 12.2, *)) { self.productMap[@"discounts"] = @[ self.discountMap ]; } if (@available(iOS 12.0, *)) { self.productMap[@"subscriptionGroupIdentifier"] = @"com.group"; } self.productResponseMap = @{@"products" : @[ self.productMap ], @"invalidProductIdentifiers" : @[]}; self.paymentMap = @{ @"productIdentifier" : @"123", @"requestData" : @"abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh", @"quantity" : @(2), @"applicationUsername" : @"app user name", @"simulatesAskToBuyInSandbox" : @(NO) }; self.paymentDiscountMap = @{ @"identifier" : @"payment_discount_identifier", @"keyIdentifier" : @"payment_discount_key_identifier", @"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52", @"signature" : @"this is a encrypted signature", @"timestamp" : @([NSDate date].timeIntervalSince1970), }; NSDictionary *originalTransactionMap = @{ @"transactionIdentifier" : @"567", @"transactionState" : @(SKPaymentTransactionStatePurchasing), @"payment" : [NSNull null], @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" code:123 userInfo:@{}]], @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), @"originalTransaction" : [NSNull null], }; self.transactionMap = @{ @"transactionIdentifier" : @"567", @"transactionState" : @(SKPaymentTransactionStatePurchasing), @"payment" : [NSNull null], @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" code:123 userInfo:@{}]], @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), @"originalTransaction" : originalTransactionMap, }; self.errorMap = @{ @"code" : @(123), @"domain" : @"test_domain", @"userInfo" : @{ @"key" : @"value", } }; self.storefrontMap = @{ @"countryCode" : @"USA", @"identifier" : @"unique_identifier", }; self.storefrontAndPaymentTransactionMap = @{ @"storefront" : self.storefrontMap, @"transaction" : self.transactionMap, }; } - (void)testSKProductSubscriptionPeriodStubToMap { if (@available(iOS 11.2, *)) { SKProductSubscriptionPeriodStub *period = [[SKProductSubscriptionPeriodStub alloc] initWithMap:self.periodMap]; NSDictionary *map = [FIAObjectTranslator getMapFromSKProductSubscriptionPeriod:period]; XCTAssertEqualObjects(map, self.periodMap); } } - (void)testSKProductDiscountStubToMap { if (@available(iOS 11.2, *)) { SKProductDiscountStub *discount = [[SKProductDiscountStub alloc] initWithMap:self.discountMap]; NSDictionary *map = [FIAObjectTranslator getMapFromSKProductDiscount:discount]; XCTAssertEqualObjects(map, self.discountMap); } } - (void)testProductToMap { SKProductStub *product = [[SKProductStub alloc] initWithMap:self.productMap]; NSDictionary *map = [FIAObjectTranslator getMapFromSKProduct:product]; XCTAssertEqualObjects(map, self.productMap); } - (void)testProductResponseToMap { SKProductsResponseStub *response = [[SKProductsResponseStub alloc] initWithMap:self.productResponseMap]; NSDictionary *map = [FIAObjectTranslator getMapFromSKProductsResponse:response]; XCTAssertEqualObjects(map, self.productResponseMap); } - (void)testPaymentToMap { SKMutablePayment *payment = [FIAObjectTranslator getSKMutablePaymentFromMap:self.paymentMap]; NSDictionary *map = [FIAObjectTranslator getMapFromSKPayment:payment]; XCTAssertEqualObjects(map, self.paymentMap); } - (void)testPaymentTransactionToMap { // payment is not KVC, cannot test payment field. SKPaymentTransactionStub *paymentTransaction = [[SKPaymentTransactionStub alloc] initWithMap:self.transactionMap]; NSDictionary *map = [FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransaction]; XCTAssertEqualObjects(map, self.transactionMap); } - (void)testError { NSErrorStub *error = [[NSErrorStub alloc] initWithMap:self.errorMap]; NSDictionary *map = [FIAObjectTranslator getMapFromNSError:error]; XCTAssertEqualObjects(map, self.errorMap); } - (void)testErrorWithNSNumberAsUserInfo { NSError *error = [NSError errorWithDomain:SKErrorDomain code:3 userInfo:@{@"key" : @42}]; NSDictionary *expectedMap = @{@"domain" : SKErrorDomain, @"code" : @3, @"userInfo" : @{@"key" : @42}}; NSDictionary *map = [FIAObjectTranslator getMapFromNSError:error]; XCTAssertEqualObjects(expectedMap, map); } - (void)testErrorWithMultipleUnderlyingErrors { NSError *underlyingErrorOne = [NSError errorWithDomain:SKErrorDomain code:2 userInfo:nil]; NSError *underlyingErrorTwo = [NSError errorWithDomain:SKErrorDomain code:1 userInfo:nil]; NSError *mainError = [NSError errorWithDomain:SKErrorDomain code:3 userInfo:@{@"underlyingErrors" : @[ underlyingErrorOne, underlyingErrorTwo ]}]; NSDictionary *expectedMap = @{ @"domain" : SKErrorDomain, @"code" : @3, @"userInfo" : @{ @"underlyingErrors" : @[ @{@"domain" : SKErrorDomain, @"code" : @2, @"userInfo" : @{}}, @{@"domain" : SKErrorDomain, @"code" : @1, @"userInfo" : @{}} ] } }; NSDictionary *map = [FIAObjectTranslator getMapFromNSError:mainError]; XCTAssertEqualObjects(expectedMap, map); } - (void)testErrorWithUnsupportedUserInfo { NSError *error = [NSError errorWithDomain:SKErrorDomain code:3 userInfo:@{@"user_info" : [[NSObject alloc] init]}]; NSDictionary *expectedMap = @{ @"domain" : SKErrorDomain, @"code" : @3, @"userInfo" : @{ @"user_info" : [NSString stringWithFormat: @"Unable to encode native userInfo object of type %@ to map. Please submit an " @"issue at https://github.com/flutter/flutter/issues/new with the title " @"\"[in_app_purchase_storekit] Unable to encode userInfo of type %@\" and add " @"reproduction steps and the error details in the description field.", [NSObject class], [NSObject class]] } }; NSDictionary *map = [FIAObjectTranslator getMapFromNSError:error]; XCTAssertEqualObjects(expectedMap, map); } - (void)testLocaleToMap { NSLocale *system = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; NSDictionary *map = [FIAObjectTranslator getMapFromNSLocale:system]; XCTAssertEqualObjects(map[@"currencySymbol"], system.currencySymbol); XCTAssertEqualObjects(map[@"countryCode"], system.countryCode); } - (void)testSKStorefrontToMap { if (@available(iOS 13.0, *)) { SKStorefront *storefront = [[SKStorefrontStub alloc] initWithMap:self.storefrontMap]; NSDictionary *map = [FIAObjectTranslator getMapFromSKStorefront:storefront]; XCTAssertEqualObjects(map, self.storefrontMap); } } - (void)testSKStorefrontAndSKPaymentTransactionToMap { if (@available(iOS 13.0, *)) { SKStorefront *storefront = [[SKStorefrontStub alloc] initWithMap:self.storefrontMap]; SKPaymentTransaction *transaction = [[SKPaymentTransactionStub alloc] initWithMap:self.transactionMap]; NSDictionary *map = [FIAObjectTranslator getMapFromSKStorefront:storefront andSKPaymentTransaction:transaction]; XCTAssertEqualObjects(map, self.storefrontAndPaymentTransactionMap); } } - (void)testSKPaymentDiscountFromMap { if (@available(iOS 12.2, *)) { NSString *error = nil; SKPaymentDiscount *paymentDiscount = [FIAObjectTranslator getSKPaymentDiscountFromMap:self.paymentDiscountMap withError:&error]; XCTAssertEqual(paymentDiscount.identifier, self.paymentDiscountMap[@"identifier"]); XCTAssertEqual(paymentDiscount.keyIdentifier, self.paymentDiscountMap[@"keyIdentifier"]); XCTAssertEqualObjects(paymentDiscount.nonce, [[NSUUID alloc] initWithUUIDString:self.paymentDiscountMap[@"nonce"]]); XCTAssertEqual(paymentDiscount.signature, self.paymentDiscountMap[@"signature"]); XCTAssertEqual(paymentDiscount.timestamp, self.paymentDiscountMap[@"timestamp"]); } } - (void)testSKPaymentDiscountFromMapMissingIdentifier { if (@available(iOS 12.2, *)) { NSArray *invalidValues = @[ [NSNull null], @(1), @"" ]; for (id value in invalidValues) { NSDictionary *discountMap = @{ @"identifier" : value, @"keyIdentifier" : @"payment_discount_key_identifier", @"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52", @"signature" : @"this is a encrypted signature", @"timestamp" : @([NSDate date].timeIntervalSince1970), }; NSString *error = nil; [FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error]; XCTAssertNotNil(error); XCTAssertEqualObjects( error, @"When specifying a payment discount the 'identifier' field is mandatory."); } } } - (void)testGetMapFromSKProductDiscountMissingIdentifier { if (@available(iOS 12.2, *)) { SKProductDiscountStub *discount = [[SKProductDiscountStub alloc] initWithMap:self.discountMissingIdentifierMap]; NSDictionary *map = [FIAObjectTranslator getMapFromSKProductDiscount:discount]; XCTAssertEqualObjects(map, self.discountMissingIdentifierMap); } } - (void)testSKPaymentDiscountFromMapMissingKeyIdentifier { if (@available(iOS 12.2, *)) { NSArray *invalidValues = @[ [NSNull null], @(1), @"" ]; for (id value in invalidValues) { NSDictionary *discountMap = @{ @"identifier" : @"payment_discount_identifier", @"keyIdentifier" : value, @"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52", @"signature" : @"this is a encrypted signature", @"timestamp" : @([NSDate date].timeIntervalSince1970), }; NSString *error = nil; [FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error]; XCTAssertNotNil(error); XCTAssertEqualObjects( error, @"When specifying a payment discount the 'keyIdentifier' field is mandatory."); } } } - (void)testSKPaymentDiscountFromMapMissingNonce { if (@available(iOS 12.2, *)) { NSArray *invalidValues = @[ [NSNull null], @(1), @"" ]; for (id value in invalidValues) { NSDictionary *discountMap = @{ @"identifier" : @"payment_discount_identifier", @"keyIdentifier" : @"payment_discount_key_identifier", @"nonce" : value, @"signature" : @"this is a encrypted signature", @"timestamp" : @([NSDate date].timeIntervalSince1970), }; NSString *error = nil; [FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error]; XCTAssertNotNil(error); XCTAssertEqualObjects(error, @"When specifying a payment discount the 'nonce' field is mandatory."); } } } - (void)testSKPaymentDiscountFromMapMissingSignature { if (@available(iOS 12.2, *)) { NSArray *invalidValues = @[ [NSNull null], @(1), @"" ]; for (id value in invalidValues) { NSDictionary *discountMap = @{ @"identifier" : @"payment_discount_identifier", @"keyIdentifier" : @"payment_discount_key_identifier", @"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52", @"signature" : value, @"timestamp" : @([NSDate date].timeIntervalSince1970), }; NSString *error = nil; [FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error]; XCTAssertNotNil(error); XCTAssertEqualObjects( error, @"When specifying a payment discount the 'signature' field is mandatory."); } } } - (void)testSKPaymentDiscountFromMapMissingTimestamp { if (@available(iOS 12.2, *)) { NSArray *invalidValues = @[ [NSNull null], @"", @(-1) ]; for (id value in invalidValues) { NSDictionary *discountMap = @{ @"identifier" : @"payment_discount_identifier", @"keyIdentifier" : @"payment_discount_key_identifier", @"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52", @"signature" : @"this is a encrypted signature", @"timestamp" : value, }; NSString *error = nil; [FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error]; XCTAssertNotNil(error); XCTAssertEqualObjects( error, @"When specifying a payment discount the 'timestamp' field is mandatory."); } } } - (void)testSKPaymentDiscountFromMapOverflowingTimestamp { if (@available(iOS 12.2, *)) { NSDictionary *discountMap = @{ @"identifier" : @"payment_discount_identifier", @"keyIdentifier" : @"payment_discount_key_identifier", @"nonce" : @"d18981e0-9003-4365-98a2-4b90e3b62c52", @"signature" : @"this is a encrypted signature", @"timestamp" : @1665044583595, // timestamp 2022 Oct }; NSString *error = nil; SKPaymentDiscount *paymentDiscount = [FIAObjectTranslator getSKPaymentDiscountFromMap:discountMap withError:&error]; XCTAssertNil(error); XCTAssertNotNil(paymentDiscount); XCTAssertEqual(paymentDiscount.identifier, discountMap[@"identifier"]); XCTAssertEqual(paymentDiscount.keyIdentifier, discountMap[@"keyIdentifier"]); XCTAssertEqualObjects(paymentDiscount.nonce, [[NSUUID alloc] initWithUUIDString:discountMap[@"nonce"]]); XCTAssertEqual(paymentDiscount.signature, discountMap[@"signature"]); XCTAssertEqual(paymentDiscount.timestamp, discountMap[@"timestamp"]); } } @end ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/in_app_purchase_storekit.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'src/in_app_purchase_storekit_platform.dart'; export 'src/in_app_purchase_storekit_platform_addition.dart'; export 'src/types/types.dart'; ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/channel.dart ================================================ // Copyright 2013 The Flutter 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'; /// Method channel for the plugin's platform<-->Dart calls. const MethodChannel channel = MethodChannel('plugins.flutter.io/in_app_purchase'); /// Method channel used to deliver the payment queue delegate system calls to /// Dart. const MethodChannel paymentQueueDelegateChannel = MethodChannel('plugins.flutter.io/in_app_purchase_payment_queue_delegate'); ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart ================================================ // Copyright 2013 The Flutter 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:flutter/services.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import '../in_app_purchase_storekit.dart'; import '../store_kit_wrappers.dart'; /// [IAPError.code] code for failed purchases. const String kPurchaseErrorCode = 'purchase_error'; /// Indicates store front is Apple AppStore. const String kIAPSource = 'app_store'; /// An [InAppPurchasePlatform] that wraps StoreKit. /// /// This translates various `StoreKit` calls and responses into the /// generic plugin API. class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { /// Creates an [InAppPurchaseStoreKitPlatform] object. /// /// This constructor should only be used for testing, for any other purpose /// get the connection from the [instance] getter. @visibleForTesting InAppPurchaseStoreKitPlatform(); static late SKPaymentQueueWrapper _skPaymentQueueWrapper; static late _TransactionObserver _observer; @override Stream> get purchaseStream => _observer.purchaseUpdatedController.stream; /// Callback handler for transaction status changes. @visibleForTesting static SKTransactionObserverWrapper get observer => _observer; /// Registers this class as the default instance of [InAppPurchasePlatform]. static void registerPlatform() { // Register the [InAppPurchaseStoreKitPlatformAddition] containing // StoreKit-specific functionality. InAppPurchasePlatformAddition.instance = InAppPurchaseStoreKitPlatformAddition(); // Register the platform-specific implementation of the idiomatic // InAppPurchase API. InAppPurchasePlatform.instance = InAppPurchaseStoreKitPlatform(); _skPaymentQueueWrapper = SKPaymentQueueWrapper(); // Create a purchaseUpdatedController and notify the native side when to // start of stop sending updates. final StreamController> updateController = StreamController>.broadcast( onListen: () => _skPaymentQueueWrapper.startObservingTransactionQueue(), onCancel: () => _skPaymentQueueWrapper.stopObservingTransactionQueue(), ); _observer = _TransactionObserver(updateController); _skPaymentQueueWrapper.setTransactionObserver(observer); } @override Future isAvailable() => SKPaymentQueueWrapper.canMakePayments(); @override Future buyNonConsumable({required PurchaseParam purchaseParam}) async { await _skPaymentQueueWrapper.addPayment(SKPaymentWrapper( productIdentifier: purchaseParam.productDetails.id, quantity: purchaseParam is AppStorePurchaseParam ? purchaseParam.quantity : 1, applicationUsername: purchaseParam.applicationUserName, simulatesAskToBuyInSandbox: purchaseParam is AppStorePurchaseParam && purchaseParam.simulatesAskToBuyInSandbox, paymentDiscount: purchaseParam is AppStorePurchaseParam ? purchaseParam.discount : null)); return true; // There's no error feedback from iOS here to return. } @override Future buyConsumable( {required PurchaseParam purchaseParam, bool autoConsume = true}) { assert(autoConsume == true, 'On iOS, we should always auto consume'); return buyNonConsumable(purchaseParam: purchaseParam); } @override Future completePurchase(PurchaseDetails purchase) { assert( purchase is AppStorePurchaseDetails, 'On iOS, the `purchase` should always be of type `AppStorePurchaseDetails`.', ); return _skPaymentQueueWrapper.finishTransaction( (purchase as AppStorePurchaseDetails).skPaymentTransaction, ); } @override Future restorePurchases({String? applicationUserName}) async { return _observer .restoreTransactions( queue: _skPaymentQueueWrapper, applicationUserName: applicationUserName) .whenComplete(() => _observer.cleanUpRestoredTransactions()); } /// Query the product detail list. /// /// This method only returns [ProductDetailsResponse]. /// To get detailed Store Kit product list, use [SkProductResponseWrapper.startProductRequest] /// to get the [SKProductResponseWrapper]. @override Future queryProductDetails( Set identifiers) async { final SKRequestMaker requestMaker = SKRequestMaker(); SkProductResponseWrapper response; PlatformException? exception; try { response = await requestMaker.startProductRequest(identifiers.toList()); } on PlatformException catch (e) { exception = e; response = SkProductResponseWrapper( products: const [], invalidProductIdentifiers: identifiers.toList()); } List productDetails = []; if (response.products != null) { productDetails = response.products .map((SKProductWrapper productWrapper) => AppStoreProductDetails.fromSKProduct(productWrapper)) .toList(); } List invalidIdentifiers = response.invalidProductIdentifiers; if (productDetails.isEmpty) { invalidIdentifiers = identifiers.toList(); } final ProductDetailsResponse productDetailsResponse = ProductDetailsResponse( productDetails: productDetails, notFoundIDs: invalidIdentifiers, error: exception == null ? null : IAPError( source: kIAPSource, code: exception.code, message: exception.message ?? '', details: exception.details), ); return productDetailsResponse; } } enum _TransactionRestoreState { notRunning, waitingForTransactions, receivedTransaction, } class _TransactionObserver implements SKTransactionObserverWrapper { _TransactionObserver(this.purchaseUpdatedController); final StreamController> purchaseUpdatedController; Completer? _restoreCompleter; late String _receiptData; _TransactionRestoreState _transactionRestoreState = _TransactionRestoreState.notRunning; Future restoreTransactions({ required SKPaymentQueueWrapper queue, String? applicationUserName, }) { _transactionRestoreState = _TransactionRestoreState.waitingForTransactions; _restoreCompleter = Completer(); queue.restoreTransactions(applicationUserName: applicationUserName); return _restoreCompleter!.future; } void cleanUpRestoredTransactions() { _restoreCompleter = null; } @override void updatedTransactions( {required List transactions}) { _handleTransationUpdates(transactions); } @override void removedTransactions( {required List transactions}) {} /// Triggered when there is an error while restoring transactions. @override void restoreCompletedTransactionsFailed({required SKError error}) { _restoreCompleter!.completeError(error); _transactionRestoreState = _TransactionRestoreState.notRunning; } @override void paymentQueueRestoreCompletedTransactionsFinished() { _restoreCompleter!.complete(); // If no restored transactions were received during the restore session // emit an empty list of purchase details to inform listeners that the // restore session finished without any results. if (_transactionRestoreState == _TransactionRestoreState.waitingForTransactions) { purchaseUpdatedController.add([]); } _transactionRestoreState = _TransactionRestoreState.notRunning; } @override bool shouldAddStorePayment( {required SKPaymentWrapper payment, required SKProductWrapper product}) { // In this unified API, we always return true to keep it consistent with the behavior on Google Play. return true; } Future getReceiptData() async { try { _receiptData = await SKReceiptManager.retrieveReceiptData(); } catch (e) { _receiptData = ''; } return _receiptData; } Future _handleTransationUpdates( List transactions) async { if (_transactionRestoreState == _TransactionRestoreState.waitingForTransactions && transactions.any((SKPaymentTransactionWrapper transaction) => transaction.transactionState == SKPaymentTransactionStateWrapper.restored)) { _transactionRestoreState = _TransactionRestoreState.receivedTransaction; } final String receiptData = await getReceiptData(); final List purchases = transactions .map((SKPaymentTransactionWrapper transaction) => AppStorePurchaseDetails.fromSKTransaction(transaction, receiptData)) .toList(); purchaseUpdatedController.add(purchases); } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform_addition.dart ================================================ // Copyright 2013 The Flutter 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: avoid_print import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import '../in_app_purchase_storekit.dart'; import '../store_kit_wrappers.dart'; /// Contains InApp Purchase features that are only available on iOS. class InAppPurchaseStoreKitPlatformAddition extends InAppPurchasePlatformAddition { /// Present Code Redemption Sheet. /// /// Available on devices running iOS 14 and iPadOS 14 and later. Future presentCodeRedemptionSheet() { return SKPaymentQueueWrapper().presentCodeRedemptionSheet(); } /// Retry loading purchase data after an initial failure. /// /// If no results, a `null` value is returned. Future refreshPurchaseVerificationData() async { await SKRequestMaker().startRefreshReceiptRequest(); try { final String receipt = await SKReceiptManager.retrieveReceiptData(); return PurchaseVerificationData( localVerificationData: receipt, serverVerificationData: receipt, source: kIAPSource); } catch (e) { print( 'Something is wrong while fetching the receipt, this normally happens when the app is ' 'running on a simulator: $e'); return null; } } /// Sets an implementation of the [SKPaymentQueueDelegateWrapper]. /// /// The [SKPaymentQueueDelegateWrapper] can be used to inform iOS how to /// finish transactions when the storefront changes or if the price consent /// sheet should be displayed when the price of a subscription has changed. If /// no delegate is registered iOS will fallback to it's default configuration. /// See the documentation on StoreKite's [`-[SKPaymentQueue delegate:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3182429-delegate?language=objc). /// /// When set to `null` the payment queue delegate will be removed and the /// default behaviour will apply (see [documentation](https://developer.apple.com/documentation/storekit/skpaymentqueue/3182429-delegate?language=objc)). Future setDelegate(SKPaymentQueueDelegateWrapper? delegate) => SKPaymentQueueWrapper().setDelegate(delegate); /// Shows the price consent sheet if the user has not yet responded to a /// subscription price change. /// /// Use this function when you have registered a [SKPaymentQueueDelegateWrapper] /// (using the [setDelegate] method) and returned `false` when the /// `SKPaymentQueueDelegateWrapper.shouldShowPriceConsent()` method was called. /// /// See documentation of StoreKit's [`-[SKPaymentQueue showPriceConsentIfNeeded]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3521327-showpriceconsentifneeded?language=objc). Future showPriceConsentIfNeeded() => SKPaymentQueueWrapper().showPriceConsentIfNeeded(); } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/README.md ================================================ # store_kit_wrappers This exposes Dart endpoints through to the [StoreKit](https://developer.apple.com/documentation/storekit) APIs. Can be used as an alternative to [in_app_purchase](../in_app_purchase/README.md). ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/enum_converters.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:json_annotation/json_annotation.dart'; import '../../store_kit_wrappers.dart'; part 'enum_converters.g.dart'; /// Serializer for [SKPaymentTransactionStateWrapper]. /// /// Use these in `@JsonSerializable()` classes by annotating them with /// `@SKTransactionStatusConverter()`. class SKTransactionStatusConverter implements JsonConverter { /// Default const constructor. const SKTransactionStatusConverter(); @override SKPaymentTransactionStateWrapper fromJson(int? json) { if (json == null) { return SKPaymentTransactionStateWrapper.unspecified; } return $enumDecode( _$SKPaymentTransactionStateWrapperEnumMap .cast(), json); } /// Converts an [SKPaymentTransactionStateWrapper] to a [PurchaseStatus]. PurchaseStatus toPurchaseStatus( SKPaymentTransactionStateWrapper object, SKError? error) { switch (object) { case SKPaymentTransactionStateWrapper.purchasing: case SKPaymentTransactionStateWrapper.deferred: return PurchaseStatus.pending; case SKPaymentTransactionStateWrapper.purchased: return PurchaseStatus.purchased; case SKPaymentTransactionStateWrapper.restored: return PurchaseStatus.restored; case SKPaymentTransactionStateWrapper.failed: // According to the Apple documentation the error code "2" indicates // the user cancelled the payment (SKErrorPaymentCancelled) and error // code "15" indicates the cancellation of the overlay (SKErrorOverlayCancelled). // An overview of all error codes can be found at: https://developer.apple.com/documentation/storekit/skerrorcode?language=objc if (error != null && (error.code == 2 || error.code == 15)) { return PurchaseStatus.canceled; } return PurchaseStatus.error; case SKPaymentTransactionStateWrapper.unspecified: return PurchaseStatus.error; } } @override int toJson(SKPaymentTransactionStateWrapper object) => _$SKPaymentTransactionStateWrapperEnumMap[object]!; } /// Serializer for [SKSubscriptionPeriodUnit]. /// /// Use these in `@JsonSerializable()` classes by annotating them with /// `@SKSubscriptionPeriodUnitConverter()`. class SKSubscriptionPeriodUnitConverter implements JsonConverter { /// Default const constructor. const SKSubscriptionPeriodUnitConverter(); @override SKSubscriptionPeriodUnit fromJson(int? json) { if (json == null) { return SKSubscriptionPeriodUnit.day; } return $enumDecode( _$SKSubscriptionPeriodUnitEnumMap .cast(), json); } @override int toJson(SKSubscriptionPeriodUnit object) => _$SKSubscriptionPeriodUnitEnumMap[object]!; } /// Serializer for [SKProductDiscountPaymentMode]. /// /// Use these in `@JsonSerializable()` classes by annotating them with /// `@SKProductDiscountPaymentModeConverter()`. class SKProductDiscountPaymentModeConverter implements JsonConverter { /// Default const constructor. const SKProductDiscountPaymentModeConverter(); @override SKProductDiscountPaymentMode fromJson(int? json) { if (json == null) { return SKProductDiscountPaymentMode.payAsYouGo; } return $enumDecode( _$SKProductDiscountPaymentModeEnumMap .cast(), json); } @override int toJson(SKProductDiscountPaymentMode object) => _$SKProductDiscountPaymentModeEnumMap[object]!; } // Define a class so we generate serializer helper methods for the enums // See https://github.com/google/json_serializable.dart/issues/778 @JsonSerializable() class _SerializedEnums { late SKPaymentTransactionStateWrapper response; late SKSubscriptionPeriodUnit unit; late SKProductDiscountPaymentMode discountPaymentMode; } /// Serializer for [SKProductDiscountType]. /// /// Use these in `@JsonSerializable()` classes by annotating them with /// `@SKProductDiscountTypeConverter()`. class SKProductDiscountTypeConverter implements JsonConverter { /// Default const constructor. const SKProductDiscountTypeConverter(); @override SKProductDiscountType fromJson(int? json) { if (json == null) { return SKProductDiscountType.introductory; } return $enumDecode( _$SKProductDiscountTypeEnumMap.cast(), json); } @override int toJson(SKProductDiscountType object) => _$SKProductDiscountTypeEnumMap[object]!; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/enum_converters.g.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND part of 'enum_converters.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** _SerializedEnums _$SerializedEnumsFromJson(Map json) => _SerializedEnums() ..response = $enumDecode(_$SKPaymentTransactionStateWrapperEnumMap, json['response']) ..unit = $enumDecode(_$SKSubscriptionPeriodUnitEnumMap, json['unit']) ..discountPaymentMode = $enumDecode( _$SKProductDiscountPaymentModeEnumMap, json['discountPaymentMode']); const _$SKPaymentTransactionStateWrapperEnumMap = { SKPaymentTransactionStateWrapper.purchasing: 0, SKPaymentTransactionStateWrapper.purchased: 1, SKPaymentTransactionStateWrapper.failed: 2, SKPaymentTransactionStateWrapper.restored: 3, SKPaymentTransactionStateWrapper.deferred: 4, SKPaymentTransactionStateWrapper.unspecified: -1, }; const _$SKSubscriptionPeriodUnitEnumMap = { SKSubscriptionPeriodUnit.day: 0, SKSubscriptionPeriodUnit.week: 1, SKSubscriptionPeriodUnit.month: 2, SKSubscriptionPeriodUnit.year: 3, }; const _$SKProductDiscountPaymentModeEnumMap = { SKProductDiscountPaymentMode.payAsYouGo: 0, SKProductDiscountPaymentMode.payUpFront: 1, SKProductDiscountPaymentMode.freeTrail: 2, SKProductDiscountPaymentMode.unspecified: -1, }; const _$SKProductDiscountTypeEnumMap = { SKProductDiscountType.introductory: 0, SKProductDiscountType.subscription: 1, }; ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_delegate_wrapper.dart ================================================ // Copyright 2013 The Flutter 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 '../../store_kit_wrappers.dart'; /// A wrapper around /// [`SKPaymentQueueDelegate`](https://developer.apple.com/documentation/storekit/skpaymentqueuedelegate?language=objc). /// /// The payment queue delegate can be implementated to provide information /// needed to complete transactions. /// /// The [SKPaymentQueueDelegateWrapper] is available on macOS and iOS 13+. /// Usage with versions below iOS 13 and macOS are ignored. abstract class SKPaymentQueueDelegateWrapper { /// Called by the system to check whether the transaction should continue if /// the device's App Store storefront has changed during a transaction. /// /// - Return `true` if the transaction should continue within the updated /// storefront (default behaviour). /// - Return `false` if the transaction should be cancelled. In this case the /// transaction will fail with the error [SKErrorStoreProductNotAvailable](https://developer.apple.com/documentation/storekit/skerrorcode/skerrorstoreproductnotavailable?language=objc). /// /// See the documentation in StoreKit's [`[-SKPaymentQueueDelegate shouldContinueTransaction]`](https://developer.apple.com/documentation/storekit/skpaymentqueuedelegate/3242935-paymentqueue?language=objc). bool shouldContinueTransaction( SKPaymentTransactionWrapper transaction, SKStorefrontWrapper storefront, ) => true; /// Called by the system to check whether to immediately show the price /// consent form. /// /// The default return value is `true`. This will inform the system to display /// the price consent sheet when the subscription price has been changed in /// App Store Connect and the subscriber has not yet taken action. See the /// documentation in StoreKit's [`[-SKPaymentQueueDelegate shouldShowPriceConsent:]`](https://developer.apple.com/documentation/storekit/skpaymentqueuedelegate/3521328-paymentqueueshouldshowpriceconse?language=objc). bool shouldShowPriceConsent() => true; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart ================================================ // Copyright 2013 The Flutter 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:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:json_annotation/json_annotation.dart'; import '../../store_kit_wrappers.dart'; import '../channel.dart'; import '../in_app_purchase_storekit_platform.dart'; part 'sk_payment_queue_wrapper.g.dart'; /// A wrapper around /// [`SKPaymentQueue`](https://developer.apple.com/documentation/storekit/skpaymentqueue?language=objc). /// /// The payment queue contains payment related operations. It communicates with /// the App Store and presents a user interface for the user to process and /// authorize payments. /// /// Full information on using `SKPaymentQueue` and processing purchases is /// available at the [In-App Purchase Programming /// Guide](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Introduction.html#//apple_ref/doc/uid/TP40008267). class SKPaymentQueueWrapper { /// Returns the default payment queue. /// /// We do not support instantiating a custom payment queue, hence the /// singleton. However, you can override the observer. factory SKPaymentQueueWrapper() { return _singleton; } SKPaymentQueueWrapper._(); static final SKPaymentQueueWrapper _singleton = SKPaymentQueueWrapper._(); SKPaymentQueueDelegateWrapper? _paymentQueueDelegate; SKTransactionObserverWrapper? _observer; /// Calls [`-[SKPaymentQueue transactions]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506026-transactions?language=objc) Future> transactions() async { return _getTransactionList((await channel .invokeListMethod('-[SKPaymentQueue transactions]'))!); } /// Calls [`-[SKPaymentQueue canMakePayments:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506139-canmakepayments?language=objc). static Future canMakePayments() async => (await channel .invokeMethod('-[SKPaymentQueue canMakePayments:]')) ?? false; /// Sets an observer to listen to all incoming transaction events. /// /// This should be called and set as soon as the app launches in order to /// avoid missing any purchase updates from the App Store. See the /// documentation on StoreKit's [`-[SKPaymentQueue /// addTransactionObserver:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506042-addtransactionobserver?language=objc). void setTransactionObserver(SKTransactionObserverWrapper observer) { _observer = observer; channel.setMethodCallHandler(handleObserverCallbacks); } /// Instructs the iOS implementation to register a transaction observer and /// start listening to it. /// /// Call this method when the first listener is subscribed to the /// [InAppPurchaseStoreKitPlatform.purchaseStream]. Future startObservingTransactionQueue() => channel .invokeMethod('-[SKPaymentQueue startObservingTransactionQueue]'); /// Instructs the iOS implementation to remove the transaction observer and /// stop listening to it. /// /// Call this when there are no longer any listeners subscribed to the /// [InAppPurchaseStoreKitPlatform.purchaseStream]. Future stopObservingTransactionQueue() => channel .invokeMethod('-[SKPaymentQueue stopObservingTransactionQueue]'); /// Sets an implementation of the [SKPaymentQueueDelegateWrapper]. /// /// The [SKPaymentQueueDelegateWrapper] can be used to inform iOS how to /// finish transactions when the storefront changes or if the price consent /// sheet should be displayed when the price of a subscription has changed. If /// no delegate is registered iOS will fallback to it's default configuration. /// See the documentation on StoreKite's [`-[SKPaymentQueue delegate:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3182429-delegate?language=objc). /// /// When set to `null` the payment queue delegate will be removed and the /// default behaviour will apply (see [documentation](https://developer.apple.com/documentation/storekit/skpaymentqueue/3182429-delegate?language=objc)). Future setDelegate(SKPaymentQueueDelegateWrapper? delegate) async { if (delegate == null) { await channel.invokeMethod('-[SKPaymentQueue removeDelegate]'); paymentQueueDelegateChannel.setMethodCallHandler(null); } else { await channel.invokeMethod('-[SKPaymentQueue registerDelegate]'); paymentQueueDelegateChannel .setMethodCallHandler(handlePaymentQueueDelegateCallbacks); } _paymentQueueDelegate = delegate; } /// Posts a payment to the queue. /// /// This sends a purchase request to the App Store for confirmation. /// Transaction updates will be delivered to the set /// [SkTransactionObserverWrapper]. /// /// A couple preconditions need to be met before calling this method. /// /// - At least one [SKTransactionObserverWrapper] should have been added to /// the payment queue using [addTransactionObserver]. /// - The [payment.productIdentifier] needs to have been previously fetched /// using [SKRequestMaker.startProductRequest] so that a valid `SKProduct` /// has been cached in the platform side already. Because of this /// [payment.productIdentifier] cannot be hardcoded. /// /// This method calls StoreKit's [`-[SKPaymentQueue addPayment:]`] /// (https://developer.apple.com/documentation/storekit/skpaymentqueue/1506036-addpayment?preferredLanguage=occ). /// /// Also see [sandbox /// testing](https://developer.apple.com/apple-pay/sandbox-testing/). Future addPayment(SKPaymentWrapper payment) async { assert(_observer != null, '[in_app_purchase]: Trying to add a payment without an observer. One must be set using `SkPaymentQueueWrapper.setTransactionObserver` before the app launches.'); final Map requestMap = payment.toMap(); await channel.invokeMethod( '-[InAppPurchasePlugin addPayment:result:]', requestMap, ); } /// Finishes a transaction and removes it from the queue. /// /// This method should be called after the given [transaction] has been /// succesfully processed and its content has been delivered to the user. /// Transaction status updates are propagated to [SkTransactionObserver]. /// /// This will throw a Platform exception if [transaction.transactionState] is /// [SKPaymentTransactionStateWrapper.purchasing]. /// /// This method calls StoreKit's [`-[SKPaymentQueue /// finishTransaction:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506003-finishtransaction?language=objc). Future finishTransaction( SKPaymentTransactionWrapper transaction) async { final Map requestMap = transaction.toFinishMap(); await channel.invokeMethod( '-[InAppPurchasePlugin finishTransaction:result:]', requestMap, ); } /// Restore previously purchased transactions. /// /// Use this to load previously purchased content on a new device. /// /// This call triggers purchase updates on the set /// [SKTransactionObserverWrapper] for previously made transactions. This will /// invoke [SKTransactionObserverWrapper.restoreCompletedTransactions], /// [SKTransactionObserverWrapper.paymentQueueRestoreCompletedTransactionsFinished], /// and [SKTransactionObserverWrapper.updatedTransaction]. These restored /// transactions need to be marked complete with [finishTransaction] once the /// content is delivered, like any other transaction. /// /// The `applicationUserName` should match the original /// [SKPaymentWrapper.applicationUsername] used in [addPayment]. /// If no `applicationUserName` was used, `applicationUserName` should be null. /// /// This method either triggers [`-[SKPayment /// restoreCompletedTransactions]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506123-restorecompletedtransactions?language=objc) /// or [`-[SKPayment restoreCompletedTransactionsWithApplicationUsername:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1505992-restorecompletedtransactionswith?language=objc) /// depending on whether the `applicationUserName` is set. Future restoreTransactions({String? applicationUserName}) async { await channel.invokeMethod( '-[InAppPurchasePlugin restoreTransactions:result:]', applicationUserName); } /// Present Code Redemption Sheet /// /// Use this to allow Users to enter and redeem Codes /// /// This method triggers [`-[SKPayment /// presentCodeRedemptionSheet]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3566726-presentcoderedemptionsheet?language=objc) Future presentCodeRedemptionSheet() async { await channel.invokeMethod( '-[InAppPurchasePlugin presentCodeRedemptionSheet:result:]'); } /// Shows the price consent sheet if the user has not yet responded to a /// subscription price change. /// /// Use this function when you have registered a [SKPaymentQueueDelegateWrapper] /// (using the [setDelegate] method) and returned `false` when the /// `SKPaymentQueueDelegateWrapper.shouldShowPriceConsent()` method was called. /// /// See documentation of StoreKit's [`-[SKPaymentQueue showPriceConsentIfNeeded]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3521327-showpriceconsentifneeded?language=objc). Future showPriceConsentIfNeeded() async { await channel .invokeMethod('-[SKPaymentQueue showPriceConsentIfNeeded]'); } /// Triage a method channel call from the platform and triggers the correct observer method. /// /// This method is public for testing purposes only and should not be used /// outside this class. @visibleForTesting Future handleObserverCallbacks(MethodCall call) async { assert(_observer != null, '[in_app_purchase]: (Fatal)The observer has not been set but we received a purchase transaction notification. Please ensure the observer has been set using `setTransactionObserver`. Make sure the observer is added right at the App Launch.'); final SKTransactionObserverWrapper observer = _observer!; switch (call.method) { case 'updatedTransactions': { final List transactions = _getTransactionList(call.arguments as List); return Future(() { observer.updatedTransactions(transactions: transactions); }); } case 'removedTransactions': { final List transactions = _getTransactionList(call.arguments as List); return Future(() { observer.removedTransactions(transactions: transactions); }); } case 'restoreCompletedTransactionsFailed': { final SKError error = SKError.fromJson(Map.from( call.arguments as Map)); return Future(() { observer.restoreCompletedTransactionsFailed(error: error); }); } case 'paymentQueueRestoreCompletedTransactionsFinished': { return Future(() { observer.paymentQueueRestoreCompletedTransactionsFinished(); }); } case 'shouldAddStorePayment': { final Map arguments = call.arguments as Map; final SKPaymentWrapper payment = SKPaymentWrapper.fromJson( (arguments['payment']! as Map) .cast()); final SKProductWrapper product = SKProductWrapper.fromJson( (arguments['product']! as Map) .cast()); return Future(() { if (observer.shouldAddStorePayment( payment: payment, product: product) == true) { SKPaymentQueueWrapper().addPayment(payment); } }); } default: break; } throw PlatformException( code: 'no_such_callback', message: 'Did not recognize the observer callback ${call.method}.'); } // Get transaction wrapper object list from arguments. List _getTransactionList( List transactionsData) { return transactionsData.map((dynamic map) { return SKPaymentTransactionWrapper.fromJson( Map.castFrom( map as Map)); }).toList(); } /// Triage a method channel call from the platform and triggers the correct /// payment queue delegate method. /// /// This method is public for testing purposes only and should not be used /// outside this class. @visibleForTesting Future handlePaymentQueueDelegateCallbacks(MethodCall call) async { assert(_paymentQueueDelegate != null, '[in_app_purchase]: (Fatal)The payment queue delegate has not been set but we received a payment queue notification. Please ensure the payment queue has been set using `setDelegate`.'); final SKPaymentQueueDelegateWrapper delegate = _paymentQueueDelegate!; switch (call.method) { case 'shouldContinueTransaction': final Map arguments = call.arguments as Map; final SKPaymentTransactionWrapper transaction = SKPaymentTransactionWrapper.fromJson( (arguments['transaction']! as Map) .cast()); final SKStorefrontWrapper storefront = SKStorefrontWrapper.fromJson( (arguments['storefront']! as Map) .cast()); return delegate.shouldContinueTransaction(transaction, storefront); case 'shouldShowPriceConsent': return delegate.shouldShowPriceConsent(); default: break; } throw PlatformException( code: 'no_such_callback', message: 'Did not recognize the payment queue delegate callback ${call.method}.'); } } /// Dart wrapper around StoreKit's /// [NSError](https://developer.apple.com/documentation/foundation/nserror?language=objc). @immutable @JsonSerializable() class SKError { /// Creates a new [SKError] object with the provided information. const SKError( {required this.code, required this.domain, required this.userInfo}); /// Constructs an instance of this from a key-value map of data. /// /// The map needs to have named string keys with values matching the names and /// types of all of the members on this class. The `map` parameter must not be /// null. factory SKError.fromJson(Map map) { return _$SKErrorFromJson(map); } /// Error [code](https://developer.apple.com/documentation/foundation/1448136-nserror_codes) /// as defined in the Cocoa Framework. @JsonKey(defaultValue: 0) final int code; /// Error /// [domain](https://developer.apple.com/documentation/foundation/nscocoaerrordomain?language=objc) /// as defined in the Cocoa Framework. @JsonKey(defaultValue: '') final String domain; /// A map that contains more detailed information about the error. /// /// Any key of the map must be a valid [NSErrorUserInfoKey](https://developer.apple.com/documentation/foundation/nserroruserinfokey?language=objc). @JsonKey(defaultValue: {}) final Map userInfo; @override bool operator ==(Object other) { if (identical(other, this)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is SKError && other.code == code && other.domain == domain && const DeepCollectionEquality.unordered() .equals(other.userInfo, userInfo); } @override int get hashCode => Object.hash( code, domain, userInfo, ); } /// Dart wrapper around StoreKit's /// [SKPayment](https://developer.apple.com/documentation/storekit/skpayment?language=objc). /// /// Used as the parameter to initiate a payment. In general, a developer should /// not need to create the payment object explicitly; instead, use /// [SKPaymentQueueWrapper.addPayment] directly with a product identifier to /// initiate a payment. @immutable @JsonSerializable(createToJson: true) class SKPaymentWrapper { /// Creates a new [SKPaymentWrapper] with the provided information. const SKPaymentWrapper({ required this.productIdentifier, this.applicationUsername, this.requestData, this.quantity = 1, this.simulatesAskToBuyInSandbox = false, this.paymentDiscount, }); /// Constructs an instance of this from a key value map of data. /// /// The map needs to have named string keys with values matching the names and /// types of all of the members on this class. The `map` parameter must not be /// null. factory SKPaymentWrapper.fromJson(Map map) { assert(map != null); return _$SKPaymentWrapperFromJson(map); } /// Creates a Map object describes the payment object. Map toMap() { return { 'productIdentifier': productIdentifier, 'applicationUsername': applicationUsername, 'requestData': requestData, 'quantity': quantity, 'simulatesAskToBuyInSandbox': simulatesAskToBuyInSandbox, 'paymentDiscount': paymentDiscount?.toMap(), }; } /// The id for the product that the payment is for. @JsonKey(defaultValue: '') final String productIdentifier; /// An opaque id for the user's account. /// /// Used to help the store detect irregular activity. See /// [applicationUsername](https://developer.apple.com/documentation/storekit/skpayment/1506116-applicationusername?language=objc) /// for more details. For example, you can use a one-way hash of the user’s /// account name on your server. Don’t use the Apple ID for your developer /// account, the user’s Apple ID, or the user’s plaintext account name on /// your server. final String? applicationUsername; /// Reserved for future use. /// /// The value must be null before sending the payment. If the value is not /// null, the payment will be rejected. /// // The iOS Platform provided this property but it is reserved for future use. // We also provide this property to match the iOS platform. Converted to // String from NSData from ios platform using UTF8Encoding. The / default is // null. final String? requestData; /// The amount of the product this payment is for. /// /// The default is 1. The minimum is 1. The maximum is 10. /// /// If the object is invalid, the value could be 0. @JsonKey(defaultValue: 0) final int quantity; /// Produces an "ask to buy" flow in the sandbox. /// /// Setting it to `true` will cause a transaction to be in the state [SKPaymentTransactionStateWrapper.deferred], /// which produce an "ask to buy" prompt that interrupts the the payment flow. /// /// Default is `false`. /// /// See https://developer.apple.com/in-app-purchase/ for a guide on Sandbox /// testing. final bool simulatesAskToBuyInSandbox; /// The details of a discount that should be applied to the payment. /// /// See [Implementing Promotional Offers in Your App](https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/subscriptions_and_offers/implementing_promotional_offers_in_your_app?language=objc) /// for more information on generating keys and creating offers for /// auto-renewable subscriptions. If set to `null` no discount will be /// applied to this payment. final SKPaymentDiscountWrapper? paymentDiscount; @override bool operator ==(Object other) { if (identical(other, this)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is SKPaymentWrapper && other.productIdentifier == productIdentifier && other.applicationUsername == applicationUsername && other.quantity == quantity && other.simulatesAskToBuyInSandbox == simulatesAskToBuyInSandbox && other.requestData == requestData; } @override int get hashCode => Object.hash(productIdentifier, applicationUsername, quantity, simulatesAskToBuyInSandbox, requestData); @override String toString() => _$SKPaymentWrapperToJson(this).toString(); } /// Dart wrapper around StoreKit's /// [SKPaymentDiscount](https://developer.apple.com/documentation/storekit/skpaymentdiscount?language=objc). /// /// Used to indicate a discount is applicable to a payment. The /// [SKPaymentDiscountWrapper] instance should be assigned to the /// [SKPaymentWrapper] object to which the discount should be applied. /// Discount offers are set up in App Store Connect. See [Implementing Promotional Offers in Your App](https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/subscriptions_and_offers/implementing_promotional_offers_in_your_app?language=objc) /// for more information. @immutable @JsonSerializable(createToJson: true) class SKPaymentDiscountWrapper { /// Creates a new [SKPaymentDiscountWrapper] with the provided information. const SKPaymentDiscountWrapper({ required this.identifier, required this.keyIdentifier, required this.nonce, required this.signature, required this.timestamp, }); /// Constructs an instance of this from a key value map of data. /// /// The map needs to have named string keys with values matching the names and /// types of all of the members on this class. factory SKPaymentDiscountWrapper.fromJson(Map map) { assert(map != null); return _$SKPaymentDiscountWrapperFromJson(map); } /// Creates a Map object describes the payment object. Map toMap() { return { 'identifier': identifier, 'keyIdentifier': keyIdentifier, 'nonce': nonce, 'signature': signature, 'timestamp': timestamp, }; } /// The identifier of the discount offer. /// /// The identifier must match one of the offers set up in App Store Connect. final String identifier; /// A string identifying the key that is used to generate the signature. /// /// Keys are generated and downloaded from App Store Connect. See /// [Generating a Signature for Promotional Offers](https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/subscriptions_and_offers/generating_a_signature_for_promotional_offers?language=objc) /// for more information. final String keyIdentifier; /// A universal unique identifier (UUID) created together with the signature. /// /// The UUID should be generated on your server when it creates the /// `signature` for the payment discount. The UUID can be used once, a new /// UUID should be created for each payment request. The string representation /// of the UUID must be lowercase. See /// [Generating a Signature for Promotional Offers](https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/subscriptions_and_offers/generating_a_signature_for_promotional_offers?language=objc) /// for more information. final String nonce; /// A cryptographically signed string representing the to properties of the /// promotional offer. /// /// The signature is string signed with a private key and contains all the /// properties of the promotional offer. To keep you private key secure the /// signature should be created on a server. See [Generating a Signature for Promotional Offers](https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/subscriptions_and_offers/generating_a_signature_for_promotional_offers?language=objc) /// for more information. final String signature; /// The date and time the signature was created. /// /// The timestamp should be formatted in Unix epoch time. See /// [Generating a Signature for Promotional Offers](https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/subscriptions_and_offers/generating_a_signature_for_promotional_offers?language=objc) /// for more information. final int timestamp; @override bool operator ==(Object other) { if (identical(other, this)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is SKPaymentDiscountWrapper && other.identifier == identifier && other.keyIdentifier == keyIdentifier && other.nonce == nonce && other.signature == signature && other.timestamp == timestamp; } @override int get hashCode => Object.hash(identifier, keyIdentifier, nonce, signature, timestamp); } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.g.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND part of 'sk_payment_queue_wrapper.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** SKError _$SKErrorFromJson(Map json) => SKError( code: json['code'] as int? ?? 0, domain: json['domain'] as String? ?? '', userInfo: (json['userInfo'] as Map?)?.map( (k, e) => MapEntry(k as String, e), ) ?? {}, ); SKPaymentWrapper _$SKPaymentWrapperFromJson(Map json) => SKPaymentWrapper( productIdentifier: json['productIdentifier'] as String? ?? '', applicationUsername: json['applicationUsername'] as String?, requestData: json['requestData'] as String?, quantity: json['quantity'] as int? ?? 0, simulatesAskToBuyInSandbox: json['simulatesAskToBuyInSandbox'] as bool? ?? false, ); Map _$SKPaymentWrapperToJson(SKPaymentWrapper instance) => { 'productIdentifier': instance.productIdentifier, 'applicationUsername': instance.applicationUsername, 'requestData': instance.requestData, 'quantity': instance.quantity, 'simulatesAskToBuyInSandbox': instance.simulatesAskToBuyInSandbox, }; SKPaymentDiscountWrapper _$SKPaymentDiscountWrapperFromJson(Map json) => SKPaymentDiscountWrapper( identifier: json['identifier'] as String, keyIdentifier: json['keyIdentifier'] as String, nonce: json['nonce'] as String, signature: json['signature'] as String, timestamp: json['timestamp'] as int, ); Map _$SKPaymentDiscountWrapperToJson( SKPaymentDiscountWrapper instance) => { 'identifier': instance.identifier, 'keyIdentifier': instance.keyIdentifier, 'nonce': instance.nonce, 'signature': instance.signature, 'timestamp': instance.timestamp, }; ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:json_annotation/json_annotation.dart'; import 'enum_converters.dart'; import 'sk_payment_queue_wrapper.dart'; import 'sk_product_wrapper.dart'; part 'sk_payment_transaction_wrappers.g.dart'; /// Callback handlers for transaction status changes. /// /// Must be subclassed. Must be instantiated and added to the /// [SKPaymentQueueWrapper] via [SKPaymentQueueWrapper.setTransactionObserver] /// at app launch. /// /// This class is a Dart wrapper around [SKTransactionObserver](https://developer.apple.com/documentation/storekit/skpaymenttransactionobserver?language=objc). abstract class SKTransactionObserverWrapper { /// Triggered when any transactions are updated. void updatedTransactions( {required List transactions}); /// Triggered when any transactions are removed from the payment queue. void removedTransactions( {required List transactions}); /// Triggered when there is an error while restoring transactions. void restoreCompletedTransactionsFailed({required SKError error}); /// Triggered when payment queue has finished sending restored transactions. void paymentQueueRestoreCompletedTransactionsFinished(); /// Triggered when a user initiates an in-app purchase from App Store. /// /// Return `true` to continue the transaction in your app. If you have /// multiple [SKTransactionObserverWrapper]s, the transaction will continue if /// any [SKTransactionObserverWrapper] returns `true`. Return `false` to defer /// or cancel the transaction. For example, you may need to defer a /// transaction if the user is in the middle of onboarding. You can also /// continue the transaction later by calling [addPayment] with the /// `payment` param from this method. bool shouldAddStorePayment( {required SKPaymentWrapper payment, required SKProductWrapper product}); } /// The state of a transaction. /// /// Dart wrapper around StoreKit's /// [SKPaymentTransactionState](https://developer.apple.com/documentation/storekit/skpaymenttransactionstate?language=objc). enum SKPaymentTransactionStateWrapper { /// Indicates the transaction is being processed in App Store. /// /// You should update your UI to indicate that you are waiting for the /// transaction to update to another state. Never complete a transaction that /// is still in a purchasing state. @JsonValue(0) purchasing, /// The user's payment has been succesfully processed. /// /// You should provide the user the content that they purchased. @JsonValue(1) purchased, /// The transaction failed. /// /// Check the [SKPaymentTransactionWrapper.error] property from /// [SKPaymentTransactionWrapper] for details. @JsonValue(2) failed, /// This transaction is restoring content previously purchased by the user. /// /// The previous transaction information can be obtained in /// [SKPaymentTransactionWrapper.originalTransaction] from /// [SKPaymentTransactionWrapper]. @JsonValue(3) restored, /// The transaction is in the queue but pending external action. Wait for /// another callback to get the final state. /// /// You should update your UI to indicate that you are waiting for the /// transaction to update to another state. @JsonValue(4) deferred, /// Indicates the transaction is in an unspecified state. @JsonValue(-1) unspecified, } /// Created when a payment is added to the [SKPaymentQueueWrapper]. /// /// Transactions are delivered to your app when a payment is finished /// processing. Completed transactions provide a receipt and a transaction /// identifier that the app can use to save a permanent record of the processed /// payment. /// /// Dart wrapper around StoreKit's /// [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction?language=objc). @JsonSerializable(createToJson: true) @immutable class SKPaymentTransactionWrapper { /// Creates a new [SKPaymentTransactionWrapper] with the provided information. // TODO(stuartmorgan): Temporarily ignore const warning in other parts of the // federated package, and remove this. // ignore: prefer_const_constructors_in_immutables SKPaymentTransactionWrapper({ required this.payment, required this.transactionState, this.originalTransaction, this.transactionTimeStamp, this.transactionIdentifier, this.error, }); /// Constructs an instance of this from a key value map of data. /// /// The map needs to have named string keys with values matching the names and /// types of all of the members on this class. The `map` parameter must not be /// null. factory SKPaymentTransactionWrapper.fromJson(Map map) { return _$SKPaymentTransactionWrapperFromJson(map); } /// Current transaction state. @SKTransactionStatusConverter() final SKPaymentTransactionStateWrapper transactionState; /// The payment that has been created and added to the payment queue which /// generated this transaction. final SKPaymentWrapper payment; /// The original Transaction. /// /// Only available if the [transactionState] is [SKPaymentTransactionStateWrapper.restored]. /// Otherwise the value is `null`. /// /// When the [transactionState] /// is [SKPaymentTransactionStateWrapper.restored], the current transaction /// object holds a new [transactionIdentifier]. final SKPaymentTransactionWrapper? originalTransaction; /// The timestamp of the transaction. /// /// Seconds since epoch. It is only defined when the [transactionState] is /// [SKPaymentTransactionStateWrapper.purchased] or /// [SKPaymentTransactionStateWrapper.restored]. /// Otherwise, the value is `null`. final double? transactionTimeStamp; /// The unique string identifer of the transaction. /// /// It is only defined when the [transactionState] is /// [SKPaymentTransactionStateWrapper.purchased] or /// [SKPaymentTransactionStateWrapper.restored]. You may wish to record this /// string as part of an audit trail for App Store purchases. The value of /// this string corresponds to the same property in the receipt. /// /// The value is `null` if it is an unsuccessful transaction. final String? transactionIdentifier; /// The error object /// /// Only available if the [transactionState] is /// [SKPaymentTransactionStateWrapper.failed]. final SKError? error; @override bool operator ==(Object other) { if (identical(other, this)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is SKPaymentTransactionWrapper && other.payment == payment && other.transactionState == transactionState && other.originalTransaction == originalTransaction && other.transactionTimeStamp == transactionTimeStamp && other.transactionIdentifier == transactionIdentifier && other.error == error; } @override int get hashCode => Object.hash(payment, transactionState, originalTransaction, transactionTimeStamp, transactionIdentifier, error); @override String toString() => _$SKPaymentTransactionWrapperToJson(this).toString(); /// The payload that is used to finish this transaction. Map toFinishMap() => { 'transactionIdentifier': transactionIdentifier, 'productIdentifier': payment.productIdentifier, }; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.g.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND part of 'sk_payment_transaction_wrappers.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** SKPaymentTransactionWrapper _$SKPaymentTransactionWrapperFromJson(Map json) => SKPaymentTransactionWrapper( payment: SKPaymentWrapper.fromJson( Map.from(json['payment'] as Map)), transactionState: const SKTransactionStatusConverter() .fromJson(json['transactionState'] as int?), originalTransaction: json['originalTransaction'] == null ? null : SKPaymentTransactionWrapper.fromJson( Map.from(json['originalTransaction'] as Map)), transactionTimeStamp: (json['transactionTimeStamp'] as num?)?.toDouble(), transactionIdentifier: json['transactionIdentifier'] as String?, error: json['error'] == null ? null : SKError.fromJson(Map.from(json['error'] as Map)), ); Map _$SKPaymentTransactionWrapperToJson( SKPaymentTransactionWrapper instance) => { 'transactionState': const SKTransactionStatusConverter() .toJson(instance.transactionState), 'payment': instance.payment, 'originalTransaction': instance.originalTransaction, 'transactionTimeStamp': instance.transactionTimeStamp, 'transactionIdentifier': instance.transactionIdentifier, 'error': instance.error, }; ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_product_wrapper.dart ================================================ // Copyright 2013 The Flutter 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:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:json_annotation/json_annotation.dart'; import 'enum_converters.dart'; // WARNING: Changes to `@JsonSerializable` classes need to be reflected in the // below generated file. Run `flutter packages pub run build_runner watch` to // rebuild and watch for further changes. part 'sk_product_wrapper.g.dart'; /// Dart wrapper around StoreKit's [SKProductsResponse](https://developer.apple.com/documentation/storekit/skproductsresponse?language=objc). /// /// Represents the response object returned by [SKRequestMaker.startProductRequest]. /// Contains information about a list of products and a list of invalid product identifiers. @JsonSerializable() @immutable class SkProductResponseWrapper { /// Creates an [SkProductResponseWrapper] with the given product details. // TODO(stuartmorgan): Temporarily ignore const warning in other parts of the // federated package, and remove this. // ignore: prefer_const_constructors_in_immutables SkProductResponseWrapper( {required this.products, required this.invalidProductIdentifiers}); /// Constructing an instance from a map from the Objective-C layer. /// /// This method should only be used with `map` values returned by [SKRequestMaker.startProductRequest]. factory SkProductResponseWrapper.fromJson(Map map) { return _$SkProductResponseWrapperFromJson(map); } /// Stores all matching successfully found products. /// /// One product in this list matches one valid product identifier passed to the [SKRequestMaker.startProductRequest]. /// Will be empty if the [SKRequestMaker.startProductRequest] method does not pass any correct product identifier. @JsonKey(defaultValue: []) final List products; /// Stores product identifiers in the `productIdentifiers` from [SKRequestMaker.startProductRequest] that are not recognized by the App Store. /// /// The App Store will not recognize a product identifier unless certain criteria are met. A detailed list of the criteria can be /// found here https://developer.apple.com/documentation/storekit/skproductsresponse/1505985-invalidproductidentifiers?language=objc. /// Will be empty if all the product identifiers are valid. @JsonKey(defaultValue: []) final List invalidProductIdentifiers; @override bool operator ==(Object other) { if (identical(other, this)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is SkProductResponseWrapper && const DeepCollectionEquality().equals(other.products, products) && const DeepCollectionEquality() .equals(other.invalidProductIdentifiers, invalidProductIdentifiers); } @override int get hashCode => Object.hash(products, invalidProductIdentifiers); } /// Dart wrapper around StoreKit's [SKProductPeriodUnit](https://developer.apple.com/documentation/storekit/skproductperiodunit?language=objc). /// /// Used as a property in the [SKProductSubscriptionPeriodWrapper]. Minimum is a day and maximum is a year. // The values of the enum options are matching the [SKProductPeriodUnit]'s values. Should there be an update or addition // in the [SKProductPeriodUnit], this need to be updated to match. enum SKSubscriptionPeriodUnit { /// An interval lasting one day. @JsonValue(0) day, /// An interval lasting one month. @JsonValue(1) /// An interval lasting one week. week, @JsonValue(2) /// An interval lasting one month. month, /// An interval lasting one year. @JsonValue(3) year, } /// Dart wrapper around StoreKit's [SKProductSubscriptionPeriod](https://developer.apple.com/documentation/storekit/skproductsubscriptionperiod?language=objc). /// /// A period is defined by a [numberOfUnits] and a [unit], e.g for a 3 months period [numberOfUnits] is 3 and [unit] is a month. /// It is used as a property in [SKProductDiscountWrapper] and [SKProductWrapper]. @JsonSerializable() @immutable class SKProductSubscriptionPeriodWrapper { /// Creates an [SKProductSubscriptionPeriodWrapper] for a `numberOfUnits`x`unit` period. // TODO(stuartmorgan): Temporarily ignore const warning in other parts of the // federated package, and remove this. // ignore: prefer_const_constructors_in_immutables SKProductSubscriptionPeriodWrapper( {required this.numberOfUnits, required this.unit}); /// Constructing an instance from a map from the Objective-C layer. /// /// This method should only be used with `map` values returned by [SKProductDiscountWrapper.fromJson] or [SKProductWrapper.fromJson]. factory SKProductSubscriptionPeriodWrapper.fromJson( Map? map) { if (map == null) { return SKProductSubscriptionPeriodWrapper( numberOfUnits: 0, unit: SKSubscriptionPeriodUnit.day); } return _$SKProductSubscriptionPeriodWrapperFromJson(map); } /// The number of [unit] units in this period. /// /// Must be greater than 0 if the object is valid. @JsonKey(defaultValue: 0) final int numberOfUnits; /// The time unit used to specify the length of this period. @SKSubscriptionPeriodUnitConverter() final SKSubscriptionPeriodUnit unit; @override bool operator ==(Object other) { if (identical(other, this)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is SKProductSubscriptionPeriodWrapper && other.numberOfUnits == numberOfUnits && other.unit == unit; } @override int get hashCode => Object.hash(numberOfUnits, unit); } /// Dart wrapper around StoreKit's [SKProductDiscountPaymentMode](https://developer.apple.com/documentation/storekit/skproductdiscountpaymentmode?language=objc). /// /// This is used as a property in the [SKProductDiscountWrapper]. // The values of the enum options are matching the [SKProductDiscountPaymentMode]'s values. Should there be an update or addition // in the [SKProductDiscountPaymentMode], this need to be updated to match. enum SKProductDiscountPaymentMode { /// Allows user to pay the discounted price at each payment period. @JsonValue(0) payAsYouGo, /// Allows user to pay the discounted price upfront and receive the product for the rest of time that was paid for. @JsonValue(1) payUpFront, /// User pays nothing during the discounted period. @JsonValue(2) freeTrail, /// Unspecified mode. @JsonValue(-1) unspecified, } /// Dart wrapper around StoreKit's [SKProductDiscountType] /// (https://developer.apple.com/documentation/storekit/skproductdiscounttype?language=objc) /// /// This is used as a property in the [SKProductDiscountWrapper]. /// The values of the enum options are matching the [SKProductDiscountType]'s /// values. /// /// Values representing the types of discount offers an app can present. enum SKProductDiscountType { /// A constant indicating the discount type is an introductory offer. @JsonValue(0) introductory, /// A constant indicating the discount type is a promotional offer. @JsonValue(1) subscription, } /// Dart wrapper around StoreKit's [SKProductDiscount](https://developer.apple.com/documentation/storekit/skproductdiscount?language=objc). /// /// It is used as a property in [SKProductWrapper]. @JsonSerializable() @immutable class SKProductDiscountWrapper { /// Creates an [SKProductDiscountWrapper] with the given discount details. // TODO(stuartmorgan): Temporarily ignore const warning in other parts of the // federated package, and remove this. // ignore: prefer_const_constructors_in_immutables SKProductDiscountWrapper( {required this.price, required this.priceLocale, required this.numberOfPeriods, required this.paymentMode, required this.subscriptionPeriod, required this.identifier, required this.type}); /// Constructing an instance from a map from the Objective-C layer. /// /// This method should only be used with `map` values returned by [SKProductWrapper.fromJson]. factory SKProductDiscountWrapper.fromJson(Map map) { return _$SKProductDiscountWrapperFromJson(map); } /// The discounted price, in the currency that is defined in [priceLocale]. @JsonKey(defaultValue: '') final String price; /// Includes locale information about the price, e.g. `$` as the currency symbol for US locale. final SKPriceLocaleWrapper priceLocale; /// The object represent the discount period length. /// /// The value must be >= 0 if the object is valid. @JsonKey(defaultValue: 0) final int numberOfPeriods; /// The object indicates how the discount price is charged. @SKProductDiscountPaymentModeConverter() final SKProductDiscountPaymentMode paymentMode; /// The object represents the duration of single subscription period for the discount. /// /// The [subscriptionPeriod] of the discount is independent of the product's [subscriptionPeriod], /// and their units and duration do not have to be matched. final SKProductSubscriptionPeriodWrapper subscriptionPeriod; /// A string used to uniquely identify a discount offer for a product. /// /// You set up offers and their identifiers in App Store Connect. @JsonKey(defaultValue: null) final String? identifier; /// Values representing the types of discount offers an app can present. @SKProductDiscountTypeConverter() final SKProductDiscountType type; @override bool operator ==(Object other) { if (identical(other, this)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is SKProductDiscountWrapper && other.price == price && other.priceLocale == priceLocale && other.numberOfPeriods == numberOfPeriods && other.paymentMode == paymentMode && other.subscriptionPeriod == subscriptionPeriod && other.identifier == identifier && other.type == type; } @override int get hashCode => Object.hash(price, priceLocale, numberOfPeriods, paymentMode, subscriptionPeriod, identifier, type); } /// Dart wrapper around StoreKit's [SKProduct](https://developer.apple.com/documentation/storekit/skproduct?language=objc). /// /// A list of [SKProductWrapper] is returned in the [SKRequestMaker.startProductRequest] method, and /// should be stored for use when making a payment. @JsonSerializable() @immutable class SKProductWrapper { /// Creates an [SKProductWrapper] with the given product details. // TODO(stuartmorgan): Temporarily ignore const warning in other parts of the // federated package, and remove this. // ignore: prefer_const_constructors_in_immutables SKProductWrapper({ required this.productIdentifier, required this.localizedTitle, required this.localizedDescription, required this.priceLocale, this.subscriptionGroupIdentifier, required this.price, this.subscriptionPeriod, this.introductoryPrice, this.discounts = const [], }); /// Constructing an instance from a map from the Objective-C layer. /// /// This method should only be used with `map` values returned by [SkProductResponseWrapper.fromJson]. factory SKProductWrapper.fromJson(Map map) { return _$SKProductWrapperFromJson(map); } /// The unique identifier of the product. @JsonKey(defaultValue: '') final String productIdentifier; /// The localizedTitle of the product. /// /// It is localized based on the current locale. @JsonKey(defaultValue: '') final String localizedTitle; /// The localized description of the product. /// /// It is localized based on the current locale. @JsonKey(defaultValue: '') final String localizedDescription; /// Includes locale information about the price, e.g. `$` as the currency symbol for US locale. final SKPriceLocaleWrapper priceLocale; /// The subscription group identifier. /// /// If the product is not a subscription, the value is `null`. /// /// A subscription group is a collection of subscription products. /// Check [SubscriptionGroup](https://developer.apple.com/app-store/subscriptions/) for more details about subscription group. final String? subscriptionGroupIdentifier; /// The price of the product, in the currency that is defined in [priceLocale]. @JsonKey(defaultValue: '') final String price; /// The object represents the subscription period of the product. /// /// Can be [null] is the product is not a subscription. final SKProductSubscriptionPeriodWrapper? subscriptionPeriod; /// The object represents the duration of single subscription period. /// /// This is only available if you set up the introductory price in the App Store Connect, otherwise the value is `null`. /// Programmer is also responsible to determine if the user is eligible to receive it. See https://developer.apple.com/documentation/storekit/in-app_purchase/offering_introductory_pricing_in_your_app?language=objc /// for more details. /// The [subscriptionPeriod] of the discount is independent of the product's [subscriptionPeriod], /// and their units and duration do not have to be matched. final SKProductDiscountWrapper? introductoryPrice; /// An array of subscription offers available for the auto-renewable subscription (available on iOS 12.2 and higher). /// /// This property lists all promotional offers set up in App Store Connect. If /// no promotional offers have been set up, this field returns an empty list. /// Each [subscriptionPeriod] of individual discounts are independent of the /// product's [subscriptionPeriod] and their units and duration do not have to /// be matched. @JsonKey(defaultValue: []) final List discounts; @override bool operator ==(Object other) { if (identical(other, this)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is SKProductWrapper && other.productIdentifier == productIdentifier && other.localizedTitle == localizedTitle && other.localizedDescription == localizedDescription && other.priceLocale == priceLocale && other.subscriptionGroupIdentifier == subscriptionGroupIdentifier && other.price == price && other.subscriptionPeriod == subscriptionPeriod && other.introductoryPrice == introductoryPrice && const DeepCollectionEquality().equals(other.discounts, discounts); } @override int get hashCode => Object.hash( productIdentifier, localizedTitle, localizedDescription, priceLocale, subscriptionGroupIdentifier, price, subscriptionPeriod, introductoryPrice, discounts); } /// Object that indicates the locale of the price /// /// It is a thin wrapper of [NSLocale](https://developer.apple.com/documentation/foundation/nslocale?language=objc). // TODO(cyanglaz): NSLocale is a complex object, want to see the actual need of getting this expanded. // Matching android to only get the currencySymbol for now. // https://github.com/flutter/flutter/issues/26610 @JsonSerializable() @immutable class SKPriceLocaleWrapper { /// Creates a new price locale for `currencySymbol` and `currencyCode`. // TODO(stuartmorgan): Temporarily ignore const warning in other parts of the // federated package, and remove this. // ignore: prefer_const_constructors_in_immutables SKPriceLocaleWrapper({ required this.currencySymbol, required this.currencyCode, required this.countryCode, }); /// Constructing an instance from a map from the Objective-C layer. /// /// This method should only be used with `map` values returned by [SKProductWrapper.fromJson] and [SKProductDiscountWrapper.fromJson]. factory SKPriceLocaleWrapper.fromJson(Map? map) { if (map == null) { return SKPriceLocaleWrapper( currencyCode: '', currencySymbol: '', countryCode: ''); } return _$SKPriceLocaleWrapperFromJson(map); } ///The currency symbol for the locale, e.g. $ for US locale. @JsonKey(defaultValue: '') final String currencySymbol; ///The currency code for the locale, e.g. USD for US locale. @JsonKey(defaultValue: '') final String currencyCode; ///The country code for the locale, e.g. US for US locale. @JsonKey(defaultValue: '') final String countryCode; @override bool operator ==(Object other) { if (identical(other, this)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is SKPriceLocaleWrapper && other.currencySymbol == currencySymbol && other.currencyCode == currencyCode; } @override int get hashCode => Object.hash(currencySymbol, currencyCode); } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_product_wrapper.g.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND part of 'sk_product_wrapper.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** SkProductResponseWrapper _$SkProductResponseWrapperFromJson(Map json) => SkProductResponseWrapper( products: (json['products'] as List?) ?.map((e) => SKProductWrapper.fromJson( Map.from(e as Map))) .toList() ?? [], invalidProductIdentifiers: (json['invalidProductIdentifiers'] as List?) ?.map((e) => e as String) .toList() ?? [], ); SKProductSubscriptionPeriodWrapper _$SKProductSubscriptionPeriodWrapperFromJson( Map json) => SKProductSubscriptionPeriodWrapper( numberOfUnits: json['numberOfUnits'] as int? ?? 0, unit: const SKSubscriptionPeriodUnitConverter() .fromJson(json['unit'] as int?), ); SKProductDiscountWrapper _$SKProductDiscountWrapperFromJson(Map json) => SKProductDiscountWrapper( price: json['price'] as String? ?? '', priceLocale: SKPriceLocaleWrapper.fromJson((json['priceLocale'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), numberOfPeriods: json['numberOfPeriods'] as int? ?? 0, paymentMode: const SKProductDiscountPaymentModeConverter() .fromJson(json['paymentMode'] as int?), subscriptionPeriod: SKProductSubscriptionPeriodWrapper.fromJson( (json['subscriptionPeriod'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), identifier: json['identifier'] as String? ?? null, type: const SKProductDiscountTypeConverter().fromJson(json['type'] as int?), ); SKProductWrapper _$SKProductWrapperFromJson(Map json) => SKProductWrapper( productIdentifier: json['productIdentifier'] as String? ?? '', localizedTitle: json['localizedTitle'] as String? ?? '', localizedDescription: json['localizedDescription'] as String? ?? '', priceLocale: SKPriceLocaleWrapper.fromJson((json['priceLocale'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), subscriptionGroupIdentifier: json['subscriptionGroupIdentifier'] as String?, price: json['price'] as String? ?? '', subscriptionPeriod: json['subscriptionPeriod'] == null ? null : SKProductSubscriptionPeriodWrapper.fromJson( (json['subscriptionPeriod'] as Map?)?.map( (k, e) => MapEntry(k as String, e), )), introductoryPrice: json['introductoryPrice'] == null ? null : SKProductDiscountWrapper.fromJson( Map.from(json['introductoryPrice'] as Map)), discounts: (json['discounts'] as List?) ?.map((e) => SKProductDiscountWrapper.fromJson( Map.from(e as Map))) .toList() ?? [], ); SKPriceLocaleWrapper _$SKPriceLocaleWrapperFromJson(Map json) => SKPriceLocaleWrapper( currencySymbol: json['currencySymbol'] as String? ?? '', currencyCode: json['currencyCode'] as String? ?? '', countryCode: json['countryCode'] as String? ?? '', ); ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_receipt_manager.dart ================================================ // Copyright 2013 The Flutter 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 '../channel.dart'; // ignore: avoid_classes_with_only_static_members /// This class contains static methods to manage StoreKit receipts. class SKReceiptManager { /// Retrieve the receipt data from your application's main bundle. /// /// The receipt data will be based64 encoded. The structure of the payload is defined using ASN.1. /// You can use the receipt data retrieved by this method to validate users' purchases. /// There are 2 ways to do so. Either validate locally or validate with App Store. /// For more details on how to validate the receipt data, you can refer to Apple's document about [`About Receipt Validation`](https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Introduction.html#//apple_ref/doc/uid/TP40010573-CH105-SW1). /// If the receipt is invalid or missing, you can use [SKRequestMaker.startRefreshReceiptRequest] to request a new receipt. static Future retrieveReceiptData() async { return (await channel.invokeMethod( '-[InAppPurchasePlugin retrieveReceiptData:result:]')) ?? ''; } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_request_maker.dart ================================================ // Copyright 2013 The Flutter 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 '../channel.dart'; import 'sk_product_wrapper.dart'; /// A request maker that handles all the requests made by SKRequest subclasses. /// /// There are multiple [SKRequest](https://developer.apple.com/documentation/storekit/skrequest?language=objc) subclasses handling different requests in the `StoreKit` with multiple delegate methods, /// we consolidated all the `SKRequest` subclasses into this class to make requests in a more straightforward way. /// The request maker will create a SKRequest object, immediately starting it, and completing the future successfully or throw an exception depending on what happened to the request. class SKRequestMaker { /// Fetches product information for a list of given product identifiers. /// /// The `productIdentifiers` should contain legitimate product identifiers that you declared for the products in the iTunes Connect. Invalid identifiers /// will be stored and returned in [SkProductResponseWrapper.invalidProductIdentifiers]. Duplicate values in `productIdentifiers` will be omitted. /// If `productIdentifiers` is null, an `storekit_invalid_argument` error will be returned. If `productIdentifiers` is empty, a [SkProductResponseWrapper] /// will still be returned with [SkProductResponseWrapper.products] being null. /// /// [SkProductResponseWrapper] is returned if there is no error during the request. /// A [PlatformException] is thrown if the platform code making the request fails. Future startProductRequest( List productIdentifiers) async { final Map? productResponseMap = await channel.invokeMapMethod( '-[InAppPurchasePlugin startProductRequest:result:]', productIdentifiers, ); if (productResponseMap == null) { throw PlatformException( code: 'storekit_no_response', message: 'StoreKit: Failed to get response from platform.', ); } return SkProductResponseWrapper.fromJson(productResponseMap); } /// Uses [SKReceiptRefreshRequest](https://developer.apple.com/documentation/storekit/skreceiptrefreshrequest?language=objc) to request a new receipt. /// /// If the receipt is invalid or missing, you can use this API to request a new receipt. /// The [receiptProperties] is optional and it exists only for [sandbox testing](https://developer.apple.com/apple-pay/sandbox-testing/). In the production app, call this API without pass in the [receiptProperties] parameter. /// To test in the sandbox, you can request a receipt with any combination of properties to test the state transitions related to [`Volume Purchase Plan`](https://www.apple.com/business/site/docs/VPP_Business_Guide.pdf) receipts. /// The valid keys in the receiptProperties are below (All of them are of type bool): /// * isExpired: whether the receipt is expired. /// * isRevoked: whether the receipt has been revoked. /// * isVolumePurchase: whether the receipt is a Volume Purchase Plan receipt. Future startRefreshReceiptRequest( {Map? receiptProperties}) { return channel.invokeMethod( '-[InAppPurchasePlugin refreshReceipt:result:]', receiptProperties, ); } } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_storefront_wrapper.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:json_annotation/json_annotation.dart'; part 'sk_storefront_wrapper.g.dart'; /// Contains the location and unique identifier of an Apple App Store storefront. /// /// Dart wrapper around StoreKit's /// [SKStorefront](https://developer.apple.com/documentation/storekit/skstorefront?language=objc). @JsonSerializable(createToJson: true) @immutable class SKStorefrontWrapper { /// Creates a new [SKStorefrontWrapper] with the provided information. // TODO(stuartmorgan): Temporarily ignore const warning in other parts of the // federated package, and remove this. // ignore: prefer_const_constructors_in_immutables SKStorefrontWrapper({ required this.countryCode, required this.identifier, }); /// Constructs an instance of the [SKStorefrontWrapper] from a key value map /// of data. /// /// The map needs to have named string keys with values matching the names and /// types of all of the members on this class. The `map` parameter must not be /// null. factory SKStorefrontWrapper.fromJson(Map map) { return _$SKStorefrontWrapperFromJson(map); } /// The three-letter code representing the country or region associated with /// the App Store storefront. final String countryCode; /// A value defined by Apple that uniquely identifies an App Store storefront. final String identifier; @override bool operator ==(Object other) { if (identical(other, this)) { return true; } if (other.runtimeType != runtimeType) { return false; } return other is SKStorefrontWrapper && other.countryCode == countryCode && other.identifier == identifier; } @override int get hashCode => Object.hash( countryCode, identifier, ); @override String toString() => _$SKStorefrontWrapperToJson(this).toString(); /// Converts the instance to a key value map which can be used to serialize /// to JSON format. Map toMap() => _$SKStorefrontWrapperToJson(this); } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_storefront_wrapper.g.dart ================================================ // GENERATED CODE - DO NOT MODIFY BY HAND part of 'sk_storefront_wrapper.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** SKStorefrontWrapper _$SKStorefrontWrapperFromJson(Map json) => SKStorefrontWrapper( countryCode: json['countryCode'] as String, identifier: json['identifier'] as String, ); Map _$SKStorefrontWrapperToJson( SKStorefrontWrapper instance) => { 'countryCode': instance.countryCode, 'identifier': instance.identifier, }; ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_product_details.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import '../../store_kit_wrappers.dart'; /// The class represents the information of a product as registered in the Apple /// AppStore. class AppStoreProductDetails extends ProductDetails { /// Creates a new AppStore specific product details object with the provided /// details. AppStoreProductDetails({ required super.id, required super.title, required super.description, required super.price, required super.rawPrice, required super.currencyCode, required this.skProduct, required super.currencySymbol, }); /// Generate a [AppStoreProductDetails] object based on an iOS [SKProductWrapper] object. factory AppStoreProductDetails.fromSKProduct(SKProductWrapper product) { return AppStoreProductDetails( id: product.productIdentifier, title: product.localizedTitle, description: product.localizedDescription, price: product.priceLocale.currencySymbol + product.price, rawPrice: double.parse(product.price), currencyCode: product.priceLocale.currencyCode, currencySymbol: product.priceLocale.currencySymbol.isNotEmpty ? product.priceLocale.currencySymbol : product.priceLocale.currencyCode, skProduct: product, ); } /// Points back to the [SKProductWrapper] object that was used to generate /// this [AppStoreProductDetails] object. final SKProductWrapper skProduct; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_details.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import '../../in_app_purchase_storekit.dart'; import '../../store_kit_wrappers.dart'; import '../store_kit_wrappers/enum_converters.dart'; /// The class represents the information of a purchase made with the Apple /// AppStore. class AppStorePurchaseDetails extends PurchaseDetails { /// Creates a new AppStore specific purchase details object with the provided /// details. AppStorePurchaseDetails({ super.purchaseID, required super.productID, required super.verificationData, required super.transactionDate, required this.skPaymentTransaction, required PurchaseStatus status, }) : super(status: status) { this.status = status; } /// Generate a [AppStorePurchaseDetails] object based on an iOS /// [SKPaymentTransactionWrapper] object. factory AppStorePurchaseDetails.fromSKTransaction( SKPaymentTransactionWrapper transaction, String base64EncodedReceipt, ) { final AppStorePurchaseDetails purchaseDetails = AppStorePurchaseDetails( productID: transaction.payment.productIdentifier, purchaseID: transaction.transactionIdentifier, skPaymentTransaction: transaction, status: const SKTransactionStatusConverter() .toPurchaseStatus(transaction.transactionState, transaction.error), transactionDate: transaction.transactionTimeStamp != null ? (transaction.transactionTimeStamp! * 1000).toInt().toString() : null, verificationData: PurchaseVerificationData( localVerificationData: base64EncodedReceipt, serverVerificationData: base64EncodedReceipt, source: kIAPSource), ); if (purchaseDetails.status == PurchaseStatus.error || purchaseDetails.status == PurchaseStatus.canceled) { purchaseDetails.error = IAPError( source: kIAPSource, code: kPurchaseErrorCode, message: transaction.error?.domain ?? '', details: transaction.error?.userInfo, ); } return purchaseDetails; } /// Points back to the [SKPaymentTransactionWrapper] which was used to /// generate this [AppStorePurchaseDetails] object. final SKPaymentTransactionWrapper skPaymentTransaction; late PurchaseStatus _status; /// The status that this [PurchaseDetails] is currently on. @override PurchaseStatus get status => _status; @override set status(PurchaseStatus status) { _pendingCompletePurchase = status != PurchaseStatus.pending; _status = status; } bool _pendingCompletePurchase = false; @override bool get pendingCompletePurchase => _pendingCompletePurchase; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_param.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import '../../store_kit_wrappers.dart'; /// Apple AppStore specific parameter object for generating a purchase. class AppStorePurchaseParam extends PurchaseParam { /// Creates a new [AppStorePurchaseParam] object with the given data. AppStorePurchaseParam({ required super.productDetails, super.applicationUserName, this.quantity = 1, this.simulatesAskToBuyInSandbox = false, this.discount, }); /// Set it to `true` to produce an "ask to buy" flow for this payment in the /// sandbox. /// /// If you want to test [simulatesAskToBuyInSandbox], you should ensure that /// you create an instance of the [AppStorePurchaseParam] class and set its /// [simulateAskToBuyInSandbox] field to `true` and use it with the /// `buyNonConsumable` or `buyConsumable` methods. /// /// See also [SKPaymentWrapper.simulatesAskToBuyInSandbox]. final bool simulatesAskToBuyInSandbox; /// Quantity of the product user requested to buy. final int quantity; /// Discount applied to the product. The value is `null` when the product does not have a discount. final SKPaymentDiscountWrapper? discount; } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/types.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // export 'app_store_product_details.dart'; export 'app_store_purchase_details.dart'; export 'app_store_purchase_param.dart'; ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/lib/store_kit_wrappers.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'src/store_kit_wrappers/sk_payment_queue_delegate_wrapper.dart'; export 'src/store_kit_wrappers/sk_payment_queue_wrapper.dart'; export 'src/store_kit_wrappers/sk_payment_transaction_wrappers.dart'; export 'src/store_kit_wrappers/sk_product_wrapper.dart'; export 'src/store_kit_wrappers/sk_receipt_manager.dart'; export 'src/store_kit_wrappers/sk_request_maker.dart'; export 'src/store_kit_wrappers/sk_storefront_wrapper.dart'; ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml ================================================ 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.6 environment: sdk: '>=2.18.0 <3.0.0' flutter: ">=3.3.0" flutter: plugin: implements: in_app_purchase platforms: ios: pluginClass: InAppPurchasePlugin sharedDarwinSource: true macos: pluginClass: InAppPurchasePlugin sharedDarwinSource: true dependencies: collection: ^1.15.0 flutter: sdk: flutter in_app_purchase_platform_interface: ^1.3.0 json_annotation: ^4.3.0 dev_dependencies: build_runner: ^2.0.0 flutter_test: sdk: flutter json_serializable: ^6.0.0 test: ^1.16.0 ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart ================================================ // Copyright 2013 The Flutter 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/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; import 'package:in_app_purchase_storekit/src/channel.dart'; import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; import '../store_kit_wrappers/sk_test_stub_objects.dart'; class FakeStoreKitPlatform { FakeStoreKitPlatform() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, onMethodCall); } // pre-configured store information String? receiptData; late Set validProductIDs; late Map validProducts; late List transactions; late List finishedTransactions; late bool testRestoredTransactionsNull; late bool testTransactionFail; late int testTransactionCancel; PlatformException? queryProductException; PlatformException? restoreException; SKError? testRestoredError; bool queueIsActive = false; Map discountReceived = {}; void reset() { transactions = []; receiptData = 'dummy base64data'; validProductIDs = {'123', '456'}; validProducts = {}; for (final String validID in validProductIDs) { final Map productWrapperMap = buildProductMap(dummyProductWrapper); productWrapperMap['productIdentifier'] = validID; if (validID == '456') { productWrapperMap['priceLocale'] = buildLocaleMap(noSymbolLocale); } validProducts[validID] = SKProductWrapper.fromJson(productWrapperMap); } finishedTransactions = []; testRestoredTransactionsNull = false; testTransactionFail = false; testTransactionCancel = -1; queryProductException = null; restoreException = null; testRestoredError = null; queueIsActive = false; discountReceived = {}; } SKPaymentTransactionWrapper createPendingTransaction(String id, {int quantity = 1}) { return SKPaymentTransactionWrapper( transactionIdentifier: '', payment: SKPaymentWrapper(productIdentifier: id, quantity: quantity), transactionState: SKPaymentTransactionStateWrapper.purchasing, transactionTimeStamp: 123123.121, ); } SKPaymentTransactionWrapper createPurchasedTransaction( String productId, String transactionId, {int quantity = 1}) { return SKPaymentTransactionWrapper( payment: SKPaymentWrapper(productIdentifier: productId, quantity: quantity), transactionState: SKPaymentTransactionStateWrapper.purchased, transactionTimeStamp: 123123.121, transactionIdentifier: transactionId); } SKPaymentTransactionWrapper createFailedTransaction(String productId, {int quantity = 1}) { return SKPaymentTransactionWrapper( transactionIdentifier: '', payment: SKPaymentWrapper(productIdentifier: productId, quantity: quantity), transactionState: SKPaymentTransactionStateWrapper.failed, transactionTimeStamp: 123123.121, error: const SKError( code: 0, domain: 'ios_domain', userInfo: {'message': 'an error message'})); } SKPaymentTransactionWrapper createCanceledTransaction( String productId, int errorCode, {int quantity = 1}) { return SKPaymentTransactionWrapper( transactionIdentifier: '', payment: SKPaymentWrapper(productIdentifier: productId, quantity: quantity), transactionState: SKPaymentTransactionStateWrapper.failed, transactionTimeStamp: 123123.121, error: SKError( code: errorCode, domain: 'ios_domain', userInfo: const {'message': 'an error message'})); } SKPaymentTransactionWrapper createRestoredTransaction( String productId, String transactionId, {int quantity = 1}) { return SKPaymentTransactionWrapper( payment: SKPaymentWrapper(productIdentifier: productId, quantity: quantity), transactionState: SKPaymentTransactionStateWrapper.restored, transactionTimeStamp: 123123.121, transactionIdentifier: transactionId); } Future onMethodCall(MethodCall call) { switch (call.method) { case '-[SKPaymentQueue canMakePayments:]': return Future.value(true); case '-[InAppPurchasePlugin startProductRequest:result:]': if (queryProductException != null) { throw queryProductException!; } final List productIDS = List.castFrom(call.arguments as List); final List invalidFound = []; final List products = []; for (final String productID in productIDS) { if (!validProductIDs.contains(productID)) { invalidFound.add(productID); } else { products.add(validProducts[productID]!); } } final SkProductResponseWrapper response = SkProductResponseWrapper( products: products, invalidProductIdentifiers: invalidFound); return Future>.value( buildProductResponseMap(response)); case '-[InAppPurchasePlugin restoreTransactions:result:]': if (restoreException != null) { throw restoreException!; } if (testRestoredError != null) { InAppPurchaseStoreKitPlatform.observer .restoreCompletedTransactionsFailed(error: testRestoredError!); return Future.sync(() {}); } if (!testRestoredTransactionsNull) { InAppPurchaseStoreKitPlatform.observer .updatedTransactions(transactions: transactions); } InAppPurchaseStoreKitPlatform.observer .paymentQueueRestoreCompletedTransactionsFinished(); return Future.sync(() {}); case '-[InAppPurchasePlugin retrieveReceiptData:result:]': if (receiptData != null) { return Future.value(receiptData); } else { throw PlatformException(code: 'no_receipt_data'); } case '-[InAppPurchasePlugin refreshReceipt:result:]': receiptData = 'refreshed receipt data'; return Future.sync(() {}); case '-[InAppPurchasePlugin addPayment:result:]': 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 (arguments['applicationUsername']! == 'userWithDiscount') { final Map? discountArgument = arguments['paymentDiscount'] as Map?; if (discountArgument != null) { discountReceived = discountArgument.cast(); } else { discountReceived = {}; } } final SKPaymentTransactionWrapper transaction = createPendingTransaction(id, quantity: quantity); transactions.add(transaction); InAppPurchaseStoreKitPlatform.observer.updatedTransactions( transactions: [transaction]); sleep(const Duration(milliseconds: 30)); if (testTransactionFail) { final SKPaymentTransactionWrapper transactionFailed = createFailedTransaction(id, quantity: quantity); InAppPurchaseStoreKitPlatform.observer.updatedTransactions( transactions: [transactionFailed]); } else if (testTransactionCancel > 0) { final SKPaymentTransactionWrapper transactionCanceled = createCanceledTransaction(id, testTransactionCancel, quantity: quantity); InAppPurchaseStoreKitPlatform.observer.updatedTransactions( transactions: [transactionCanceled]); } else { final SKPaymentTransactionWrapper transactionFinished = createPurchasedTransaction( id, transaction.transactionIdentifier ?? '', quantity: quantity); InAppPurchaseStoreKitPlatform.observer.updatedTransactions( transactions: [transactionFinished]); } break; case '-[InAppPurchasePlugin finishTransaction:result:]': final Map arguments = _getArgumentDictionary(call); finishedTransactions.add(createPurchasedTransaction( arguments['productIdentifier']! as String, arguments['transactionIdentifier']! as String, quantity: transactions.first.payment.quantity)); break; case '-[SKPaymentQueue startObservingTransactionQueue]': queueIsActive = true; break; case '-[SKPaymentQueue stopObservingTransactionQueue]': queueIsActive = false; break; } 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(); } } /// 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; ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_addtion_test.dart ================================================ // Copyright 2013 The Flutter 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:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; import 'fakes/fake_storekit_platform.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); final FakeStoreKitPlatform fakeStoreKitPlatform = FakeStoreKitPlatform(); setUpAll(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( SystemChannels.platform, fakeStoreKitPlatform.onMethodCall); }); group('present code redemption sheet', () { test('null', () async { expect( InAppPurchaseStoreKitPlatformAddition().presentCodeRedemptionSheet(), completes); }); }); group('refresh receipt data', () { test('should refresh receipt data', () async { final PurchaseVerificationData? receiptData = await InAppPurchaseStoreKitPlatformAddition() .refreshPurchaseVerificationData(); expect(receiptData, isNotNull); expect(receiptData!.source, kIAPSource); expect(receiptData.localVerificationData, 'refreshed receipt data'); expect(receiptData.serverVerificationData, 'refreshed receipt data'); }); }); } /// 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; ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; import 'package:in_app_purchase_storekit/src/store_kit_wrappers/enum_converters.dart'; import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; import 'fakes/fake_storekit_platform.dart'; import 'store_kit_wrappers/sk_test_stub_objects.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); final FakeStoreKitPlatform fakeStoreKitPlatform = FakeStoreKitPlatform(); late InAppPurchaseStoreKitPlatform iapStoreKitPlatform; setUpAll(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( SystemChannels.platform, fakeStoreKitPlatform.onMethodCall); }); setUp(() { InAppPurchaseStoreKitPlatform.registerPlatform(); iapStoreKitPlatform = InAppPurchasePlatform.instance as InAppPurchaseStoreKitPlatform; fakeStoreKitPlatform.reset(); }); tearDown(() => fakeStoreKitPlatform.reset()); group('isAvailable', () { test('true', () async { expect(await iapStoreKitPlatform.isAvailable(), isTrue); }); }); group('query product list', () { test('should get product list and correct invalid identifiers', () async { final InAppPurchaseStoreKitPlatform connection = InAppPurchaseStoreKitPlatform(); final ProductDetailsResponse response = await connection.queryProductDetails({'123', '456', '789'}); final List products = response.productDetails; expect(products.first.id, '123'); expect(products[1].id, '456'); expect(response.notFoundIDs, ['789']); expect(response.error, isNull); expect(response.productDetails.first.currencySymbol, r'$'); expect(response.productDetails[1].currencySymbol, 'EUR'); }); test( 'if query products throws error, should get error object in the response', () async { fakeStoreKitPlatform.queryProductException = PlatformException( code: 'error_code', message: 'error_message', details: {'info': 'error_info'}); final InAppPurchaseStoreKitPlatform connection = InAppPurchaseStoreKitPlatform(); final ProductDetailsResponse response = await connection.queryProductDetails({'123', '456', '789'}); expect(response.productDetails, []); expect(response.notFoundIDs, ['123', '456', '789']); expect(response.error, isNotNull); expect(response.error!.source, kIAPSource); expect(response.error!.code, 'error_code'); expect(response.error!.message, 'error_message'); expect(response.error!.details, {'info': 'error_info'}); }); }); group('restore purchases', () { test('should emit restored transactions on purchase stream', () async { fakeStoreKitPlatform.transactions.insert( 0, fakeStoreKitPlatform.createRestoredTransaction('foo', 'RT1')); fakeStoreKitPlatform.transactions.insert( 1, fakeStoreKitPlatform.createRestoredTransaction('foo', 'RT2')); final Completer> completer = Completer>(); final Stream> stream = iapStoreKitPlatform.purchaseStream; late StreamSubscription> subscription; subscription = stream.listen((List purchaseDetailsList) { if (purchaseDetailsList.first.status == PurchaseStatus.restored) { subscription.cancel(); completer.complete(purchaseDetailsList); } }); await iapStoreKitPlatform.restorePurchases(); final List details = await completer.future; expect(details.length, 2); for (int i = 0; i < fakeStoreKitPlatform.transactions.length; i++) { final SKPaymentTransactionWrapper expected = fakeStoreKitPlatform.transactions[i]; final PurchaseDetails actual = details[i]; expect(actual.purchaseID, expected.transactionIdentifier); expect(actual.verificationData, isNotNull); expect(actual.status, PurchaseStatus.restored); expect(actual.verificationData.localVerificationData, fakeStoreKitPlatform.receiptData); expect(actual.verificationData.serverVerificationData, fakeStoreKitPlatform.receiptData); expect(actual.pendingCompletePurchase, true); } }); test( 'should emit empty transaction list on purchase stream when there is nothing to restore', () async { fakeStoreKitPlatform.testRestoredTransactionsNull = true; final Completer?> completer = Completer?>(); final Stream> stream = iapStoreKitPlatform.purchaseStream; late StreamSubscription> subscription; subscription = stream.listen((List purchaseDetailsList) { expect(purchaseDetailsList.isEmpty, true); subscription.cancel(); completer.complete(); }); await iapStoreKitPlatform.restorePurchases(); await completer.future; }); test('should not block transaction updates', () async { fakeStoreKitPlatform.transactions.insert( 0, fakeStoreKitPlatform.createRestoredTransaction('foo', 'RT1')); fakeStoreKitPlatform.transactions.insert( 1, fakeStoreKitPlatform.createPurchasedTransaction('foo', 'bar')); fakeStoreKitPlatform.transactions.insert( 2, fakeStoreKitPlatform.createRestoredTransaction('foo', 'RT2')); final Completer> completer = Completer>(); final Stream> stream = iapStoreKitPlatform.purchaseStream; late StreamSubscription> subscription; subscription = stream.listen((List purchaseDetailsList) { if (purchaseDetailsList[1].status == PurchaseStatus.purchased) { completer.complete(purchaseDetailsList); subscription.cancel(); } }); await iapStoreKitPlatform.restorePurchases(); final List details = await completer.future; expect(details.length, 3); for (int i = 0; i < fakeStoreKitPlatform.transactions.length; i++) { final SKPaymentTransactionWrapper expected = fakeStoreKitPlatform.transactions[i]; final PurchaseDetails actual = details[i]; expect(actual.purchaseID, expected.transactionIdentifier); expect(actual.verificationData, isNotNull); expect( actual.status, const SKTransactionStatusConverter() .toPurchaseStatus(expected.transactionState, expected.error), ); expect(actual.verificationData.localVerificationData, fakeStoreKitPlatform.receiptData); expect(actual.verificationData.serverVerificationData, fakeStoreKitPlatform.receiptData); expect(actual.pendingCompletePurchase, true); } }); test( 'should emit empty transaction if transactions array does not contain a transaction with PurchaseStatus.restored status.', () async { fakeStoreKitPlatform.transactions.insert( 0, fakeStoreKitPlatform.createPurchasedTransaction('foo', 'bar')); final Completer>> completer = Completer>>(); final Stream> stream = iapStoreKitPlatform.purchaseStream; final List> purchaseDetails = >[]; late StreamSubscription> subscription; subscription = stream.listen((List purchaseDetailsList) { purchaseDetails.add(purchaseDetailsList); if (purchaseDetails.length == 2) { completer.complete(purchaseDetails); subscription.cancel(); } }); await iapStoreKitPlatform.restorePurchases(); final List> details = await completer.future; expect(details.length, 2); expect(details[0], >[]); for (int i = 0; i < fakeStoreKitPlatform.transactions.length; i++) { final SKPaymentTransactionWrapper expected = fakeStoreKitPlatform.transactions[i]; final PurchaseDetails actual = details[1][i]; expect(actual.purchaseID, expected.transactionIdentifier); expect(actual.verificationData, isNotNull); expect( actual.status, const SKTransactionStatusConverter() .toPurchaseStatus(expected.transactionState, expected.error), ); expect(actual.verificationData.localVerificationData, fakeStoreKitPlatform.receiptData); expect(actual.verificationData.serverVerificationData, fakeStoreKitPlatform.receiptData); expect(actual.pendingCompletePurchase, true); } }); test('receipt error should populate null to verificationData.data', () async { fakeStoreKitPlatform.transactions.insert( 0, fakeStoreKitPlatform.createRestoredTransaction('foo', 'RT1')); fakeStoreKitPlatform.transactions.insert( 1, fakeStoreKitPlatform.createRestoredTransaction('foo', 'RT2')); fakeStoreKitPlatform.receiptData = null; final Completer> completer = Completer>(); final Stream> stream = iapStoreKitPlatform.purchaseStream; late StreamSubscription> subscription; subscription = stream.listen((List purchaseDetailsList) { if (purchaseDetailsList.first.status == PurchaseStatus.restored) { completer.complete(purchaseDetailsList); subscription.cancel(); } }); await iapStoreKitPlatform.restorePurchases(); final List details = await completer.future; for (final PurchaseDetails purchase in details) { expect(purchase.verificationData.localVerificationData, isEmpty); expect(purchase.verificationData.serverVerificationData, isEmpty); } }); test('test restore error', () { fakeStoreKitPlatform.testRestoredError = const SKError( code: 123, domain: 'error_test', userInfo: {'message': 'errorMessage'}); expect( () => iapStoreKitPlatform.restorePurchases(), throwsA( isA() .having((SKError error) => error.code, 'code', 123) .having((SKError error) => error.domain, 'domain', 'error_test') .having((SKError error) => error.userInfo, 'userInfo', {'message': 'errorMessage'}), )); }); }); group('make payment', () { test( 'buying non consumable, should get purchase objects in the purchase update callback', () async { final List details = []; final Completer> completer = Completer>(); final Stream> stream = iapStoreKitPlatform.purchaseStream; late StreamSubscription> subscription; subscription = stream.listen((List purchaseDetailsList) { details.addAll(purchaseDetailsList); if (purchaseDetailsList.first.status == PurchaseStatus.purchased) { completer.complete(details); subscription.cancel(); } }); final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam( productDetails: AppStoreProductDetails.fromSKProduct(dummyProductWrapper), applicationUserName: 'appName'); await iapStoreKitPlatform.buyNonConsumable(purchaseParam: purchaseParam); final List result = await completer.future; expect(result.length, 2); expect(result.first.productID, dummyProductWrapper.productIdentifier); }); test( 'buying consumable, should get purchase objects in the purchase update callback', () async { final List details = []; final Completer> completer = Completer>(); final Stream> stream = iapStoreKitPlatform.purchaseStream; late StreamSubscription> subscription; subscription = stream.listen((List purchaseDetailsList) { details.addAll(purchaseDetailsList); if (purchaseDetailsList.first.status == PurchaseStatus.purchased) { completer.complete(details); subscription.cancel(); } }); final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam( productDetails: AppStoreProductDetails.fromSKProduct(dummyProductWrapper), applicationUserName: 'appName'); await iapStoreKitPlatform.buyConsumable(purchaseParam: purchaseParam); final List result = await completer.future; expect(result.length, 2); expect(result.first.productID, dummyProductWrapper.productIdentifier); }); test('buying consumable, should throw when autoConsume is false', () async { final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam( productDetails: AppStoreProductDetails.fromSKProduct(dummyProductWrapper), applicationUserName: 'appName'); expect( () => iapStoreKitPlatform.buyConsumable( purchaseParam: purchaseParam, autoConsume: false), throwsA(isInstanceOf())); }); test('should get failed purchase status', () async { fakeStoreKitPlatform.testTransactionFail = true; final List details = []; final Completer completer = Completer(); late IAPError error; final Stream> stream = iapStoreKitPlatform.purchaseStream; late StreamSubscription> subscription; subscription = stream.listen((List purchaseDetailsList) { details.addAll(purchaseDetailsList); for (final PurchaseDetails purchaseDetails in purchaseDetailsList) { if (purchaseDetails.status == PurchaseStatus.error) { error = purchaseDetails.error!; completer.complete(error); subscription.cancel(); } } }); final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam( productDetails: AppStoreProductDetails.fromSKProduct(dummyProductWrapper), applicationUserName: 'appName'); await iapStoreKitPlatform.buyNonConsumable(purchaseParam: purchaseParam); final IAPError completerError = await completer.future; expect(completerError.code, 'purchase_error'); expect(completerError.source, kIAPSource); expect(completerError.message, 'ios_domain'); expect(completerError.details, {'message': 'an error message'}); }); test( 'should get canceled purchase status when error code is SKErrorPaymentCancelled', () async { fakeStoreKitPlatform.testTransactionCancel = 2; final List details = []; final Completer completer = Completer(); final Stream> stream = iapStoreKitPlatform.purchaseStream; late StreamSubscription> subscription; subscription = stream.listen((List purchaseDetailsList) { details.addAll(purchaseDetailsList); for (final PurchaseDetails purchaseDetails in purchaseDetailsList) { if (purchaseDetails.status == PurchaseStatus.canceled) { completer.complete(purchaseDetails.status); subscription.cancel(); } } }); final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam( productDetails: AppStoreProductDetails.fromSKProduct(dummyProductWrapper), applicationUserName: 'appName'); await iapStoreKitPlatform.buyNonConsumable(purchaseParam: purchaseParam); final PurchaseStatus purchaseStatus = await completer.future; expect(purchaseStatus, PurchaseStatus.canceled); }); test( 'should get canceled purchase status when error code is SKErrorOverlayCancelled', () async { fakeStoreKitPlatform.testTransactionCancel = 15; final List details = []; final Completer completer = Completer(); final Stream> stream = iapStoreKitPlatform.purchaseStream; late StreamSubscription> subscription; subscription = stream.listen((List purchaseDetailsList) { details.addAll(purchaseDetailsList); for (final PurchaseDetails purchaseDetails in purchaseDetailsList) { if (purchaseDetails.status == PurchaseStatus.canceled) { completer.complete(purchaseDetails.status); subscription.cancel(); } } }); final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam( productDetails: AppStoreProductDetails.fromSKProduct(dummyProductWrapper), applicationUserName: 'appName'); await iapStoreKitPlatform.buyNonConsumable(purchaseParam: purchaseParam); final PurchaseStatus purchaseStatus = await completer.future; expect(purchaseStatus, PurchaseStatus.canceled); }); test( 'buying non consumable, should be able to purchase multiple quantity of one product', () async { final List details = []; final Completer> completer = Completer>(); final Stream> stream = iapStoreKitPlatform.purchaseStream; late StreamSubscription> subscription; subscription = stream.listen((List purchaseDetailsList) { details.addAll(purchaseDetailsList); for (final PurchaseDetails purchaseDetails in purchaseDetailsList) { if (purchaseDetails.pendingCompletePurchase) { iapStoreKitPlatform.completePurchase(purchaseDetails); completer.complete(details); subscription.cancel(); } } }); final AppStoreProductDetails productDetails = AppStoreProductDetails.fromSKProduct(dummyProductWrapper); final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam( productDetails: productDetails, quantity: 5, applicationUserName: 'appName'); await iapStoreKitPlatform.buyNonConsumable(purchaseParam: purchaseParam); await completer.future; expect( fakeStoreKitPlatform.finishedTransactions.first.payment.quantity, 5); }); test( 'buying consumable, should be able to purchase multiple quantity of one product', () async { final List details = []; final Completer> completer = Completer>(); final Stream> stream = iapStoreKitPlatform.purchaseStream; late StreamSubscription> subscription; subscription = stream.listen((List purchaseDetailsList) { details.addAll(purchaseDetailsList); for (final PurchaseDetails purchaseDetails in purchaseDetailsList) { if (purchaseDetails.pendingCompletePurchase) { iapStoreKitPlatform.completePurchase(purchaseDetails); completer.complete(details); subscription.cancel(); } } }); final AppStoreProductDetails productDetails = AppStoreProductDetails.fromSKProduct(dummyProductWrapper); final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam( productDetails: productDetails, quantity: 5, applicationUserName: 'appName'); await iapStoreKitPlatform.buyConsumable(purchaseParam: purchaseParam); await completer.future; expect( fakeStoreKitPlatform.finishedTransactions.first.payment.quantity, 5); }); test( 'buying non consumable with discount, should get purchase objects in the purchase update callback', () async { final List details = []; final Completer> completer = Completer>(); final Stream> stream = iapStoreKitPlatform.purchaseStream; late StreamSubscription> subscription; subscription = stream.listen((List purchaseDetailsList) { details.addAll(purchaseDetailsList); if (purchaseDetailsList.first.status == PurchaseStatus.purchased) { completer.complete(details); subscription.cancel(); } }); final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam( productDetails: AppStoreProductDetails.fromSKProduct(dummyProductWrapper), applicationUserName: 'userWithDiscount', discount: dummyPaymentDiscountWrapper, ); await iapStoreKitPlatform.buyNonConsumable(purchaseParam: purchaseParam); final List result = await completer.future; expect(result.length, 2); expect(result.first.productID, dummyProductWrapper.productIdentifier); expect(fakeStoreKitPlatform.discountReceived, dummyPaymentDiscountWrapper.toMap()); }); }); group('complete purchase', () { test('should complete purchase', () async { final List details = []; final Completer> completer = Completer>(); final Stream> stream = iapStoreKitPlatform.purchaseStream; late StreamSubscription> subscription; subscription = stream.listen((List purchaseDetailsList) { details.addAll(purchaseDetailsList); for (final PurchaseDetails purchaseDetails in purchaseDetailsList) { if (purchaseDetails.pendingCompletePurchase) { iapStoreKitPlatform.completePurchase(purchaseDetails); completer.complete(details); subscription.cancel(); } } }); final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam( productDetails: AppStoreProductDetails.fromSKProduct(dummyProductWrapper), applicationUserName: 'appName'); await iapStoreKitPlatform.buyNonConsumable(purchaseParam: purchaseParam); final List result = await completer.future; expect(result.length, 2); expect(result.first.productID, dummyProductWrapper.productIdentifier); expect(fakeStoreKitPlatform.finishedTransactions.length, 1); }); }); group('purchase stream', () { test('Should only have active queue when purchaseStream has listeners', () { final Stream> stream = iapStoreKitPlatform.purchaseStream; expect(fakeStoreKitPlatform.queueIsActive, false); final StreamSubscription> subscription1 = stream.listen((List event) {}); expect(fakeStoreKitPlatform.queueIsActive, true); final StreamSubscription> subscription2 = stream.listen((List event) {}); expect(fakeStoreKitPlatform.queueIsActive, true); subscription1.cancel(); expect(fakeStoreKitPlatform.queueIsActive, true); subscription2.cancel(); expect(fakeStoreKitPlatform.queueIsActive, false); }); }); } /// 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; ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart ================================================ // Copyright 2013 The Flutter 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:in_app_purchase_storekit/src/channel.dart'; import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; import 'sk_test_stub_objects.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); final FakeStoreKitPlatform fakeStoreKitPlatform = FakeStoreKitPlatform(); setUpAll(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( SystemChannels.platform, fakeStoreKitPlatform.onMethodCall); }); setUp(() {}); tearDown(() { fakeStoreKitPlatform.testReturnNull = false; fakeStoreKitPlatform.queueIsActive = null; fakeStoreKitPlatform.getReceiptFailTest = false; }); group('sk_request_maker', () { test('get products method channel', () async { final SkProductResponseWrapper productResponseWrapper = await SKRequestMaker().startProductRequest(['xxx']); expect( productResponseWrapper.products, isNotEmpty, ); expect( productResponseWrapper.products.first.priceLocale.currencySymbol, r'$', ); expect( productResponseWrapper.products.first.priceLocale.currencySymbol, isNot('A'), ); expect( productResponseWrapper.products.first.priceLocale.currencyCode, 'USD', ); expect( productResponseWrapper.products.first.priceLocale.countryCode, 'US', ); expect( productResponseWrapper.invalidProductIdentifiers, isNotEmpty, ); expect( fakeStoreKitPlatform.startProductRequestParam, ['xxx'], ); }); test('get products method channel should throw exception', () async { fakeStoreKitPlatform.getProductRequestFailTest = true; expect( SKRequestMaker().startProductRequest(['xxx']), throwsException, ); fakeStoreKitPlatform.getProductRequestFailTest = false; }); test('refreshed receipt', () async { final int receiptCountBefore = fakeStoreKitPlatform.refreshReceipt; await SKRequestMaker().startRefreshReceiptRequest( receiptProperties: {'isExpired': true}); expect(fakeStoreKitPlatform.refreshReceipt, receiptCountBefore + 1); expect(fakeStoreKitPlatform.refreshReceiptParam, {'isExpired': true}); }); test('should get null receipt if any exceptions are raised', () async { fakeStoreKitPlatform.getReceiptFailTest = true; expect(() async => SKReceiptManager.retrieveReceiptData(), throwsA(const TypeMatcher())); }); }); group('sk_receipt_manager', () { test('should get receipt (faking it by returning a `receipt data` string)', () async { final String receiptData = await SKReceiptManager.retrieveReceiptData(); expect(receiptData, 'receipt data'); }); }); group('sk_payment_queue', () { test('canMakePayment should return true', () async { expect(await SKPaymentQueueWrapper.canMakePayments(), true); }); test('canMakePayment returns false if method channel returns null', () async { fakeStoreKitPlatform.testReturnNull = true; expect(await SKPaymentQueueWrapper.canMakePayments(), false); }); test('transactions should return a valid list of transactions', () async { expect(await SKPaymentQueueWrapper().transactions(), isNotEmpty); }); test( 'throws if observer is not set for payment queue before adding payment', () async { expect(SKPaymentQueueWrapper().addPayment(dummyPayment), throwsAssertionError); }); test('should add payment to the payment queue', () async { final SKPaymentQueueWrapper queue = SKPaymentQueueWrapper(); final TestPaymentTransactionObserver observer = TestPaymentTransactionObserver(); queue.setTransactionObserver(observer); await queue.addPayment(dummyPayment); expect(fakeStoreKitPlatform.payments.first, equals(dummyPayment)); }); test('should finish transaction', () async { final SKPaymentQueueWrapper queue = SKPaymentQueueWrapper(); final TestPaymentTransactionObserver observer = TestPaymentTransactionObserver(); queue.setTransactionObserver(observer); await queue.finishTransaction(dummyTransaction); expect(fakeStoreKitPlatform.transactionsFinished.first, equals(dummyTransaction.toFinishMap())); }); test('should restore transaction', () async { final SKPaymentQueueWrapper queue = SKPaymentQueueWrapper(); final TestPaymentTransactionObserver observer = TestPaymentTransactionObserver(); queue.setTransactionObserver(observer); await queue.restoreTransactions(applicationUserName: 'aUserID'); expect(fakeStoreKitPlatform.applicationNameHasTransactionRestored, 'aUserID'); }); test('startObservingTransactionQueue should call methodChannel', () async { expect(fakeStoreKitPlatform.queueIsActive, isNot(true)); await SKPaymentQueueWrapper().startObservingTransactionQueue(); expect(fakeStoreKitPlatform.queueIsActive, true); }); test('stopObservingTransactionQueue should call methodChannel', () async { expect(fakeStoreKitPlatform.queueIsActive, isNot(false)); await SKPaymentQueueWrapper().stopObservingTransactionQueue(); expect(fakeStoreKitPlatform.queueIsActive, false); }); test('setDelegate should call methodChannel', () async { expect(fakeStoreKitPlatform.isPaymentQueueDelegateRegistered, false); await SKPaymentQueueWrapper().setDelegate(TestPaymentQueueDelegate()); expect(fakeStoreKitPlatform.isPaymentQueueDelegateRegistered, true); await SKPaymentQueueWrapper().setDelegate(null); expect(fakeStoreKitPlatform.isPaymentQueueDelegateRegistered, false); }); test('showPriceConsentIfNeeded should call methodChannel', () async { expect(fakeStoreKitPlatform.showPriceConsentIfNeeded, false); await SKPaymentQueueWrapper().showPriceConsentIfNeeded(); expect(fakeStoreKitPlatform.showPriceConsentIfNeeded, true); }); }); group('Code Redemption Sheet', () { test('presentCodeRedemptionSheet should not throw', () async { expect(fakeStoreKitPlatform.presentCodeRedemption, false); await SKPaymentQueueWrapper().presentCodeRedemptionSheet(); expect(fakeStoreKitPlatform.presentCodeRedemption, true); fakeStoreKitPlatform.presentCodeRedemption = false; }); }); } class FakeStoreKitPlatform { FakeStoreKitPlatform() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, onMethodCall); } // get product request List startProductRequestParam = []; bool getProductRequestFailTest = false; bool testReturnNull = false; // get receipt request bool getReceiptFailTest = false; // refresh receipt request int refreshReceipt = 0; late Map refreshReceiptParam; // payment queue List payments = []; List> transactionsFinished = >[]; String applicationNameHasTransactionRestored = ''; // present Code Redemption bool presentCodeRedemption = false; // show price consent sheet bool showPriceConsentIfNeeded = false; // indicate if the payment queue delegate is registered bool isPaymentQueueDelegateRegistered = false; // Listen to purchase updates bool? queueIsActive; Future onMethodCall(MethodCall call) { switch (call.method) { // request makers case '-[InAppPurchasePlugin startProductRequest:result:]': startProductRequestParam = call.arguments as List; if (getProductRequestFailTest) { return Future.value(); } return Future>.value( buildProductResponseMap(dummyProductResponseWrapper)); case '-[InAppPurchasePlugin refreshReceipt:result:]': refreshReceipt++; refreshReceiptParam = Map.castFrom( call.arguments as Map); return Future.sync(() {}); // receipt manager case '-[InAppPurchasePlugin retrieveReceiptData:result:]': if (getReceiptFailTest) { throw Exception('some arbitrary error'); } return Future.value('receipt data'); // payment queue case '-[SKPaymentQueue canMakePayments:]': if (testReturnNull) { return Future.value(); } return Future.value(true); case '-[SKPaymentQueue transactions]': return Future>.value( [buildTransactionMap(dummyTransaction)]); case '-[InAppPurchasePlugin addPayment:result:]': payments.add(SKPaymentWrapper.fromJson(Map.from( call.arguments as Map))); return Future.sync(() {}); case '-[InAppPurchasePlugin finishTransaction:result:]': transactionsFinished.add( Map.from(call.arguments as Map)); return Future.sync(() {}); case '-[InAppPurchasePlugin restoreTransactions:result:]': applicationNameHasTransactionRestored = call.arguments as String; return Future.sync(() {}); case '-[InAppPurchasePlugin presentCodeRedemptionSheet:result:]': presentCodeRedemption = true; return Future.sync(() {}); case '-[SKPaymentQueue startObservingTransactionQueue]': queueIsActive = true; return Future.sync(() {}); case '-[SKPaymentQueue stopObservingTransactionQueue]': queueIsActive = false; return Future.sync(() {}); case '-[SKPaymentQueue registerDelegate]': isPaymentQueueDelegateRegistered = true; return Future.sync(() {}); case '-[SKPaymentQueue removeDelegate]': isPaymentQueueDelegateRegistered = false; return Future.sync(() {}); case '-[SKPaymentQueue showPriceConsentIfNeeded]': showPriceConsentIfNeeded = true; return Future.sync(() {}); } return Future.error('method not mocked'); } } class TestPaymentQueueDelegate extends SKPaymentQueueDelegateWrapper {} class TestPaymentTransactionObserver extends SKTransactionObserverWrapper { @override void updatedTransactions( {required List transactions}) {} @override void removedTransactions( {required List transactions}) {} @override void restoreCompletedTransactionsFailed({required SKError error}) {} @override void paymentQueueRestoreCompletedTransactionsFinished() {} @override bool shouldAddStorePayment( {required SKPaymentWrapper payment, required SKProductWrapper product}) { return true; } } /// 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; ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_payment_queue_delegate_api_test.dart ================================================ // Copyright 2013 The Flutter 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:in_app_purchase_storekit/src/channel.dart'; import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); final FakeStoreKitPlatform fakeStoreKitPlatform = FakeStoreKitPlatform(); setUpAll(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( SystemChannels.platform, fakeStoreKitPlatform.onMethodCall); }); test( 'handlePaymentQueueDelegateCallbacks should call SKPaymentQueueDelegateWrapper.shouldContinueTransaction', () async { final SKPaymentQueueWrapper queue = SKPaymentQueueWrapper(); final TestPaymentQueueDelegate testDelegate = TestPaymentQueueDelegate(); await queue.setDelegate(testDelegate); final Map arguments = { 'storefront': { 'countryCode': 'USA', 'identifier': 'unique_identifier', }, 'transaction': { 'payment': { 'productIdentifier': 'product_identifier', } }, }; final Object? result = await queue.handlePaymentQueueDelegateCallbacks( MethodCall('shouldContinueTransaction', arguments), ); expect(result, false); expect( testDelegate.log, { equals('shouldContinueTransaction'), }, ); }); test( 'handlePaymentQueueDelegateCallbacks should call SKPaymentQueueDelegateWrapper.shouldShowPriceConsent', () async { final SKPaymentQueueWrapper queue = SKPaymentQueueWrapper(); final TestPaymentQueueDelegate testDelegate = TestPaymentQueueDelegate(); await queue.setDelegate(testDelegate); final bool result = (await queue.handlePaymentQueueDelegateCallbacks( const MethodCall('shouldShowPriceConsent'), ))! as bool; expect(result, false); expect( testDelegate.log, { equals('shouldShowPriceConsent'), }, ); }); test( 'handleObserverCallbacks should call SKTransactionObserverWrapper.restoreCompletedTransactionsFailed', () async { final SKPaymentQueueWrapper queue = SKPaymentQueueWrapper(); final TestTransactionObserverWrapper testObserver = TestTransactionObserverWrapper(); queue.setTransactionObserver(testObserver); final Map arguments = { 'code': 100, 'domain': 'domain', 'userInfo': {'error': 'underlying_error'}, }; await queue.handleObserverCallbacks( MethodCall('restoreCompletedTransactionsFailed', arguments), ); expect( testObserver.log, { equals('restoreCompletedTransactionsFailed'), }, ); }); } class TestTransactionObserverWrapper extends SKTransactionObserverWrapper { final List log = []; @override void updatedTransactions( {required List transactions}) { log.add('updatedTransactions'); } @override void removedTransactions( {required List transactions}) { log.add('removedTransactions'); } @override void restoreCompletedTransactionsFailed({required SKError error}) { log.add('restoreCompletedTransactionsFailed'); } @override void paymentQueueRestoreCompletedTransactionsFinished() { log.add('paymentQueueRestoreCompletedTransactionsFinished'); } @override bool shouldAddStorePayment( {required SKPaymentWrapper payment, required SKProductWrapper product}) { log.add('shouldAddStorePayment'); return false; } } class TestPaymentQueueDelegate extends SKPaymentQueueDelegateWrapper { final List log = []; @override bool shouldContinueTransaction( SKPaymentTransactionWrapper transaction, SKStorefrontWrapper storefront) { log.add('shouldContinueTransaction'); return false; } @override bool shouldShowPriceConsent() { log.add('shouldShowPriceConsent'); return false; } } class FakeStoreKitPlatform { FakeStoreKitPlatform() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, onMethodCall); } // indicate if the payment queue delegate is registered bool isPaymentQueueDelegateRegistered = false; Future onMethodCall(MethodCall call) { switch (call.method) { case '-[SKPaymentQueue registerDelegate]': isPaymentQueueDelegateRegistered = true; return Future.sync(() {}); case '-[SKPaymentQueue removeDelegate]': isPaymentQueueDelegateRegistered = false; return Future.sync(() {}); } return Future.error('method not mocked'); } } /// 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; ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_product_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:in_app_purchase_storekit/src/types/app_store_product_details.dart'; import 'package:in_app_purchase_storekit/src/types/app_store_purchase_details.dart'; import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; import 'package:test/test.dart'; import 'sk_test_stub_objects.dart'; void main() { group('product related object wrapper test', () { test( 'SKProductSubscriptionPeriodWrapper should have property values consistent with map', () { final SKProductSubscriptionPeriodWrapper wrapper = SKProductSubscriptionPeriodWrapper.fromJson( buildSubscriptionPeriodMap(dummySubscription)); expect(wrapper, equals(dummySubscription)); }); test( 'SKProductSubscriptionPeriodWrapper should have properties to be default values if map is empty', () { final SKProductSubscriptionPeriodWrapper wrapper = SKProductSubscriptionPeriodWrapper.fromJson( const {}); expect(wrapper.numberOfUnits, 0); expect(wrapper.unit, SKSubscriptionPeriodUnit.day); }); test( 'SKProductDiscountWrapper should have property values consistent with map', () { final SKProductDiscountWrapper wrapper = SKProductDiscountWrapper.fromJson(buildDiscountMap(dummyDiscount)); expect(wrapper, equals(dummyDiscount)); }); test( 'SKProductDiscountWrapper missing identifier and type should have ' 'property values consistent with map', () { final SKProductDiscountWrapper wrapper = SKProductDiscountWrapper.fromJson( buildDiscountMapMissingIdentifierAndType( dummyDiscountMissingIdentifierAndType)); expect(wrapper, equals(dummyDiscountMissingIdentifierAndType)); }); test( 'SKProductDiscountWrapper should have properties to be default if map is empty', () { final SKProductDiscountWrapper wrapper = SKProductDiscountWrapper.fromJson(const {}); expect(wrapper.price, ''); expect( wrapper.priceLocale, SKPriceLocaleWrapper( currencyCode: '', currencySymbol: '', countryCode: '', )); expect(wrapper.numberOfPeriods, 0); expect(wrapper.paymentMode, SKProductDiscountPaymentMode.payAsYouGo); expect( wrapper.subscriptionPeriod, SKProductSubscriptionPeriodWrapper( numberOfUnits: 0, unit: SKSubscriptionPeriodUnit.day)); }); test('SKProductWrapper should have property values consistent with map', () { final SKProductWrapper wrapper = SKProductWrapper.fromJson(buildProductMap(dummyProductWrapper)); expect(wrapper, equals(dummyProductWrapper)); }); test( 'SKProductWrapper should have properties to be default if map is empty', () { final SKProductWrapper wrapper = SKProductWrapper.fromJson(const {}); expect(wrapper.productIdentifier, ''); expect(wrapper.localizedTitle, ''); expect(wrapper.localizedDescription, ''); expect( wrapper.priceLocale, SKPriceLocaleWrapper( currencyCode: '', currencySymbol: '', countryCode: '', )); expect(wrapper.subscriptionGroupIdentifier, null); expect(wrapper.price, ''); expect(wrapper.subscriptionPeriod, null); expect(wrapper.discounts, []); }); test('toProductDetails() should return correct Product object', () { final SKProductWrapper wrapper = SKProductWrapper.fromJson(buildProductMap(dummyProductWrapper)); final AppStoreProductDetails product = AppStoreProductDetails.fromSKProduct(wrapper); expect(product.title, wrapper.localizedTitle); expect(product.description, wrapper.localizedDescription); expect(product.id, wrapper.productIdentifier); expect(product.price, wrapper.priceLocale.currencySymbol + wrapper.price); expect(product.skProduct, wrapper); }); test('SKProductResponse wrapper should match', () { final SkProductResponseWrapper wrapper = SkProductResponseWrapper.fromJson( buildProductResponseMap(dummyProductResponseWrapper)); expect(wrapper, equals(dummyProductResponseWrapper)); }); test('SKProductResponse wrapper should default to empty list', () { final Map> productResponseMapEmptyList = >{ 'products': >[], 'invalidProductIdentifiers': [], }; final SkProductResponseWrapper wrapper = SkProductResponseWrapper.fromJson(productResponseMapEmptyList); expect(wrapper.products.length, 0); expect(wrapper.invalidProductIdentifiers.length, 0); }); test('LocaleWrapper should have property values consistent with map', () { final SKPriceLocaleWrapper wrapper = SKPriceLocaleWrapper.fromJson(buildLocaleMap(dollarLocale)); expect(wrapper, equals(dollarLocale)); }); }); group('Payment queue related object tests', () { test('Should construct correct SKPaymentWrapper from json', () { final SKPaymentWrapper payment = SKPaymentWrapper.fromJson(dummyPayment.toMap()); expect(payment, equals(dummyPayment)); }); test('SKPaymentWrapper should have propery values consistent with .toMap()', () { final Map mapResult = dummyPaymentWithDiscount.toMap(); expect(mapResult['productIdentifier'], dummyPaymentWithDiscount.productIdentifier); expect(mapResult['applicationUsername'], dummyPaymentWithDiscount.applicationUsername); expect(mapResult['requestData'], dummyPaymentWithDiscount.requestData); expect(mapResult['quantity'], dummyPaymentWithDiscount.quantity); expect(mapResult['simulatesAskToBuyInSandbox'], dummyPaymentWithDiscount.simulatesAskToBuyInSandbox); expect(mapResult['paymentDiscount'], equals(dummyPaymentWithDiscount.paymentDiscount?.toMap())); }); test('Should construct correct SKError from json', () { final SKError error = SKError.fromJson(buildErrorMap(dummyError)); expect(error, equals(dummyError)); }); test('Should construct correct SKTransactionWrapper from json', () { final SKPaymentTransactionWrapper transaction = SKPaymentTransactionWrapper.fromJson( buildTransactionMap(dummyTransaction)); expect(transaction, equals(dummyTransaction)); }); test('toPurchaseDetails() should return correct PurchaseDetail object', () { final AppStorePurchaseDetails details = AppStorePurchaseDetails.fromSKTransaction( dummyTransaction, 'receipt data'); expect(dummyTransaction.transactionIdentifier, details.purchaseID); expect(dummyTransaction.payment.productIdentifier, details.productID); expect(dummyTransaction.transactionTimeStamp, isNotNull); expect((dummyTransaction.transactionTimeStamp! * 1000).toInt().toString(), details.transactionDate); expect(details.verificationData.localVerificationData, 'receipt data'); expect(details.verificationData.serverVerificationData, 'receipt data'); expect(details.verificationData.source, 'app_store'); expect(details.skPaymentTransaction, dummyTransaction); expect(details.pendingCompletePurchase, true); }); test('SKPaymentTransactionWrapper.toFinishMap set correct value', () { final SKPaymentTransactionWrapper transactionWrapper = SKPaymentTransactionWrapper( payment: dummyPayment, transactionState: SKPaymentTransactionStateWrapper.failed, transactionIdentifier: 'abcd'); final Map finishMap = transactionWrapper.toFinishMap(); expect(finishMap['transactionIdentifier'], 'abcd'); expect(finishMap['productIdentifier'], dummyPayment.productIdentifier); }); test( 'SKPaymentTransactionWrapper.toFinishMap should set transactionIdentifier to null when necessary', () { final SKPaymentTransactionWrapper transactionWrapper = SKPaymentTransactionWrapper( payment: dummyPayment, transactionState: SKPaymentTransactionStateWrapper.failed); final Map finishMap = transactionWrapper.toFinishMap(); expect(finishMap['transactionIdentifier'], null); }); test('Should generate correct map of the payment object', () { final Map map = dummyPayment.toMap(); expect(map['productIdentifier'], dummyPayment.productIdentifier); expect(map['applicationUsername'], dummyPayment.applicationUsername); expect(map['requestData'], dummyPayment.requestData); expect(map['quantity'], dummyPayment.quantity); expect(map['simulatesAskToBuyInSandbox'], dummyPayment.simulatesAskToBuyInSandbox); }); }); } ================================================ FILE: packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_test_stub_objects.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; const SKPaymentWrapper dummyPayment = SKPaymentWrapper( productIdentifier: 'prod-id', applicationUsername: 'app-user-name', requestData: 'fake-data-utf8', quantity: 2, simulatesAskToBuyInSandbox: true); final SKPaymentWrapper dummyPaymentWithDiscount = SKPaymentWrapper( productIdentifier: 'prod-id', applicationUsername: 'app-user-name', requestData: 'fake-data-utf8', quantity: 2, simulatesAskToBuyInSandbox: true, paymentDiscount: dummyPaymentDiscountWrapper); const SKError dummyError = SKError( code: 111, domain: 'dummy-domain', userInfo: {'key': 'value'}); final SKPaymentTransactionWrapper dummyOriginalTransaction = SKPaymentTransactionWrapper( transactionState: SKPaymentTransactionStateWrapper.purchased, payment: dummyPayment, transactionTimeStamp: 1231231231.00, transactionIdentifier: '123123', error: dummyError, ); final SKPaymentTransactionWrapper dummyTransaction = SKPaymentTransactionWrapper( transactionState: SKPaymentTransactionStateWrapper.purchased, payment: dummyPayment, originalTransaction: dummyOriginalTransaction, transactionTimeStamp: 1231231231.00, transactionIdentifier: '123123', error: dummyError, ); final SKPriceLocaleWrapper dollarLocale = SKPriceLocaleWrapper( currencySymbol: r'$', currencyCode: 'USD', countryCode: 'US', ); final SKPriceLocaleWrapper noSymbolLocale = SKPriceLocaleWrapper( currencySymbol: '', currencyCode: 'EUR', countryCode: 'UK', ); final SKProductSubscriptionPeriodWrapper dummySubscription = SKProductSubscriptionPeriodWrapper( numberOfUnits: 1, unit: SKSubscriptionPeriodUnit.month, ); final SKProductDiscountWrapper dummyDiscount = SKProductDiscountWrapper( price: '1.0', priceLocale: dollarLocale, numberOfPeriods: 1, paymentMode: SKProductDiscountPaymentMode.payUpFront, subscriptionPeriod: dummySubscription, identifier: 'id', type: SKProductDiscountType.subscription, ); final SKProductDiscountWrapper dummyDiscountMissingIdentifierAndType = SKProductDiscountWrapper( price: '1.0', priceLocale: dollarLocale, numberOfPeriods: 1, paymentMode: SKProductDiscountPaymentMode.payUpFront, subscriptionPeriod: dummySubscription, identifier: null, type: SKProductDiscountType.introductory, ); final SKProductWrapper dummyProductWrapper = SKProductWrapper( productIdentifier: 'id', localizedTitle: 'title', localizedDescription: 'description', priceLocale: dollarLocale, subscriptionGroupIdentifier: 'com.group', price: '1.0', subscriptionPeriod: dummySubscription, introductoryPrice: dummyDiscount, discounts: [dummyDiscount], ); final SkProductResponseWrapper dummyProductResponseWrapper = SkProductResponseWrapper( products: [dummyProductWrapper], invalidProductIdentifiers: const ['123'], ); Map buildLocaleMap(SKPriceLocaleWrapper local) { return { 'currencySymbol': local.currencySymbol, 'currencyCode': local.currencyCode, 'countryCode': local.countryCode, }; } Map? buildSubscriptionPeriodMap( SKProductSubscriptionPeriodWrapper? sub) { if (sub == null) { return null; } return { 'numberOfUnits': sub.numberOfUnits, 'unit': SKSubscriptionPeriodUnit.values.indexOf(sub.unit), }; } Map buildDiscountMap(SKProductDiscountWrapper discount) { return { 'price': discount.price, 'priceLocale': buildLocaleMap(discount.priceLocale), 'numberOfPeriods': discount.numberOfPeriods, 'paymentMode': SKProductDiscountPaymentMode.values.indexOf(discount.paymentMode), 'subscriptionPeriod': buildSubscriptionPeriodMap(discount.subscriptionPeriod), 'identifier': discount.identifier, 'type': SKProductDiscountType.values.indexOf(discount.type) }; } Map buildDiscountMapMissingIdentifierAndType( SKProductDiscountWrapper discount) { return { 'price': discount.price, 'priceLocale': buildLocaleMap(discount.priceLocale), 'numberOfPeriods': discount.numberOfPeriods, 'paymentMode': SKProductDiscountPaymentMode.values.indexOf(discount.paymentMode), 'subscriptionPeriod': buildSubscriptionPeriodMap(discount.subscriptionPeriod) }; } Map buildProductMap(SKProductWrapper product) { return { 'productIdentifier': product.productIdentifier, 'localizedTitle': product.localizedTitle, 'localizedDescription': product.localizedDescription, 'priceLocale': buildLocaleMap(product.priceLocale), 'subscriptionGroupIdentifier': product.subscriptionGroupIdentifier, 'price': product.price, 'subscriptionPeriod': buildSubscriptionPeriodMap(product.subscriptionPeriod), 'introductoryPrice': buildDiscountMap(product.introductoryPrice!), 'discounts': [buildDiscountMap(product.introductoryPrice!)], }; } Map buildProductResponseMap( SkProductResponseWrapper response) { final List productsMap = response.products .map((SKProductWrapper product) => buildProductMap(product)) .toList(); return { 'products': productsMap, 'invalidProductIdentifiers': response.invalidProductIdentifiers }; } Map buildErrorMap(SKError error) { return { 'code': error.code, 'domain': error.domain, 'userInfo': error.userInfo, }; } Map buildTransactionMap( SKPaymentTransactionWrapper transaction) { final Map map = { 'transactionState': SKPaymentTransactionStateWrapper.values .indexOf(SKPaymentTransactionStateWrapper.purchased), 'payment': transaction.payment.toMap(), 'originalTransaction': transaction.originalTransaction == null ? null : buildTransactionMap(transaction.originalTransaction!), 'transactionTimeStamp': transaction.transactionTimeStamp, 'transactionIdentifier': transaction.transactionIdentifier, 'error': buildErrorMap(transaction.error!), }; return map; } final SKPaymentDiscountWrapper dummyPaymentDiscountWrapper = SKPaymentDiscountWrapper.fromJson(const { 'identifier': 'dummy-discount-identifier', 'keyIdentifier': 'KEYIDTEST1', 'nonce': '00000000-0000-0000-0000-000000000000', 'signature': 'dummy-signature-string', 'timestamp': 1231231231, }); ================================================ FILE: packages/integration_test/.gitignore ================================================ .DS_Store .dart_tool/ .packages .pub/ build/ ================================================ FILE: packages/integration_test/.metadata ================================================ # 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: 3374ee380b499d99c50ed6dfdd45510aa8318741 channel: master project_type: plugin ================================================ FILE: packages/integration_test/README.md ================================================ # integration_test (moved) ## MOVED This package has [moved to the Flutter SDK](https://github.com/flutter/flutter/tree/master/packages/integration_test), and the pub.dev version is deprecated. As of Flutter 2.0, include it in your pubspec's dev dependencies section, as follows: ``` dev_dependencies: integration_test: sdk: flutter ``` For the latest documentation, see [Integration testing](https://flutter.dev/docs/testing/integration-tests). ================================================ FILE: packages/ios_platform_images/.gitignore ================================================ .DS_Store .dart_tool/ .packages .pub/ build/ pubspec.lock .idea/ .metadata ================================================ FILE: packages/ios_platform_images/AUTHORS ================================================ # 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> ================================================ FILE: packages/ios_platform_images/CHANGELOG.md ================================================ ## 0.2.2 * Updates minimum version to iOS 11. ## 0.2.1+1 * Add lint ignore comments ## 0.2.1 * Updates minimum Flutter version to 3.3.0. * Removes usage of deprecated [ImageProvider.load]. * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/106316). ## 0.2.0+9 * Ignores the warning for the upcoming deprecation of `DecoderCallback`. ## 0.2.0+8 * Ignores the warning for the upcoming deprecation of `ImageProvider.load` in the correct line. ## 0.2.0+7 * Ignores the warning for the upcoming deprecation of `ImageProvider.load`. ## 0.2.0+6 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.2.0+5 * Migrates from `ui.hash*` to `Object.hash*`. * Adds OS version support information to README. ## 0.2.0+4 * Internal code cleanup for stricter analysis options. ## 0.2.0+3 * Internal fix for unused field formal parameter. ## 0.2.0+2 * Update minimum Flutter SDK to 2.5 and iOS deployment target to 9.0. ## 0.2.0+1 * Add iOS unit test target. * Fix repository link in pubspec.yaml. ## 0.2.0 * Migrate to null safety. ## 0.1.2+4 * Update Flutter SDK constraint. ## 0.1.2+3 * Remove no-op android folder in the example app. ## 0.1.2+2 * Post-v2 Android embedding cleanups. ## 0.1.2+1 * Remove Android folder from `ios_platform_images`. ## 0.1.2 * Fix crash when parameter extension is null. * Fix CocoaPods podspec lint warnings. ## 0.1.1 * Remove Android dependencies fallback. * Require Flutter SDK 1.12.13+hotfix.5 or greater. ## 0.1.0+2 * Make the pedantic dev_dependency explicit. ## 0.1.0+1 * Removed Android support from the pubspec. ## 0.1.0 * Fixed a bug where the scale value of the image wasn't respected. ## 0.0.1 * Initial release. Includes functionality to share images iOS images with Flutter and Flutter assets with iOS. ================================================ FILE: packages/ios_platform_images/LICENSE ================================================ 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. ================================================ FILE: packages/ios_platform_images/README.md ================================================ # IOS Platform Images A Flutter plugin to share images between Flutter and iOS. This allows Flutter to load images from Images.xcassets and iOS code to load Flutter images. When loading images from Image.xcassets the device specific variant is chosen ([iOS documentation](https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/image-size-and-resolution/)). | | iOS | |-------------|-------| | **Support** | 11.0+ | ## Usage ### iOS->Flutter Example ``` dart // Import package import 'package:ios_platform_images/ios_platform_images.dart'; Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Center( child: Image(image: IosPlatformImages.load("flutter")), ), //.. ), ); } ``` `IosPlatformImages.load` functions like [[UIImage imageNamed:]](https://developer.apple.com/documentation/uikit/uiimage/1624146-imagenamed). ### Flutter->iOS Example ```objc #import static UIImageView* MakeImage() { UIImage* image = [UIImage flutterImageWithName:@"assets/foo.png"]; return [[UIImageView alloc] initWithImage:image]; } ``` ================================================ FILE: packages/ios_platform_images/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: packages/ios_platform_images/example/README.md ================================================ # ios_platform_images_example Demonstrates how to use the ios_platform_images plugin. ================================================ FILE: packages/ios_platform_images/example/ios/.gitignore ================================================ *.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/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 ================================================ FILE: packages/ios_platform_images/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 11.0 ================================================ FILE: packages/ios_platform_images/example/ios/Flutter/Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/ios_platform_images/example/ios/Flutter/Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/ios_platform_images/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/ios_platform_images/example/ios/Runner/AppDelegate.swift ================================================ // Copyright 2013 The Flutter 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) } } ================================================ FILE: packages/ios_platform_images/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/ios_platform_images/example/ios/Runner/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/ios_platform_images/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "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" } } ================================================ FILE: packages/ios_platform_images/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: packages/ios_platform_images/example/ios/Runner/Assets.xcassets/flutter.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "flutter.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "flutter@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/ios_platform_images/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/ios_platform_images/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/ios_platform_images/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ios_platform_images_example CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents ================================================ FILE: packages/ios_platform_images/example/ios/Runner/Runner-Bridging-Header.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "GeneratedPluginRegistrant.h" ================================================ FILE: packages/ios_platform_images/example/ios/Runner/textfile ================================================ hello ================================================ FILE: packages/ios_platform_images/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 0DE21BF72447752100097E3A /* textfile in Resources */ = {isa = PBXBuildFile; fileRef = 0DE21BF62447752100097E3A /* textfile */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; A30D9778BC0D4D09580CF4BE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 906079E3CC5A6FAB808EAF1E /* Pods_Runner.framework */; }; F76AC1C1266713D00040C8BC /* IosPlatformImagesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F76AC1C0266713D00040C8BC /* IosPlatformImagesTests.m */; }; FC73B055B2CD2E32A3E50B27 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD2C5EF0E06B6EC7EBCB922C /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ F76AC1C3266713D00040C8BC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0B20D3254D8E1A2B01D83810 /* 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 = ""; }; 0DE21BF62447752100097E3A /* textfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = textfile; sourceTree = ""; }; 0EF1CD9A3A3064B5289EF22E /* 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 = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 4B56C310C5932F84CD6C17AC /* 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 = ""; }; 80830F517E3E8B75B2D3AC0A /* 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 = ""; }; 906079E3CC5A6FAB808EAF1E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; AD2C5EF0E06B6EC7EBCB922C /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D1A761179BC59B1BAEE63036 /* 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 = ""; }; D36FEDC657E1CE88220062D7 /* 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 = ""; }; F76AC1BE266713D00040C8BC /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F76AC1C0266713D00040C8BC /* IosPlatformImagesTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IosPlatformImagesTests.m; sourceTree = ""; }; F76AC1C2266713D00040C8BC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( A30D9778BC0D4D09580CF4BE /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; F76AC1BB266713D00040C8BC /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( FC73B055B2CD2E32A3E50B27 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 790F6E36EBDB3EC4A899BEF5 /* Pods */ = { isa = PBXGroup; children = ( D1A761179BC59B1BAEE63036 /* Pods-Runner.debug.xcconfig */, 0EF1CD9A3A3064B5289EF22E /* Pods-Runner.release.xcconfig */, 4B56C310C5932F84CD6C17AC /* Pods-Runner.profile.xcconfig */, 0B20D3254D8E1A2B01D83810 /* Pods-RunnerTests.debug.xcconfig */, D36FEDC657E1CE88220062D7 /* Pods-RunnerTests.release.xcconfig */, 80830F517E3E8B75B2D3AC0A /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, F76AC1BF266713D00040C8BC /* RunnerTests */, 97C146EF1CF9000F007C117D /* Products */, 790F6E36EBDB3EC4A899BEF5 /* Pods */, DBEBA2309FD49D5C34798105 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, F76AC1BE266713D00040C8BC /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 0DE21BF62447752100097E3A /* textfile */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; DBEBA2309FD49D5C34798105 /* Frameworks */ = { isa = PBXGroup; children = ( 906079E3CC5A6FAB808EAF1E /* Pods_Runner.framework */, AD2C5EF0E06B6EC7EBCB922C /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; }; F76AC1BF266713D00040C8BC /* RunnerTests */ = { isa = PBXGroup; children = ( F76AC1C0266713D00040C8BC /* IosPlatformImagesTests.m */, F76AC1C2266713D00040C8BC /* Info.plist */, ); path = RunnerTests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 73331024E8B67D581A0862F0 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 54C54D6BB826835E8AB0FA51 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; F76AC1BD266713D00040C8BC /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = F76AC1C8266713D00040C8BC /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( C102F13F37851E08F0608EE5 /* [CP] Check Pods Manifest.lock */, F76AC1BA266713D00040C8BC /* Sources */, F76AC1BB266713D00040C8BC /* Frameworks */, F76AC1BC266713D00040C8BC /* Resources */, ); buildRules = ( ); dependencies = ( F76AC1C4266713D00040C8BC /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = F76AC1BE266713D00040C8BC /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; F76AC1BD266713D00040C8BC = { CreatedOnToolsVersion = 12.5; DevelopmentTeam = S8QB4VV633; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, F76AC1BD266713D00040C8BC /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 0DE21BF72447752100097E3A /* textfile in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; F76AC1BC266713D00040C8BC /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 54C54D6BB826835E8AB0FA51 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", "${BUILT_PRODUCTS_DIR}/ios_platform_images/ios_platform_images.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ios_platform_images.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 73331024E8B67D581A0862F0 /* [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", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; 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"; }; C102F13F37851E08F0608EE5 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F76AC1BA266713D00040C8BC /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F76AC1C1266713D00040C8BC /* IosPlatformImagesTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ F76AC1C4266713D00040C8BC /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = F76AC1C3266713D00040C8BC /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; 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; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.iosPlatformImagesExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.iosPlatformImagesExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.iosPlatformImagesExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; F76AC1C5266713D00040C8BC /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 0B20D3254D8E1A2B01D83810 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = S8QB4VV633; 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; }; F76AC1C6266713D00040C8BC /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = D36FEDC657E1CE88220062D7 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = S8QB4VV633; 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 = Release; }; F76AC1C7266713D00040C8BC /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 80830F517E3E8B75B2D3AC0A /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = S8QB4VV633; 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 = Profile; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F76AC1C8266713D00040C8BC /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( F76AC1C5266713D00040C8BC /* Debug */, F76AC1C6266713D00040C8BC /* Release */, F76AC1C7266713D00040C8BC /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/ios_platform_images/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/ios_platform_images/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/ios_platform_images/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/ios_platform_images/example/ios/RunnerTests/Info.plist ================================================ 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 ================================================ FILE: packages/ios_platform_images/example/ios/RunnerTests/IosPlatformImagesTests.m ================================================ // Copyright 2013 The Flutter 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 ios_platform_images; @import XCTest; @interface IosPlatformImagesTests : XCTestCase @end @implementation IosPlatformImagesTests - (void)testPlugin { IosPlatformImagesPlugin *plugin = [[IosPlatformImagesPlugin alloc] init]; XCTAssertNotNil(plugin); } @end ================================================ FILE: packages/ios_platform_images/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:ios_platform_images/ios_platform_images.dart'; void main() => runApp(const MyApp()); /// Main widget for the example app. class MyApp extends StatefulWidget { /// Default Constructor const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override void initState() { super.initState(); IosPlatformImages.resolveURL('textfile') // ignore: avoid_print .then((String? value) => print(value)); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Plugin example app'), ), body: Center( // "flutter" is a resource in Assets.xcassets. child: Image( image: IosPlatformImages.load('flutter'), semanticLabel: 'Flutter logo', ), ), ), ); } } ================================================ FILE: packages/ios_platform_images/example/pubspec.yaml ================================================ name: ios_platform_images_example description: Demonstrates how to use the ios_platform_images plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: cupertino_icons: ^1.0.2 flutter: sdk: flutter ios_platform_images: # When depending on this package from a real application you should use: # ios_platform_images: ^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: ../ dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/ios_platform_images/example/test/widget_test.dart ================================================ // Copyright 2013 The Flutter 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/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:ios_platform_images_example/main.dart'; void main() { testWidgets('Verify loads image', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(const MyApp()); expect( find.byWidgetPredicate( (Widget widget) => widget is Image && (!Platform.isIOS || widget.image != null), ), findsOneWidget, ); }); } ================================================ FILE: packages/ios_platform_images/ios/.gitignore ================================================ .idea/ .vagrant/ .sconsign.dblite .svn/ .DS_Store *.swp profile DerivedData/ build/ GeneratedPluginRegistrant.h GeneratedPluginRegistrant.m .generated/ *.pbxuser *.mode1v3 *.mode2v3 *.perspectivev3 !default.pbxuser !default.mode1v3 !default.mode2v3 !default.perspectivev3 xcuserdata *.moved-aside *.pyc *sync/ Icon? .tags* /Flutter/Generated.xcconfig /Flutter/flutter_export_environment.sh ================================================ FILE: packages/ios_platform_images/ios/Assets/.gitkeep ================================================ ================================================ FILE: packages/ios_platform_images/ios/Classes/IosPlatformImagesPlugin.h ================================================ // Copyright 2013 The Flutter 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 /// A plugin for Flutter that allows Flutter to load images in a platform /// specific way on iOS. @interface IosPlatformImagesPlugin : NSObject @end ================================================ FILE: packages/ios_platform_images/ios/Classes/IosPlatformImagesPlugin.m ================================================ // Copyright 2013 The Flutter 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 "IosPlatformImagesPlugin.h" #if !__has_feature(objc_arc) #error ARC must be enabled! #endif @interface IosPlatformImagesPlugin () @end @implementation IosPlatformImagesPlugin + (void)registerWithRegistrar:(NSObject *)registrar { FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/ios_platform_images" binaryMessenger:[registrar messenger]]; [channel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) { if ([@"loadImage" isEqualToString:call.method]) { NSString *name = call.arguments; UIImage *image = [UIImage imageNamed:name]; NSData *data = UIImagePNGRepresentation(image); if (data) { result(@{ @"scale" : @(image.scale), @"data" : [FlutterStandardTypedData typedDataWithBytes:data], }); } else { result(nil); } return; } else if ([@"resolveURL" isEqualToString:call.method]) { NSArray *args = call.arguments; NSString *name = args[0]; NSString *extension = (args[1] == (id)NSNull.null) ? nil : args[1]; NSURL *url = [[NSBundle mainBundle] URLForResource:name withExtension:extension]; result(url.absoluteString); return; } result(FlutterMethodNotImplemented); }]; } @end ================================================ FILE: packages/ios_platform_images/ios/Classes/UIImage+ios_platform_images.h ================================================ // Copyright 2013 The Flutter 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 UIImage (ios_platform_images) /// Loads a UIImage from the embedded Flutter project's assets. /// /// This method loads the Flutter asset that is appropriate for the current /// screen. If you are on a 2x retina device where usually `UIImage` would be /// loading `@2x` assets, it will attempt to load the `2.0x` variant. It will /// load the standard image if it can't find the `2.0x` variant. /// /// For example, if your Flutter project's `pubspec.yaml` lists "assets/foo.png" /// and "assets/2.0x/foo.png", calling /// `[UIImage flutterImageWithName:@"assets/foo.png"]` will load /// "assets/2.0x/foo.png". /// /// See also https://flutter.dev/docs/development/ui/assets-and-images /// /// Note: We don't yet support images from package dependencies (ex. /// `AssetImage('icons/heart.png', package: 'my_icons')`). + (UIImage *)flutterImageWithName:(NSString *)name; @end ================================================ FILE: packages/ios_platform_images/ios/Classes/UIImage+ios_platform_images.m ================================================ // Copyright 2013 The Flutter 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 "UIImage+ios_platform_images.h" @implementation UIImage (ios_platform_images) + (UIImage *)flutterImageWithName:(NSString *)name { NSString *filename = [name lastPathComponent]; NSString *path = [name stringByDeletingLastPathComponent]; for (int screenScale = [UIScreen mainScreen].scale; screenScale > 1; --screenScale) { NSString *key = [FlutterDartProject lookupKeyForAsset:[NSString stringWithFormat:@"%@/%d.0x/%@", path, screenScale, filename]]; UIImage *image = [UIImage imageNamed:key inBundle:[NSBundle mainBundle] compatibleWithTraitCollection:nil]; if (image) { return image; } } NSString *key = [FlutterDartProject lookupKeyForAsset:name]; return [UIImage imageNamed:key inBundle:[NSBundle mainBundle] compatibleWithTraitCollection:nil]; } @end ================================================ FILE: packages/ios_platform_images/ios/ios_platform_images.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. # Run `pod lib lint ios_platform_images.podspec' to validate before publishing. # Pod::Spec.new do |s| s.name = 'ios_platform_images' s.version = '0.0.1' s.summary = 'Flutter iOS Platform Images' s.description = <<-DESC A Flutter plugin to share images between Flutter and iOS. 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/ios_platform_images' } s.documentation_url = 'https://pub.dev/packages/ios_platform_images' s.source_files = 'Classes/**/*' s.dependency 'Flutter' s.platform = :ios, '11.0' # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' end ================================================ FILE: packages/ios_platform_images/lib/ios_platform_images.dart ================================================ // Copyright 2013 The Flutter 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 'dart:ui' as ui; import 'package:flutter/foundation.dart' show SynchronousFuture, describeIdentity, immutable, objectRuntimeType; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; class _FutureImageStreamCompleter extends ImageStreamCompleter { _FutureImageStreamCompleter({ required Future codec, required this.futureScale, }) { codec.then(_onCodecReady, onError: (Object error, StackTrace stack) { reportError( context: ErrorDescription('resolving a single-frame image stream'), exception: error, stack: stack, silent: true, ); }); } final Future futureScale; Future _onCodecReady(ui.Codec codec) async { try { final ui.FrameInfo nextFrame = await codec.getNextFrame(); final double scale = await futureScale; setImage(ImageInfo(image: nextFrame.image, scale: scale)); } catch (exception, stack) { reportError( context: ErrorDescription('resolving an image frame'), exception: exception, stack: stack, silent: true, ); } } } /// Performs exactly like a [MemoryImage] but instead of taking in bytes it takes /// in a future that represents bytes. @immutable class _FutureMemoryImage extends ImageProvider<_FutureMemoryImage> { /// Constructor for FutureMemoryImage. [_futureBytes] is the bytes that will /// be loaded into an image and [_futureScale] is the scale that will be applied to /// that image to account for high-resolution images. const _FutureMemoryImage(this._futureBytes, this._futureScale); final Future _futureBytes; final Future _futureScale; /// See [ImageProvider.obtainKey]. @override Future<_FutureMemoryImage> obtainKey(ImageConfiguration configuration) { return SynchronousFuture<_FutureMemoryImage>(this); } @override ImageStreamCompleter loadBuffer( _FutureMemoryImage key, DecoderBufferCallback decode, // ignore: deprecated_member_use ) { return _FutureImageStreamCompleter( codec: _loadAsync(key, decode), futureScale: _futureScale, ); } Future _loadAsync( _FutureMemoryImage key, DecoderBufferCallback decode, // ignore: deprecated_member_use ) { assert(key == this); return _futureBytes.then(ui.ImmutableBuffer.fromUint8List).then(decode); } /// See [ImageProvider.operator==]. @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) { return false; } return other is _FutureMemoryImage && _futureBytes == other._futureBytes && _futureScale == other._futureScale; } /// See [ImageProvider.hashCode]. @override int get hashCode => Object.hash(_futureBytes.hashCode, _futureScale); /// See [ImageProvider.toString]. @override String toString() => '${objectRuntimeType(this, '_FutureMemoryImage')}' '(${describeIdentity(_futureBytes)}, scale: $_futureScale)'; } // ignore: avoid_classes_with_only_static_members /// Class to help loading of iOS platform images into Flutter. /// /// For example, loading an image that is in `Assets.xcassts`. class IosPlatformImages { static const MethodChannel _channel = MethodChannel('plugins.flutter.io/ios_platform_images'); /// Loads an image from asset catalogs. The equivalent would be: /// `[UIImage imageNamed:name]`. /// /// Throws an exception if the image can't be found. /// /// See [https://developer.apple.com/documentation/uikit/uiimage/1624146-imagenamed?language=objc] static ImageProvider load(String name) { final Future?> loadInfo = _channel.invokeMapMethod('loadImage', name); final Completer bytesCompleter = Completer(); final Completer scaleCompleter = Completer(); loadInfo.then((Map? map) { if (map == null) { scaleCompleter.completeError( Exception("Image couldn't be found: $name"), ); bytesCompleter.completeError( Exception("Image couldn't be found: $name"), ); return; } scaleCompleter.complete(map['scale']! as double); bytesCompleter.complete(map['data']! as Uint8List); }); return _FutureMemoryImage(bytesCompleter.future, scaleCompleter.future); } /// Resolves an URL for a resource. The equivalent would be: /// `[[NSBundle mainBundle] URLForResource:name withExtension:ext]`. /// /// Returns null if the resource can't be found. /// /// See [https://developer.apple.com/documentation/foundation/nsbundle/1411540-urlforresource?language=objc] static Future resolveURL(String name, {String? extension}) { return _channel .invokeMethod('resolveURL', [name, extension]); } } ================================================ FILE: packages/ios_platform_images/pubspec.yaml ================================================ 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.2 environment: sdk: '>=2.18.0 <3.0.0' flutter: ">=3.3.0" flutter: plugin: platforms: ios: pluginClass: IosPlatformImagesPlugin dependencies: flutter: sdk: flutter dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/ios_platform_images/test/ios_platform_images_test.dart ================================================ // Copyright 2013 The Flutter 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:ios_platform_images/ios_platform_images.dart'; void main() { const MethodChannel channel = MethodChannel('plugins.flutter.io/ios_platform_images'); TestWidgetsFlutterBinding.ensureInitialized(); setUp(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, (MethodCall methodCall) async { return '42'; }); }); tearDown(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, null); }); test('resolveURL', () async { expect(await IosPlatformImages.resolveURL('foobar'), '42'); }); } /// 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; ================================================ FILE: packages/local_auth/local_auth/AUTHORS ================================================ # 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> Bodhi Mulders ================================================ FILE: packages/local_auth/local_auth/CHANGELOG.md ================================================ ## 2.1.4 * Updates minimum Flutter version to 3.0. * Updates documentation for Android version 8 and below theme compatibility. ## 2.1.3 * Updates minimum Flutter version to 2.10. * Removes unused `intl` dependency. ## 2.1.2 * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 2.1.1 * Replaces `USE_FINGERPRINT` permission with `USE_BIOMETRIC` in README and example project. ## 2.1.0 * Adds Windows support. ## 2.0.2 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 2.0.1 * Restores the ability to import `error_codes.dart`. * Updates README to match API changes in 2.0, and to improve clarity in general. * Removes unnecessary imports. ## 2.0.0 * Migrates plugin to federated architecture. * Adds OS version support information to README. * BREAKING CHANGE: Deprecated method `authenticateWithBiometrics` has been removed. Use `authenticate` instead. * BREAKING CHANGE: Enum `BiometricType` has been expanded with options for `strong` and `weak`, and applications should be updated to handle these accordingly. * BREAKING CHANGE: Parameters of `authenticate` have been changed. Example: ```dart // Old way of calling `authenticate`. Future authenticate( localizedReason: 'localized reason', useErrorDialogs: true, stickyAuth: false, androidAuthStrings: const AndroidAuthMessages(), iOSAuthStrings: const IOSAuthMessages(), sensitiveTransaction: true, biometricOnly: false, ); // New way of calling `authenticate`. Future authenticate( localizedReason: 'localized reason', authMessages: const [ IOSAuthMessages(), AndroidAuthMessages() ], options: const AuthenticationOptions( useErrorDialogs: true, stickyAuth: false, sensitiveTransaction: true, biometricOnly: false, ), ); ``` ## 1.1.11 * Adds support `localizedFallbackTitle` in authenticateWithBiometrics on iOS. ## 1.1.10 * Removes dependency on `meta`. ## 1.1.9 * Updates code for analysis option changes. * Updates Android compileSdkVersion to 31. ## 1.1.8 * Update minimum Flutter SDK to 2.5 and iOS deployment target to 9.0. * Updated Android lint settings. ## 1.1.7 * Remove references to the Android V1 embedding. ## 1.1.6 * Migrate maven repository from jcenter to mavenCentral. ## 1.1.5 * Updated grammatical errors and inaccurate information in README. ## 1.1.4 * Add debug assertion that `localizedReason` in `LocalAuthentication.authenticateWithBiometrics` must not be empty. ## 1.1.3 * Fix crashes due to threading issues in iOS implementation. ## 1.1.2 * Update Jetpack dependencies to latest stable versions. ## 1.1.1 * Update flutter_plugin_android_lifecycle dependency to 2.0.1 to fix an R8 issue on some versions. ## 1.1.0 * Migrate to null safety. * Allow pin, passcode, and pattern authentication with `authenticate` method. * Fix incorrect error handling switch case fallthrough. * Update README for Android Integration. * Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets. * Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)). * **Breaking change**. Parameter names refactored to use the generic `biometric` prefix in place of `fingerprint` in the `AndroidAuthMessages` class * `fingerprintHint` is now `biometricHint` * `fingerprintNotRecognized`is now `biometricNotRecognized` * `fingerprintSuccess`is now `biometricSuccess` * `fingerprintRequiredTitle` is now `biometricRequiredTitle` ## 0.6.3+5 * Update Flutter SDK constraint. ## 0.6.3+4 * Update Dart SDK constraint in example. ## 0.6.3+3 * Update android compileSdkVersion to 29. ## 0.6.3+2 * Keep handling deprecated Android v1 classes for backward compatibility. ## 0.6.3+1 * Update package:e2e -> package:integration_test ## 0.6.3 * Increase upper range of `package:platform` constraint to allow 3.X versions. ## 0.6.2+4 * Update package:e2e reference to use the local version in the flutter/plugins repository. ## 0.6.2+3 * Post-v2 Android embedding cleanup. ## 0.6.2+2 * Update lower bound of dart dependency to 2.1.0. ## 0.6.2+1 * Fix CocoaPods podspec lint warnings. ## 0.6.2 * Remove Android dependencies fallback. * Require Flutter SDK 1.12.13+hotfix.5 or greater. * Fix block implicitly retains 'self' warning. ## 0.6.1+4 * Replace deprecated `getFlutterEngine` call on Android. ## 0.6.1+3 * Make the pedantic dev_dependency explicit. ## 0.6.1+2 * Support v2 embedding. ## 0.6.1+1 * Remove the deprecated `author:` field from pubspec.yaml * Migrate the plugin to the pubspec platforms manifest. * Require Flutter SDK 1.10.0 or greater. ## 0.6.1 * Added ability to stop authentication (For Android). ## 0.6.0+3 * Remove AndroidX warnings. ## 0.6.0+2 * Update and migrate iOS example project. * Define clang module for iOS. ## 0.6.0+1 * Update the `intl` constraint to ">=0.15.1 <0.17.0" (0.16.0 isn't really a breaking change). ## 0.6.0 * Define a new parameter for signaling that the transaction is sensitive. * Up the biometric version to beta01. * Handle no device credential error. ## 0.5.3 * Add face id detection as well by not relying on FingerprintCompat. ## 0.5.2+4 * Update README to fix syntax error. ## 0.5.2+3 * Update documentation to clarify the need for FragmentActivity. ## 0.5.2+2 * Add missing template type parameter to `invokeMethod` calls. * Bump minimum Flutter version to 1.5.0. * Replace invokeMethod with invokeMapMethod wherever necessary. ## 0.5.2+1 * Use post instead of postDelayed to show the dialog onResume. ## 0.5.2 * Executor thread needs to be UI thread. ## 0.5.1 * Fix crash on Android versions earlier than 28. * [`authenticateWithBiometrics`](https://pub.dev/documentation/local_auth/latest/local_auth/LocalAuthentication/authenticateWithBiometrics.html) will not return result unless Biometric Dialog is closed. * Added two more error codes `LockedOut` and `PermanentlyLockedOut`. ## 0.5.0 * **Breaking change**. Update the Android API to use androidx Biometric package. This gives the prompt the updated Material look. However, it also requires the activity to be a FragmentActivity. Users can switch to FlutterFragmentActivity in their main app to migrate. ## 0.4.0+1 * Log a more detailed warning at build time about the previous AndroidX migration. ## 0.4.0 * **Breaking change**. Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to [also migrate](https://developer.android.com/jetpack/androidx/migrate) if they're using the original support library. ## 0.3.1 * Fix crash on Android versions earlier than 24. ## 0.3.0 * **Breaking change**. Add canCheckBiometrics and getAvailableBiometrics which leads to a new API. ## 0.2.1 * Updated Gradle tooling to match Android Studio 3.1.2. ## 0.2.0 * **Breaking change**. Set SDK constraints to match the Flutter beta release. ## 0.1.2 * Fixed Dart 2 type error. ## 0.1.1 * Simplified and upgraded Android project template to Android SDK 27. * Updated package description. ## 0.1.0 * **Breaking change**. Upgraded to Gradle 4.1 and Android Studio Gradle plugin 3.0.1. Older Flutter projects need to upgrade their Gradle setup as well in order to use this version of the plugin. Instructions can be found [here](https://github.com/flutter/flutter/wiki/Updating-Flutter-projects-to-Gradle-4.1-and-Android-Studio-Gradle-plugin-3.0.1). ## 0.0.3 * Add FLT prefix to iOS types ## 0.0.2+1 * Update messaging to support Face ID. ## 0.0.2 * Support stickyAuth mode. ## 0.0.1 * Initial release of local authentication plugin. ================================================ FILE: packages/local_auth/local_auth/LICENSE ================================================ 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. ================================================ FILE: packages/local_auth/local_auth/README.md ================================================ # local_auth This Flutter plugin provides means to perform local, on-device authentication of the user. On supported devices, this includes authentication with biometrics such as fingerprint or facial recognition. | | Android | iOS | Windows | |-------------|-----------|------|-------------| | **Support** | SDK 16+\* | 9.0+ | Windows 10+ | ## Usage ### Device Capabilities To check whether there is local authentication available on this device or not, call `canCheckBiometrics` (if you need biometrics support) and/or `isDeviceSupported()` (if you just need some device-level authentication): ```dart import 'package:local_auth/local_auth.dart'; // ··· final LocalAuthentication auth = LocalAuthentication(); // ··· final bool canAuthenticateWithBiometrics = await auth.canCheckBiometrics; final bool canAuthenticate = canAuthenticateWithBiometrics || await auth.isDeviceSupported(); ``` Currently the following biometric types are implemented: - BiometricType.face - BiometricType.fingerprint - BiometricType.weak - BiometricType.strong ### Enrolled Biometrics `canCheckBiometrics` only indicates whether hardware support is available, not whether the device has any biometrics enrolled. To get a list of enrolled biometrics, call `getAvailableBiometrics()`. The types are device-specific and platform-specific, and other types may be added in the future, so when possible you should not rely on specific biometric types and only check that some biometric is enrolled: ```dart final List availableBiometrics = await auth.getAvailableBiometrics(); if (availableBiometrics.isNotEmpty) { // Some biometrics are enrolled. } if (availableBiometrics.contains(BiometricType.strong) || availableBiometrics.contains(BiometricType.face)) { // Specific types of biometrics are available. // Use checks like this with caution! } ``` ### Options The `authenticate()` method uses biometric authentication when possible, but also allows fallback to pin, pattern, or passcode. ```dart try { final bool didAuthenticate = await auth.authenticate( localizedReason: 'Please authenticate to show account balance'); // ··· } on PlatformException { // ... } ``` To require biometric authentication, pass `AuthenticationOptions` with `biometricOnly` set to `true`. ```dart final bool didAuthenticate = await auth.authenticate( localizedReason: 'Please authenticate to show account balance', options: const AuthenticationOptions(biometricOnly: true)); ``` *Note*: `biometricOnly` is not supported on Windows since the Windows implementation's underlying API (Windows Hello) doesn't support selecting the authentication method. #### Dialogs The plugin provides default dialogs for the following cases: 1. Passcode/PIN/Pattern Not Set: The user has not yet configured a passcode on iOS or PIN/pattern on Android. 2. Biometrics Not Enrolled: The user has not enrolled any biometrics on the device. If a user does not have the necessary authentication enrolled when `authenticate` is called, they will be given the option to enroll at that point, or cancel authentication. If you don't want to use the default dialogs, set the `useErrorDialogs` option to `false` to have `authenticate` immediately return an error in those cases. ```dart import 'package:local_auth/error_codes.dart' as auth_error; // ··· try { final bool didAuthenticate = await auth.authenticate( localizedReason: 'Please authenticate to show account balance', options: const AuthenticationOptions(useErrorDialogs: false)); // ··· } on PlatformException catch (e) { if (e.code == auth_error.notAvailable) { // Add handling of no hardware here. } else if (e.code == auth_error.notEnrolled) { // ... } else { // ... } } ``` If you want to customize the messages in the dialogs, you can pass `AuthMessages` for each platform you support. These are platform-specific, so you will need to import the platform-specific implementation packages. For instance, to customize Android and iOS: ```dart import 'package:local_auth_android/local_auth_android.dart'; import 'package:local_auth_ios/local_auth_ios.dart'; // ··· final bool didAuthenticate = await auth.authenticate( localizedReason: 'Please authenticate to show account balance', authMessages: const [ AndroidAuthMessages( signInTitle: 'Oops! Biometric authentication required!', cancelButton: 'No thanks', ), IOSAuthMessages( cancelButton: 'No thanks', ), ]); ``` See the platform-specific classes for details about what can be customized on each platform. ### Exceptions `authenticate` throws `PlatformException`s in many error cases. See `error_codes.dart` for known error codes that you may want to have specific handling for. For example: ```dart import 'package:flutter/services.dart'; import 'package:local_auth/error_codes.dart' as auth_error; import 'package:local_auth/local_auth.dart'; // ··· final LocalAuthentication auth = LocalAuthentication(); // ··· try { final bool didAuthenticate = await auth.authenticate( localizedReason: 'Please authenticate to show account balance', options: const AuthenticationOptions(useErrorDialogs: false)); // ··· } on PlatformException catch (e) { if (e.code == auth_error.notEnrolled) { // Add handling of no hardware here. } else if (e.code == auth_error.lockedOut || e.code == auth_error.permanentlyLockedOut) { // ... } else { // ... } } ``` ## iOS Integration Note that this plugin works with both Touch ID and Face ID. However, to use the latter, you need to also add: ```xml NSFaceIDUsageDescription Why is my app authenticating using face id? ``` to your Info.plist file. Failure to do so results in a dialog that tells the user your app has not been updated to use Face ID. ## Android Integration \* The plugin will build and run on SDK 16+, but `isDeviceSupported()` will always return false before SDK 23 (Android 6.0). ### Activity Changes Note that `local_auth` requires the use of a `FragmentActivity` instead of an `Activity`. To update your application: * If you are using `FlutterActivity` directly, change it to `FlutterFragmentActivity` in your `AndroidManifest.xml`. * If you are using a custom activity, update your `MainActivity.java`: ```java import io.flutter.embedding.android.FlutterFragmentActivity; public class MainActivity extends FlutterFragmentActivity { // ... } ``` or MainActivity.kt: ```kotlin import io.flutter.embedding.android.FlutterFragmentActivity class MainActivity: FlutterFragmentActivity() { // ... } ``` to inherit from `FlutterFragmentActivity`. ### Permissions Update your project's `AndroidManifest.xml` file to include the `USE_BIOMETRIC` permissions: ```xml ``` ### 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 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. #### Android theme 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`. Then change the parent for that style as follows: ```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 ... ... ``` ## Sticky Auth You can set the `stickyAuth` option on the plugin to true so that plugin does not return failure if the app is put to background by the system. This might happen if the user receives a phone call before they get a chance to authenticate. With `stickyAuth` set to false, this would result in plugin returning failure result to the Dart app. If set to true, the plugin will retry authenticating when the app resumes. ================================================ FILE: packages/local_auth/local_auth/example/README.md ================================================ # local_auth_example Demonstrates how to use the local_auth plugin. ================================================ FILE: packages/local_auth/local_auth/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 33 lintOptions { disable 'InvalidPackage' } defaultConfig { applicationId "io.flutter.plugins.localauthexample" minSdkVersion 16 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } ================================================ FILE: packages/local_auth/local_auth/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/local_auth/local_auth/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/local_auth/local_auth/example/android/app/src/androidTest/java/io/flutter/plugins/localauth/FlutterFragmentActivityTest.java ================================================ // Copyright 2013 The Flutter 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.localauth; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterFragmentActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class FlutterFragmentActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterFragmentActivity.class); } ================================================ FILE: packages/local_auth/local_auth/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/local_auth/local_auth/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.3.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/local_auth/local_auth/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Sun Jan 03 14:07:08 CST 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip ================================================ FILE: packages/local_auth/local_auth/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true android.enableR8=true ================================================ FILE: packages/local_auth/local_auth/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withInputStream { stream -> plugins.load(stream) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/local_auth/local_auth/example/android/settings_aar.gradle ================================================ include ':app' ================================================ FILE: packages/local_auth/local_auth/example/build.excerpt.yaml ================================================ targets: $default: sources: include: - lib/** # Some default includes that aren't really used here but will prevent # false-negative warnings: - $package$ - lib/$lib$ exclude: - '**/.*/**' - '**/build/**' builders: code_excerpter|code_excerpter: enabled: true ================================================ FILE: packages/local_auth/local_auth/example/integration_test/local_auth_test.dart ================================================ // Copyright 2013 The Flutter 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:local_auth/local_auth.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('canCheckBiometrics', (WidgetTester tester) async { expect( LocalAuthentication().getAvailableBiometrics(), completion(isList), ); }); } ================================================ FILE: packages/local_auth/local_auth/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 UIRequiredDeviceCapabilities arm64 MinimumOSVersion 9.0 ================================================ FILE: packages/local_auth/local_auth/example/ios/Flutter/Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/local_auth/local_auth/example/ios/Flutter/Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/local_auth/local_auth/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/local_auth/local_auth/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/local_auth/local_auth/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end ================================================ FILE: packages/local_auth/local_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/local_auth/local_auth/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/local_auth/local_auth/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/local_auth/local_auth/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName local_auth_example CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance NSFaceIDUsageDescription App needs to authenticate using faces. ================================================ FILE: packages/local_auth/local_auth/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/local_auth/local_auth/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 0CCCD07A2CE24E13C9C1EEA4 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D274A3F79473B1549B2BBD5 /* libPods-Runner.a */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3398D2DC261649CD005A052F /* liblocal_auth.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = liblocal_auth.a; sourceTree = BUILT_PRODUCTS_DIR; }; 3398D2DF26164A03005A052F /* liblocal_auth.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = liblocal_auth.a; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 658CDD04B21E4EA92F8EF229 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 719FE2C7EAF8D9A045E09C29 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 99302E79EC77497F2F274D12 /* 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 = ""; }; 9D274A3F79473B1549B2BBD5 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; EB36DF6C3F25E00DF4175422 /* 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 = ""; }; FEA527BB0A821430FEAA1566 /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 0CCCD07A2CE24E13C9C1EEA4 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, F8CC53B854B121315C7319D2 /* Pods */, E2D5FA899A019BD3E0DB0917 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; E2D5FA899A019BD3E0DB0917 /* Frameworks */ = { isa = PBXGroup; children = ( 3398D2DF26164A03005A052F /* liblocal_auth.a */, 3398D2DC261649CD005A052F /* liblocal_auth.a */, 9D274A3F79473B1549B2BBD5 /* libPods-Runner.a */, 719FE2C7EAF8D9A045E09C29 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; F8CC53B854B121315C7319D2 /* Pods */ = { isa = PBXGroup; children = ( EB36DF6C3F25E00DF4175422 /* Pods-Runner.debug.xcconfig */, 658CDD04B21E4EA92F8EF229 /* Pods-Runner.release.xcconfig */, 99302E79EC77497F2F274D12 /* Pods-RunnerTests.debug.xcconfig */, FEA527BB0A821430FEAA1566 /* Pods-RunnerTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 98D96A2D1A74AF66E3DD2DBC /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; 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"; }; 98D96A2D1A74AF66E3DD2DBC /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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 = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.localAuthExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.localAuthExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/local_auth/local_auth/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/local_auth/local_auth/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/local_auth/local_auth/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/local_auth/local_auth/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/local_auth/local_auth/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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, avoid_print import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:local_auth/local_auth.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { final LocalAuthentication auth = LocalAuthentication(); _SupportState _supportState = _SupportState.unknown; bool? _canCheckBiometrics; List? _availableBiometrics; String _authorized = 'Not Authorized'; bool _isAuthenticating = false; @override void initState() { super.initState(); auth.isDeviceSupported().then( (bool isSupported) => setState(() => _supportState = isSupported ? _SupportState.supported : _SupportState.unsupported), ); } Future _checkBiometrics() async { late bool canCheckBiometrics; try { canCheckBiometrics = await auth.canCheckBiometrics; } on PlatformException catch (e) { canCheckBiometrics = false; print(e); } if (!mounted) { return; } setState(() { _canCheckBiometrics = canCheckBiometrics; }); } Future _getAvailableBiometrics() async { late List availableBiometrics; try { availableBiometrics = await auth.getAvailableBiometrics(); } on PlatformException catch (e) { availableBiometrics = []; print(e); } if (!mounted) { return; } setState(() { _availableBiometrics = availableBiometrics; }); } Future _authenticate() async { bool authenticated = false; try { setState(() { _isAuthenticating = true; _authorized = 'Authenticating'; }); authenticated = await auth.authenticate( localizedReason: 'Let OS determine authentication method', options: const AuthenticationOptions( stickyAuth: true, ), ); setState(() { _isAuthenticating = false; }); } on PlatformException catch (e) { print(e); setState(() { _isAuthenticating = false; _authorized = 'Error - ${e.message}'; }); return; } if (!mounted) { return; } setState( () => _authorized = authenticated ? 'Authorized' : 'Not Authorized'); } Future _authenticateWithBiometrics() async { bool authenticated = false; try { setState(() { _isAuthenticating = true; _authorized = 'Authenticating'; }); authenticated = await auth.authenticate( localizedReason: 'Scan your fingerprint (or face or whatever) to authenticate', 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 auth.stopAuthentication(); setState(() => _isAuthenticating = false); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Plugin example app'), ), body: ListView( padding: const EdgeInsets.only(top: 30), children: [ Column( mainAxisAlignment: MainAxisAlignment.center, children: [ if (_supportState == _SupportState.unknown) const CircularProgressIndicator() else if (_supportState == _SupportState.supported) const Text('This device is supported') else const Text('This device is not supported'), const Divider(height: 100), Text('Can check biometrics: $_canCheckBiometrics\n'), ElevatedButton( onPressed: _checkBiometrics, child: const Text('Check biometrics'), ), const Divider(height: 100), Text('Available biometrics: $_availableBiometrics\n'), ElevatedButton( onPressed: _getAvailableBiometrics, child: const Text('Get available biometrics'), ), const Divider(height: 100), Text('Current State: $_authorized\n'), 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 [ Text('Cancel Authentication'), Icon(Icons.cancel), ], ), ) else Column( 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 [ Text('Authenticate'), Icon(Icons.perm_device_information), ], ), ), ElevatedButton( onPressed: _authenticateWithBiometrics, child: Row( mainAxisSize: MainAxisSize.min, children: [ Text(_isAuthenticating ? 'Cancel' : 'Authenticate: biometrics only'), const Icon(Icons.fingerprint), ], ), ), ], ), ], ), ], ), ), ); } } enum _SupportState { unknown, supported, unsupported, } ================================================ FILE: packages/local_auth/local_auth/example/lib/readme_excerpts.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This file exists solely to host compiled excerpts for README.md, and is not // intended for use as an actual example application. // ignore_for_file: public_member_api_docs, avoid_print import 'package:flutter/material.dart'; // #docregion ErrorHandling import 'package:flutter/services.dart'; // #docregion NoErrorDialogs import 'package:local_auth/error_codes.dart' as auth_error; // #enddocregion NoErrorDialogs // #docregion CanCheck import 'package:local_auth/local_auth.dart'; // #enddocregion CanCheck // #enddocregion ErrorHandling // #docregion CustomMessages import 'package:local_auth_android/local_auth_android.dart'; import 'package:local_auth_ios/local_auth_ios.dart'; // #enddocregion CustomMessages void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { // #docregion CanCheck // #docregion ErrorHandling final LocalAuthentication auth = LocalAuthentication(); // #enddocregion CanCheck // #enddocregion ErrorHandling @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('README example app'), ), body: const Text('See example in main.dart'), ), ); } Future checkSupport() async { // #docregion CanCheck final bool canAuthenticateWithBiometrics = await auth.canCheckBiometrics; final bool canAuthenticate = canAuthenticateWithBiometrics || await auth.isDeviceSupported(); // #enddocregion CanCheck print('Can authenticate: $canAuthenticate'); print('Can authenticate with biometrics: $canAuthenticateWithBiometrics'); } Future getEnrolledBiometrics() async { // #docregion Enrolled final List availableBiometrics = await auth.getAvailableBiometrics(); if (availableBiometrics.isNotEmpty) { // Some biometrics are enrolled. } if (availableBiometrics.contains(BiometricType.strong) || availableBiometrics.contains(BiometricType.face)) { // Specific types of biometrics are available. // Use checks like this with caution! } // #enddocregion Enrolled } Future authenticate() async { // #docregion AuthAny try { final bool didAuthenticate = await auth.authenticate( localizedReason: 'Please authenticate to show account balance'); // #enddocregion AuthAny print(didAuthenticate); // #docregion AuthAny } on PlatformException { // ... } // #enddocregion AuthAny } Future authenticateWithBiometrics() async { // #docregion AuthBioOnly final bool didAuthenticate = await auth.authenticate( localizedReason: 'Please authenticate to show account balance', options: const AuthenticationOptions(biometricOnly: true)); // #enddocregion AuthBioOnly print(didAuthenticate); } Future authenticateWithoutDialogs() async { // #docregion NoErrorDialogs try { final bool didAuthenticate = await auth.authenticate( localizedReason: 'Please authenticate to show account balance', options: const AuthenticationOptions(useErrorDialogs: false)); // #enddocregion NoErrorDialogs print(didAuthenticate ? 'Success!' : 'Failure'); // #docregion NoErrorDialogs } on PlatformException catch (e) { if (e.code == auth_error.notAvailable) { // Add handling of no hardware here. } else if (e.code == auth_error.notEnrolled) { // ... } else { // ... } } // #enddocregion NoErrorDialogs } Future authenticateWithErrorHandling() async { // #docregion ErrorHandling try { final bool didAuthenticate = await auth.authenticate( localizedReason: 'Please authenticate to show account balance', options: const AuthenticationOptions(useErrorDialogs: false)); // #enddocregion ErrorHandling print(didAuthenticate ? 'Success!' : 'Failure'); // #docregion ErrorHandling } on PlatformException catch (e) { if (e.code == auth_error.notEnrolled) { // Add handling of no hardware here. } else if (e.code == auth_error.lockedOut || e.code == auth_error.permanentlyLockedOut) { // ... } else { // ... } } // #enddocregion ErrorHandling } Future authenticateWithCustomDialogMessages() async { // #docregion CustomMessages final bool didAuthenticate = await auth.authenticate( localizedReason: 'Please authenticate to show account balance', authMessages: const [ AndroidAuthMessages( signInTitle: 'Oops! Biometric authentication required!', cancelButton: 'No thanks', ), IOSAuthMessages( cancelButton: 'No thanks', ), ]); // #enddocregion CustomMessages print(didAuthenticate ? 'Success!' : 'Failure'); } } ================================================ FILE: packages/local_auth/local_auth/example/pubspec.yaml ================================================ name: local_auth_example description: Demonstrates how to use the local_auth plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter local_auth: # When depending on this package from a real application you should use: # local_auth: ^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: ../ local_auth_android: ^1.0.0 local_auth_ios: ^1.0.1 dev_dependencies: build_runner: ^2.1.10 flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/local_auth/local_auth/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/local_auth/local_auth/example/windows/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) project(example LANGUAGES CXX) set(BINARY_NAME "example") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" CACHE STRING "" FORCE) else() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() endif() set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") # Use Unicode for all projects. add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") target_compile_options(${TARGET} PRIVATE /EHsc) target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build add_subdirectory("runner") # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # Support files are copied into place next to the executable, so that it can # run in place. This is done instead of making a separate bundle (as on Linux) # so that building and running from within Visual Studio will work. set(BUILD_BUNDLE_DIR "$") # Make the "install" step default, as it's required to run. set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" CONFIGURATIONS Profile;Release COMPONENT Runtime) ================================================ FILE: packages/local_auth/local_auth/example/windows/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "flutter_export.h" "flutter_windows.h" "flutter_messenger.h" "flutter_plugin_registrar.h" "flutter_texture_registrar.h" ) list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") add_dependencies(flutter flutter_assemble) # === Wrapper === list(APPEND CPP_WRAPPER_SOURCES_CORE "core_implementations.cc" "standard_codec.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_PLUGIN "plugin_registrar.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_APP "flutter_engine.cc" "flutter_view_controller.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") # Wrapper sources needed for a plugin. add_library(flutter_wrapper_plugin STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ) apply_standard_settings(flutter_wrapper_plugin) set_target_properties(flutter_wrapper_plugin PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(flutter_wrapper_plugin PROPERTIES CXX_VISIBILITY_PRESET hidden) target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) target_include_directories(flutter_wrapper_plugin PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_plugin flutter_assemble) # Wrapper sources needed for the runner. add_library(flutter_wrapper_app STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_APP} ) apply_standard_settings(flutter_wrapper_app) target_link_libraries(flutter_wrapper_app PUBLIC flutter) target_include_directories(flutter_wrapper_app PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_app flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" windows-x64 $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ) ================================================ FILE: packages/local_auth/local_auth/example/windows/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST local_auth_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/local_auth/local_auth/example/windows/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) project(runner LANGUAGES CXX) add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) apply_standard_settings(${BINARY_NAME}) target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") add_dependencies(${BINARY_NAME} flutter_assemble) ================================================ FILE: packages/local_auth/local_auth/example/windows/runner/Runner.rc ================================================ // Microsoft Visual C++ generated resource script. // #pragma code_page(65001) #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APP_ICON ICON "resources\\app_icon.ico" ///////////////////////////////////////////////////////////////////////////// // // Version // #ifdef FLUTTER_BUILD_NUMBER #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER #else #define VERSION_AS_NUMBER 1,0,0 #endif #ifdef FLUTTER_BUILD_NAME #define VERSION_AS_STRING #FLUTTER_BUILD_NAME #else #define VERSION_AS_STRING "1.0.0" #endif VS_VERSION_INFO VERSIONINFO FILEVERSION VERSION_AS_NUMBER PRODUCTVERSION VERSION_AS_NUMBER FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "io.flutter.plugins" "\0" VALUE "FileDescription", "example" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "example" "\0" VALUE "LegalCopyright", "Copyright (C) 2022 io.flutter.plugins. All rights reserved." "\0" VALUE "OriginalFilename", "example.exe" "\0" VALUE "ProductName", "example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: packages/local_auth/local_auth/example/windows/runner/flutter_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "flutter_window.h" #include #include "flutter/generated_plugin_registrant.h" FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {} FlutterWindow::~FlutterWindow() {} bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opportunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) { return *result; } } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); } ================================================ FILE: packages/local_auth/local_auth/example/windows/runner/flutter_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_ #include #include #include #include "win32_window.h" // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { public: // Creates a new FlutterWindow hosting a Flutter view running |project|. explicit FlutterWindow(const flutter::DartProject& project); virtual ~FlutterWindow(); protected: // Win32Window: bool OnCreate() override; void OnDestroy() override; LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; private: // The project to run. flutter::DartProject project_; // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; }; #endif // RUNNER_FLUTTER_WINDOW_H_ ================================================ FILE: packages/local_auth/local_auth/example/windows/runner/main.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include "flutter_window.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t* command_line, _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { CreateAndAttachConsole(); } // Initialize COM, so that it is available for use in the library and/or // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); flutter::DartProject project(L"data"); std::vector command_line_arguments = GetCommandLineArguments(); project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.CreateAndShow(L"example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); ::MSG msg; while (::GetMessage(&msg, nullptr, 0, 0)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } ::CoUninitialize(); return EXIT_SUCCESS; } ================================================ FILE: packages/local_auth/local_auth/example/windows/runner/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Runner.rc // #define IDI_APP_ICON 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: packages/local_auth/local_auth/example/windows/runner/runner.exe.manifest ================================================ PerMonitorV2 ================================================ FILE: packages/local_auth/local_auth/example/windows/runner/utils.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "utils.h" #include #include #include #include #include void CreateAndAttachConsole() { if (::AllocConsole()) { FILE* unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } if (freopen_s(&unused, "CONOUT$", "w", stderr)) { _dup2(_fileno(stdout), 2); } std::ios::sync_with_stdio(); FlutterDesktopResyncOutputStreams(); } } std::vector GetCommandLineArguments() { // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. int argc; wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); if (argv == nullptr) { return std::vector(); } std::vector command_line_arguments; // Skip the first argument as it's the binary name. for (int i = 1; i < argc; i++) { command_line_arguments.push_back(Utf8FromUtf16(argv[i])); } ::LocalFree(argv); return command_line_arguments; } std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); } int target_length = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, nullptr, 0, nullptr, nullptr); if (target_length == 0) { return std::string(); } std::string utf8_string; utf8_string.resize(target_length); int converted_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); } return utf8_string; } ================================================ FILE: packages/local_auth/local_auth/example/windows/runner/utils.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_UTILS_H_ #define RUNNER_UTILS_H_ #include #include // Creates a console for the process, and redirects stdout and stderr to // it for both the runner and the Flutter library. void CreateAndAttachConsole(); // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string // encoded in UTF-8. Returns an empty std::string on failure. std::string Utf8FromUtf16(const wchar_t* utf16_string); // Gets the command line arguments passed in as a std::vector, // encoded in UTF-8. Returns an empty std::vector on failure. std::vector GetCommandLineArguments(); #endif // RUNNER_UTILS_H_ ================================================ FILE: packages/local_auth/local_auth/example/windows/runner/win32_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "win32_window.h" #include #include "resource.h" namespace { constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); // Scale helper to convert logical scaler values to physical using passed in // scale factor int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); if (!user32_module) { return; } auto enable_non_client_dpi_scaling = reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); FreeLibrary(user32_module); } } } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; // Returns the singleton registar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; } // Returns the name of the window class, registering the class if it hasn't // previously been registered. const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; bool class_registered_ = false; }; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; RegisterClass(&window_class); class_registered_ = true; } return kWindowClassName; } void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, const Size& size) { Destroy(); const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), static_cast(origin.y)}; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } return OnCreate(); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { case WM_DESTROY: window_handle_ = nullptr; Destroy(); if (quit_on_close_) { PostQuitMessage(0); } return 0; case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left; LONG newHeight = newRectSize->bottom - newRectSize->top; SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_SIZE: { RECT rect = GetClientArea(); if (child_content_ != nullptr) { // Size and position the child window. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); } return 0; } case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_); } return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } void Win32Window::Destroy() { OnDestroy(); if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, true); SetFocus(child_content_); } RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } void Win32Window::OnDestroy() { // No-op; provided for subclasses. } ================================================ FILE: packages/local_auth/local_auth/example/windows/runner/win32_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_ #include #include #include #include // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { public: struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) : width(width), height(height) {} }; Win32Window(); virtual ~Win32Window(); // Creates and shows a win32 window with |title| and position and size using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a // consistent size to will treat the width height passed in to this function // as logical pixels and scale to appropriate for the default monitor. Returns // true if the window was created successfully. bool CreateAndShow(const std::wstring& title, const Point& origin, const Size& size); // Release OS resources associated with window. void Destroy(); // Inserts |content| into the window tree. void SetChildContent(HWND content); // Returns the backing Window handle to enable clients to set icon and other // window properties. Returns nullptr if the window has been destroyed. HWND GetHandle(); // If true, closing this window will quit the application. void SetQuitOnClose(bool quit_on_close); // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Called when CreateAndShow is called, allowing subclass window-related // setup. Subclasses should return false if setup fails. virtual bool OnCreate(); // Called when Destroy is called. virtual void OnDestroy(); private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically // responsponds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; bool quit_on_close_ = false; // window handle for top level window. HWND window_handle_ = nullptr; // window handle for hosted content. HWND child_content_ = nullptr; }; #endif // RUNNER_WIN32_WINDOW_H_ ================================================ FILE: packages/local_auth/local_auth/lib/error_codes.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Exception codes for `PlatformException` returned by // `authenticate`. /// Indicates that the user has not yet configured a passcode (iOS) or /// PIN/pattern/password (Android) on the device. const String passcodeNotSet = 'PasscodeNotSet'; /// Indicates the user has not enrolled any biometrics on the device. const String notEnrolled = 'NotEnrolled'; /// Indicates the device does not have hardware support for biometrics. const String notAvailable = 'NotAvailable'; /// Indicates the device operating system is unsupported. const String otherOperatingSystem = 'OtherOperatingSystem'; /// Indicates the API is temporarily locked out due to too many attempts. const String lockedOut = 'LockedOut'; /// Indicates the API is locked out more persistently than [lockedOut]. /// Strong authentication like PIN/Pattern/Password is required to unlock. const String permanentlyLockedOut = 'PermanentlyLockedOut'; /// Indicates that the biometricOnly parameter can't be true on Windows const String biometricOnlyNotSupported = 'biometricOnlyNotSupported'; ================================================ FILE: packages/local_auth/local_auth/lib/local_auth.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'package:local_auth/src/local_auth.dart' show LocalAuthentication; export 'package:local_auth_platform_interface/types/auth_options.dart' show AuthenticationOptions; export 'package:local_auth_platform_interface/types/biometric_type.dart' show BiometricType; ================================================ FILE: packages/local_auth/local_auth/lib/src/local_auth.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This is a temporary ignore to allow us to land a new set of linter rules in a // series of manageable patches instead of one gigantic PR. It disables some of // the new lints that are already failing on this plugin, for this plugin. It // should be deleted and the failing lints addressed as soon as possible. // ignore_for_file: public_member_api_docs import 'dart:async'; import 'package:flutter/services.dart'; import 'package:local_auth_android/local_auth_android.dart'; import 'package:local_auth_ios/local_auth_ios.dart'; import 'package:local_auth_platform_interface/local_auth_platform_interface.dart'; import 'package:local_auth_windows/local_auth_windows.dart'; /// A Flutter plugin for authenticating the user identity locally. class LocalAuthentication { /// Authenticates the user with biometrics available on the device while also /// allowing the user to use device authentication - pin, pattern, passcode. /// /// Returns true if the user successfully authenticated, false otherwise. /// /// [localizedReason] is the message to show to user while prompting them /// for authentication. This is typically along the lines of: 'Authenticate /// to access MyApp.'. This must not be empty. /// /// Provide [authMessages] if you want to /// customize messages in the dialogs. /// /// Provide [options] for configuring further authentication related options. /// /// Throws a [PlatformException] if there were technical problems with local /// authentication (e.g. lack of relevant hardware). This might throw /// [PlatformException] with error code [otherOperatingSystem] on the iOS /// simulator. Future authenticate( {required String localizedReason, Iterable authMessages = const [ IOSAuthMessages(), AndroidAuthMessages(), WindowsAuthMessages() ], AuthenticationOptions options = const AuthenticationOptions()}) { return LocalAuthPlatform.instance.authenticate( localizedReason: localizedReason, authMessages: authMessages, options: options, ); } /// Cancels any in-progress authentication, returning true if auth was /// cancelled successfully. /// /// This API is not supported by all platforms. /// Returns false if there was some error, no authentication in progress, /// or the current platform lacks support. Future stopAuthentication() async { return LocalAuthPlatform.instance.stopAuthentication(); } /// Returns true if device is capable of checking biometrics. Future get canCheckBiometrics => LocalAuthPlatform.instance.deviceSupportsBiometrics(); /// Returns true if device is capable of checking biometrics or is able to /// fail over to device credentials. Future isDeviceSupported() async => LocalAuthPlatform.instance.isDeviceSupported(); /// Returns a list of enrolled biometrics. Future> getAvailableBiometrics() => LocalAuthPlatform.instance.getEnrolledBiometrics(); } ================================================ FILE: packages/local_auth/local_auth/pubspec.yaml ================================================ name: local_auth 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 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: platforms: android: default_package: local_auth_android ios: default_package: local_auth_ios windows: default_package: local_auth_windows dependencies: flutter: sdk: flutter local_auth_android: ^1.0.0 local_auth_ios: ^1.0.1 local_auth_platform_interface: ^1.0.1 local_auth_windows: ^1.0.0 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter mockito: ^5.1.0 plugin_platform_interface: ^2.1.2 ================================================ FILE: packages/local_auth/local_auth/test/local_auth_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:local_auth/local_auth.dart'; import 'package:local_auth_android/local_auth_android.dart'; import 'package:local_auth_ios/local_auth_ios.dart'; import 'package:local_auth_platform_interface/local_auth_platform_interface.dart'; import 'package:local_auth_windows/local_auth_windows.dart'; import 'package:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); late LocalAuthentication localAuthentication; late MockLocalAuthPlatform mockLocalAuthPlatform; setUp(() { localAuthentication = LocalAuthentication(); mockLocalAuthPlatform = MockLocalAuthPlatform(); LocalAuthPlatform.instance = mockLocalAuthPlatform; }); test('authenticate calls platform implementation', () { when(mockLocalAuthPlatform.authenticate( localizedReason: anyNamed('localizedReason'), authMessages: anyNamed('authMessages'), options: anyNamed('options'), )).thenAnswer((_) async => true); localAuthentication.authenticate(localizedReason: 'Test Reason'); verify(mockLocalAuthPlatform.authenticate( localizedReason: 'Test Reason', authMessages: [ const IOSAuthMessages(), const AndroidAuthMessages(), const WindowsAuthMessages(), ], )).called(1); }); test('isDeviceSupported calls platform implementation', () { when(mockLocalAuthPlatform.isDeviceSupported()) .thenAnswer((_) async => true); localAuthentication.isDeviceSupported(); verify(mockLocalAuthPlatform.isDeviceSupported()).called(1); }); test('getEnrolledBiometrics calls platform implementation', () { when(mockLocalAuthPlatform.getEnrolledBiometrics()) .thenAnswer((_) async => []); localAuthentication.getAvailableBiometrics(); verify(mockLocalAuthPlatform.getEnrolledBiometrics()).called(1); }); test('stopAuthentication calls platform implementation', () { when(mockLocalAuthPlatform.stopAuthentication()) .thenAnswer((_) async => true); localAuthentication.stopAuthentication(); verify(mockLocalAuthPlatform.stopAuthentication()).called(1); }); test('canCheckBiometrics returns correct result', () async { when(mockLocalAuthPlatform.deviceSupportsBiometrics()) .thenAnswer((_) async => false); bool? result; result = await localAuthentication.canCheckBiometrics; expect(result, false); when(mockLocalAuthPlatform.deviceSupportsBiometrics()) .thenAnswer((_) async => true); result = await localAuthentication.canCheckBiometrics; expect(result, true); verify(mockLocalAuthPlatform.deviceSupportsBiometrics()).called(2); }); } class MockLocalAuthPlatform extends Mock with MockPlatformInterfaceMixin implements LocalAuthPlatform { MockLocalAuthPlatform() { throwOnMissingStub(this); } @override Future authenticate({ required String? localizedReason, required Iterable? authMessages, AuthenticationOptions? options = const AuthenticationOptions(), }) => super.noSuchMethod( Invocation.method(#authenticate, [], { #localizedReason: localizedReason, #authMessages: authMessages, #options: options, }), returnValue: Future.value(false)) as Future; @override Future> getEnrolledBiometrics() => super.noSuchMethod(Invocation.method(#getEnrolledBiometrics, []), returnValue: Future>.value([])) as Future>; @override Future isDeviceSupported() => super.noSuchMethod(Invocation.method(#isDeviceSupported, []), returnValue: Future.value(false)) as Future; @override Future stopAuthentication() => super.noSuchMethod(Invocation.method(#stopAuthentication, []), returnValue: Future.value(false)) as Future; @override Future deviceSupportsBiometrics() => super.noSuchMethod( Invocation.method(#deviceSupportsBiometrics, []), returnValue: Future.value(false)) as Future; } ================================================ FILE: packages/local_auth/local_auth_android/AUTHORS ================================================ # 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> Bodhi Mulders ================================================ FILE: packages/local_auth/local_auth_android/CHANGELOG.md ================================================ ## 1.0.18 * Updates minimum Flutter version to 3.0. * Updates androidx.core version to 1.9.0. * Upgrades compile SDK version to 33. ## 1.0.17 * Adds compatibility with `intl` 0.18.0. ## 1.0.16 * Updates androidx.fragment version to 1.5.5. ## 1.0.15 * Updates androidx.fragment version to 1.5.4. ## 1.0.14 * Fixes device credential authentication for API versions before R. ## 1.0.13 * Updates imports for `prefer_relative_imports`. ## 1.0.12 * Updates androidx.fragment version to 1.5.2. * Updates minimum Flutter version to 2.10. ## 1.0.11 * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 1.0.10 * Updates `local_auth_platform_interface` constraint to the correct minimum version. ## 1.0.9 * Updates androidx.fragment version to 1.5.1. ## 1.0.8 * Removes usages of `FingerprintManager` and other `BiometricManager` deprecated method usages. ## 1.0.7 * Updates gradle version to 7.2.1. ## 1.0.6 * Updates androidx.core version to 1.8.0. ## 1.0.5 * Updates references to the obsolete master branch. ## 1.0.4 * Minor fixes for new analysis options. ## 1.0.3 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 1.0.2 * Fixes `getEnrolledBiometrics` to match documented behaviour: Present biometrics that are not enrolled are no longer returned. * `getEnrolledBiometrics` now only returns `weak` and `strong` biometric types. * `deviceSupportsBiometrics` now returns the correct value regardless of enrollment state. ## 1.0.1 * Adopts `Object.hash`. ## 1.0.0 * Initial release from migration to federated architecture. ================================================ FILE: packages/local_auth/local_auth_android/LICENSE ================================================ 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. ================================================ FILE: packages/local_auth/local_auth_android/README.md ================================================ # local\_auth\_android The Android implementation of [`local_auth`][1]. ## Usage This package is [endorsed][2], which means you can simply use `local_auth` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/local_auth [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/local_auth/local_auth_android/android/build.gradle ================================================ group 'io.flutter.plugins.localauth' version '1.0-SNAPSHOT' buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.3.1' } } rootProject.allprojects { repositories { google() mavenCentral() } } apply plugin: 'com.android.library' android { compileSdkVersion 33 defaultConfig { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' baseline file("lint-baseline.xml") } testOptions { unitTests.includeAndroidResources = true unitTests.returnDefaultValues = true unitTests.all { testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" outputs.upToDateWhen {false} showStandardStreams = true } } } } dependencies { api "androidx.core:core:1.9.0" 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: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.5.1' } ================================================ FILE: packages/local_auth/local_auth_android/android/lint-baseline.xml ================================================ ================================================ FILE: packages/local_auth/local_auth_android/android/settings.gradle ================================================ rootProject.name = 'local_auth' ================================================ FILE: packages/local_auth/local_auth_android/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java ================================================ // Copyright 2013 The Flutter 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.localauth; import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; import android.app.Application; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.biometric.BiometricManager; import androidx.biometric.BiometricPrompt; import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; import io.flutter.plugin.common.MethodCall; import java.util.concurrent.Executor; /** * Authenticates the user with biometrics and sends corresponding response back to Flutter. * *

One instance per call is generated to ensure readable separation of executable paths across * method calls. */ @SuppressWarnings("deprecation") class AuthenticationHelper extends BiometricPrompt.AuthenticationCallback implements Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver { /** The callback that handles the result of this authentication process. */ interface AuthCompletionHandler { /** Called when authentication was successful. */ void onSuccess(); /** * Called when authentication failed due to user. For instance, when user cancels the auth or * quits the app. */ void onFailure(); /** * Called when authentication fails due to non-user related problems such as system errors, * phone not having a FP reader etc. * * @param code The error code to be returned to Flutter app. * @param error The description of the error. */ void onError(String code, String error); } // This is null when not using v2 embedding; private final Lifecycle lifecycle; private final FragmentActivity activity; private final AuthCompletionHandler completionHandler; private final MethodCall call; private final BiometricPrompt.PromptInfo promptInfo; private final boolean isAuthSticky; private final UiThreadExecutor uiThreadExecutor; private boolean activityPaused = false; private BiometricPrompt biometricPrompt; AuthenticationHelper( Lifecycle lifecycle, FragmentActivity activity, MethodCall call, AuthCompletionHandler completionHandler, boolean allowCredentials) { this.lifecycle = lifecycle; this.activity = activity; this.completionHandler = completionHandler; this.call = call; this.isAuthSticky = call.argument("stickyAuth"); this.uiThreadExecutor = new UiThreadExecutor(); BiometricPrompt.PromptInfo.Builder promptBuilder = new BiometricPrompt.PromptInfo.Builder() .setDescription((String) call.argument("localizedReason")) .setTitle((String) call.argument("signInTitle")) .setSubtitle((String) call.argument("biometricHint")) .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction")) .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction")); int allowedAuthenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK | BiometricManager.Authenticators.BIOMETRIC_STRONG; if (allowCredentials) { allowedAuthenticators |= BiometricManager.Authenticators.DEVICE_CREDENTIAL; } else { promptBuilder.setNegativeButtonText((String) call.argument("cancelButton")); } promptBuilder.setAllowedAuthenticators(allowedAuthenticators); this.promptInfo = promptBuilder.build(); } /** Start the biometric listener. */ void authenticate() { if (lifecycle != null) { lifecycle.addObserver(this); } else { activity.getApplication().registerActivityLifecycleCallbacks(this); } biometricPrompt = new BiometricPrompt(activity, uiThreadExecutor, this); biometricPrompt.authenticate(promptInfo); } /** Cancels the biometric authentication. */ void stopAuthentication() { if (biometricPrompt != null) { biometricPrompt.cancelAuthentication(); biometricPrompt = null; } } /** Stops the biometric listener. */ private void stop() { if (lifecycle != null) { lifecycle.removeObserver(this); return; } activity.getApplication().unregisterActivityLifecycleCallbacks(this); } @SuppressLint("SwitchIntDef") @Override public void onAuthenticationError(int errorCode, CharSequence errString) { switch (errorCode) { case BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL: if (call.argument("useErrorDialogs")) { showGoToSettingsDialog( (String) call.argument("deviceCredentialsRequired"), (String) call.argument("deviceCredentialsSetupDescription")); return; } completionHandler.onError("NotAvailable", "Security credentials not available."); break; case BiometricPrompt.ERROR_NO_SPACE: case BiometricPrompt.ERROR_NO_BIOMETRICS: if (call.argument("useErrorDialogs")) { showGoToSettingsDialog( (String) call.argument("biometricRequired"), (String) call.argument("goToSettingDescription")); return; } completionHandler.onError("NotEnrolled", "No Biometrics enrolled on this device."); break; case BiometricPrompt.ERROR_HW_UNAVAILABLE: case BiometricPrompt.ERROR_HW_NOT_PRESENT: completionHandler.onError("NotAvailable", "Security credentials not available."); break; case BiometricPrompt.ERROR_LOCKOUT: completionHandler.onError( "LockedOut", "The operation was canceled because the API is locked out due to too many attempts. This occurs after 5 failed attempts, and lasts for 30 seconds."); break; case BiometricPrompt.ERROR_LOCKOUT_PERMANENT: completionHandler.onError( "PermanentlyLockedOut", "The operation was canceled because ERROR_LOCKOUT occurred too many times. Biometric authentication is disabled until the user unlocks with strong authentication (PIN/Pattern/Password)"); break; case BiometricPrompt.ERROR_CANCELED: // If we are doing sticky auth and the activity has been paused, // ignore this error. We will start listening again when resumed. if (activityPaused && isAuthSticky) { return; } else { completionHandler.onFailure(); } break; default: completionHandler.onFailure(); } stop(); } @Override public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) { completionHandler.onSuccess(); stop(); } @Override public void onAuthenticationFailed() {} /** * If the activity is paused, we keep track because biometric dialog simply returns "User * cancelled" when the activity is paused. */ @Override public void onActivityPaused(Activity ignored) { if (isAuthSticky) { activityPaused = true; } } @Override public void onActivityResumed(Activity ignored) { if (isAuthSticky) { activityPaused = false; final BiometricPrompt prompt = new BiometricPrompt(activity, uiThreadExecutor, this); // When activity is resuming, we cannot show the prompt right away. We need to post it to the // UI queue. uiThreadExecutor.handler.post( new Runnable() { @Override public void run() { prompt.authenticate(promptInfo); } }); } } @Override public void onPause(@NonNull LifecycleOwner owner) { onActivityPaused(null); } @Override public void onResume(@NonNull LifecycleOwner owner) { onActivityResumed(null); } // Suppress inflateParams lint because dialogs do not need to attach to a parent view. @SuppressLint("InflateParams") private void showGoToSettingsDialog(String title, String descriptionText) { View view = LayoutInflater.from(activity).inflate(R.layout.go_to_setting, null, false); TextView message = (TextView) view.findViewById(R.id.fingerprint_required); TextView description = (TextView) view.findViewById(R.id.go_to_setting_description); message.setText(title); description.setText(descriptionText); Context context = new ContextThemeWrapper(activity, R.style.AlertDialogCustom); OnClickListener goToSettingHandler = new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { completionHandler.onFailure(); stop(); activity.startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS)); } }; OnClickListener cancelHandler = new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { completionHandler.onFailure(); stop(); } }; new AlertDialog.Builder(context) .setView(view) .setPositiveButton((String) call.argument("goToSetting"), goToSettingHandler) .setNegativeButton((String) call.argument("cancelButton"), cancelHandler) .setCancelable(false) .show(); } // Unused methods for activity lifecycle. @Override public void onActivityCreated(Activity activity, Bundle bundle) {} @Override public void onActivityStarted(Activity activity) {} @Override public void onActivityStopped(Activity activity) {} @Override public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {} @Override public void onActivityDestroyed(Activity activity) {} @Override public void onDestroy(@NonNull LifecycleOwner owner) {} @Override public void onStop(@NonNull LifecycleOwner owner) {} @Override public void onStart(@NonNull LifecycleOwner owner) {} @Override public void onCreate(@NonNull LifecycleOwner owner) {} private static class UiThreadExecutor implements Executor { final Handler handler = new Handler(Looper.getMainLooper()); @Override public void execute(Runnable command) { handler.post(command); } } } ================================================ FILE: packages/local_auth/local_auth_android/android/src/main/java/io/flutter/plugins/localauth/LocalAuthPlugin.java ================================================ // Copyright 2013 The Flutter 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.localauth; import static android.app.Activity.RESULT_OK; import static android.content.Context.KEYGUARD_SERVICE; import android.app.Activity; import android.app.KeyguardManager; import android.content.Context; import android.content.Intent; import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.biometric.BiometricManager; import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.Lifecycle; 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.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry.Registrar; import io.flutter.plugins.localauth.AuthenticationHelper.AuthCompletionHandler; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; /** * Flutter plugin providing access to local authentication. * *

Instantiate this in an add to app scenario to gracefully handle activity and context changes. */ @SuppressWarnings("deprecation") public class LocalAuthPlugin implements MethodCallHandler, FlutterPlugin, ActivityAware { private static final String CHANNEL_NAME = "plugins.flutter.io/local_auth_android"; private static final int LOCK_REQUEST_CODE = 221; private Activity activity; private AuthenticationHelper authHelper; @VisibleForTesting final AtomicBoolean authInProgress = new AtomicBoolean(false); // These are null when not using v2 embedding. private MethodChannel channel; private Lifecycle lifecycle; private BiometricManager biometricManager; private KeyguardManager keyguardManager; private Result lockRequestResult; private final PluginRegistry.ActivityResultListener resultListener = new PluginRegistry.ActivityResultListener() { @Override public boolean onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == LOCK_REQUEST_CODE) { if (resultCode == RESULT_OK && lockRequestResult != null) { authenticateSuccess(lockRequestResult); } else { authenticateFail(lockRequestResult); } lockRequestResult = null; } return false; } }; /** * Registers a plugin with the v1 embedding api {@code io.flutter.plugin.common}. * *

Calling this will register the plugin with the passed registrar. However, plugins * initialized this way won't react to changes in activity or context. * * @param registrar attaches this plugin's {@link * io.flutter.plugin.common.MethodChannel.MethodCallHandler} to the registrar's {@link * io.flutter.plugin.common.BinaryMessenger}. */ @SuppressWarnings("deprecation") public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME); final LocalAuthPlugin plugin = new LocalAuthPlugin(); plugin.activity = registrar.activity(); channel.setMethodCallHandler(plugin); registrar.addActivityResultListener(plugin.resultListener); } /** * Default constructor for LocalAuthPlugin. * *

Use this constructor when adding this plugin to an app with v2 embedding. */ public LocalAuthPlugin() {} @Override public void onMethodCall(MethodCall call, @NonNull final Result result) { switch (call.method) { case "authenticate": authenticate(call, result); break; case "getEnrolledBiometrics": getEnrolledBiometrics(result); break; case "isDeviceSupported": isDeviceSupported(result); break; case "stopAuthentication": stopAuthentication(result); break; case "deviceSupportsBiometrics": deviceSupportsBiometrics(result); break; default: result.notImplemented(); break; } } /* * Starts authentication process */ private void authenticate(MethodCall call, final Result result) { if (authInProgress.get()) { result.error("auth_in_progress", "Authentication in progress", null); return; } if (activity == null || activity.isFinishing()) { result.error("no_activity", "local_auth plugin requires a foreground activity", null); return; } if (!(activity instanceof FragmentActivity)) { result.error( "no_fragment_activity", "local_auth plugin requires activity to be a FragmentActivity.", null); return; } if (!isDeviceSupported()) { authInProgress.set(false); result.error("NotAvailable", "Required security features not enabled", null); return; } authInProgress.set(true); AuthCompletionHandler completionHandler = createAuthCompletionHandler(result); boolean isBiometricOnly = call.argument("biometricOnly"); boolean allowCredentials = !isBiometricOnly && canAuthenticateWithDeviceCredential(); sendAuthenticationRequest(call, completionHandler, allowCredentials); return; } @VisibleForTesting public AuthCompletionHandler createAuthCompletionHandler(final Result result) { return new AuthCompletionHandler() { @Override public void onSuccess() { authenticateSuccess(result); } @Override public void onFailure() { authenticateFail(result); } @Override public void onError(String code, String error) { if (authInProgress.compareAndSet(true, false)) { result.error(code, error, null); } } }; } @VisibleForTesting public void sendAuthenticationRequest( MethodCall call, AuthCompletionHandler completionHandler, boolean allowCredentials) { authHelper = new AuthenticationHelper( lifecycle, (FragmentActivity) activity, call, completionHandler, allowCredentials); authHelper.authenticate(); } private void authenticateSuccess(Result result) { if (authInProgress.compareAndSet(true, false)) { result.success(true); } } private void authenticateFail(Result result) { if (authInProgress.compareAndSet(true, false)) { result.success(false); } } /* * Stops the authentication if in progress. */ private void stopAuthentication(Result result) { try { if (authHelper != null && authInProgress.get()) { authHelper.stopAuthentication(); authHelper = null; } authInProgress.set(false); result.success(true); } catch (Exception e) { result.success(false); } } private void deviceSupportsBiometrics(final Result result) { result.success(hasBiometricHardware()); } /* * Returns enrolled biometric types available on device. */ private void getEnrolledBiometrics(final Result result) { try { if (activity == null || activity.isFinishing()) { result.error("no_activity", "local_auth plugin requires a foreground activity", null); return; } ArrayList biometrics = getEnrolledBiometrics(); result.success(biometrics); } catch (Exception e) { result.error("no_biometrics_available", e.getMessage(), null); } } @VisibleForTesting public ArrayList getEnrolledBiometrics() { ArrayList biometrics = new ArrayList<>(); if (activity == null || activity.isFinishing()) { return biometrics; } if (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS) { biometrics.add("weak"); } if (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) == BiometricManager.BIOMETRIC_SUCCESS) { biometrics.add("strong"); } return biometrics; } @VisibleForTesting public boolean isDeviceSecure() { if (keyguardManager == null) return false; return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && keyguardManager.isDeviceSecure()); } @VisibleForTesting public boolean isDeviceSupported() { return isDeviceSecure() || canAuthenticateWithBiometrics(); } private boolean canAuthenticateWithBiometrics() { if (biometricManager == null) return false; return biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS; } private boolean hasBiometricHardware() { if (biometricManager == null) return false; return biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) != BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE; } @VisibleForTesting public boolean canAuthenticateWithDeviceCredential() { if (Build.VERSION.SDK_INT < 30) { // Checking for device credential only authentication via the BiometricManager // is not allowed before API level 30, so we check for presence of PIN, pattern, // or password instead. return isDeviceSecure(); } if (biometricManager == null) return false; return biometricManager.canAuthenticate(BiometricManager.Authenticators.DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS; } private void isDeviceSupported(Result result) { result.success(isDeviceSupported()); } @Override public void onAttachedToEngine(FlutterPluginBinding binding) { channel = new MethodChannel(binding.getFlutterEngine().getDartExecutor(), CHANNEL_NAME); channel.setMethodCallHandler(this); } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {} private void setServicesFromActivity(Activity activity) { if (activity == null) return; this.activity = activity; Context context = activity.getBaseContext(); biometricManager = BiometricManager.from(activity); keyguardManager = (KeyguardManager) context.getSystemService(KEYGUARD_SERVICE); } @Override public void onAttachedToActivity(ActivityPluginBinding binding) { binding.addActivityResultListener(resultListener); setServicesFromActivity(binding.getActivity()); lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); channel.setMethodCallHandler(this); } @Override public void onDetachedFromActivityForConfigChanges() { lifecycle = null; activity = null; } @Override public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { binding.addActivityResultListener(resultListener); setServicesFromActivity(binding.getActivity()); lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); } @Override public void onDetachedFromActivity() { lifecycle = null; channel.setMethodCallHandler(null); activity = null; } @VisibleForTesting final Activity getActivity() { return activity; } @VisibleForTesting void setBiometricManager(BiometricManager biometricManager) { this.biometricManager = biometricManager; } @VisibleForTesting void setKeyguardManager(KeyguardManager keyguardManager) { this.keyguardManager = keyguardManager; } } ================================================ FILE: packages/local_auth/local_auth_android/android/src/main/res/drawable/fingerprint_initial_icon.xml ================================================ ================================================ FILE: packages/local_auth/local_auth_android/android/src/main/res/drawable/fingerprint_success_icon.xml ================================================ ================================================ FILE: packages/local_auth/local_auth_android/android/src/main/res/drawable/fingerprint_warning_icon.xml ================================================ ================================================ FILE: packages/local_auth/local_auth_android/android/src/main/res/drawable/ic_done_white_24dp.xml ================================================ ================================================ FILE: packages/local_auth/local_auth_android/android/src/main/res/drawable/ic_fingerprint_white_24dp.xml ================================================ ================================================ FILE: packages/local_auth/local_auth_android/android/src/main/res/drawable/ic_priority_high_white_24dp.xml ================================================ ================================================ FILE: packages/local_auth/local_auth_android/android/src/main/res/layout/go_to_setting.xml ================================================ ================================================ FILE: packages/local_auth/local_auth_android/android/src/main/res/layout/scan_fp.xml ================================================ ================================================ FILE: packages/local_auth/local_auth_android/android/src/main/res/values/colors.xml ================================================ #E53935 #BDBDBD #43A047 #212121 #757575 ================================================ FILE: packages/local_auth/local_auth_android/android/src/main/res/values/dimens.xml ================================================ 14sp 16sp 20sp ================================================ FILE: packages/local_auth/local_auth_android/android/src/main/res/values/styles.xml ================================================ ================================================ FILE: packages/local_auth/local_auth_android/android/src/test/java/io/flutter/plugins/localauth/LocalAuthTest.java ================================================ // Copyright 2013 The Flutter 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.localauth; 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 static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Activity; import android.app.KeyguardManager; import android.app.NativeActivity; import android.content.Context; import androidx.biometric.BiometricManager; import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.Lifecycle; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.embedding.engine.plugins.lifecycle.HiddenLifecycleReference; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugins.localauth.AuthenticationHelper.AuthCompletionHandler; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) public class LocalAuthTest { @Test public void authenticate_returnsErrorWhenAuthInProgress() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); plugin.authInProgress.set(true); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); plugin.onMethodCall(new MethodCall("authenticate", null), mockResult); verify(mockResult).error("auth_in_progress", "Authentication in progress", null); } @Test public void authenticate_returnsErrorWithNoForegroundActivity() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); plugin.onMethodCall(new MethodCall("authenticate", null), mockResult); verify(mockResult) .error("no_activity", "local_auth plugin requires a foreground activity", null); } @Test public void authenticate_returnsErrorWhenActivityNotFragmentActivity() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); setPluginActivity(plugin, buildMockActivityWithContext(mock(NativeActivity.class))); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); plugin.onMethodCall(new MethodCall("authenticate", null), mockResult); verify(mockResult) .error( "no_fragment_activity", "local_auth plugin requires activity to be a FragmentActivity.", null); } @Test public void authenticate_returnsErrorWhenDeviceNotSupported() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); setPluginActivity(plugin, buildMockActivityWithContext(mock(FragmentActivity.class))); plugin.onMethodCall(new MethodCall("authenticate", null), mockResult); assertFalse(plugin.authInProgress.get()); verify(mockResult).error("NotAvailable", "Required security features not enabled", null); } @Test public void authenticate_properlyConfiguresBiometricOnlyAuthenticationRequest() { final LocalAuthPlugin plugin = spy(new LocalAuthPlugin()); setPluginActivity(plugin, buildMockActivityWithContext(mock(FragmentActivity.class))); when(plugin.isDeviceSupported()).thenReturn(true); final BiometricManager mockBiometricManager = mock(BiometricManager.class); when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)) .thenReturn(BiometricManager.BIOMETRIC_SUCCESS); when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.DEVICE_CREDENTIAL)) .thenReturn(BiometricManager.BIOMETRIC_SUCCESS); plugin.setBiometricManager(mockBiometricManager); ArgumentCaptor allowCredentialsCaptor = ArgumentCaptor.forClass(Boolean.class); doNothing() .when(plugin) .sendAuthenticationRequest( any(MethodCall.class), any(AuthCompletionHandler.class), allowCredentialsCaptor.capture()); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); HashMap arguments = new HashMap<>(); arguments.put("biometricOnly", true); plugin.onMethodCall(new MethodCall("authenticate", arguments), mockResult); assertFalse(allowCredentialsCaptor.getValue()); } @Test @Config(sdk = 30) public void authenticate_properlyConfiguresBiometricAndDeviceCredentialAuthenticationRequest() { final LocalAuthPlugin plugin = spy(new LocalAuthPlugin()); setPluginActivity(plugin, buildMockActivityWithContext(mock(FragmentActivity.class))); when(plugin.isDeviceSupported()).thenReturn(true); final BiometricManager mockBiometricManager = mock(BiometricManager.class); when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.DEVICE_CREDENTIAL)) .thenReturn(BiometricManager.BIOMETRIC_SUCCESS); plugin.setBiometricManager(mockBiometricManager); ArgumentCaptor allowCredentialsCaptor = ArgumentCaptor.forClass(Boolean.class); doNothing() .when(plugin) .sendAuthenticationRequest( any(MethodCall.class), any(AuthCompletionHandler.class), allowCredentialsCaptor.capture()); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); HashMap arguments = new HashMap<>(); arguments.put("biometricOnly", false); plugin.onMethodCall(new MethodCall("authenticate", arguments), mockResult); assertTrue(allowCredentialsCaptor.getValue()); } @Test @Config(sdk = 30) public void authenticate_properlyConfiguresDeviceCredentialOnlyAuthenticationRequest() { final LocalAuthPlugin plugin = spy(new LocalAuthPlugin()); setPluginActivity(plugin, buildMockActivityWithContext(mock(FragmentActivity.class))); when(plugin.isDeviceSupported()).thenReturn(true); final BiometricManager mockBiometricManager = mock(BiometricManager.class); when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)) .thenReturn(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED); when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.DEVICE_CREDENTIAL)) .thenReturn(BiometricManager.BIOMETRIC_SUCCESS); plugin.setBiometricManager(mockBiometricManager); ArgumentCaptor allowCredentialsCaptor = ArgumentCaptor.forClass(Boolean.class); doNothing() .when(plugin) .sendAuthenticationRequest( any(MethodCall.class), any(AuthCompletionHandler.class), allowCredentialsCaptor.capture()); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); HashMap arguments = new HashMap<>(); arguments.put("biometricOnly", false); plugin.onMethodCall(new MethodCall("authenticate", arguments), mockResult); assertTrue(allowCredentialsCaptor.getValue()); } @Test public void isDeviceSupportedReturnsFalse() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); plugin.onMethodCall(new MethodCall("isDeviceSupported", null), mockResult); verify(mockResult).success(false); } @Test public void deviceSupportsBiometrics_returnsTrueForPresentNonEnrolledBiometrics() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); final BiometricManager mockBiometricManager = mock(BiometricManager.class); when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)) .thenReturn(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED); plugin.setBiometricManager(mockBiometricManager); plugin.onMethodCall(new MethodCall("deviceSupportsBiometrics", null), mockResult); verify(mockResult).success(true); } @Test public void deviceSupportsBiometrics_returnsTrueForPresentEnrolledBiometrics() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); final BiometricManager mockBiometricManager = mock(BiometricManager.class); when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)) .thenReturn(BiometricManager.BIOMETRIC_SUCCESS); plugin.setBiometricManager(mockBiometricManager); plugin.onMethodCall(new MethodCall("deviceSupportsBiometrics", null), mockResult); verify(mockResult).success(true); } @Test public void deviceSupportsBiometrics_returnsFalseForNoBiometricHardware() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); final BiometricManager mockBiometricManager = mock(BiometricManager.class); when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)) .thenReturn(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE); plugin.setBiometricManager(mockBiometricManager); plugin.onMethodCall(new MethodCall("deviceSupportsBiometrics", null), mockResult); verify(mockResult).success(false); } @Test public void deviceSupportsBiometrics_returnsFalseForNullBiometricManager() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); plugin.setBiometricManager(null); plugin.onMethodCall(new MethodCall("deviceSupportsBiometrics", null), mockResult); verify(mockResult).success(false); } @Test public void onDetachedFromActivity_ShouldReleaseActivity() { final Activity mockActivity = mock(Activity.class); final ActivityPluginBinding mockActivityBinding = mock(ActivityPluginBinding.class); when(mockActivityBinding.getActivity()).thenReturn(mockActivity); Context mockContext = mock(Context.class); when(mockActivity.getBaseContext()).thenReturn(mockContext); when(mockActivity.getApplicationContext()).thenReturn(mockContext); final HiddenLifecycleReference mockLifecycleReference = mock(HiddenLifecycleReference.class); when(mockActivityBinding.getLifecycle()).thenReturn(mockLifecycleReference); final Lifecycle mockLifecycle = mock(Lifecycle.class); when(mockLifecycleReference.getLifecycle()).thenReturn(mockLifecycle); final FlutterPluginBinding mockPluginBinding = mock(FlutterPluginBinding.class); final FlutterEngine mockFlutterEngine = mock(FlutterEngine.class); when(mockPluginBinding.getFlutterEngine()).thenReturn(mockFlutterEngine); DartExecutor mockDartExecutor = mock(DartExecutor.class); when(mockFlutterEngine.getDartExecutor()).thenReturn(mockDartExecutor); final LocalAuthPlugin plugin = new LocalAuthPlugin(); plugin.onAttachedToEngine(mockPluginBinding); plugin.onAttachedToActivity(mockActivityBinding); assertNotNull(plugin.getActivity()); plugin.onDetachedFromActivity(); assertNull(plugin.getActivity()); } @Test public void getEnrolledBiometrics_shouldReturnError_whenNoActivity() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); plugin.onMethodCall(new MethodCall("getEnrolledBiometrics", null), mockResult); verify(mockResult) .error("no_activity", "local_auth plugin requires a foreground activity", null); } @Test public void getEnrolledBiometrics_shouldReturnError_whenFinishingActivity() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); final Activity mockActivity = buildMockActivityWithContext(mock(Activity.class)); when(mockActivity.isFinishing()).thenReturn(true); setPluginActivity(plugin, mockActivity); plugin.onMethodCall(new MethodCall("getEnrolledBiometrics", null), mockResult); verify(mockResult) .error("no_activity", "local_auth plugin requires a foreground activity", null); } @Test public void getEnrolledBiometrics_shouldReturnEmptyList_withoutHardwarePresent() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); setPluginActivity(plugin, buildMockActivityWithContext(mock(Activity.class))); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); final BiometricManager mockBiometricManager = mock(BiometricManager.class); when(mockBiometricManager.canAuthenticate(anyInt())) .thenReturn(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE); plugin.setBiometricManager(mockBiometricManager); plugin.onMethodCall(new MethodCall("getEnrolledBiometrics", null), mockResult); verify(mockResult).success(Collections.emptyList()); } @Test public void getEnrolledBiometrics_shouldReturnEmptyList_withNoMethodsEnrolled() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); setPluginActivity(plugin, buildMockActivityWithContext(mock(Activity.class))); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); final BiometricManager mockBiometricManager = mock(BiometricManager.class); when(mockBiometricManager.canAuthenticate(anyInt())) .thenReturn(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED); plugin.setBiometricManager(mockBiometricManager); plugin.onMethodCall(new MethodCall("getEnrolledBiometrics", null), mockResult); verify(mockResult).success(Collections.emptyList()); } @Test public void getEnrolledBiometrics_shouldOnlyAddEnrolledBiometrics() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); setPluginActivity(plugin, buildMockActivityWithContext(mock(Activity.class))); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); final BiometricManager mockBiometricManager = mock(BiometricManager.class); when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)) .thenReturn(BiometricManager.BIOMETRIC_SUCCESS); when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)) .thenReturn(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED); plugin.setBiometricManager(mockBiometricManager); plugin.onMethodCall(new MethodCall("getEnrolledBiometrics", null), mockResult); verify(mockResult) .success( new ArrayList() { { add("weak"); } }); } @Test public void getEnrolledBiometrics_shouldAddStrongBiometrics() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); setPluginActivity(plugin, buildMockActivityWithContext(mock(Activity.class))); final MethodChannel.Result mockResult = mock(MethodChannel.Result.class); final BiometricManager mockBiometricManager = mock(BiometricManager.class); when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)) .thenReturn(BiometricManager.BIOMETRIC_SUCCESS); when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)) .thenReturn(BiometricManager.BIOMETRIC_SUCCESS); plugin.setBiometricManager(mockBiometricManager); plugin.onMethodCall(new MethodCall("getEnrolledBiometrics", null), mockResult); verify(mockResult) .success( new ArrayList() { { add("weak"); add("strong"); } }); } @Test @Config(sdk = 22) public void isDeviceSecure_returnsFalseOnBelowApi23() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); assertFalse(plugin.isDeviceSecure()); } @Test @Config(sdk = 23) public void isDeviceSecure_returnsTrueIfDeviceIsSecure() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); KeyguardManager mockKeyguardManager = mock(KeyguardManager.class); plugin.setKeyguardManager(mockKeyguardManager); when(mockKeyguardManager.isDeviceSecure()).thenReturn(true); assertTrue(plugin.isDeviceSecure()); when(mockKeyguardManager.isDeviceSecure()).thenReturn(false); assertFalse(plugin.isDeviceSecure()); } @Test @Config(sdk = 30) public void canAuthenticateWithDeviceCredential_returnsTrueIfHasBiometricManagerSupportAboveApi30() { final LocalAuthPlugin plugin = new LocalAuthPlugin(); final BiometricManager mockBiometricManager = mock(BiometricManager.class); plugin.setBiometricManager(mockBiometricManager); when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.DEVICE_CREDENTIAL)) .thenReturn(BiometricManager.BIOMETRIC_SUCCESS); assertTrue(plugin.canAuthenticateWithDeviceCredential()); when(mockBiometricManager.canAuthenticate(BiometricManager.Authenticators.DEVICE_CREDENTIAL)) .thenReturn(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED); assertFalse(plugin.canAuthenticateWithDeviceCredential()); } private Activity buildMockActivityWithContext(Activity mockActivity) { final Context mockContext = mock(Context.class); when(mockActivity.getBaseContext()).thenReturn(mockContext); when(mockActivity.getApplicationContext()).thenReturn(mockContext); return mockActivity; } private void setPluginActivity(LocalAuthPlugin plugin, Activity activity) { final HiddenLifecycleReference mockLifecycleReference = mock(HiddenLifecycleReference.class); final FlutterPluginBinding mockPluginBinding = mock(FlutterPluginBinding.class); final ActivityPluginBinding mockActivityBinding = mock(ActivityPluginBinding.class); final FlutterEngine mockFlutterEngine = mock(FlutterEngine.class); final DartExecutor mockDartExecutor = mock(DartExecutor.class); when(mockPluginBinding.getFlutterEngine()).thenReturn(mockFlutterEngine); when(mockFlutterEngine.getDartExecutor()).thenReturn(mockDartExecutor); when(mockActivityBinding.getActivity()).thenReturn(activity); when(mockActivityBinding.getLifecycle()).thenReturn(mockLifecycleReference); plugin.onAttachedToEngine(mockPluginBinding); plugin.onAttachedToActivity(mockActivityBinding); } } ================================================ FILE: packages/local_auth/local_auth_android/example/README.md ================================================ # 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. ================================================ FILE: packages/local_auth/local_auth_android/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 33 lintOptions { disable 'InvalidPackage' } defaultConfig { applicationId "io.flutter.plugins.localauthexample" minSdkVersion 16 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } ================================================ FILE: packages/local_auth/local_auth_android/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/local_auth/local_auth_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/local_auth/local_auth_android/example/android/app/src/androidTest/java/io/flutter/plugins/localauth/FlutterFragmentActivityTest.java ================================================ // Copyright 2013 The Flutter 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.localauth; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterFragmentActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class FlutterFragmentActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterFragmentActivity.class); } ================================================ FILE: packages/local_auth/local_auth_android/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/local_auth/local_auth_android/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.3.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/local_auth/local_auth_android/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Sun Jan 03 14:07:08 CST 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip ================================================ FILE: packages/local_auth/local_auth_android/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true android.enableR8=true ================================================ FILE: packages/local_auth/local_auth_android/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withInputStream { stream -> plugins.load(stream) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/local_auth/local_auth_android/example/android/settings_aar.gradle ================================================ include ':app' ================================================ FILE: packages/local_auth/local_auth_android/example/integration_test/local_auth_test.dart ================================================ // Copyright 2013 The Flutter 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:local_auth_android/local_auth_android.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('canCheckBiometrics', (WidgetTester tester) async { expect( LocalAuthAndroid().getEnrolledBiometrics(), completion(isList), ); }); } ================================================ FILE: packages/local_auth/local_auth_android/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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, avoid_print import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:local_auth_android/local_auth_android.dart'; import 'package:local_auth_platform_interface/local_auth_platform_interface.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { _SupportState _supportState = _SupportState.unknown; bool? _deviceSupportsBiometrics; List? _enrolledBiometrics; String _authorized = 'Not Authorized'; bool _isAuthenticating = false; @override void initState() { super.initState(); LocalAuthPlatform.instance.isDeviceSupported().then( (bool isSupported) => setState(() => _supportState = isSupported ? _SupportState.supported : _SupportState.unsupported), ); } Future _checkBiometrics() async { late bool deviceSupportsBiometrics; try { deviceSupportsBiometrics = await LocalAuthPlatform.instance.deviceSupportsBiometrics(); } on PlatformException catch (e) { deviceSupportsBiometrics = false; print(e); } if (!mounted) { return; } setState(() { _deviceSupportsBiometrics = deviceSupportsBiometrics; }); } Future _getEnrolledBiometrics() async { late List availableBiometrics; try { availableBiometrics = await LocalAuthPlatform.instance.getEnrolledBiometrics(); } on PlatformException catch (e) { availableBiometrics = []; print(e); } if (!mounted) { return; } setState(() { _enrolledBiometrics = availableBiometrics; }); } Future _authenticate() async { bool authenticated = false; try { setState(() { _isAuthenticating = true; _authorized = 'Authenticating'; }); authenticated = await LocalAuthPlatform.instance.authenticate( localizedReason: 'Let OS determine authentication method', authMessages: [const AndroidAuthMessages()], options: const AuthenticationOptions( stickyAuth: true, ), ); setState(() { _isAuthenticating = false; }); } on PlatformException catch (e) { print(e); setState(() { _isAuthenticating = false; _authorized = 'Error - ${e.message}'; }); return; } if (!mounted) { return; } setState( () => _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 AndroidAuthMessages()], 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); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Plugin example app'), ), body: ListView( padding: const EdgeInsets.only(top: 30), children: [ Column( mainAxisAlignment: MainAxisAlignment.center, children: [ if (_supportState == _SupportState.unknown) const CircularProgressIndicator() else if (_supportState == _SupportState.supported) const Text('This device is supported') else const Text('This device is not supported'), const Divider(height: 100), Text( 'Device supports biometrics: $_deviceSupportsBiometrics\n'), ElevatedButton( onPressed: _checkBiometrics, child: const Text('Check biometrics'), ), const Divider(height: 100), Text('Enrolled biometrics: $_enrolledBiometrics\n'), ElevatedButton( onPressed: _getEnrolledBiometrics, child: const Text('Get enrolled biometrics'), ), const Divider(height: 100), Text('Current State: $_authorized\n'), 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 [ Text('Cancel Authentication'), Icon(Icons.cancel), ], ), ) else Column( 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 [ Text('Authenticate'), Icon(Icons.perm_device_information), ], ), ), ElevatedButton( onPressed: _authenticateWithBiometrics, child: Row( mainAxisSize: MainAxisSize.min, children: [ Text(_isAuthenticating ? 'Cancel' : 'Authenticate: biometrics only'), const Icon(Icons.fingerprint), ], ), ), ], ), ], ), ], ), ), ); } } enum _SupportState { unknown, supported, unsupported, } ================================================ FILE: packages/local_auth/local_auth_android/example/pubspec.yaml ================================================ name: local_auth_android_example description: Demonstrates how to use the local_auth_android plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter local_auth_android: # When depending on this package from a real application you should use: # local_auth_android: ^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: ../ local_auth_platform_interface: ^1.0.0 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/local_auth/local_auth_android/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/local_auth/local_auth_android/lib/local_auth_android.dart ================================================ // Copyright 2013 The Flutter 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:local_auth_platform_interface/local_auth_platform_interface.dart'; import 'types/auth_messages_android.dart'; export 'package:local_auth_android/types/auth_messages_android.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'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/local_auth_android'); /// The implementation of [LocalAuthPlatform] for Android. class LocalAuthAndroid extends LocalAuthPlatform { /// Registers this class as the default instance of [LocalAuthPlatform]. static void registerWith() { LocalAuthPlatform.instance = LocalAuthAndroid(); } @override Future authenticate({ required String localizedReason, required Iterable authMessages, 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 AndroidAuthMessages().args); for (final AuthMessages messages in authMessages) { if (messages is AndroidAuthMessages) { args.addAll(messages.args); } } return (await _channel.invokeMethod('authenticate', args)) ?? false; } @override Future deviceSupportsBiometrics() async { return (await _channel.invokeMethod('deviceSupportsBiometrics')) ?? false; } @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; } } return biometrics; } @override Future isDeviceSupported() async => (await _channel.invokeMethod('isDeviceSupported')) ?? false; @override Future stopAuthentication() async => await _channel.invokeMethod('stopAuthentication') ?? false; } ================================================ FILE: packages/local_auth/local_auth_android/lib/types/auth_messages_android.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:intl/intl.dart'; import 'package:local_auth_platform_interface/types/auth_messages.dart'; /// Android side authentication messages. /// /// Provides default values for all messages. @immutable class AndroidAuthMessages extends AuthMessages { /// Constructs a new instance. const AndroidAuthMessages({ this.biometricHint, this.biometricNotRecognized, this.biometricRequiredTitle, this.biometricSuccess, this.cancelButton, this.deviceCredentialsRequiredTitle, this.deviceCredentialsSetupDescription, this.goToSettingsButton, this.goToSettingsDescription, this.signInTitle, }); /// Hint message advising the user how to authenticate with biometrics. /// Maximum 60 characters. final String? biometricHint; /// Message to let the user know that authentication was failed. /// Maximum 60 characters. final String? biometricNotRecognized; /// Message shown as a title in a dialog which indicates the user /// has not set up biometric authentication on their device. /// Maximum 60 characters. final String? biometricRequiredTitle; /// Message to let the user know that authentication was successful. /// Maximum 60 characters final String? biometricSuccess; /// Message shown on a button that the user can click to leave the /// current dialog. /// Maximum 30 characters. final String? cancelButton; /// Message shown as a title in a dialog which indicates the user /// has not set up credentials authentication on their device. /// Maximum 60 characters. final String? deviceCredentialsRequiredTitle; /// Message advising the user to go to the settings and configure /// device credentials on their device. final String? deviceCredentialsSetupDescription; /// Message shown on a button that the user can click to go to settings pages /// from the current dialog. /// Maximum 30 characters. final String? goToSettingsButton; /// Message advising the user to go to the settings and configure /// biometric on their device. final String? goToSettingsDescription; /// Message shown as a title in a dialog which indicates the user /// that they need to scan biometric to continue. /// Maximum 60 characters. final String? signInTitle; @override Map get args { return { 'biometricHint': biometricHint ?? androidBiometricHint, 'biometricNotRecognized': biometricNotRecognized ?? androidBiometricNotRecognized, 'biometricSuccess': biometricSuccess ?? androidBiometricSuccess, 'biometricRequired': biometricRequiredTitle ?? androidBiometricRequiredTitle, 'cancelButton': cancelButton ?? androidCancelButton, 'deviceCredentialsRequired': deviceCredentialsRequiredTitle ?? androidDeviceCredentialsRequiredTitle, 'deviceCredentialsSetupDescription': deviceCredentialsSetupDescription ?? androidDeviceCredentialsSetupDescription, 'goToSetting': goToSettingsButton ?? goToSettings, 'goToSettingDescription': goToSettingsDescription ?? androidGoToSettingsDescription, 'signInTitle': signInTitle ?? androidSignInTitle, }; } @override bool operator ==(Object other) => identical(this, other) || other is AndroidAuthMessages && runtimeType == other.runtimeType && biometricHint == other.biometricHint && biometricNotRecognized == other.biometricNotRecognized && biometricRequiredTitle == other.biometricRequiredTitle && biometricSuccess == other.biometricSuccess && cancelButton == other.cancelButton && deviceCredentialsRequiredTitle == other.deviceCredentialsRequiredTitle && deviceCredentialsSetupDescription == other.deviceCredentialsSetupDescription && goToSettingsButton == other.goToSettingsButton && goToSettingsDescription == other.goToSettingsDescription && signInTitle == other.signInTitle; @override int get hashCode => Object.hash( super.hashCode, biometricHint, biometricNotRecognized, biometricRequiredTitle, biometricSuccess, cancelButton, deviceCredentialsRequiredTitle, deviceCredentialsSetupDescription, goToSettingsButton, goToSettingsDescription, signInTitle); } // Default strings for AndroidAuthMessages. Currently supports English. // Intl.message must be string literals. /// Message shown on a button that the user can click to go to settings pages /// from the current dialog. String get goToSettings => Intl.message('Go to settings', desc: 'Message shown on a button that the user can click to go to ' 'settings pages from the current dialog. Maximum 30 characters.'); /// Hint message advising the user how to authenticate with biometrics. String get androidBiometricHint => Intl.message('Verify identity', desc: 'Hint message advising the user how to authenticate with biometrics. ' 'Maximum 60 characters.'); /// Message to let the user know that authentication was failed. String get androidBiometricNotRecognized => Intl.message('Not recognized. Try again.', desc: 'Message to let the user know that authentication was failed. ' 'Maximum 60 characters.'); /// Message to let the user know that authentication was successful. It String get androidBiometricSuccess => Intl.message('Success', desc: 'Message to let the user know that authentication was successful. ' 'Maximum 60 characters.'); /// Message shown on a button that the user can click to leave the /// current dialog. String get androidCancelButton => Intl.message('Cancel', desc: 'Message shown on a button that the user can click to leave the ' 'current dialog. Maximum 30 characters.'); /// Message shown as a title in a dialog which indicates the user /// that they need to scan biometric to continue. String get androidSignInTitle => Intl.message('Authentication required', desc: 'Message shown as a title in a dialog which indicates the user ' 'that they need to scan biometric to continue. Maximum 60 characters.'); /// Message shown as a title in a dialog which indicates the user /// has not set up biometric authentication on their device. String get androidBiometricRequiredTitle => Intl.message('Biometric required', desc: 'Message shown as a title in a dialog which indicates the user ' 'has not set up biometric authentication on their device. ' 'Maximum 60 characters.'); /// Message shown as a title in a dialog which indicates the user /// has not set up credentials authentication on their device. String get androidDeviceCredentialsRequiredTitle => Intl.message('Device credentials required', desc: 'Message shown as a title in a dialog which indicates the user ' 'has not set up credentials authentication on their device. ' 'Maximum 60 characters.'); /// Message advising the user to go to the settings and configure /// device credentials on their device. String get androidDeviceCredentialsSetupDescription => Intl.message('Device credentials required', desc: 'Message advising the user to go to the settings and configure ' 'device credentials on their device.'); /// Message advising the user to go to the settings and configure /// biometric on their device. String get androidGoToSettingsDescription => Intl.message( 'Biometric authentication is not set up on your device. Go to ' "'Settings > Security' to add biometric authentication.", desc: 'Message advising the user to go to the settings and configure ' 'biometric on their device.'); ================================================ FILE: packages/local_auth/local_auth_android/pubspec.yaml ================================================ name: local_auth_android description: Android implementation of the local_auth plugin. repository: https://github.com/flutter/plugins/tree/main/packages/local_auth/local_auth_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22 version: 1.0.18 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: local_auth platforms: android: package: io.flutter.plugins.localauth pluginClass: LocalAuthPlugin dartPluginClass: LocalAuthAndroid dependencies: flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.1 intl: ">=0.17.0 <0.19.0" local_auth_platform_interface: ^1.0.1 dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/local_auth/local_auth_android/test/local_auth_test.dart ================================================ // Copyright 2013 The Flutter 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:local_auth_android/local_auth_android.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('LocalAuth', () { const MethodChannel channel = MethodChannel( 'plugins.flutter.io/local_auth_android', ); final List log = []; late LocalAuthAndroid localAuthentication; setUp(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, (MethodCall methodCall) { log.add(methodCall); switch (methodCall.method) { case 'getEnrolledBiometrics': return Future>.value(['weak', 'strong']); default: return Future.value(true); } }); localAuthentication = LocalAuthAndroid(); log.clear(); }); test('deviceSupportsBiometrics calls platform', () async { final bool result = await localAuthentication.deviceSupportsBiometrics(); expect( log, [ isMethodCall('deviceSupportsBiometrics', arguments: null), ], ); expect(result, true); }); test('getEnrolledBiometrics calls platform', () async { final List result = await localAuthentication.getEnrolledBiometrics(); expect( log, [ isMethodCall('getEnrolledBiometrics', arguments: null), ], ); expect(result, [ BiometricType.weak, BiometricType.strong, ]); }); test('isDeviceSupported calls platform', () async { await localAuthentication.isDeviceSupported(); expect( log, [ isMethodCall('isDeviceSupported', arguments: null), ], ); }); test('stopAuthentication calls platform', () async { await localAuthentication.stopAuthentication(); expect( log, [ isMethodCall('stopAuthentication', arguments: null), ], ); }); group('With device auth fail over', () { test('authenticate with no args.', () async { await localAuthentication.authenticate( authMessages: [const AndroidAuthMessages()], localizedReason: 'Needs secure', options: const AuthenticationOptions(biometricOnly: true), ); expect( log, [ isMethodCall('authenticate', arguments: { 'localizedReason': 'Needs secure', 'useErrorDialogs': true, 'stickyAuth': false, 'sensitiveTransaction': true, 'biometricOnly': true, }..addAll(const AndroidAuthMessages().args)), ], ); }); test('authenticate with no sensitive transaction.', () async { await localAuthentication.authenticate( authMessages: [const AndroidAuthMessages()], localizedReason: 'Insecure', options: const AuthenticationOptions( sensitiveTransaction: false, useErrorDialogs: false, biometricOnly: true, ), ); expect( log, [ isMethodCall('authenticate', arguments: { 'localizedReason': 'Insecure', 'useErrorDialogs': false, 'stickyAuth': false, 'sensitiveTransaction': false, 'biometricOnly': true, }..addAll(const AndroidAuthMessages().args)), ], ); }); }); group('With biometrics only', () { test('authenticate with no args.', () async { await localAuthentication.authenticate( authMessages: [const AndroidAuthMessages()], localizedReason: 'Needs secure', ); expect( log, [ isMethodCall('authenticate', arguments: { 'localizedReason': 'Needs secure', 'useErrorDialogs': true, 'stickyAuth': false, 'sensitiveTransaction': true, 'biometricOnly': false, }..addAll(const AndroidAuthMessages().args)), ], ); }); test('authenticate with no sensitive transaction.', () async { await localAuthentication.authenticate( authMessages: [const AndroidAuthMessages()], localizedReason: 'Insecure', options: const AuthenticationOptions( sensitiveTransaction: false, useErrorDialogs: false, ), ); expect( log, [ isMethodCall('authenticate', arguments: { 'localizedReason': 'Insecure', 'useErrorDialogs': false, 'stickyAuth': false, 'sensitiveTransaction': false, 'biometricOnly': false, }..addAll(const AndroidAuthMessages().args)), ], ); }); }); }); } /// 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; ================================================ FILE: packages/local_auth/local_auth_ios/AUTHORS ================================================ # 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> Bodhi Mulders ================================================ FILE: packages/local_auth/local_auth_ios/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 1.0.12 * Adds compatibility with `intl` 0.18.0. ## 1.0.11 * Fixes issue where failed authentication was failing silently ## 1.0.10 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. ## 1.0.9 * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 1.0.8 * Updates `local_auth_platform_interface` constraint to the correct minimum version. ## 1.0.7 * Updates references to the obsolete master branch. ## 1.0.6 * Suppresses warnings for pre-iOS-11 codepaths. ## 1.0.5 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 1.0.4 * Fixes `deviceSupportsBiometrics` to return true when biometric hardware is available but not enrolled. ## 1.0.3 * Adopts `Object.hash`. ## 1.0.2 * Adds support `localizedFallbackTitle` in authenticateWithBiometrics on iOS. ## 1.0.1 * BREAKING CHANGE: Changes `stopAuthentication` to always return false instead of throwing an error. ## 1.0.0 * Initial release from migration to federated architecture. ================================================ FILE: packages/local_auth/local_auth_ios/LICENSE ================================================ 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. ================================================ FILE: packages/local_auth/local_auth_ios/README.md ================================================ # local\_auth\_ios The iOS implementation of [`local_auth`][1]. ## Usage This package is [endorsed][2], which means you can simply use `local_auth` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/local_auth [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/local_auth/local_auth_ios/example/README.md ================================================ # 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. ================================================ FILE: packages/local_auth/local_auth_ios/example/integration_test/local_auth_test.dart ================================================ // Copyright 2013 The Flutter 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:local_auth_ios/local_auth_ios.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('canCheckBiometrics', (WidgetTester tester) async { expect( LocalAuthIOS().getEnrolledBiometrics(), completion(isList), ); }); } ================================================ FILE: packages/local_auth/local_auth_ios/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 UIRequiredDeviceCapabilities arm64 MinimumOSVersion 9.0 ================================================ FILE: packages/local_auth/local_auth_ios/example/ios/Flutter/Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/local_auth/local_auth_ios/example/ios/Flutter/Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/local_auth/local_auth_ios/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths pod 'OCMock','3.5' end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/local_auth/local_auth_ios/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/local_auth/local_auth_ios/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end ================================================ FILE: packages/local_auth/local_auth_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/local_auth/local_auth_ios/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/local_auth/local_auth_ios/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/local_auth/local_auth_ios/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName local_auth_example CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance NSFaceIDUsageDescription App needs to authenticate using faces. ================================================ FILE: packages/local_auth/local_auth_ios/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/local_auth/local_auth_ios/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 0CCCD07A2CE24E13C9C1EEA4 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D274A3F79473B1549B2BBD5 /* libPods-Runner.a */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3398D2E426164AD8005A052F /* FLTLocalAuthPluginTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3398D2E326164AD8005A052F /* FLTLocalAuthPluginTests.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 691CB38B382734AF80FBCA4C /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ADBFA21B380E07A3A585383D /* libPods-RunnerTests.a */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 3398D2D226163948005A052F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3398D2CD26163948005A052F /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3398D2D126163948005A052F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3398D2DC261649CD005A052F /* liblocal_auth.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = liblocal_auth.a; sourceTree = BUILT_PRODUCTS_DIR; }; 3398D2DF26164A03005A052F /* liblocal_auth.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = liblocal_auth.a; sourceTree = BUILT_PRODUCTS_DIR; }; 3398D2E326164AD8005A052F /* FLTLocalAuthPluginTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTLocalAuthPluginTests.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 658CDD04B21E4EA92F8EF229 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 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 = ""; }; 8D6545CD14E27D6F8299FFD5 /* 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 = ""; }; 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 = ""; }; 9D274A3F79473B1549B2BBD5 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; ADBFA21B380E07A3A585383D /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; D9B3BCBC68F8928E2907FB87 /* 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 = ""; }; EB36DF6C3F25E00DF4175422 /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 3398D2CA26163948005A052F /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 691CB38B382734AF80FBCA4C /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 0CCCD07A2CE24E13C9C1EEA4 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 33BF11D226680B2E002967F3 /* RunnerTests */ = { isa = PBXGroup; children = ( 3398D2E326164AD8005A052F /* FLTLocalAuthPluginTests.m */, 3398D2D126163948005A052F /* Info.plist */, ); path = RunnerTests; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 33BF11D226680B2E002967F3 /* RunnerTests */, 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, F8CC53B854B121315C7319D2 /* Pods */, E2D5FA899A019BD3E0DB0917 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, 3398D2CD26163948005A052F /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; E2D5FA899A019BD3E0DB0917 /* Frameworks */ = { isa = PBXGroup; children = ( 3398D2DF26164A03005A052F /* liblocal_auth.a */, 3398D2DC261649CD005A052F /* liblocal_auth.a */, 9D274A3F79473B1549B2BBD5 /* libPods-Runner.a */, ADBFA21B380E07A3A585383D /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; F8CC53B854B121315C7319D2 /* Pods */ = { isa = PBXGroup; children = ( EB36DF6C3F25E00DF4175422 /* Pods-Runner.debug.xcconfig */, 658CDD04B21E4EA92F8EF229 /* Pods-Runner.release.xcconfig */, 8D6545CD14E27D6F8299FFD5 /* Pods-RunnerTests.debug.xcconfig */, D9B3BCBC68F8928E2907FB87 /* Pods-RunnerTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 3398D2CC26163948005A052F /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = 3398D2D426163948005A052F /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( 9C21D3AD392EA849AEB09231 /* [CP] Check Pods Manifest.lock */, 3398D2C926163948005A052F /* Sources */, 3398D2CA26163948005A052F /* Frameworks */, 3398D2CB26163948005A052F /* Resources */, ); buildRules = ( ); dependencies = ( 3398D2D326163948005A052F /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = 3398D2CD26163948005A052F /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 98D96A2D1A74AF66E3DD2DBC /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 3398D2CC26163948005A052F = { CreatedOnToolsVersion = 12.4; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, 3398D2CC26163948005A052F /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 3398D2CB26163948005A052F /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; 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"; }; 98D96A2D1A74AF66E3DD2DBC /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 9C21D3AD392EA849AEB09231 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 3398D2C926163948005A052F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 3398D2E426164AD8005A052F /* FLTLocalAuthPluginTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 3398D2D326163948005A052F /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = 3398D2D226163948005A052F /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 3398D2D526163948005A052F /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 8D6545CD14E27D6F8299FFD5 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = RunnerTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.google.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Debug; }; 3398D2D626163948005A052F /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = D9B3BCBC68F8928E2907FB87 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = RunnerTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.google.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; }; 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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 = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.localAuthExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.localAuthExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 3398D2D426163948005A052F /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 3398D2D526163948005A052F /* Debug */, 3398D2D626163948005A052F /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/local_auth/local_auth_ios/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/local_auth/local_auth_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/local_auth/local_auth_ios/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/local_auth/local_auth_ios/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/local_auth/local_auth_ios/example/ios/RunnerTests/FLTLocalAuthPluginTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import LocalAuthentication; @import XCTest; #import #if __has_include() #import #else @import local_auth_ios; #endif // Private API needed for tests. @interface FLTLocalAuthPlugin (Test) - (void)setAuthContextOverrides:(NSArray *)authContexts; @end // Set a long timeout to avoid flake due to slow CI. static const NSTimeInterval kTimeout = 30.0; @interface FLTLocalAuthPluginTests : XCTestCase @end @implementation FLTLocalAuthPluginTests - (void)setUp { self.continueAfterFailure = NO; } - (void)testSuccessfullAuthWithBiometrics { FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init]; id mockAuthContext = OCMClassMock([LAContext class]); plugin.authContextOverrides = @[ mockAuthContext ]; const LAPolicy policy = LAPolicyDeviceOwnerAuthenticationWithBiometrics; NSString *reason = @"a reason"; OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on // a background thread. void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) { void (^reply)(BOOL, NSError *); [invocation getArgument:&reply atIndex:4]; dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ reply(YES, nil); }); }; OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:reason reply:[OCMArg any]]) .andDo(backgroundThreadReplyCaller); FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"authenticate" arguments:@{ @"biometricOnly" : @(YES), @"localizedReason" : reason, }]; XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; [plugin handleMethodCall:call result:^(id _Nullable result) { XCTAssertTrue([NSThread isMainThread]); XCTAssertTrue([result isKindOfClass:[NSNumber class]]); XCTAssertTrue([result boolValue]); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTimeout handler:nil]; } - (void)testSuccessfullAuthWithoutBiometrics { FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init]; id mockAuthContext = OCMClassMock([LAContext class]); plugin.authContextOverrides = @[ mockAuthContext ]; const LAPolicy policy = LAPolicyDeviceOwnerAuthentication; NSString *reason = @"a reason"; OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on // a background thread. void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) { void (^reply)(BOOL, NSError *); [invocation getArgument:&reply atIndex:4]; dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ reply(YES, nil); }); }; OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:reason reply:[OCMArg any]]) .andDo(backgroundThreadReplyCaller); FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"authenticate" arguments:@{ @"biometricOnly" : @(NO), @"localizedReason" : reason, }]; XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; [plugin handleMethodCall:call result:^(id _Nullable result) { XCTAssertTrue([NSThread isMainThread]); XCTAssertTrue([result isKindOfClass:[NSNumber class]]); XCTAssertTrue([result boolValue]); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTimeout handler:nil]; } - (void)testFailedAuthWithBiometrics { FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init]; id mockAuthContext = OCMClassMock([LAContext class]); plugin.authContextOverrides = @[ mockAuthContext ]; const LAPolicy policy = LAPolicyDeviceOwnerAuthenticationWithBiometrics; NSString *reason = @"a reason"; OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on // a background thread. void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) { void (^reply)(BOOL, NSError *); [invocation getArgument:&reply atIndex:4]; dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ reply(NO, [NSError errorWithDomain:@"error" code:LAErrorAuthenticationFailed userInfo:nil]); }); }; OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:reason reply:[OCMArg any]]) .andDo(backgroundThreadReplyCaller); FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"authenticate" arguments:@{ @"biometricOnly" : @(YES), @"localizedReason" : reason, }]; XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; [plugin handleMethodCall:call result:^(id _Nullable result) { XCTAssertTrue([NSThread isMainThread]); XCTAssertTrue([result isKindOfClass:[FlutterError class]]); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTimeout handler:nil]; } - (void)testFailedWithUnknownErrorCode { FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init]; id mockAuthContext = OCMClassMock([LAContext class]); plugin.authContextOverrides = @[ mockAuthContext ]; const LAPolicy policy = LAPolicyDeviceOwnerAuthentication; NSString *reason = @"a reason"; OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on // a background thread. void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) { void (^reply)(BOOL, NSError *); [invocation getArgument:&reply atIndex:4]; dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ reply(NO, [NSError errorWithDomain:@"error" code:99 userInfo:nil]); }); }; OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:reason reply:[OCMArg any]]) .andDo(backgroundThreadReplyCaller); FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"authenticate" arguments:@{ @"biometricOnly" : @(NO), @"localizedReason" : reason, }]; XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; [plugin handleMethodCall:call result:^(id _Nullable result) { XCTAssertTrue([NSThread isMainThread]); XCTAssertTrue([result isKindOfClass:[FlutterError class]]); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTimeout handler:nil]; } - (void)testSystemCancelledWithoutStickyAuth { FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init]; id mockAuthContext = OCMClassMock([LAContext class]); plugin.authContextOverrides = @[ mockAuthContext ]; const LAPolicy policy = LAPolicyDeviceOwnerAuthentication; NSString *reason = @"a reason"; OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on // a background thread. void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) { void (^reply)(BOOL, NSError *); [invocation getArgument:&reply atIndex:4]; dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ reply(NO, [NSError errorWithDomain:@"error" code:LAErrorSystemCancel userInfo:nil]); }); }; OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:reason reply:[OCMArg any]]) .andDo(backgroundThreadReplyCaller); FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"authenticate" arguments:@{ @"biometricOnly" : @(NO), @"localizedReason" : reason, @"stickyAuth" : @(NO) }]; XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; [plugin handleMethodCall:call result:^(id _Nullable result) { XCTAssertTrue([NSThread isMainThread]); XCTAssertTrue([result isKindOfClass:[NSNumber class]]); XCTAssertFalse([result boolValue]); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTimeout handler:nil]; } - (void)testFailedAuthWithoutBiometrics { FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init]; id mockAuthContext = OCMClassMock([LAContext class]); plugin.authContextOverrides = @[ mockAuthContext ]; const LAPolicy policy = LAPolicyDeviceOwnerAuthentication; NSString *reason = @"a reason"; OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on // a background thread. void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) { void (^reply)(BOOL, NSError *); [invocation getArgument:&reply atIndex:4]; dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ reply(NO, [NSError errorWithDomain:@"error" code:LAErrorAuthenticationFailed userInfo:nil]); }); }; OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:reason reply:[OCMArg any]]) .andDo(backgroundThreadReplyCaller); FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"authenticate" arguments:@{ @"biometricOnly" : @(NO), @"localizedReason" : reason, }]; XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; [plugin handleMethodCall:call result:^(id _Nullable result) { XCTAssertTrue([NSThread isMainThread]); XCTAssertTrue([result isKindOfClass:[FlutterError class]]); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTimeout handler:nil]; } - (void)testLocalizedFallbackTitle { FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init]; id mockAuthContext = OCMClassMock([LAContext class]); plugin.authContextOverrides = @[ mockAuthContext ]; const LAPolicy policy = LAPolicyDeviceOwnerAuthentication; NSString *reason = @"a reason"; NSString *localizedFallbackTitle = @"a title"; OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on // a background thread. void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) { void (^reply)(BOOL, NSError *); [invocation getArgument:&reply atIndex:4]; dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ reply(YES, nil); }); }; OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:reason reply:[OCMArg any]]) .andDo(backgroundThreadReplyCaller); FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"authenticate" arguments:@{ @"biometricOnly" : @(NO), @"localizedReason" : reason, @"localizedFallbackTitle" : localizedFallbackTitle, }]; XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; [plugin handleMethodCall:call result:^(id _Nullable result) { OCMVerify([mockAuthContext setLocalizedFallbackTitle:localizedFallbackTitle]); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTimeout handler:nil]; } - (void)testSkippedLocalizedFallbackTitle { FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init]; id mockAuthContext = OCMClassMock([LAContext class]); plugin.authContextOverrides = @[ mockAuthContext ]; const LAPolicy policy = LAPolicyDeviceOwnerAuthentication; NSString *reason = @"a reason"; OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on // a background thread. void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) { void (^reply)(BOOL, NSError *); [invocation getArgument:&reply atIndex:4]; dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ reply(YES, nil); }); }; OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:reason reply:[OCMArg any]]) .andDo(backgroundThreadReplyCaller); FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"authenticate" arguments:@{ @"biometricOnly" : @(NO), @"localizedReason" : reason, }]; XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; [plugin handleMethodCall:call result:^(id _Nullable result) { OCMVerify([mockAuthContext setLocalizedFallbackTitle:nil]); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTimeout handler:nil]; } - (void)testDeviceSupportsBiometrics_withEnrolledHardware { FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init]; id mockAuthContext = OCMClassMock([LAContext class]); plugin.authContextOverrides = @[ mockAuthContext ]; const LAPolicy policy = LAPolicyDeviceOwnerAuthenticationWithBiometrics; OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"deviceSupportsBiometrics" arguments:@{}]; XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; [plugin handleMethodCall:call result:^(id _Nullable result) { XCTAssertTrue([NSThread isMainThread]); XCTAssertTrue([result isKindOfClass:[NSNumber class]]); XCTAssertTrue([result boolValue]); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTimeout handler:nil]; } - (void)testDeviceSupportsBiometrics_withNonEnrolledHardware_iOS11 { if (@available(iOS 11, *)) { FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init]; id mockAuthContext = OCMClassMock([LAContext class]); plugin.authContextOverrides = @[ mockAuthContext ]; const LAPolicy policy = LAPolicyDeviceOwnerAuthenticationWithBiometrics; void (^canEvaluatePolicyHandler)(NSInvocation *) = ^(NSInvocation *invocation) { // Write error NSError *__autoreleasing *authError; [invocation getArgument:&authError atIndex:3]; *authError = [NSError errorWithDomain:@"error" code:LAErrorBiometryNotEnrolled userInfo:nil]; // Write return value BOOL returnValue = NO; NSValue *nsReturnValue = [NSValue valueWithBytes:&returnValue objCType:@encode(BOOL)]; [invocation setReturnValue:&nsReturnValue]; }; OCMStub([mockAuthContext canEvaluatePolicy:policy error:(NSError * __autoreleasing *)[OCMArg anyPointer]]) .andDo(canEvaluatePolicyHandler); FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"deviceSupportsBiometrics" arguments:@{}]; XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; [plugin handleMethodCall:call result:^(id _Nullable result) { XCTAssertTrue([NSThread isMainThread]); XCTAssertTrue([result isKindOfClass:[NSNumber class]]); XCTAssertTrue([result boolValue]); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTimeout handler:nil]; } } - (void)testDeviceSupportsBiometrics_withNoBiometricHardware { FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init]; id mockAuthContext = OCMClassMock([LAContext class]); plugin.authContextOverrides = @[ mockAuthContext ]; const LAPolicy policy = LAPolicyDeviceOwnerAuthenticationWithBiometrics; void (^canEvaluatePolicyHandler)(NSInvocation *) = ^(NSInvocation *invocation) { // Write error NSError *__autoreleasing *authError; [invocation getArgument:&authError atIndex:3]; *authError = [NSError errorWithDomain:@"error" code:0 userInfo:nil]; // Write return value BOOL returnValue = NO; NSValue *nsReturnValue = [NSValue valueWithBytes:&returnValue objCType:@encode(BOOL)]; [invocation setReturnValue:&nsReturnValue]; }; OCMStub([mockAuthContext canEvaluatePolicy:policy error:(NSError * __autoreleasing *)[OCMArg anyPointer]]) .andDo(canEvaluatePolicyHandler); FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"deviceSupportsBiometrics" arguments:@{}]; XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; [plugin handleMethodCall:call result:^(id _Nullable result) { XCTAssertTrue([NSThread isMainThread]); XCTAssertTrue([result isKindOfClass:[NSNumber class]]); XCTAssertFalse([result boolValue]); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTimeout handler:nil]; } - (void)testGetEnrolledBiometrics_withFaceID_iOS11 { if (@available(iOS 11, *)) { FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init]; id mockAuthContext = OCMClassMock([LAContext class]); plugin.authContextOverrides = @[ mockAuthContext ]; const LAPolicy policy = LAPolicyDeviceOwnerAuthenticationWithBiometrics; OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); OCMStub([mockAuthContext biometryType]).andReturn(LABiometryTypeFaceID); FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"getEnrolledBiometrics" arguments:@{}]; XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; [plugin handleMethodCall:call result:^(id _Nullable result) { XCTAssertTrue([NSThread isMainThread]); XCTAssertTrue([result isKindOfClass:[NSArray class]]); XCTAssertEqual([result count], 1); XCTAssertEqualObjects(result[0], @"face"); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTimeout handler:nil]; } } - (void)testGetEnrolledBiometrics_withTouchID_iOS11 { if (@available(iOS 11, *)) { FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init]; id mockAuthContext = OCMClassMock([LAContext class]); plugin.authContextOverrides = @[ mockAuthContext ]; const LAPolicy policy = LAPolicyDeviceOwnerAuthenticationWithBiometrics; OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); OCMStub([mockAuthContext biometryType]).andReturn(LABiometryTypeTouchID); FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"getEnrolledBiometrics" arguments:@{}]; XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; [plugin handleMethodCall:call result:^(id _Nullable result) { XCTAssertTrue([NSThread isMainThread]); XCTAssertTrue([result isKindOfClass:[NSArray class]]); XCTAssertEqual([result count], 1); XCTAssertEqualObjects(result[0], @"fingerprint"); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTimeout handler:nil]; } } - (void)testGetEnrolledBiometrics_withTouchID_preIOS11 { if (@available(iOS 11, *)) { return; } FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init]; id mockAuthContext = OCMClassMock([LAContext class]); plugin.authContextOverrides = @[ mockAuthContext ]; const LAPolicy policy = LAPolicyDeviceOwnerAuthenticationWithBiometrics; OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES); FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"getEnrolledBiometrics" arguments:@{}]; XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; [plugin handleMethodCall:call result:^(id _Nullable result) { XCTAssertTrue([NSThread isMainThread]); XCTAssertTrue([result isKindOfClass:[NSArray class]]); XCTAssertEqual([result count], 1); XCTAssertEqualObjects(result[0], @"fingerprint"); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTimeout handler:nil]; } - (void)testGetEnrolledBiometrics_withoutEnrolledHardware_iOS11 { if (@available(iOS 11, *)) { FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init]; id mockAuthContext = OCMClassMock([LAContext class]); plugin.authContextOverrides = @[ mockAuthContext ]; const LAPolicy policy = LAPolicyDeviceOwnerAuthenticationWithBiometrics; void (^canEvaluatePolicyHandler)(NSInvocation *) = ^(NSInvocation *invocation) { // Write error NSError *__autoreleasing *authError; [invocation getArgument:&authError atIndex:3]; *authError = [NSError errorWithDomain:@"error" code:LAErrorBiometryNotEnrolled userInfo:nil]; // Write return value BOOL returnValue = NO; NSValue *nsReturnValue = [NSValue valueWithBytes:&returnValue objCType:@encode(BOOL)]; [invocation setReturnValue:&nsReturnValue]; }; OCMStub([mockAuthContext canEvaluatePolicy:policy error:(NSError * __autoreleasing *)[OCMArg anyPointer]]) .andDo(canEvaluatePolicyHandler); FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"getEnrolledBiometrics" arguments:@{}]; XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"]; [plugin handleMethodCall:call result:^(id _Nullable result) { XCTAssertTrue([NSThread isMainThread]); XCTAssertTrue([result isKindOfClass:[NSArray class]]); XCTAssertEqual([result count], 0); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:kTimeout handler:nil]; } } @end ================================================ FILE: packages/local_auth/local_auth_ios/example/ios/RunnerTests/Info.plist ================================================ 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 ================================================ FILE: packages/local_auth/local_auth_ios/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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, avoid_print import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:local_auth_ios/local_auth_ios.dart'; import 'package:local_auth_platform_interface/local_auth_platform_interface.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { _SupportState _supportState = _SupportState.unknown; bool? _canCheckBiometrics; List? _enrolledBiometrics; String _authorized = 'Not Authorized'; bool _isAuthenticating = false; @override void initState() { super.initState(); LocalAuthPlatform.instance.isDeviceSupported().then( (bool isSupported) => setState(() => _supportState = isSupported ? _SupportState.supported : _SupportState.unsupported), ); } Future _checkBiometrics() async { late bool deviceSupportsBiometrics; try { deviceSupportsBiometrics = await LocalAuthPlatform.instance.deviceSupportsBiometrics(); } on PlatformException catch (e) { deviceSupportsBiometrics = false; print(e); } if (!mounted) { return; } setState(() { _canCheckBiometrics = deviceSupportsBiometrics; }); } Future _getEnrolledBiometrics() async { late List enrolledBiometrics; try { enrolledBiometrics = await LocalAuthPlatform.instance.getEnrolledBiometrics(); } on PlatformException catch (e) { enrolledBiometrics = []; print(e); } if (!mounted) { return; } setState(() { _enrolledBiometrics = enrolledBiometrics; }); } Future _authenticate() async { bool authenticated = false; try { setState(() { _isAuthenticating = true; _authorized = 'Authenticating'; }); authenticated = await LocalAuthPlatform.instance.authenticate( localizedReason: 'Let OS determine authentication method', authMessages: [const IOSAuthMessages()], options: const AuthenticationOptions( stickyAuth: true, ), ); setState(() { _isAuthenticating = false; }); } on PlatformException catch (e) { print(e); setState(() { _isAuthenticating = false; _authorized = 'Error - ${e.message}'; }); return; } if (!mounted) { return; } setState( () => _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 IOSAuthMessages()], 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); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Plugin example app'), ), body: ListView( padding: const EdgeInsets.only(top: 30), children: [ Column( mainAxisAlignment: MainAxisAlignment.center, children: [ if (_supportState == _SupportState.unknown) const CircularProgressIndicator() else if (_supportState == _SupportState.supported) const Text('This device is supported') else const Text('This device is not supported'), const Divider(height: 100), Text('Device supports biometrics: $_canCheckBiometrics\n'), ElevatedButton( onPressed: _checkBiometrics, child: const Text('Check biometrics'), ), const Divider(height: 100), Text('Enrolled biometrics: $_enrolledBiometrics\n'), ElevatedButton( onPressed: _getEnrolledBiometrics, child: const Text('Get enrolled biometrics'), ), const Divider(height: 100), Text('Current State: $_authorized\n'), 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 [ Text('Cancel Authentication'), Icon(Icons.cancel), ], ), ) else Column( 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 [ Text('Authenticate'), Icon(Icons.perm_device_information), ], ), ), ElevatedButton( onPressed: _authenticateWithBiometrics, child: Row( mainAxisSize: MainAxisSize.min, children: [ Text(_isAuthenticating ? 'Cancel' : 'Authenticate: biometrics only'), const Icon(Icons.fingerprint), ], ), ), ], ), ], ), ], ), ), ); } } enum _SupportState { unknown, supported, unsupported, } ================================================ FILE: packages/local_auth/local_auth_ios/example/pubspec.yaml ================================================ name: local_auth_ios_example description: Demonstrates how to use the local_auth_ios plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter local_auth_ios: # When depending on this package from a real application you should use: # local_auth: ^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: ../ local_auth_platform_interface: ^1.0.0 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/local_auth/local_auth_ios/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/local_auth/local_auth_ios/ios/Assets/.gitkeep ================================================ ================================================ FILE: packages/local_auth/local_auth_ios/ios/Classes/FLTLocalAuthPlugin.h ================================================ // Copyright 2013 The Flutter 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 FLTLocalAuthPlugin : NSObject @end ================================================ FILE: packages/local_auth/local_auth_ios/ios/Classes/FLTLocalAuthPlugin.m ================================================ // Copyright 2013 The Flutter 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 "FLTLocalAuthPlugin.h" @interface FLTLocalAuthPlugin () @property(nonatomic, copy, nullable) NSDictionary *lastCallArgs; @property(nonatomic, nullable) FlutterResult lastResult; // For unit tests to inject dummy LAContext instances that will be used when a new context would // normally be created. Each call to createAuthContext will remove the current first element from // the array. - (void)setAuthContextOverrides:(NSArray *)authContexts; @end @implementation FLTLocalAuthPlugin { NSMutableArray *_authContextOverrides; } + (void)registerWithRegistrar:(NSObject *)registrar { FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/local_auth_ios" binaryMessenger:[registrar messenger]]; FLTLocalAuthPlugin *instance = [[FLTLocalAuthPlugin alloc] init]; [registrar addMethodCallDelegate:instance channel:channel]; [registrar addApplicationDelegate:instance]; } - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if ([@"authenticate" isEqualToString:call.method]) { bool isBiometricOnly = [call.arguments[@"biometricOnly"] boolValue]; if (isBiometricOnly) { [self authenticateWithBiometrics:call.arguments withFlutterResult:result]; } else { [self authenticate:call.arguments withFlutterResult:result]; } } else if ([@"getEnrolledBiometrics" isEqualToString:call.method]) { [self getEnrolledBiometrics:result]; } else if ([@"deviceSupportsBiometrics" isEqualToString:call.method]) { [self deviceSupportsBiometrics:result]; } else if ([@"isDeviceSupported" isEqualToString:call.method]) { result(@YES); } else { result(FlutterMethodNotImplemented); } } #pragma mark Private Methods - (void)setAuthContextOverrides:(NSArray *)authContexts { _authContextOverrides = [authContexts mutableCopy]; } - (LAContext *)createAuthContext { if ([_authContextOverrides count] > 0) { LAContext *context = [_authContextOverrides firstObject]; [_authContextOverrides removeObjectAtIndex:0]; return context; } return [[LAContext alloc] init]; } - (void)alertMessage:(NSString *)message firstButton:(NSString *)firstButton flutterResult:(FlutterResult)result additionalButton:(NSString *)secondButton { UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:firstButton style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { result(@NO); }]; [alert addAction:defaultAction]; if (secondButton != nil) { UIAlertAction *additionalAction = [UIAlertAction actionWithTitle:secondButton style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { if (UIApplicationOpenSettingsURLString != NULL) { NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString]; if (@available(iOS 10, *)) { [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:NULL]; } else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [[UIApplication sharedApplication] openURL:url]; #pragma clang diagnostic pop } result(@NO); } }]; [alert addAction:additionalAction]; } [[UIApplication sharedApplication].delegate.window.rootViewController presentViewController:alert animated:YES completion:nil]; } - (void)deviceSupportsBiometrics:(FlutterResult)result { LAContext *context = self.createAuthContext; NSError *authError = nil; // Check if authentication with biometrics is possible. if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) { if (authError == nil) { result(@YES); return; } } // If not, check if it is because no biometrics are enrolled (but still present). if (authError != nil) { if (@available(iOS 11, *)) { if (authError.code == LAErrorBiometryNotEnrolled) { result(@YES); return; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" } else if (authError.code == LAErrorTouchIDNotEnrolled) { result(@YES); return; #pragma clang diagnostic pop } } result(@NO); } - (void)getEnrolledBiometrics:(FlutterResult)result { LAContext *context = self.createAuthContext; NSError *authError = nil; NSMutableArray *biometrics = [[NSMutableArray alloc] init]; if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) { if (authError == nil) { if (@available(iOS 11, *)) { if (context.biometryType == LABiometryTypeFaceID) { [biometrics addObject:@"face"]; } else if (context.biometryType == LABiometryTypeTouchID) { [biometrics addObject:@"fingerprint"]; } } else { [biometrics addObject:@"fingerprint"]; } } } result(biometrics); } - (void)authenticateWithBiometrics:(NSDictionary *)arguments withFlutterResult:(FlutterResult)result { LAContext *context = self.createAuthContext; NSError *authError = nil; self.lastCallArgs = nil; self.lastResult = nil; context.localizedFallbackTitle = arguments[@"localizedFallbackTitle"] == [NSNull null] ? nil : arguments[@"localizedFallbackTitle"]; if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) { [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:arguments[@"localizedReason"] reply:^(BOOL success, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ [self handleAuthReplyWithSuccess:success error:error flutterArguments:arguments flutterResult:result]; }); }]; } else { [self handleErrors:authError flutterArguments:arguments withFlutterResult:result]; } } - (void)authenticate:(NSDictionary *)arguments withFlutterResult:(FlutterResult)result { LAContext *context = self.createAuthContext; NSError *authError = nil; _lastCallArgs = nil; _lastResult = nil; context.localizedFallbackTitle = arguments[@"localizedFallbackTitle"] == [NSNull null] ? nil : arguments[@"localizedFallbackTitle"]; if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&authError]) { [context evaluatePolicy:kLAPolicyDeviceOwnerAuthentication localizedReason:arguments[@"localizedReason"] reply:^(BOOL success, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ [self handleAuthReplyWithSuccess:success error:error flutterArguments:arguments flutterResult:result]; }); }]; } else { [self handleErrors:authError flutterArguments:arguments withFlutterResult:result]; } } - (void)handleAuthReplyWithSuccess:(BOOL)success error:(NSError *)error flutterArguments:(NSDictionary *)arguments flutterResult:(FlutterResult)result { NSAssert([NSThread isMainThread], @"Response handling must be done on the main thread."); if (success) { result(@YES); } else { switch (error.code) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" // TODO(stuartmorgan): Remove the pragma and s/TouchID/Biometry/ in these constants when // iOS 10 support is dropped. The values are the same, only the names have changed. case LAErrorTouchIDNotAvailable: case LAErrorTouchIDNotEnrolled: case LAErrorTouchIDLockout: #pragma clang diagnostic pop case LAErrorUserFallback: case LAErrorPasscodeNotSet: case LAErrorAuthenticationFailed: [self handleErrors:error flutterArguments:arguments withFlutterResult:result]; return; case LAErrorSystemCancel: if ([arguments[@"stickyAuth"] boolValue]) { self->_lastCallArgs = arguments; self->_lastResult = result; } else { result(@NO); } return; } [self handleErrors:error flutterArguments:arguments withFlutterResult:result]; } } - (void)handleErrors:(NSError *)authError flutterArguments:(NSDictionary *)arguments withFlutterResult:(FlutterResult)result { NSString *errorCode = @"NotAvailable"; switch (authError.code) { case LAErrorPasscodeNotSet: #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" // TODO(stuartmorgan): Remove the pragma and s/TouchID/Biometry/ in this constant when // iOS 10 support is dropped. The values are the same, only the names have changed. case LAErrorTouchIDNotEnrolled: #pragma clang diagnostic pop if ([arguments[@"useErrorDialogs"] boolValue]) { [self alertMessage:arguments[@"goToSettingDescriptionIOS"] firstButton:arguments[@"okButton"] flutterResult:result additionalButton:arguments[@"goToSetting"]]; return; } errorCode = authError.code == LAErrorPasscodeNotSet ? @"PasscodeNotSet" : @"NotEnrolled"; break; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" // TODO(stuartmorgan): Remove the pragma and s/TouchID/Biometry/ in this constant when // iOS 10 support is dropped. The values are the same, only the names have changed. case LAErrorTouchIDLockout: #pragma clang diagnostic pop [self alertMessage:arguments[@"lockOut"] firstButton:arguments[@"okButton"] flutterResult:result additionalButton:nil]; return; } result([FlutterError errorWithCode:errorCode message:authError.localizedDescription details:authError.domain]); } #pragma mark - AppDelegate - (void)applicationDidBecomeActive:(UIApplication *)application { if (self.lastCallArgs != nil && self.lastResult != nil) { [self authenticateWithBiometrics:_lastCallArgs withFlutterResult:self.lastResult]; } } @end ================================================ FILE: packages/local_auth/local_auth_ios/ios/local_auth_ios.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| s.name = 'local_auth_ios' s.version = '0.0.1' s.summary = 'Flutter Local Auth' s.description = <<-DESC This Flutter plugin provides means to perform local, on-device authentication of the user. 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/local_auth' } s.documentation_url = 'https://pub.dev/packages/local_auth_ios' 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 ================================================ FILE: packages/local_auth/local_auth_ios/lib/local_auth_ios.dart ================================================ // Copyright 2013 The Flutter 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:local_auth_platform_interface/local_auth_platform_interface.dart'; import 'types/auth_messages_ios.dart'; export 'package:local_auth_ios/types/auth_messages_ios.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'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/local_auth_ios'); /// The implementation of [LocalAuthPlatform] for iOS. class LocalAuthIOS extends LocalAuthPlatform { /// Registers this class as the default instance of [LocalAuthPlatform]. static void registerWith() { LocalAuthPlatform.instance = LocalAuthIOS(); } @override Future authenticate({ required String localizedReason, required Iterable authMessages, 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 IOSAuthMessages().args); for (final AuthMessages messages in authMessages) { if (messages is IOSAuthMessages) { args.addAll(messages.args); } } return (await _channel.invokeMethod('authenticate', args)) ?? false; } @override Future deviceSupportsBiometrics() async { return (await _channel.invokeMethod('deviceSupportsBiometrics')) ?? false; } @override Future> getEnrolledBiometrics() async { final List result = (await _channel.invokeListMethod( 'getEnrolledBiometrics', )) ?? []; final List biometrics = []; for (final String value in result) { switch (value) { case 'face': biometrics.add(BiometricType.face); break; case 'fingerprint': biometrics.add(BiometricType.fingerprint); break; case 'iris': biometrics.add(BiometricType.iris); break; } } return biometrics; } @override Future isDeviceSupported() async => (await _channel.invokeMethod('isDeviceSupported')) ?? false; /// Always returns false as this method is not supported on iOS. @override Future stopAuthentication() async { return false; } } ================================================ FILE: packages/local_auth/local_auth_ios/lib/types/auth_messages_ios.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:intl/intl.dart'; import 'package:local_auth_platform_interface/types/auth_messages.dart'; /// Class wrapping all authentication messages needed on iOS. /// Provides default values for all messages. @immutable class IOSAuthMessages extends AuthMessages { /// Constructs a new instance. const IOSAuthMessages({ this.lockOut, this.goToSettingsButton, this.goToSettingsDescription, this.cancelButton, this.localizedFallbackTitle, }); /// Message advising the user to re-enable biometrics on their device. final String? lockOut; /// Message shown on a button that the user can click to go to settings pages /// from the current dialog. /// Maximum 30 characters. final String? goToSettingsButton; /// Message advising the user to go to the settings and configure Biometrics /// for their device. final String? goToSettingsDescription; /// Message shown on a button that the user can click to leave the current /// dialog. /// Maximum 30 characters. final String? cancelButton; /// The localized title for the fallback button in the dialog presented to /// the user during authentication. final String? localizedFallbackTitle; @override Map get args { return { 'lockOut': lockOut ?? iOSLockOut, 'goToSetting': goToSettingsButton ?? goToSettings, 'goToSettingDescriptionIOS': goToSettingsDescription ?? iOSGoToSettingsDescription, 'okButton': cancelButton ?? iOSOkButton, if (localizedFallbackTitle != null) 'localizedFallbackTitle': localizedFallbackTitle!, }; } @override bool operator ==(Object other) => identical(this, other) || other is IOSAuthMessages && runtimeType == other.runtimeType && lockOut == other.lockOut && goToSettingsButton == other.goToSettingsButton && goToSettingsDescription == other.goToSettingsDescription && cancelButton == other.cancelButton && localizedFallbackTitle == other.localizedFallbackTitle; @override int get hashCode => Object.hash( super.hashCode, lockOut, goToSettingsButton, goToSettingsDescription, cancelButton, localizedFallbackTitle, ); } // Default Strings for IOSAuthMessages plugin. Currently supports English. // Intl.message must be string literals. /// Message shown on a button that the user can click to go to settings pages /// from the current dialog. String get goToSettings => Intl.message('Go to settings', desc: 'Message shown on a button that the user can click to go to ' 'settings pages from the current dialog. Maximum 30 characters.'); /// Message advising the user to re-enable biometrics on their device. /// It shows in a dialog on iOS. String get iOSLockOut => Intl.message( 'Biometric authentication is disabled. Please lock and unlock your screen to ' 'enable it.', desc: 'Message advising the user to re-enable biometrics on their device.'); /// Message advising the user to go to the settings and configure Biometrics /// for their device. String get iOSGoToSettingsDescription => Intl.message( 'Biometric authentication is not set up on your device. Please either enable ' 'Touch ID or Face ID on your phone.', desc: 'Message advising the user to go to the settings and configure Biometrics ' 'for their device.'); /// Message shown on a button that the user can click to leave the current /// dialog. String get iOSOkButton => Intl.message('OK', desc: 'Message showed on a button that the user can click to leave the ' 'current dialog. Maximum 30 characters.'); ================================================ FILE: packages/local_auth/local_auth_ios/pubspec.yaml ================================================ name: local_auth_ios description: iOS implementation of the local_auth plugin. repository: https://github.com/flutter/plugins/tree/main/packages/local_auth/local_auth_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22 version: 1.0.12 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: local_auth platforms: ios: pluginClass: FLTLocalAuthPlugin dartPluginClass: LocalAuthIOS dependencies: flutter: sdk: flutter intl: ">=0.17.0 <0.19.0" local_auth_platform_interface: ^1.0.1 dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/local_auth/local_auth_ios/test/local_auth_test.dart ================================================ // Copyright 2013 The Flutter 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:local_auth_ios/local_auth_ios.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('LocalAuth', () { const MethodChannel channel = MethodChannel( 'plugins.flutter.io/local_auth_ios', ); final List log = []; late LocalAuthIOS localAuthentication; setUp(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, (MethodCall methodCall) { log.add(methodCall); switch (methodCall.method) { case 'getEnrolledBiometrics': return Future>.value( ['face', 'fingerprint', 'iris', 'undefined']); default: return Future.value(true); } }); localAuthentication = LocalAuthIOS(); log.clear(); }); test('deviceSupportsBiometrics calls platform', () async { final bool result = await localAuthentication.deviceSupportsBiometrics(); expect( log, [ isMethodCall('deviceSupportsBiometrics', arguments: null), ], ); expect(result, true); }); test('getEnrolledBiometrics calls platform', () async { final List result = await localAuthentication.getEnrolledBiometrics(); expect( log, [ isMethodCall('getEnrolledBiometrics', arguments: null), ], ); expect(result, [ BiometricType.face, BiometricType.fingerprint, BiometricType.iris ]); }); test('isDeviceSupported calls platform', () async { await localAuthentication.isDeviceSupported(); expect( log, [ isMethodCall('isDeviceSupported', arguments: null), ], ); }); test('stopAuthentication returns false', () async { final bool result = await localAuthentication.stopAuthentication(); expect(result, false); }); group('With device auth fail over', () { test('authenticate with no args.', () async { await localAuthentication.authenticate( authMessages: [const IOSAuthMessages()], localizedReason: 'Needs secure', options: const AuthenticationOptions(biometricOnly: true), ); expect( log, [ isMethodCall('authenticate', arguments: { 'localizedReason': 'Needs secure', 'useErrorDialogs': true, 'stickyAuth': false, 'sensitiveTransaction': true, 'biometricOnly': true, }..addAll(const IOSAuthMessages().args)), ], ); }); test('authenticate with no localizedReason.', () async { await expectLater( localAuthentication.authenticate( authMessages: [const IOSAuthMessages()], localizedReason: '', options: const AuthenticationOptions(biometricOnly: true), ), throwsAssertionError, ); }); }); group('With biometrics only', () { test('authenticate with no args.', () async { await localAuthentication.authenticate( authMessages: [const IOSAuthMessages()], localizedReason: 'Needs secure', ); expect( log, [ isMethodCall('authenticate', arguments: { 'localizedReason': 'Needs secure', 'useErrorDialogs': true, 'stickyAuth': false, 'sensitiveTransaction': true, 'biometricOnly': false, }..addAll(const IOSAuthMessages().args)), ], ); }); test('authenticate with `localizedFallbackTitle`', () async { await localAuthentication.authenticate( authMessages: [ const IOSAuthMessages(localizedFallbackTitle: 'Enter PIN'), ], localizedReason: 'Needs secure', ); expect( log, [ isMethodCall('authenticate', arguments: { 'localizedReason': 'Needs secure', 'useErrorDialogs': true, 'stickyAuth': false, 'sensitiveTransaction': true, 'biometricOnly': false, 'localizedFallbackTitle': 'Enter PIN', }..addAll(const IOSAuthMessages().args)), ], ); }); test('authenticate with no sensitive transaction.', () async { await localAuthentication.authenticate( authMessages: [const IOSAuthMessages()], localizedReason: 'Insecure', options: const AuthenticationOptions( sensitiveTransaction: false, useErrorDialogs: false, ), ); expect( log, [ isMethodCall('authenticate', arguments: { 'localizedReason': 'Insecure', 'useErrorDialogs': false, 'stickyAuth': false, 'sensitiveTransaction': false, 'biometricOnly': false, }..addAll(const IOSAuthMessages().args)), ], ); }); }); }); } /// 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; ================================================ FILE: packages/local_auth/local_auth_platform_interface/AUTHORS ================================================ # 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> Bodhi Mulders ================================================ FILE: packages/local_auth/local_auth_platform_interface/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 1.0.6 * Removes unused `intl` dependency. ## 1.0.5 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. ## 1.0.4 * Updates references to the obsolete master branch. * Removes unnecessary imports. ## 1.0.3 * Fixes regression in the default method channel implementation of `deviceSupportsBiometrics` from federation that would cause it to return true only if something is enrolled. ## 1.0.2 * Adopts `Object.hash`. ## 1.0.1 * Export externally used types from local_auth_platform_interface.dart directly. ## 1.0.0 * Initial release. ================================================ FILE: packages/local_auth/local_auth_platform_interface/LICENSE ================================================ 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. ================================================ FILE: packages/local_auth/local_auth_platform_interface/README.md ================================================ # local_auth_platform_interface A common platform interface for the [`local_auth`][1] plugin. This interface allows platform-specific implementations of the `local_auth` plugin, as well as the plugin itself, to ensure they are supporting the same interface. # Usage To implement a new platform-specific implementation of `local_auth`, extend [`LocalAuthPlatform`][2] with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `LocalAuthPlatform` by calling `LocalAuthPlatform.instance = MyLocalAuthPlatform()`. # Note on breaking changes Strongly prefer non-breaking changes (such as adding a method to the interface) over breaking changes for this package. See https://flutter.dev/go/platform-interface-breaking-changes for a discussion on why a less-clean interface is preferable to a breaking change. [1]: ../local_auth [2]: lib/local_auth_platform_interface.dart ================================================ FILE: packages/local_auth/local_auth_platform_interface/lib/default_method_channel_platform.dart ================================================ // Copyright 2013 The Flutter 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 'local_auth_platform_interface.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/local_auth'); /// The default interface implementation acting as a placeholder for /// the native implementation to be set. /// /// This implementation is not used by any of the implementations in this /// repository, and exists only for backward compatibility with any /// clients that were relying on internal details of the method channel /// in the pre-federated plugin. class DefaultLocalAuthPlatform extends LocalAuthPlatform { @override Future authenticate({ required String localizedReason, required Iterable authMessages, 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, }; for (final AuthMessages messages in authMessages) { args.addAll(messages.args); } return (await _channel.invokeMethod('authenticate', args)) ?? false; } @override Future> getEnrolledBiometrics() async { final List result = (await _channel.invokeListMethod( 'getAvailableBiometrics', )) ?? []; final List biometrics = []; for (final String value in result) { switch (value) { case 'face': biometrics.add(BiometricType.face); break; case 'fingerprint': biometrics.add(BiometricType.fingerprint); break; case 'iris': biometrics.add(BiometricType.iris); break; case 'undefined': // Sentinel value for the case when nothing is enrolled, but hardware // support for biometrics is available. break; } } return biometrics; } @override Future deviceSupportsBiometrics() async { final List availableBiometrics = (await _channel.invokeListMethod( 'getAvailableBiometrics', )) ?? []; // If anything, including the 'undefined' sentinel, is returned, then there // is device support for biometrics. return availableBiometrics.isNotEmpty; } @override Future isDeviceSupported() async => (await _channel.invokeMethod('isDeviceSupported')) ?? false; @override Future stopAuthentication() async => await _channel.invokeMethod('stopAuthentication') ?? false; } ================================================ FILE: packages/local_auth/local_auth_platform_interface/lib/local_auth_platform_interface.dart ================================================ // Copyright 2013 The Flutter 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:plugin_platform_interface/plugin_platform_interface.dart'; import 'default_method_channel_platform.dart'; import 'types/types.dart'; export 'package:local_auth_platform_interface/types/types.dart'; /// The interface that implementations of local_auth must implement. /// /// Platform implementations should extend this class rather than implement it as `local_auth` /// does not consider newly added methods to be breaking changes. Extending this class /// (using `extends`) ensures that the subclass will get the default implementation, while /// platform implementations that `implements` this interface will be broken by newly added /// [LocalAuthPlatform] methods. abstract class LocalAuthPlatform extends PlatformInterface { /// Constructs a LocalAuthPlatform. LocalAuthPlatform() : super(token: _token); static final Object _token = Object(); static LocalAuthPlatform _instance = DefaultLocalAuthPlatform(); /// The default instance of [LocalAuthPlatform] to use. /// /// Defaults to [DefaultLocalAuthPlatform]. static LocalAuthPlatform get instance => _instance; /// Platform-specific implementations should set this with their own /// platform-specific class that extends [LocalAuthPlatform] when they /// register themselves. static set instance(LocalAuthPlatform instance) { PlatformInterface.verifyToken(instance, _token); _instance = instance; } /// Authenticates the user with biometrics available on the device while also /// allowing the user to use device authentication - pin, pattern, passcode. /// /// Returns true if the user successfully authenticated, false otherwise. /// /// [localizedReason] is the message to show to user while prompting them /// for authentication. This is typically along the lines of: 'Please scan /// your finger to access MyApp.'. This must not be empty. /// /// Provide [authMessages] if you want to /// customize messages in the dialogs. /// /// Provide [options] for configuring further authentication related options. /// /// Throws a [PlatformException] if there were technical problems with local /// authentication (e.g. lack of relevant hardware). This might throw /// [PlatformException] with error code [otherOperatingSystem] on the iOS /// simulator. Future authenticate({ required String localizedReason, required Iterable authMessages, AuthenticationOptions options = const AuthenticationOptions(), }) async { throw UnimplementedError('authenticate() has not been implemented.'); } /// Returns true if the device is capable of checking biometrics. /// /// This will return true even if there are no biometrics currently enrolled. Future deviceSupportsBiometrics() async { throw UnimplementedError('canCheckBiometrics() has not been implemented.'); } /// Returns a list of enrolled biometrics. /// /// Possible values include: /// - BiometricType.face /// - BiometricType.fingerprint /// - BiometricType.iris (not yet implemented) /// - BiometricType.strong /// - BiometricType.weak Future> getEnrolledBiometrics() async { throw UnimplementedError( 'getAvailableBiometrics() has not been implemented.'); } /// Returns true if device is capable of checking biometrics or is able to /// fail over to device credentials. Future isDeviceSupported() async { throw UnimplementedError('isDeviceSupported() has not been implemented.'); } /// Cancels any authentication currently in progress. /// /// Returns true if auth was cancelled successfully. /// Returns false if there was no authentication in progress, /// or an error occurred. Future stopAuthentication() async { throw UnimplementedError('stopAuthentication() has not been implemented.'); } } ================================================ FILE: packages/local_auth/local_auth_platform_interface/lib/types/auth_messages.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Abstract class for storing platform specific strings. abstract class AuthMessages { /// Constructs an instance of [AuthMessages]. const AuthMessages(); /// Returns all platform-specific messages as a map. Map get args; } ================================================ FILE: packages/local_auth/local_auth_platform_interface/lib/types/auth_options.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; /// Options wrapper for [LocalAuthPlatform.authenticate] parameters. @immutable class AuthenticationOptions { /// Constructs a new instance. const AuthenticationOptions({ this.useErrorDialogs = true, this.stickyAuth = false, this.sensitiveTransaction = true, this.biometricOnly = false, }); /// Whether the system will attempt to handle user-fixable issues encountered /// while authenticating. For instance, if a fingerprint reader exists on the /// device but there's no fingerprint registered, the plugin might attempt to /// take the user to settings to add one. Anything that is not user fixable, /// such as no biometric sensor on device, will still result in /// a [PlatformException]. final bool useErrorDialogs; /// Used when the application goes into background for any reason while the /// authentication is in progress. Due to security reasons, the /// authentication has to be stopped at that time. If stickyAuth is set to /// true, authentication resumes when the app is resumed. If it is set to /// false (default), then as soon as app is paused a failure message is sent /// back to Dart and it is up to the client app to restart authentication or /// do something else. final bool stickyAuth; /// Whether platform specific precautions are enabled. For instance, on face /// unlock, Android opens a confirmation dialog after the face is recognized /// to make sure the user meant to unlock their device. final bool sensitiveTransaction; /// Prevent authentications from using non-biometric local authentication /// such as pin, passcode, or pattern. final bool biometricOnly; @override bool operator ==(Object other) => identical(this, other) || other is AuthenticationOptions && runtimeType == other.runtimeType && useErrorDialogs == other.useErrorDialogs && stickyAuth == other.stickyAuth && sensitiveTransaction == other.sensitiveTransaction && biometricOnly == other.biometricOnly; @override int get hashCode => Object.hash( useErrorDialogs, stickyAuth, sensitiveTransaction, biometricOnly, ); } ================================================ FILE: packages/local_auth/local_auth_platform_interface/lib/types/biometric_type.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Various types of biometric authentication. /// Some platforms report specific biometric types, while others report only /// classifications like strong and weak. enum BiometricType { /// Face authentication. face, /// Fingerprint authentication. fingerprint, /// Iris authentication. iris, /// Any biometric (e.g. fingerprint, iris, or face) on the device that the /// platform API considers to be strong. For example, on Android this /// corresponds to Class 3. strong, /// Any biometric (e.g. fingerprint, iris, or face) on the device that the /// platform API considers to be weak. For example, on Android this /// corresponds to Class 2. weak, } ================================================ FILE: packages/local_auth/local_auth_platform_interface/lib/types/types.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'auth_messages.dart'; export 'auth_options.dart'; export 'biometric_type.dart'; ================================================ FILE: packages/local_auth/local_auth_platform_interface/pubspec.yaml ================================================ name: local_auth_platform_interface description: A common platform interface for the local_auth plugin. repository: https://github.com/flutter/plugins/tree/main/packages/local_auth/local_auth_platform_interface issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%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: 1.0.6 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.1.2 dev_dependencies: flutter_test: sdk: flutter mockito: ^5.0.0 ================================================ FILE: packages/local_auth/local_auth_platform_interface/test/default_method_channel_platform_test.dart ================================================ // Copyright 2013 The Flutter 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:local_auth_platform_interface/default_method_channel_platform.dart'; import 'package:local_auth_platform_interface/local_auth_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); const MethodChannel channel = MethodChannel( 'plugins.flutter.io/local_auth', ); late List log; late LocalAuthPlatform localAuthentication; setUp(() async { log = []; }); test( 'DefaultLocalAuthPlatform is registered as the default platform implementation', () async { expect(LocalAuthPlatform.instance, const TypeMatcher()); }); test('getAvailableBiometrics', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, (MethodCall methodCall) { log.add(methodCall); return Future.value([]); }); localAuthentication = DefaultLocalAuthPlatform(); await localAuthentication.getEnrolledBiometrics(); expect( log, [ isMethodCall('getAvailableBiometrics', arguments: null), ], ); }); test('deviceSupportsBiometrics handles special sentinal value', () async { // The pre-federation implementation of the platform channels, which the // default implementation retains compatibility with for the benefit of any // existing unendorsed implementations, used 'undefined' as a special // return value from `getAvailableBiometrics` to indicate that nothing was // enrolled, but that the hardware does support biometrics. _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, (MethodCall methodCall) { log.add(methodCall); return Future.value(['undefined']); }); localAuthentication = DefaultLocalAuthPlatform(); final bool supportsBiometrics = await localAuthentication.deviceSupportsBiometrics(); expect(supportsBiometrics, true); expect( log, [ isMethodCall('getAvailableBiometrics', arguments: null), ], ); }); group('Boolean returning methods', () { setUp(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, (MethodCall methodCall) { log.add(methodCall); return Future.value(true); }); localAuthentication = DefaultLocalAuthPlatform(); }); test('isDeviceSupported', () async { await localAuthentication.isDeviceSupported(); expect( log, [ isMethodCall('isDeviceSupported', arguments: null), ], ); }); test('stopAuthentication', () async { await localAuthentication.stopAuthentication(); expect( log, [ isMethodCall('stopAuthentication', arguments: null), ], ); }); group('authenticate with device auth fail over', () { test('authenticate with no args.', () async { await localAuthentication.authenticate( authMessages: [], localizedReason: 'Needs secure', options: const AuthenticationOptions(biometricOnly: true), ); expect( log, [ isMethodCall( 'authenticate', arguments: { 'localizedReason': 'Needs secure', 'useErrorDialogs': true, 'stickyAuth': false, 'sensitiveTransaction': true, 'biometricOnly': true, }, ), ], ); }); test('authenticate with no sensitive transaction.', () async { await localAuthentication.authenticate( authMessages: [], localizedReason: 'Insecure', options: const AuthenticationOptions( sensitiveTransaction: false, useErrorDialogs: false, biometricOnly: true, ), ); expect( log, [ isMethodCall( 'authenticate', arguments: { 'localizedReason': 'Insecure', 'useErrorDialogs': false, 'stickyAuth': false, 'sensitiveTransaction': false, 'biometricOnly': true, }, ), ], ); }); }); group('authenticate with biometrics only', () { test('authenticate with no args.', () async { await localAuthentication.authenticate( authMessages: [], localizedReason: 'Needs secure', ); expect( log, [ isMethodCall( 'authenticate', arguments: { 'localizedReason': 'Needs secure', 'useErrorDialogs': true, 'stickyAuth': false, 'sensitiveTransaction': true, 'biometricOnly': false, }, ), ], ); }); test('authenticate with no sensitive transaction.', () async { await localAuthentication.authenticate( authMessages: [], localizedReason: 'Insecure', options: const AuthenticationOptions( sensitiveTransaction: false, useErrorDialogs: false, ), ); expect( log, [ isMethodCall( 'authenticate', arguments: { 'localizedReason': 'Insecure', 'useErrorDialogs': false, 'stickyAuth': false, 'sensitiveTransaction': false, 'biometricOnly': false, }, ), ], ); }); }); }); } /// 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; ================================================ FILE: packages/local_auth/local_auth_windows/AUTHORS ================================================ # 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. Alexandre Zollinger Chohfi ================================================ FILE: packages/local_auth/local_auth_windows/CHANGELOG.md ================================================ ## 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`. * Updates minimum Flutter version to 2.10. ## 1.0.3 * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 1.0.2 * Updates `local_auth_platform_interface` constraint to the correct minimum version. ## 1.0.1 * Updates references to the obsolete master branch. ## 1.0.0 * Initial release of Windows support. ================================================ FILE: packages/local_auth/local_auth_windows/LICENSE ================================================ 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. ================================================ FILE: packages/local_auth/local_auth_windows/README.md ================================================ # local\_auth\_windows The Windows implementation of [`local_auth`][1]. ## Usage This package is [endorsed][2], which means you can simply use `local_auth` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/local_auth [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/local_auth/local_auth_windows/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Android Studio will place build artifacts here /android/app/debug /android/app/profile /android/app/release ================================================ FILE: packages/local_auth/local_auth_windows/example/.metadata ================================================ # 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: c860cba910319332564e1e9d470a17074c1f2dfd channel: stable project_type: app ================================================ FILE: packages/local_auth/local_auth_windows/example/README.md ================================================ # 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. ================================================ FILE: packages/local_auth/local_auth_windows/example/integration_test/local_auth_test.dart ================================================ // Copyright 2013 The Flutter 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:local_auth_windows/local_auth_windows.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('canCheckBiometrics', (WidgetTester tester) async { expect( LocalAuthWindows().getEnrolledBiometrics(), completion(isList), ); }); } ================================================ FILE: packages/local_auth/local_auth_windows/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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, avoid_print import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:local_auth_platform_interface/local_auth_platform_interface.dart'; import 'package:local_auth_windows/local_auth_windows.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { _SupportState _supportState = _SupportState.unknown; bool? _deviceSupportsBiometrics; List? _enrolledBiometrics; String _authorized = 'Not Authorized'; bool _isAuthenticating = false; @override void initState() { super.initState(); LocalAuthPlatform.instance.isDeviceSupported().then( (bool isSupported) => setState(() => _supportState = isSupported ? _SupportState.supported : _SupportState.unsupported), ); } Future _checkBiometrics() async { late bool deviceSupportsBiometrics; try { deviceSupportsBiometrics = await LocalAuthPlatform.instance.deviceSupportsBiometrics(); } on PlatformException catch (e) { deviceSupportsBiometrics = false; print(e); } if (!mounted) { return; } setState(() { _deviceSupportsBiometrics = deviceSupportsBiometrics; }); } Future _getEnrolledBiometrics() async { late List availableBiometrics; try { availableBiometrics = await LocalAuthPlatform.instance.getEnrolledBiometrics(); } on PlatformException catch (e) { availableBiometrics = []; print(e); } if (!mounted) { return; } setState(() { _enrolledBiometrics = availableBiometrics; }); } Future _authenticate() async { bool authenticated = false; try { setState(() { _isAuthenticating = true; _authorized = 'Authenticating'; }); authenticated = await LocalAuthPlatform.instance.authenticate( localizedReason: 'Let OS determine authentication method', authMessages: [const WindowsAuthMessages()], options: const AuthenticationOptions( stickyAuth: true, ), ); setState(() { _isAuthenticating = false; }); } on PlatformException catch (e) { print(e); setState(() { _isAuthenticating = false; _authorized = 'Error - ${e.message}'; }); return; } if (!mounted) { return; } setState( () => _authorized = authenticated ? 'Authorized' : 'Not Authorized'); } Future _cancelAuthentication() async { await LocalAuthPlatform.instance.stopAuthentication(); setState(() => _isAuthenticating = false); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Plugin example app'), ), body: ListView( padding: const EdgeInsets.only(top: 30), children: [ Column( mainAxisAlignment: MainAxisAlignment.center, children: [ if (_supportState == _SupportState.unknown) const CircularProgressIndicator() else if (_supportState == _SupportState.supported) const Text('This device is supported') else const Text('This device is not supported'), const Divider(height: 100), Text( 'Device supports biometrics: $_deviceSupportsBiometrics\n'), ElevatedButton( onPressed: _checkBiometrics, child: const Text('Check biometrics'), ), const Divider(height: 100), Text('Enrolled biometrics: $_enrolledBiometrics\n'), ElevatedButton( onPressed: _getEnrolledBiometrics, child: const Text('Get enrolled biometrics'), ), const Divider(height: 100), Text('Current State: $_authorized\n'), 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 [ Text('Cancel Authentication'), Icon(Icons.cancel), ], ), ) else Column( 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 [ Text('Authenticate'), Icon(Icons.perm_device_information), ], ), ), ], ), ], ), ], ), ), ); } } enum _SupportState { unknown, supported, unsupported, } ================================================ FILE: packages/local_auth/local_auth_windows/example/pubspec.yaml ================================================ name: local_auth_windows_example description: Demonstrates how to use the local_auth_windows plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter local_auth_platform_interface: ^1.0.0 local_auth_windows: # When depending on this package from a real application you should use: # local_auth_windows: ^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: ../ dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/local_auth/local_auth_windows/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/local_auth/local_auth_windows/example/windows/.gitignore ================================================ flutter/ephemeral/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: packages/local_auth/local_auth_windows/example/windows/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) project(local_auth_windows_example LANGUAGES CXX) set(BINARY_NAME "local_auth_windows_example") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" CACHE STRING "" FORCE) else() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() endif() set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") # Use Unicode for all projects. add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") target_compile_options(${TARGET} PRIVATE /EHsc) target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build add_subdirectory("runner") # Enable the test target. set(include_local_auth_windows_tests TRUE) # Provide an alias for the test target using the name expected by repo tooling. add_custom_target(unit_tests DEPENDS local_auth_windows_test) # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # Support files are copied into place next to the executable, so that it can # run in place. This is done instead of making a separate bundle (as on Linux) # so that building and running from within Visual Studio will work. set(BUILD_BUNDLE_DIR "$") # Make the "install" step default, as it's required to run. set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" CONFIGURATIONS Profile;Release COMPONENT Runtime) ================================================ FILE: packages/local_auth/local_auth_windows/example/windows/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "flutter_export.h" "flutter_windows.h" "flutter_messenger.h" "flutter_plugin_registrar.h" "flutter_texture_registrar.h" ) list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") add_dependencies(flutter flutter_assemble) # === Wrapper === list(APPEND CPP_WRAPPER_SOURCES_CORE "core_implementations.cc" "standard_codec.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_PLUGIN "plugin_registrar.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_APP "flutter_engine.cc" "flutter_view_controller.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") # Wrapper sources needed for a plugin. add_library(flutter_wrapper_plugin STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ) apply_standard_settings(flutter_wrapper_plugin) set_target_properties(flutter_wrapper_plugin PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(flutter_wrapper_plugin PROPERTIES CXX_VISIBILITY_PRESET hidden) target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) target_include_directories(flutter_wrapper_plugin PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_plugin flutter_assemble) # Wrapper sources needed for the runner. add_library(flutter_wrapper_app STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_APP} ) apply_standard_settings(flutter_wrapper_app) target_link_libraries(flutter_wrapper_app PUBLIC flutter) target_include_directories(flutter_wrapper_app PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_app flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" windows-x64 $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ) ================================================ FILE: packages/local_auth/local_auth_windows/example/windows/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST local_auth_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/local_auth/local_auth_windows/example/windows/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.14) project(runner LANGUAGES CXX) add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) apply_standard_settings(${BINARY_NAME}) target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") add_dependencies(${BINARY_NAME} flutter_assemble) ================================================ FILE: packages/local_auth/local_auth_windows/example/windows/runner/Runner.rc ================================================ // Microsoft Visual C++ generated resource script. // #pragma code_page(65001) #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APP_ICON ICON "resources\\app_icon.ico" ///////////////////////////////////////////////////////////////////////////// // // Version // #ifdef FLUTTER_BUILD_NUMBER #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER #else #define VERSION_AS_NUMBER 1,0,0 #endif #ifdef FLUTTER_BUILD_NAME #define VERSION_AS_STRING #FLUTTER_BUILD_NAME #else #define VERSION_AS_STRING "1.0.0" #endif VS_VERSION_INFO VERSIONINFO FILEVERSION VERSION_AS_NUMBER PRODUCTVERSION VERSION_AS_NUMBER FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "com.example" "\0" VALUE "FileDescription", "example" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "example" "\0" VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0" VALUE "OriginalFilename", "example.exe" "\0" VALUE "ProductName", "example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: packages/local_auth/local_auth_windows/example/windows/runner/flutter_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "flutter_window.h" #include #include "flutter/generated_plugin_registrant.h" FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {} FlutterWindow::~FlutterWindow() {} bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opportunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) { return *result; } } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); } ================================================ FILE: packages/local_auth/local_auth_windows/example/windows/runner/flutter_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_ #include #include #include #include "win32_window.h" // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { public: // Creates a new FlutterWindow hosting a Flutter view running |project|. explicit FlutterWindow(const flutter::DartProject& project); virtual ~FlutterWindow(); protected: // Win32Window: bool OnCreate() override; void OnDestroy() override; LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; private: // The project to run. flutter::DartProject project_; // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; }; #endif // RUNNER_FLUTTER_WINDOW_H_ ================================================ FILE: packages/local_auth/local_auth_windows/example/windows/runner/main.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include "flutter_window.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t* command_line, _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { CreateAndAttachConsole(); } // Initialize COM, so that it is available for use in the library and/or // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); flutter::DartProject project(L"data"); std::vector command_line_arguments = GetCommandLineArguments(); project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.CreateAndShow(L"local_auth_windows_example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); ::MSG msg; while (::GetMessage(&msg, nullptr, 0, 0)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } ::CoUninitialize(); return EXIT_SUCCESS; } ================================================ FILE: packages/local_auth/local_auth_windows/example/windows/runner/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Runner.rc // #define IDI_APP_ICON 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: packages/local_auth/local_auth_windows/example/windows/runner/runner.exe.manifest ================================================ PerMonitorV2 ================================================ FILE: packages/local_auth/local_auth_windows/example/windows/runner/utils.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "utils.h" #include #include #include #include #include void CreateAndAttachConsole() { if (::AllocConsole()) { FILE* unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } if (freopen_s(&unused, "CONOUT$", "w", stderr)) { _dup2(_fileno(stdout), 2); } std::ios::sync_with_stdio(); FlutterDesktopResyncOutputStreams(); } } std::vector GetCommandLineArguments() { // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. int argc; wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); if (argv == nullptr) { return std::vector(); } std::vector command_line_arguments; // Skip the first argument as it's the binary name. for (int i = 1; i < argc; i++) { command_line_arguments.push_back(Utf8FromUtf16(argv[i])); } ::LocalFree(argv); return command_line_arguments; } std::string Utf8FromUtf16(const wchar_t* utf16_string) { if (utf16_string == nullptr) { return std::string(); } int target_length = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, nullptr, 0, nullptr, nullptr); if (target_length == 0) { return std::string(); } std::string utf8_string; utf8_string.resize(target_length); int converted_length = ::WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, utf8_string.data(), target_length, nullptr, nullptr); if (converted_length == 0) { return std::string(); } return utf8_string; } ================================================ FILE: packages/local_auth/local_auth_windows/example/windows/runner/utils.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_UTILS_H_ #define RUNNER_UTILS_H_ #include #include // Creates a console for the process, and redirects stdout and stderr to // it for both the runner and the Flutter library. void CreateAndAttachConsole(); // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string // encoded in UTF-8. Returns an empty std::string on failure. std::string Utf8FromUtf16(const wchar_t* utf16_string); // Gets the command line arguments passed in as a std::vector, // encoded in UTF-8. Returns an empty std::vector on failure. std::vector GetCommandLineArguments(); #endif // RUNNER_UTILS_H_ ================================================ FILE: packages/local_auth/local_auth_windows/example/windows/runner/win32_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "win32_window.h" #include #include "resource.h" namespace { constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); // Scale helper to convert logical scaler values to physical using passed in // scale factor int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); if (!user32_module) { return; } auto enable_non_client_dpi_scaling = reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); FreeLibrary(user32_module); } } } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; // Returns the singleton registar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; } // Returns the name of the window class, registering the class if it hasn't // previously been registered. const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; bool class_registered_ = false; }; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; RegisterClass(&window_class); class_registered_ = true; } return kWindowClassName; } void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, const Size& size) { Destroy(); const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), static_cast(origin.y)}; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } return OnCreate(); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { case WM_DESTROY: window_handle_ = nullptr; Destroy(); if (quit_on_close_) { PostQuitMessage(0); } return 0; case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left; LONG newHeight = newRectSize->bottom - newRectSize->top; SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_SIZE: { RECT rect = GetClientArea(); if (child_content_ != nullptr) { // Size and position the child window. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); } return 0; } case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_); } return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } void Win32Window::Destroy() { OnDestroy(); if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, true); SetFocus(child_content_); } RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } void Win32Window::OnDestroy() { // No-op; provided for subclasses. } ================================================ FILE: packages/local_auth/local_auth_windows/example/windows/runner/win32_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_ #include #include #include #include // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { public: struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) : width(width), height(height) {} }; Win32Window(); virtual ~Win32Window(); // Creates and shows a win32 window with |title| and position and size using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a // consistent size to will treat the width height passed in to this function // as logical pixels and scale to appropriate for the default monitor. Returns // true if the window was created successfully. bool CreateAndShow(const std::wstring& title, const Point& origin, const Size& size); // Release OS resources associated with window. void Destroy(); // Inserts |content| into the window tree. void SetChildContent(HWND content); // Returns the backing Window handle to enable clients to set icon and other // window properties. Returns nullptr if the window has been destroyed. HWND GetHandle(); // If true, closing this window will quit the application. void SetQuitOnClose(bool quit_on_close); // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Called when CreateAndShow is called, allowing subclass window-related // setup. Subclasses should return false if setup fails. virtual bool OnCreate(); // Called when Destroy is called. virtual void OnDestroy(); private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically // responsponds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; bool quit_on_close_ = false; // window handle for top level window. HWND window_handle_ = nullptr; // window handle for hosted content. HWND child_content_ = nullptr; }; #endif // RUNNER_WIN32_WINDOW_H_ ================================================ FILE: packages/local_auth/local_auth_windows/lib/local_auth_windows.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:local_auth_platform_interface/local_auth_platform_interface.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'; /// 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(); } @override Future authenticate({ required String localizedReason, required Iterable authMessages, AuthenticationOptions options = const AuthenticationOptions(), }) async { assert(localizedReason.isNotEmpty); if (options.biometricOnly) { throw UnsupportedError( "Windows doesn't support the biometricOnly parameter."); } return _api.authenticate(localizedReason); } @override Future deviceSupportsBiometrics() async { // Biometrics are supported on any supported device. return isDeviceSupported(); } @override Future> getEnrolledBiometrics() async { // 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 []; } @override Future isDeviceSupported() async => _api.isDeviceSupported(); /// Always returns false as this method is not supported on Windows. @override Future stopAuthentication() async => false; } ================================================ FILE: packages/local_auth/local_auth_windows/lib/src/messages.g.dart ================================================ // Copyright 2013 The Flutter 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?)!; } } } ================================================ FILE: packages/local_auth/local_auth_windows/lib/types/auth_messages_windows.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:local_auth_platform_interface/types/auth_messages.dart'; /// Windows side authentication messages. /// /// Provides default values for all messages. /// /// Currently unused. @immutable class WindowsAuthMessages extends AuthMessages { /// Constructs a new instance. const WindowsAuthMessages(); @override Map get args { return {}; } } ================================================ FILE: packages/local_auth/local_auth_windows/pigeons/copyright.txt ================================================ Copyright 2013 The Flutter Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. ================================================ FILE: packages/local_auth/local_auth_windows/pigeons/messages.dart ================================================ // Copyright 2013 The Flutter 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); } ================================================ FILE: packages/local_auth/local_auth_windows/pubspec.yaml ================================================ 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.5 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: local_auth platforms: windows: pluginClass: LocalAuthPlugin dartPluginClass: LocalAuthWindows dependencies: flutter: sdk: flutter local_auth_platform_interface: ^1.0.1 dev_dependencies: flutter_test: sdk: flutter pigeon: ^5.0.1 ================================================ FILE: packages/local_auth/local_auth_windows/test/local_auth_test.dart ================================================ // Copyright 2013 The Flutter 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:local_auth_windows/local_auth_windows.dart'; import 'package:local_auth_windows/src/messages.g.dart'; void main() { group('authenticate', () { late _FakeLocalAuthApi api; late LocalAuthWindows plugin; setUp(() { api = _FakeLocalAuthApi(); plugin = LocalAuthWindows(api: api); }); test('authenticate handles success', () async { api.returnValue = true; final bool result = await plugin.authenticate( authMessages: [const WindowsAuthMessages()], localizedReason: 'My localized reason'); 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 throws for biometricOnly', () async { expect( 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; } } ================================================ FILE: packages/local_auth/local_auth_windows/windows/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) set(PROJECT_NAME "local_auth_windows") set(WIL_VERSION "1.0.220201.1") set(CPPWINRT_VERSION "2.0.220418.1") project(${PROJECT_NAME} LANGUAGES CXX) include(FetchContent) set(PLUGIN_NAME "${PROJECT_NAME}_plugin") FetchContent_Declare(nuget URL "https://dist.nuget.org/win-x86-commandline/v6.0.0/nuget.exe" URL_HASH SHA256=04eb6c4fe4213907e2773e1be1bbbd730e9a655a3c9c58387ce8d4a714a5b9e1 DOWNLOAD_NO_EXTRACT true ) find_program(NUGET nuget) if (NOT NUGET) message("Nuget.exe not found, trying to download or use cached version.") FetchContent_MakeAvailable(nuget) set(NUGET ${nuget_SOURCE_DIR}/nuget.exe) endif() execute_process(COMMAND ${NUGET} install Microsoft.Windows.ImplementationLibrary -Version ${WIL_VERSION} -OutputDirectory ${CMAKE_BINARY_DIR}/packages WORKING_DIRECTORY ${CMAKE_BINARY_DIR} RESULT_VARIABLE ret) if (NOT ret EQUAL 0) message(FATAL_ERROR "Failed to install nuget package Microsoft.Windows.ImplementationLibrary.${WIL_VERSION}") endif() execute_process(COMMAND ${NUGET} install Microsoft.Windows.CppWinRT -Version ${CPPWINRT_VERSION} -OutputDirectory packages WORKING_DIRECTORY ${CMAKE_BINARY_DIR} RESULT_VARIABLE ret) if (NOT ret EQUAL 0) message(FATAL_ERROR "Failed to install nuget package Microsoft.Windows.CppWinRT.${CPPWINRT_VERSION}") endif() set(CPPWINRT ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT.${CPPWINRT_VERSION}/bin/cppwinrt.exe) execute_process(COMMAND ${CPPWINRT} -input sdk -output include WORKING_DIRECTORY ${CMAKE_BINARY_DIR} RESULT_VARIABLE ret) if (NOT ret EQUAL 0) message(FATAL_ERROR "Failed to run cppwinrt.exe") endif() 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" ${PLUGIN_SOURCES} ) apply_standard_settings(${PLUGIN_NAME}) set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden) target_compile_features(${PLUGIN_NAME} PRIVATE cxx_std_20) target_compile_options(${PLUGIN_NAME} PRIVATE /await) target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) target_include_directories(${PLUGIN_NAME} INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(${PLUGIN_NAME} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.ImplementationLibrary.${WIL_VERSION}/build/native/Microsoft.Windows.ImplementationLibrary.targets) target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin windowsapp) # List of absolute paths to libraries that should be bundled with the plugin set(file_chooser_bundled_libraries "" PARENT_SCOPE ) # === Tests === if (${include_${PROJECT_NAME}_tests}) set(TEST_RUNNER "${PROJECT_NAME}_test") enable_testing() # TODO(stuartmorgan): Consider using a single shared, pre-checked-in googletest # instance rather than downloading for each plugin. This approach makes sense # for a template, but not for a monorepo with many plugins. FetchContent_Declare( googletest URL https://github.com/google/googletest/archive/release-1.11.0.zip ) # Prevent overriding the parent project's compiler/linker settings set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Disable install commands for gtest so it doesn't end up in the bundle. set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE) FetchContent_MakeAvailable(googletest) # The plugin's C API is not very useful for unit testing, so build the sources # directly into the test binary rather than using the DLL. add_executable(${TEST_RUNNER} test/mocks.h test/local_auth_plugin_test.cpp ${PLUGIN_SOURCES} ) apply_standard_settings(${TEST_RUNNER}) target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") target_compile_features(${TEST_RUNNER} PRIVATE cxx_std_20) target_compile_options(${TEST_RUNNER} PRIVATE /await) target_link_libraries(${TEST_RUNNER} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.ImplementationLibrary.${WIL_VERSION}/build/native/Microsoft.Windows.ImplementationLibrary.targets) target_link_libraries(${TEST_RUNNER} PRIVATE flutter_wrapper_plugin) target_link_libraries(${TEST_RUNNER} PRIVATE windowsapp) target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock) # flutter_wrapper_plugin has link dependencies on the Flutter DLL. add_custom_command(TARGET ${TEST_RUNNER} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${FLUTTER_LIBRARY}" $ ) include(GoogleTest) gtest_discover_tests(${TEST_RUNNER}) endif() ================================================ FILE: packages/local_auth/local_auth_windows/windows/include/local_auth_windows/local_auth_plugin.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef FLUTTER_PLUGIN_LOCAL_AUTH_WINDOWS_PLUGIN_H_ #define FLUTTER_PLUGIN_LOCAL_AUTH_WINDOWS_PLUGIN_H_ #include #ifdef FLUTTER_PLUGIN_IMPL #define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) #else #define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) #endif #if defined(__cplusplus) extern "C" { #endif FLUTTER_PLUGIN_EXPORT void LocalAuthPluginRegisterWithRegistrar( FlutterDesktopPluginRegistrarRef registrar); #if defined(__cplusplus) } // extern "C" #endif #endif // FLUTTER_PLUGIN_LOCAL_AUTH_WINDOWS_PLUGIN_H_ ================================================ FILE: packages/local_auth/local_auth_windows/windows/local_auth.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include #include #include // Include prior to C++/WinRT Headers #include #include #include #include #include #include #include #include #include "messages.g.h" namespace local_auth_windows { // Abstract class that is used to determine whether a user // has given consent to a particular action, and if the system // supports asking this question. class UserConsentVerifier { public: UserConsentVerifier() {} virtual ~UserConsentVerifier() = default; // Abstract method that request the user's verification // given the provided reason. virtual winrt::Windows::Foundation::IAsyncOperation< winrt::Windows::Security::Credentials::UI::UserConsentVerificationResult> RequestVerificationForWindowAsync(std::wstring localized_reason) = 0; // Abstract method that returns weather the system supports Windows Hello. virtual winrt::Windows::Foundation::IAsyncOperation< winrt::Windows::Security::Credentials::UI:: UserConsentVerifierAvailability> CheckAvailabilityAsync() = 0; // Disallow copy and move. UserConsentVerifier(const UserConsentVerifier&) = delete; UserConsentVerifier& operator=(const UserConsentVerifier&) = delete; }; class LocalAuthPlugin : public flutter::Plugin, public LocalAuthApi { public: static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar); // Creates a plugin instance that will create the dialog and associate // it with the HWND returned from the provided function. LocalAuthPlugin(std::function window_provider); // Creates a plugin instance with the given UserConsentVerifier instance. // Exists for unit testing with mock implementations. LocalAuthPlugin(std::unique_ptr user_consent_verifier); 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 AuthenticateCoroutine( const std::string& localized_reason, std::function reply)> result); // Returns whether the system supports Windows Hello. winrt::fire_and_forget IsDeviceSupportedCoroutine( std::function reply)> result); }; } // namespace local_auth_windows ================================================ FILE: packages/local_auth/local_auth_windows/windows/local_auth_plugin.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include "local_auth.h" #include "messages.g.h" namespace { // Returns the window's HWND for a given FlutterView. HWND GetRootWindow(flutter::FlutterView* view) { return ::GetAncestor(view->GetNativeWindow(), GA_ROOT); } // Converts the given UTF-8 string to UTF-16. std::wstring Utf16FromUtf8(const std::string& utf8_string) { if (utf8_string.empty()) { return std::wstring(); } int target_length = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), static_cast(utf8_string.length()), nullptr, 0); if (target_length == 0) { return std::wstring(); } std::wstring utf16_string; utf16_string.resize(target_length); int converted_length = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), static_cast(utf8_string.length()), utf16_string.data(), target_length); if (converted_length == 0) { return std::wstring(); } return utf16_string; } } // namespace namespace local_auth_windows { // Creates an instance of the UserConsentVerifier that // calls the native Windows APIs to get the user's consent. class UserConsentVerifierImpl : public UserConsentVerifier { public: explicit UserConsentVerifierImpl(std::function window_provider) : get_root_window_(std::move(window_provider)){}; virtual ~UserConsentVerifierImpl() = default; // Calls the native Windows API to get the user's consent // with the provided reason. winrt::Windows::Foundation::IAsyncOperation< winrt::Windows::Security::Credentials::UI::UserConsentVerificationResult> RequestVerificationForWindowAsync(std::wstring localized_reason) override { winrt::impl::com_ref user_consent_verifier_interop = winrt::get_activation_factory< winrt::Windows::Security::Credentials::UI::UserConsentVerifier, IUserConsentVerifierInterop>(); HWND root_window_handle = get_root_window_(); auto reason = wil::make_unique_string( localized_reason.c_str(), localized_reason.size()); winrt::Windows::Security::Credentials::UI::UserConsentVerificationResult consent_result = co_await winrt::capture< winrt::Windows::Foundation::IAsyncOperation< winrt::Windows::Security::Credentials::UI:: UserConsentVerificationResult>>( user_consent_verifier_interop, &::IUserConsentVerifierInterop::RequestVerificationForWindowAsync, root_window_handle, reason.get()); return consent_result; } // Calls the native Windows API to check for the Windows Hello availability. winrt::Windows::Foundation::IAsyncOperation< winrt::Windows::Security::Credentials::UI:: UserConsentVerifierAvailability> CheckAvailabilityAsync() override { return winrt::Windows::Security::Credentials::UI::UserConsentVerifier:: CheckAvailabilityAsync(); } // Disallow copy and move. UserConsentVerifierImpl(const UserConsentVerifierImpl&) = delete; UserConsentVerifierImpl& operator=(const UserConsentVerifierImpl&) = delete; private: // The provider for the root window to attach the dialog to. std::function get_root_window_; }; // static void LocalAuthPlugin::RegisterWithRegistrar( flutter::PluginRegistrarWindows* registrar) { auto plugin = std::make_unique( [registrar]() { return GetRootWindow(registrar->GetView()); }); LocalAuthApi::SetUp(registrar->messenger(), plugin.get()); registrar->AddPlugin(std::move(plugin)); } // Default constructor for LocalAuthPlugin. LocalAuthPlugin::LocalAuthPlugin(std::function window_provider) : user_consent_verifier_(std::make_unique( std::move(window_provider))) {} LocalAuthPlugin::LocalAuthPlugin( std::unique_ptr user_consent_verifier) : user_consent_verifier_(std::move(user_consent_verifier)) {} LocalAuthPlugin::~LocalAuthPlugin() {} 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::AuthenticateCoroutine( const std::string& localized_reason, std::function reply)> result) { std::wstring reason = Utf16FromUtf8(localized_reason); winrt::Windows::Security::Credentials::UI::UserConsentVerifierAvailability ucv_availability = co_await user_consent_verifier_->CheckAvailabilityAsync(); if (ucv_availability == winrt::Windows::Security::Credentials::UI:: UserConsentVerifierAvailability::DeviceNotPresent) { result(FlutterError("NoHardware", "No biometric hardware found")); co_return; } else if (ucv_availability == winrt::Windows::Security::Credentials::UI:: UserConsentVerifierAvailability::NotConfiguredForUser) { result( FlutterError("NotEnrolled", "No biometrics enrolled on this device.")); co_return; } else if (ucv_availability != winrt::Windows::Security::Credentials::UI:: UserConsentVerifierAvailability::Available) { result( FlutterError("NotAvailable", "Required security features not enabled")); co_return; } try { winrt::Windows::Security::Credentials::UI::UserConsentVerificationResult consent_result = co_await user_consent_verifier_->RequestVerificationForWindowAsync( reason); result(consent_result == winrt::Windows::Security::Credentials::UI:: UserConsentVerificationResult::Verified); } catch (...) { result(false); } } // Returns whether the device supports Windows Hello or not. 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(ucv_availability == winrt::Windows::Security::Credentials::UI:: UserConsentVerifierAvailability::Available); } } // namespace local_auth_windows ================================================ FILE: packages/local_auth/local_auth_windows/windows/local_auth_windows.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include "include/local_auth_windows/local_auth_plugin.h" #include "local_auth.h" void LocalAuthPluginRegisterWithRegistrar( FlutterDesktopPluginRegistrarRef registrar) { local_auth_windows::LocalAuthPlugin::RegisterWithRegistrar( flutter::PluginRegistrarManager::GetInstance() ->GetRegistrar(registrar)); } ================================================ FILE: packages/local_auth/local_auth_windows/windows/messages.g.cpp ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/local_auth/local_auth_windows/windows/messages.g.h ================================================ // Copyright 2013 The Flutter 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_ ================================================ FILE: packages/local_auth/local_auth_windows/windows/test/local_auth_plugin_test.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "include/local_auth_windows/local_auth_plugin.h" #include #include #include #include #include #include #include "mocks.h" namespace local_auth_windows { namespace test { using flutter::EncodableList; using flutter::EncodableMap; using flutter::EncodableValue; using ::testing::_; using ::testing::DoAll; using ::testing::EndsWith; using ::testing::Eq; using ::testing::Pointee; using ::testing::Return; TEST(LocalAuthPlugin, IsDeviceSupportedHandlerSuccessIfVerifierAvailable) { 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)); ErrorOr result(false); plugin.IsDeviceSupported([&result](ErrorOr reply) { result = reply; }); EXPECT_FALSE(result.has_error()); EXPECT_TRUE(result.value()); } TEST(LocalAuthPlugin, IsDeviceSupportedHandlerSuccessIfVerifierNotAvailable) { 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)); ErrorOr result(true); plugin.IsDeviceSupported([&result](ErrorOr reply) { result = reply; }); EXPECT_FALSE(result.has_error()); EXPECT_FALSE(result.value()); } TEST(LocalAuthPlugin, AuthenticateHandlerWorksWhenAuthorized) { 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; }); EXPECT_CALL(*mockConsentVerifier, RequestVerificationForWindowAsync) .Times(1) .WillOnce([](std::wstring localizedReason) -> winrt::Windows::Foundation::IAsyncOperation< winrt::Windows::Security::Credentials::UI:: UserConsentVerificationResult> { EXPECT_EQ(localizedReason, L"My Reason"); co_return winrt::Windows::Security::Credentials::UI:: UserConsentVerificationResult::Verified; }); LocalAuthPlugin plugin(std::move(mockConsentVerifier)); ErrorOr result(false); plugin.Authenticate("My Reason", [&result](ErrorOr reply) { result = reply; }); EXPECT_FALSE(result.has_error()); EXPECT_TRUE(result.value()); } TEST(LocalAuthPlugin, AuthenticateHandlerWorksWhenNotAuthorized) { 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; }); EXPECT_CALL(*mockConsentVerifier, RequestVerificationForWindowAsync) .Times(1) .WillOnce([](std::wstring localizedReason) -> winrt::Windows::Foundation::IAsyncOperation< winrt::Windows::Security::Credentials::UI:: UserConsentVerificationResult> { EXPECT_EQ(localizedReason, L"My Reason"); co_return winrt::Windows::Security::Credentials::UI:: UserConsentVerificationResult::Canceled; }); LocalAuthPlugin plugin(std::move(mockConsentVerifier)); ErrorOr result(true); plugin.Authenticate("My Reason", [&result](ErrorOr reply) { result = reply; }); EXPECT_FALSE(result.has_error()); EXPECT_FALSE(result.value()); } } // namespace test } // namespace local_auth_windows ================================================ FILE: packages/local_auth/local_auth_windows/windows/test/mocks.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_LOCAL_AUTH_LOCAL_AUTH_WINDOWS_WINDOWS_TEST_MOCKS_H_ #define PACKAGES_LOCAL_AUTH_LOCAL_AUTH_WINDOWS_WINDOWS_TEST_MOCKS_H_ #include #include #include "../local_auth.h" namespace local_auth_windows { namespace test { namespace { using ::testing::_; class MockUserConsentVerifier : public UserConsentVerifier { public: explicit MockUserConsentVerifier(){}; virtual ~MockUserConsentVerifier() = default; MOCK_METHOD(winrt::Windows::Foundation::IAsyncOperation< winrt::Windows::Security::Credentials::UI:: UserConsentVerificationResult>, RequestVerificationForWindowAsync, (std::wstring localizedReason), (override)); MOCK_METHOD(winrt::Windows::Foundation::IAsyncOperation< winrt::Windows::Security::Credentials::UI:: UserConsentVerifierAvailability>, CheckAvailabilityAsync, (), (override)); // Disallow copy and move. MockUserConsentVerifier(const MockUserConsentVerifier&) = delete; MockUserConsentVerifier& operator=(const MockUserConsentVerifier&) = delete; }; } // namespace } // namespace test } // namespace local_auth_windows #endif // PACKAGES_LOCAL_AUTH_LOCAL_AUTH_WINDOWS_WINDOWS_TEST_MOCKS_H_ ================================================ FILE: packages/path_provider/path_provider/AUTHORS ================================================ # 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> ================================================ FILE: packages/path_provider/path_provider/CHANGELOG.md ================================================ ## 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. ## 2.0.11 * Updates references to the obsolete master branch. * Fixes integration test permission issue on recent versions of macOS. ## 2.0.10 * Removes unnecessary imports. * Adds OS version support information to README. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 2.0.9 * Updates documentation on README.md. * Updates example application. ## 2.0.8 * Updates example app Android compileSdkVersion to 31. * Removes obsolete manual registration of Windows and Linux implementations. ## 2.0.7 * Moved Android and iOS implementations to federated packages. ## 2.0.6 * Added support for Background Platform Channels on Android when it is available. ## 2.0.5 * Update minimum Flutter SDK to 2.5 and iOS deployment target to 9.0. ## 2.0.4 * Updated Android lint settings. * Specify Java 8 for Android build. ## 2.0.3 * Add iOS unit test target. * Remove references to the Android V1 embedding. ## 2.0.2 * Migrate maven repository from jcenter to mavenCentral. ## 2.0.1 * Update platform_plugin_interface version requirement. ## 2.0.0 * Migrate to null safety. * BREAKING CHANGE: Path accessors that return non-nullable results will throw a `MissingPlatformDirectoryException` if the platform implementation is unable to get the corresponding directory (except on platforms where the method is explicitly unsupported, where they will continue to throw `UnsupportedError`). ## 1.6.28 * Drop unused UUID dependency for tests. ## 1.6.27 * Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets. ## 1.6.26 * Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) ## 1.6.25 * Update Flutter SDK constraint. ## 1.6.24 * Remove unused `test` dependency. * Update Dart SDK constraint in example. ## 1.6.23 * Check in windows/ directory for example/ ## 1.6.22 * Switch to guava-android dependency instead of full guava. ## 1.6.21 * Update android compileSdkVersion to 29. ## 1.6.20 * Check in linux/ directory for example/ ## 1.6.19 * Android implementation does path queries in the background thread rather than UI thread. ## 1.6.18 * Keep handling deprecated Android v1 classes for backward compatibility. ## 1.6.17 * Update Windows endorsement verison again, to pick up the fix for web compilation in projects that include path_provider. ## 1.6.16 * Update Windows endorsement verison ## 1.6.15 * Endorse Windows implementation. * Remove the need to call disablePathProviderPlatformOverride in tests ## 1.6.14 * Update package:e2e -> package:integration_test ## 1.6.13 * Update package:e2e reference to use the local version in the flutter/plugins repository. ## 1.6.12 * Fixed a Java lint in a test. ## 1.6.11 * Updated documentation to reflect the need for changes in testing for federated plugins ## 1.6.10 * Linux implementation endorsement ## 1.6.9 * Post-v2 Android embedding cleanups. ## 1.6.8 * Update lower bound of dart dependency to 2.1.0. ## 1.6.7 * Remove Android dependencies fallback. * Require Flutter SDK 1.12.13+hotfix.5 or greater. * Fix CocoaPods podspec lint warnings. ## 1.6.6 * Replace deprecated `getFlutterEngine` call on Android. ## 1.6.5 * Remove unused class name in pubspec. ## 1.6.4 * Endorsed macOS implementation. ## 1.6.3 * Use `path_provider_platform_interface` in core plugin. ## 1.6.2 * Move package contents into `path_provider` for platform federation. ## 1.6.1 * Make the pedantic dev_dependency explicit. ## 1.6.0 * Support for retrieving the downloads directory was added. The call for this is `getDownloadsDirectory`. ## 1.5.1 * Remove the deprecated `author:` field from pubspec.yaml * Migrate the plugin to the pubspec platforms manifest. * Require Flutter SDK 1.10.0 or greater. ## 1.5.0 * Add macOS support. ## 1.4.5 * Add support for v2 plugins APIs. ## 1.4.4 * Update driver tests in the example app to e2e tests. ## 1.4.3 * Update driver tests in the example app to e2e tests. * Add missing DartDocs and a lint to prevent further regressions. ## 1.4.2 * Update and migrate iOS example project by removing flutter_assets, change "English" to "en", remove extraneous xcconfigs, update to Xcode 11 build settings, remove ARCHS, and build pods as libraries instead of frameworks. ## 1.4.1 * Remove AndroidX warnings. ## 1.4.0 * Support retrieving storage paths on Android devices with multiple external storage options. This adds a new class `AndroidEnvironment` that shadows the directory names from Androids `android.os.Environment` class. * Fixes `getLibraryDirectory` semantics & tests. ## 1.3.1 * Define clang module for iOS. ## 1.3.0 * Added iOS-only support for `getLibraryDirectory`. * Update integration tests and example test. * Update example app UI to use a `ListView` show the list of content. * Update .gitignore to include Xcode build output folder `**/DerivedData/` ## 1.2.2 * Correct the integration test for Android's `getApplicationSupportDirectory` call. * Introduce `setMockPathProviderPlatform` for API for tests. * Adds missing unit and integration tests. ## 1.2.1 * Fix fall through bug. ## 1.2.0 * On Android, `getApplicationSupportDirectory` is now supported using `getFilesDir`. * `getExternalStorageDirectory` now returns `null` instead of throwing an exception if no external files directory is available. ## 1.1.2 * `getExternalStorageDirectory` now uses `getExternalFilesDir` on Android. ## 1.1.1 * Cast error codes as longs in iOS error strings to ensure compatibility between arm32 and arm64. ## 1.1.0 * Added `getApplicationSupportDirectory`. * Updated documentation for `getApplicationDocumentsDirectory` to suggest using `getApplicationSupportDirectory` on iOS and `getExternalStorageDirectory` on Android. * Updated documentation for `getTemporaryDirectory` to suggest using it for caches of files that do not need to be backed up. * Updated integration tests and example to reflect the above changes. ## 1.0.0 * Added integration tests. ## 0.5.0+1 * Log a more detailed warning at build time about the previous AndroidX migration. ## 0.5.0 * **Breaking change**. Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to [also migrate](https://developer.android.com/jetpack/androidx/migrate) if they're using the original support library. ## 0.4.1 * Updated Gradle tooling to match Android Studio 3.1.2. ## 0.4.0 * **Breaking change**. Set SDK constraints to match the Flutter beta release. ## 0.3.1 * Simplified and upgraded Android project template to Android SDK 27. * Updated package description. ## 0.3.0 * **Breaking change**. Upgraded to Gradle 4.1 and Android Studio Gradle plugin 3.0.1. Older Flutter projects need to upgrade their Gradle setup as well in order to use this version of the plugin. Instructions can be found [here](https://github.com/flutter/flutter/wiki/Updating-Flutter-projects-to-Gradle-4.1-and-Android-Studio-Gradle-plugin-3.0.1). ## 0.2.2 * Add FLT prefix to iOS types ## 0.2.1+1 * Updated README ## 0.2.1 * Add function to determine external storage directory. ## 0.2.0 * Upgrade to new plugin registration. (https://groups.google.com/forum/#!topic/flutter-dev/zba1Ynf2OKM) ## 0.1.3 * Upgrade Android SDK Build Tools to 25.0.3. ## 0.1.2 * Add test. ## 0.1.1 * Change to README.md. ## 0.1.0 * Initial Open Source release. ================================================ FILE: packages/path_provider/path_provider/LICENSE ================================================ 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. ================================================ FILE: packages/path_provider/path_provider/README.md ================================================ # path_provider [![pub package](https://img.shields.io/pub/v/path_provider.svg)](https://pub.dev/packages/path_provider) A Flutter plugin for finding commonly used locations on the filesystem. Supports Android, iOS, Linux, macOS and Windows. Not all methods are supported on all platforms. | | Android | iOS | Linux | macOS | Windows | |-------------|---------|------|-------|--------|-------------| | **Support** | SDK 16+ | 9.0+ | Any | 10.11+ | Windows 10+ | ## Usage To use this plugin, add `path_provider` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels). ## Example ```dart Directory tempDir = await getTemporaryDirectory(); String tempPath = tempDir.path; Directory appDocDir = await getApplicationDocumentsDirectory(); String appDocPath = appDocDir.path; ``` ## Supported platforms and paths Directories support by platform: | Directory | Android | iOS | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | :---: | :---: | | Temporary | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | | Application Support | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | | Application Library | ❌️ | ✔️ | ❌️ | ✔️ | ❌️ | | Application Documents | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | | External Storage | ✔️ | ❌ | ❌ | ❌️ | ❌️ | | External Cache Directories | ✔️ | ❌ | ❌ | ❌️ | ❌️ | | External Storage Directories | ✔️ | ❌ | ❌ | ❌️ | ❌️ | | Downloads | ❌ | ✔️ | ✔️ | ✔️ | ✔️ | ## Testing `path_provider` now uses a `PlatformInterface`, meaning that not all platforms share a single `PlatformChannel`-based implementation. With that change, tests should be updated to mock `PathProviderPlatform` rather than `PlatformChannel`. See this `path_provider` [test](https://github.com/flutter/plugins/blob/main/packages/path_provider/path_provider/test/path_provider_test.dart) for an example. ================================================ FILE: packages/path_provider/path_provider/example/README.md ================================================ # path_provider_example Demonstrates how to use the path_provider plugin. ================================================ FILE: packages/path_provider/path_provider/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 lintOptions { disable 'InvalidPackage' } defaultConfig { applicationId "io.flutter.plugins.pathproviderexample" minSdkVersion 16 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } ================================================ FILE: packages/path_provider/path_provider/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/path_provider/path_provider/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/path_provider/path_provider/example/android/app/src/androidTest/java/io/flutter/plugins/pathprovider/MainActivityTest.java ================================================ // Copyright 2013 The Flutter 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.pathprovider; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class MainActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/path_provider/path_provider/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/path_provider/path_provider/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/path_provider/path_provider/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/path_provider/path_provider/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true ================================================ FILE: packages/path_provider/path_provider/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withInputStream { stream -> plugins.load(stream) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/path_provider/path_provider/example/integration_test/path_provider_test.dart ================================================ // Copyright 2013 The Flutter 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/path_provider.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('getTemporaryDirectory', (WidgetTester tester) async { final Directory result = await getTemporaryDirectory(); _verifySampleFile(result, 'temporaryDirectory'); }); testWidgets('getApplicationDocumentsDirectory', (WidgetTester tester) async { final Directory result = await getApplicationDocumentsDirectory(); _verifySampleFile(result, 'applicationDocuments'); }); testWidgets('getApplicationSupportDirectory', (WidgetTester tester) async { final Directory result = await getApplicationSupportDirectory(); _verifySampleFile(result, 'applicationSupport'); }); testWidgets('getLibraryDirectory', (WidgetTester tester) async { if (Platform.isIOS) { final Directory result = await getLibraryDirectory(); _verifySampleFile(result, 'library'); } else if (Platform.isAndroid) { final Future result = getLibraryDirectory(); expect(result, throwsA(isInstanceOf())); } }); testWidgets('getExternalStorageDirectory', (WidgetTester tester) async { if (Platform.isIOS) { final Future result = getExternalStorageDirectory(); expect(result, throwsA(isInstanceOf())); } else if (Platform.isAndroid) { final Directory? result = await getExternalStorageDirectory(); _verifySampleFile(result, 'externalStorage'); } }); testWidgets('getExternalCacheDirectories', (WidgetTester tester) async { if (Platform.isIOS) { final Future?> result = getExternalCacheDirectories(); expect(result, throwsA(isInstanceOf())); } else if (Platform.isAndroid) { final List? directories = await getExternalCacheDirectories(); expect(directories, isNotNull); for (final Directory result in directories!) { _verifySampleFile(result, 'externalCache'); } } }); final List allDirs = [ null, StorageDirectory.music, StorageDirectory.podcasts, StorageDirectory.ringtones, StorageDirectory.alarms, StorageDirectory.notifications, StorageDirectory.pictures, StorageDirectory.movies, ]; for (final StorageDirectory? type in allDirs) { testWidgets('getExternalStorageDirectories (type: $type)', (WidgetTester tester) async { if (Platform.isIOS) { final Future?> result = getExternalStorageDirectories(); expect(result, throwsA(isInstanceOf())); } else if (Platform.isAndroid) { final List? directories = await getExternalStorageDirectories(type: type); expect(directories, isNotNull); for (final Directory result in directories!) { _verifySampleFile(result, '$type'); } } }); } testWidgets('getDownloadsDirectory', (WidgetTester tester) async { if (Platform.isAndroid) { final Future result = getDownloadsDirectory(); expect(result, throwsA(isInstanceOf())); } else { final Directory? result = await getDownloadsDirectory(); // 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); } }); } /// Verify a file called [name] in [directory] by recreating it with test /// contents when necessary. void _verifySampleFile(Directory? directory, String name) { expect(directory, isNotNull); if (directory == null) { return; } final File file = File('${directory.path}/$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(); } ================================================ FILE: packages/path_provider/path_provider/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 UIRequiredDeviceCapabilities arm64 MinimumOSVersion 9.0 ================================================ FILE: packages/path_provider/path_provider/example/ios/Flutter/Debug.xcconfig ================================================ #include "Generated.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" ================================================ FILE: packages/path_provider/path_provider/example/ios/Flutter/Release.xcconfig ================================================ #include "Generated.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" ================================================ FILE: packages/path_provider/path_provider/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/path_provider/path_provider/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/path_provider/path_provider/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/path_provider/path_provider/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/path_provider/path_provider/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/path_provider/path_provider/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/path_provider/path_provider/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName path_provider_example CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: packages/path_provider/path_provider/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/path_provider/path_provider/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 2D9222481EC32A19007564B0 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D9222471EC32A19007564B0 /* GeneratedPluginRegistrant.m */; }; 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 */; }; 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 */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ F76AC1E126671E960040C8BC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 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 = ""; }; 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 = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 85DDFCF6BBDEE02B9D9F8138 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; F76AC1D926671E960040C8BC /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 60774162343BF6F19B3D65CE /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 840012C8B5EDBCF56B0E4AC1 /* Pods */ = { isa = PBXGroup; children = ( 694A199F61914F41AAFD0B7F /* Pods-Runner.debug.xcconfig */, D317CA1E83064E01753D8BB5 /* Pods-Runner.release.xcconfig */, 6EB685EA3DDA2EED39600D11 /* Pods-RunnerTests.debug.xcconfig */, 29F2567B3AE74A9113ED3394 /* Pods-RunnerTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, F76AC1DD26671E960040C8BC /* RunnerTests */, 97C146EF1CF9000F007C117D /* Products */, 840012C8B5EDBCF56B0E4AC1 /* Pods */, CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, F76AC1DC26671E960040C8BC /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 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 */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = { isa = PBXGroup; children = ( C0EE60090AA5F3AAAF2175B6 /* libPods-Runner.a */, 0E2EF24BBF807F7F7B95F2B9 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; F76AC1DD26671E960040C8BC /* RunnerTests */ = { isa = PBXGroup; children = ( F76AC1DE26671E960040C8BC /* PathProviderTests.m */, F76AC1E026671E960040C8BC /* Info.plist */, ); path = RunnerTests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; 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 = { LastUpgradeCheck = 1100; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; }; F76AC1DB26671E960040C8BC = { CreatedOnToolsVersion = 12.5; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, F76AC1DB26671E960040C8BC /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; F76AC1DA26671E960040C8BC /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 31566AD39C1C7EF9EB261E6F /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; 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"; }; AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 2D9222481EC32A19007564B0 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F76AC1D826671E960040C8BC /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F76AC1DF26671E960040C8BC /* PathProviderTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ F76AC1E226671E960040C8BC /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = F76AC1E126671E960040C8BC /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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 = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.pathProviderExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); 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_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F76AC1E526671E960040C8BC /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( F76AC1E326671E960040C8BC /* Debug */, F76AC1E426671E960040C8BC /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/path_provider/path_provider/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/path_provider/path_provider/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/path_provider/path_provider/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/path_provider/path_provider/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/path_provider/path_provider/example/ios/RunnerTests/Info.plist ================================================ 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 ================================================ FILE: packages/path_provider/path_provider/example/ios/RunnerTests/PathProviderTests.m ================================================ // Copyright 2013 The Flutter 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; @import XCTest; @interface PathProviderTests : XCTestCase @end @implementation PathProviderTests - (void)testPlugin { FLTPathProviderPlugin *plugin = [[FLTPathProviderPlugin alloc] init]; XCTAssertNotNil(plugin); } @end ================================================ FILE: packages/path_provider/path_provider/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.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 { Future? _tempDirectory; Future? _appSupportDirectory; Future? _appLibraryDirectory; Future? _appDocumentsDirectory; Future? _externalDocumentsDirectory; Future?>? _externalStorageDirectories; Future?>? _externalCacheDirectories; Future? _downloadsDirectory; void _requestTempDirectory() { setState(() { _tempDirectory = getTemporaryDirectory(); }); } 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!.path}'); } else { text = const Text('path unavailable'); } } return Padding(padding: const EdgeInsets.all(16.0), child: text); } Widget _buildDirectories( 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) { final String combined = snapshot.data!.map((Directory d) => d.path).join(', '); text = Text('paths: $combined'); } else { text = const Text('path unavailable'); } } return Padding(padding: const EdgeInsets.all(16.0), child: text); } void _requestAppDocumentsDirectory() { setState(() { _appDocumentsDirectory = getApplicationDocumentsDirectory(); }); } void _requestAppSupportDirectory() { setState(() { _appSupportDirectory = getApplicationSupportDirectory(); }); } void _requestAppLibraryDirectory() { setState(() { _appLibraryDirectory = getLibraryDirectory(); }); } void _requestExternalStorageDirectory() { setState(() { _externalDocumentsDirectory = getExternalStorageDirectory(); }); } void _requestExternalStorageDirectories(StorageDirectory type) { setState(() { _externalStorageDirectories = getExternalStorageDirectories(type: type); }); } void _requestExternalCacheDirectories() { setState(() { _externalCacheDirectories = getExternalCacheDirectories(); }); } void _requestDownloadsDirectory() { setState(() { _downloadsDirectory = getDownloadsDirectory(); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: ListView( children: [ Column( children: [ Padding( padding: const EdgeInsets.all(16.0), child: ElevatedButton( onPressed: _requestTempDirectory, child: const Text( 'Get Temporary Directory', ), ), ), FutureBuilder( future: _tempDirectory, builder: _buildDirectory, ), ], ), Column( children: [ Padding( padding: const EdgeInsets.all(16.0), child: ElevatedButton( onPressed: _requestAppDocumentsDirectory, child: const Text( 'Get Application Documents Directory', ), ), ), FutureBuilder( future: _appDocumentsDirectory, builder: _buildDirectory, ), ], ), Column( children: [ Padding( padding: const EdgeInsets.all(16.0), child: ElevatedButton( onPressed: _requestAppSupportDirectory, child: const Text( 'Get Application Support Directory', ), ), ), FutureBuilder( future: _appSupportDirectory, builder: _buildDirectory, ), ], ), Column( children: [ Padding( padding: const EdgeInsets.all(16.0), child: ElevatedButton( onPressed: Platform.isAndroid ? null : _requestAppLibraryDirectory, child: Text( Platform.isAndroid ? 'Application Library Directory unavailable' : 'Get Application Library Directory', ), ), ), FutureBuilder( future: _appLibraryDirectory, builder: _buildDirectory, ), ], ), Column( children: [ Padding( padding: const EdgeInsets.all(16.0), child: ElevatedButton( onPressed: !Platform.isAndroid ? null : _requestExternalStorageDirectory, child: Text( !Platform.isAndroid ? 'External storage is unavailable' : 'Get External Storage Directory', ), ), ), FutureBuilder( future: _externalDocumentsDirectory, builder: _buildDirectory, ), ], ), Column( children: [ Padding( padding: const EdgeInsets.all(16.0), child: ElevatedButton( onPressed: !Platform.isAndroid ? null : () { _requestExternalStorageDirectories( StorageDirectory.music, ); }, child: Text( !Platform.isAndroid ? 'External directories are unavailable' : 'Get External Storage Directories', ), ), ), FutureBuilder?>( future: _externalStorageDirectories, builder: _buildDirectories, ), ], ), Column( children: [ Padding( padding: const EdgeInsets.all(16.0), child: ElevatedButton( onPressed: !Platform.isAndroid ? null : _requestExternalCacheDirectories, child: Text( !Platform.isAndroid ? 'External directories are unavailable' : 'Get External Cache Directories', ), ), ), FutureBuilder?>( future: _externalCacheDirectories, builder: _buildDirectories, ), ], ), Column( children: [ Padding( padding: const EdgeInsets.all(16.0), child: ElevatedButton( onPressed: Platform.isAndroid || Platform.isIOS ? null : _requestDownloadsDirectory, child: Text( Platform.isAndroid || Platform.isIOS ? 'Downloads directory is unavailable' : 'Get Downloads Directory', ), ), ), FutureBuilder( future: _downloadsDirectory, builder: _buildDirectory, ), ], ), ], ), ), ); } } ================================================ FILE: packages/path_provider/path_provider/example/linux/.gitignore ================================================ flutter/ephemeral ================================================ FILE: packages/path_provider/path_provider/example/linux/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(runner LANGUAGES CXX) set(BINARY_NAME "example") set(APPLICATION_ID "dev.flutter.plugins.path_provider_example") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_14) target_compile_options(${TARGET} PRIVATE -Wall -Werror) target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") # Application build add_executable(${BINARY_NAME} "main.cc" "my_application.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" ) apply_standard_settings(${BINARY_NAME}) target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) add_dependencies(${BINARY_NAME} flutter_assemble) # Only the install-generated bundle's copy of the executable will launch # correctly, since the resources must in the right relative locations. To avoid # people trying to run the unbundled copy, put it in a subdirectory instead of # the default top-level location. set_target_properties(${BINARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" ) # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # By default, "installing" just makes a relocatable bundle in the build # directory. set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() # Start with a clean build bundle directory every time. install(CODE " file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") " COMPONENT Runtime) set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() ================================================ FILE: packages/path_provider/path_provider/example/linux/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. # Serves the same purpose as list(TRANSFORM ... PREPEND ...), # which isn't available in 3.10. function(list_prepend LIST_NAME PREFIX) set(NEW_LIST "") foreach(element ${${LIST_NAME}}) list(APPEND NEW_LIST "${PREFIX}${element}") endforeach(element) set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) endfunction() # === Flutter Library === # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) pkg_check_modules(BLKID REQUIRED IMPORTED_TARGET blkid) set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "fl_basic_message_channel.h" "fl_binary_codec.h" "fl_binary_messenger.h" "fl_dart_project.h" "fl_engine.h" "fl_json_message_codec.h" "fl_json_method_codec.h" "fl_message_codec.h" "fl_method_call.h" "fl_method_channel.h" "fl_method_codec.h" "fl_method_response.h" "fl_plugin_registrar.h" "fl_plugin_registry.h" "fl_standard_message_codec.h" "fl_standard_method_codec.h" "fl_string_codec.h" "fl_value.h" "fl_view.h" "flutter_linux.h" ) list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") target_link_libraries(flutter INTERFACE PkgConfig::GTK PkgConfig::GLIB PkgConfig::GIO PkgConfig::BLKID ) add_dependencies(flutter flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/_phony_ COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" linux-x64 ${CMAKE_BUILD_TYPE} ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ) ================================================ FILE: packages/path_provider/path_provider/example/linux/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/path_provider/path_provider/example/linux/main.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "my_application.h" int main(int argc, char** argv) { // Only X11 is currently supported. // Wayland support is being developed: // https://github.com/flutter/flutter/issues/57932. gdk_set_allowed_backends("x11"); g_autoptr(MyApplication) app = my_application_new(); return g_application_run(G_APPLICATION(app), argc, argv); } ================================================ FILE: packages/path_provider/path_provider/example/linux/my_application.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "my_application.h" #include #include "flutter/generated_plugin_registrant.h" struct _MyApplication { GtkApplication parent_instance; }; G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // Implements GApplication::activate. static void my_application_activate(GApplication* application) { GtkWindow* window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); gtk_header_bar_set_title(header_bar, "example"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); gtk_window_set_default_size(window, 1280, 720); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); FlView* view = fl_view_new(project); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view)); gtk_widget_grab_focus(GTK_WIDGET(view)); } static void my_application_class_init(MyApplicationClass* klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; } static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { return MY_APPLICATION(g_object_new( my_application_get_type(), "application-id", APPLICATION_ID, nullptr)); } ================================================ FILE: packages/path_provider/path_provider/example/linux/my_application.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef FLUTTER_MY_APPLICATION_H_ #define FLUTTER_MY_APPLICATION_H_ #include G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, GtkApplication) /** * my_application_new: * * Creates a new Flutter-based application. * * Returns: a new #MyApplication. */ MyApplication* my_application_new(); #endif // FLUTTER_MY_APPLICATION_H_ ================================================ FILE: packages/path_provider/path_provider/example/macos/Flutter/Flutter-Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/path_provider/path_provider/example/macos/Flutter/Flutter-Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/path_provider/path_provider/example/macos/Podfile ================================================ platform :osx, '10.11' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_macos_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) end end ================================================ FILE: packages/path_provider/path_provider/example/macos/Runner/AppDelegate.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } } ================================================ FILE: packages/path_provider/path_provider/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_64.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_1024.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/path_provider/path_provider/example/macos/Runner/Base.lproj/MainMenu.xib ================================================

================================================ FILE: packages/path_provider/path_provider/example/macos/Runner/Configs/AppInfo.xcconfig ================================================ // Application-level settings for the Runner target. // // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the // future. If not, the values below would default to using the project name when this becomes a // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. PRODUCT_NAME = path_provider_example // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.pathProviderExample // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2019 The Flutter Authors. All rights reserved. ================================================ FILE: packages/path_provider/path_provider/example/macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: packages/path_provider/path_provider/example/macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: packages/path_provider/path_provider/example/macos/Runner/Configs/Warnings.xcconfig ================================================ WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings GCC_WARN_UNDECLARED_SELECTOR = YES CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE CLANG_WARN__DUPLICATE_METHOD_MATCH = YES CLANG_WARN_PRAGMA_PACK = YES CLANG_WARN_STRICT_PROTOTYPES = YES CLANG_WARN_COMMA = YES GCC_WARN_STRICT_SELECTOR_MATCH = YES CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES GCC_WARN_SHADOW = YES CLANG_WARN_UNREACHABLE_CODE = YES ================================================ FILE: packages/path_provider/path_provider/example/macos/Runner/DebugProfile.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server com.apple.security.files.downloads.read-write ================================================ FILE: packages/path_provider/path_provider/example/macos/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: packages/path_provider/path_provider/example/macos/Runner/MainFlutterWindow.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { let flutterViewController = FlutterViewController.init() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } } ================================================ FILE: packages/path_provider/path_provider/example/macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.files.downloads.read-write ================================================ FILE: packages/path_provider/path_provider/example/macos/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 51; objects = { /* Begin PBXAggregateTarget section */ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { isa = PBXAggregateTarget; buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; buildPhases = ( 33CC111E2044C6BF0003C045 /* ShellScript */, ); dependencies = ( ); name = "Flutter Assemble"; productName = FLX; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 23F6FAA3AF82DFCF2B7DD79A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1523F64D34B952AB303BFFA8 /* Pods_Runner.framework */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 33D1A10422148B71006C7A3E /* FlutterMacOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */; }; 33D1A10522148B93006C7A3E /* FlutterMacOS.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D73912F022F37F9E000D13A0 /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D73912EF22F37F9E000D13A0 /* App.framework */; }; D73912F222F3801D000D13A0 /* App.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = D73912EF22F37F9E000D13A0 /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC111A2044C6BA0003C045; remoteInfo = FLX; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 33CC110E2044A8840003C045 /* Bundle Framework */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( D73912F222F3801D000D13A0 /* App.framework in Bundle Framework */, 33D1A10522148B93006C7A3E /* FlutterMacOS.framework in Bundle Framework */, ); name = "Bundle Framework"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0A1A53CF00FD04D6ED0A8E4A /* 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 = ""; }; 1523F64D34B952AB303BFFA8 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* path_provider_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = path_provider_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FlutterMacOS.framework; path = Flutter/ephemeral/FlutterMacOS.framework; sourceTree = SOURCE_ROOT; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 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 = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; D73912EF22F37F9E000D13A0 /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/ephemeral/App.framework; sourceTree = SOURCE_ROOT; }; F4586DA69948E3A954A2FC9C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( D73912F022F37F9E000D13A0 /* App.framework in Frameworks */, 33D1A10422148B71006C7A3E /* FlutterMacOS.framework in Frameworks */, 23F6FAA3AF82DFCF2B7DD79A /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 30697CBF35C100C7DD4B4699 /* Pods */ = { isa = PBXGroup; children = ( 46139048DB9F59D473B61B5E /* Pods-Runner.debug.xcconfig */, F4586DA69948E3A954A2FC9C /* Pods-Runner.release.xcconfig */, 0A1A53CF00FD04D6ED0A8E4A /* Pods-Runner.profile.xcconfig */, ); name = Pods; path = Pods; sourceTree = ""; }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( 33E5194F232828860026EE4D /* AppInfo.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, ); path = Configs; sourceTree = ""; }; 33CC10E42044A3C60003C045 = { isa = PBXGroup; children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, 30697CBF35C100C7DD4B4699 /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* path_provider_example.app */, ); name = Products; sourceTree = ""; }; 33CC11242044D66E0003C045 /* Resources */ = { isa = PBXGroup; children = ( 33CC10F22044A3C60003C045 /* Assets.xcassets */, 33CC10F42044A3C60003C045 /* MainMenu.xib */, 33CC10F72044A3C60003C045 /* Info.plist */, ); name = Resources; path = ..; sourceTree = ""; }; 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, D73912EF22F37F9E000D13A0 /* App.framework */, 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */, ); path = Flutter; sourceTree = ""; }; 33FAB671232836740065AC1E /* Runner */ = { isa = PBXGroup; children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, 33CC11242044D66E0003C045 /* Resources */, 33BA886A226E78AF003329D5 /* Configs */, ); path = Runner; sourceTree = ""; }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( 1523F64D34B952AB303BFFA8 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 82C3ED26F2C350499338A54B /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, 7413A74A1ECFDFE67CD0521B /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* path_provider_example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; LastUpgradeCheck = 0930; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; }; }; }; 33CC111A2044C6BA0003C045 = { CreatedOnToolsVersion = 9.2; ProvisioningStyle = Manual; }; }; }; buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 8.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 33CC10E42044A3C60003C045; productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename\n"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( Flutter/ephemeral/FlutterInputs.xcfilelist, ); inputPaths = ( Flutter/ephemeral/tripwire, ); outputFileListPaths = ( Flutter/ephemeral/FlutterOutputs.xcfilelist, ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh\ntouch Flutter/ephemeral/tripwire\n"; }; 7413A74A1ECFDFE67CD0521B /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 82C3ED26F2C350499338A54B /* [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", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 33CC10F52044A3C60003C045 /* Base */, ); name = MainMenu.xib; path = Runner; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Profile; }; 338D0CEA231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter/ephemeral", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Profile; }; 338D0CEB231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Profile; }; 33CC10F92044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 33CC10FA2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; 33CC10FC2044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter/ephemeral", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; 33CC10FD2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter/ephemeral", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Release; }; 33CC111C2044C6BA0003C045 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 33CC111D2044C6BA0003C045 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10F92044A3C60003C045 /* Debug */, 33CC10FA2044A3C60003C045 /* Release */, 338D0CE9231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10FC2044A3C60003C045 /* Debug */, 33CC10FD2044A3C60003C045 /* Release */, 338D0CEA231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC111C2044C6BA0003C045 /* Debug */, 33CC111D2044C6BA0003C045 /* Release */, 338D0CEB231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } ================================================ FILE: packages/path_provider/path_provider/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/path_provider/path_provider/example/macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/path_provider/path_provider/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/path_provider/path_provider/example/pubspec.yaml ================================================ 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: ">=3.0.0" dependencies: flutter: sdk: flutter path_provider: # When depending on this package from a real application you should use: # path_provider: ^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: ../ dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/path_provider/path_provider/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/path_provider/path_provider/example/windows/.gitignore ================================================ flutter/ephemeral/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: packages/path_provider/path_provider/example/windows/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(example LANGUAGES CXX) set(BINARY_NAME "example") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" CACHE STRING "" FORCE) else() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() endif() set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") # Use Unicode for all projects. add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") target_compile_options(${TARGET} PRIVATE /EHsc) target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build add_subdirectory("runner") # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # Support files are copied into place next to the executable, so that it can # run in place. This is done instead of making a separate bundle (as on Linux) # so that building and running from within Visual Studio will work. set(BUILD_BUNDLE_DIR "$") # Make the "install" step default, as it's required to run. set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" CONFIGURATIONS Profile;Release COMPONENT Runtime) ================================================ FILE: packages/path_provider/path_provider/example/windows/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "flutter_export.h" "flutter_windows.h" "flutter_messenger.h" "flutter_plugin_registrar.h" ) list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") add_dependencies(flutter flutter_assemble) # === Wrapper === list(APPEND CPP_WRAPPER_SOURCES_CORE "core_implementations.cc" "standard_codec.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_PLUGIN "plugin_registrar.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_APP "flutter_engine.cc" "flutter_view_controller.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") # Wrapper sources needed for a plugin. add_library(flutter_wrapper_plugin STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ) apply_standard_settings(flutter_wrapper_plugin) set_target_properties(flutter_wrapper_plugin PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(flutter_wrapper_plugin PROPERTIES CXX_VISIBILITY_PRESET hidden) target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) target_include_directories(flutter_wrapper_plugin PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_plugin flutter_assemble) # Wrapper sources needed for the runner. add_library(flutter_wrapper_app STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_APP} ) apply_standard_settings(flutter_wrapper_app) target_link_libraries(flutter_wrapper_app PUBLIC flutter) target_include_directories(flutter_wrapper_app PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_app flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" windows-x64 $ ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ) ================================================ FILE: packages/path_provider/path_provider/example/windows/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/path_provider/path_provider/example/windows/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(runner LANGUAGES CXX) add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" "run_loop.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) apply_standard_settings(${BINARY_NAME}) target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") add_dependencies(${BINARY_NAME} flutter_assemble) ================================================ FILE: packages/path_provider/path_provider/example/windows/runner/Runner.rc ================================================ // Microsoft Visual C++ generated resource script. // #pragma code_page(65001) #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APP_ICON ICON "resources\\app_icon.ico" ///////////////////////////////////////////////////////////////////////////// // // Version // #ifdef FLUTTER_BUILD_NUMBER #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER #else #define VERSION_AS_NUMBER 1,0,0 #endif #ifdef FLUTTER_BUILD_NAME #define VERSION_AS_STRING #FLUTTER_BUILD_NAME #else #define VERSION_AS_STRING "1.0.0" #endif VS_VERSION_INFO VERSIONINFO FILEVERSION VERSION_AS_NUMBER PRODUCTVERSION VERSION_AS_NUMBER FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "Flutter Dev" "\0" VALUE "FileDescription", "A new Flutter project." "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "example" "\0" VALUE "LegalCopyright", "Copyright (C) 2020 The Flutter Authors. All rights reserved." "\0" VALUE "OriginalFilename", "example.exe" "\0" VALUE "ProductName", "example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: packages/path_provider/path_provider/example/windows/runner/flutter_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "flutter_window.h" #include #include "flutter/generated_plugin_registrant.h" FlutterWindow::FlutterWindow(RunLoop* run_loop, const flutter::DartProject& project) : run_loop_(run_loop), project_(project) {} FlutterWindow::~FlutterWindow() {} bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opporutunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) { return *result; } } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); } ================================================ FILE: packages/path_provider/path_provider/example/windows/runner/flutter_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_ #include #include #include #include "run_loop.h" #include "win32_window.h" // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { public: // Creates a new FlutterWindow driven by the |run_loop|, hosting a // Flutter view running |project|. explicit FlutterWindow(RunLoop* run_loop, const flutter::DartProject& project); virtual ~FlutterWindow(); protected: // Win32Window: bool OnCreate() override; void OnDestroy() override; LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; private: // The run loop driving events for this window. RunLoop* run_loop_; // The project to run. flutter::DartProject project_; // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; }; #endif // RUNNER_FLUTTER_WINDOW_H_ ================================================ FILE: packages/path_provider/path_provider/example/windows/runner/main.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include "flutter_window.h" #include "run_loop.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t* command_line, _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { CreateAndAttachConsole(); } // Initialize COM, so that it is available for use in the library and/or // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); RunLoop run_loop; flutter::DartProject project(L"data"); FlutterWindow window(&run_loop, project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.CreateAndShow(L"example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); run_loop.Run(); ::CoUninitialize(); return EXIT_SUCCESS; } ================================================ FILE: packages/path_provider/path_provider/example/windows/runner/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Runner.rc // #define IDI_APP_ICON 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: packages/path_provider/path_provider/example/windows/runner/run_loop.cpp ================================================ // Copyright 2013 The Flutter 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 "run_loop.h" #include #include RunLoop::RunLoop() {} RunLoop::~RunLoop() {} void RunLoop::Run() { bool keep_running = true; TimePoint next_flutter_event_time = TimePoint::clock::now(); while (keep_running) { std::chrono::nanoseconds wait_duration = std::max(std::chrono::nanoseconds(0), next_flutter_event_time - TimePoint::clock::now()); ::MsgWaitForMultipleObjects( 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000), QS_ALLINPUT); bool processed_events = false; MSG message; // All pending Windows messages must be processed; MsgWaitForMultipleObjects // won't return again for items left in the queue after PeekMessage. while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) { processed_events = true; if (message.message == WM_QUIT) { keep_running = false; break; } ::TranslateMessage(&message); ::DispatchMessage(&message); // Allow Flutter to process messages each time a Windows message is // processed, to prevent starvation. next_flutter_event_time = std::min(next_flutter_event_time, ProcessFlutterMessages()); } // If the PeekMessage loop didn't run, process Flutter messages. if (!processed_events) { next_flutter_event_time = std::min(next_flutter_event_time, ProcessFlutterMessages()); } } } void RunLoop::RegisterFlutterInstance( flutter::FlutterEngine* flutter_instance) { flutter_instances_.insert(flutter_instance); } void RunLoop::UnregisterFlutterInstance( flutter::FlutterEngine* flutter_instance) { flutter_instances_.erase(flutter_instance); } RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { TimePoint next_event_time = TimePoint::max(); for (auto instance : flutter_instances_) { std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); if (wait_duration != std::chrono::nanoseconds::max()) { next_event_time = std::min(next_event_time, TimePoint::clock::now() + wait_duration); } } return next_event_time; } ================================================ FILE: packages/path_provider/path_provider/example/windows/runner/run_loop.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_RUN_LOOP_H_ #define RUNNER_RUN_LOOP_H_ #include #include #include // A runloop that will service events for Flutter instances as well // as native messages. class RunLoop { public: RunLoop(); ~RunLoop(); // Prevent copying RunLoop(RunLoop const&) = delete; RunLoop& operator=(RunLoop const&) = delete; // Runs the run loop until the application quits. void Run(); // Registers the given Flutter instance for event servicing. void RegisterFlutterInstance(flutter::FlutterEngine* flutter_instance); // Unregisters the given Flutter instance from event servicing. void UnregisterFlutterInstance(flutter::FlutterEngine* flutter_instance); private: using TimePoint = std::chrono::steady_clock::time_point; // Processes all currently pending messages for registered Flutter instances. TimePoint ProcessFlutterMessages(); std::set flutter_instances_; }; #endif // RUNNER_RUN_LOOP_H_ ================================================ FILE: packages/path_provider/path_provider/example/windows/runner/runner.exe.manifest ================================================ PerMonitorV2 ================================================ FILE: packages/path_provider/path_provider/example/windows/runner/utils.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "utils.h" #include #include #include #include #include void CreateAndAttachConsole() { if (::AllocConsole()) { FILE* unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } if (freopen_s(&unused, "CONOUT$", "w", stderr)) { _dup2(_fileno(stdout), 2); } std::ios::sync_with_stdio(); FlutterDesktopResyncOutputStreams(); } } ================================================ FILE: packages/path_provider/path_provider/example/windows/runner/utils.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_UTILS_H_ #define RUNNER_UTILS_H_ // Creates a console for the process, and redirects stdout and stderr to // it for both the runner and the Flutter library. void CreateAndAttachConsole(); #endif // RUNNER_UTILS_H_ ================================================ FILE: packages/path_provider/path_provider/example/windows/runner/win32_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "win32_window.h" #include #include "resource.h" namespace { constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); // Scale helper to convert logical scaler values to physical using passed in // scale factor int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); if (!user32_module) { return; } auto enable_non_client_dpi_scaling = reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); FreeLibrary(user32_module); } } } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; // Returns the singleton registar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; } // Returns the name of the window class, registering the class if it hasn't // previously been registered. const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; bool class_registered_ = false; }; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; RegisterClass(&window_class); class_registered_ = true; } return kWindowClassName; } void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, const Size& size) { Destroy(); const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), static_cast(origin.y)}; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } return OnCreate(); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { case WM_DESTROY: window_handle_ = nullptr; Destroy(); if (quit_on_close_) { PostQuitMessage(0); } return 0; case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left; LONG newHeight = newRectSize->bottom - newRectSize->top; SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_SIZE: RECT rect = GetClientArea(); if (child_content_ != nullptr) { // Size and position the child window. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); } return 0; case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_); } return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } void Win32Window::Destroy() { OnDestroy(); if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, true); SetFocus(child_content_); } RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } void Win32Window::OnDestroy() { // No-op; provided for subclasses. } ================================================ FILE: packages/path_provider/path_provider/example/windows/runner/win32_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_ #include #include #include #include // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { public: struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) : width(width), height(height) {} }; Win32Window(); virtual ~Win32Window(); // Creates and shows a win32 window with |title| and position and size using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a // consistent size to will treat the width height passed in to this function // as logical pixels and scale to appropriate for the default monitor. Returns // true if the window was created successfully. bool CreateAndShow(const std::wstring& title, const Point& origin, const Size& size); // Release OS resources associated with window. void Destroy(); // Inserts |content| into the window tree. void SetChildContent(HWND content); // Returns the backing Window handle to enable clients to set icon and other // window properties. Returns nullptr if the window has been destroyed. HWND GetHandle(); // If true, closing this window will quit the application. void SetQuitOnClose(bool quit_on_close); // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Called when CreateAndShow is called, allowing subclass window-related // setup. Subclasses should return false if setup fails. virtual bool OnCreate(); // Called when Destroy is called. virtual void OnDestroy(); private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically // responsponds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; bool quit_on_close_ = false; // window handle for top level window. HWND window_handle_ = nullptr; // window handle for hosted content. HWND child_content_ = nullptr; }; #endif // RUNNER_WIN32_WINDOW_H_ ================================================ FILE: packages/path_provider/path_provider/lib/path_provider.dart ================================================ // Copyright 2013 The Flutter 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' show Directory; import 'package:flutter/foundation.dart' show visibleForTesting; import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; export 'package:path_provider_platform_interface/path_provider_platform_interface.dart' show StorageDirectory; @visibleForTesting @Deprecated('This is no longer necessary, and is now a no-op') set disablePathProviderPlatformOverride(bool override) {} /// An exception thrown when a directory that should always be available on /// the current platform cannot be obtained. class MissingPlatformDirectoryException implements Exception { /// Creates a new exception MissingPlatformDirectoryException(this.message, {this.details}); /// The explanation of the exception. final String message; /// Added details, if any. /// /// E.g., an error object from the platform implementation. final Object? details; @override String toString() { final String detailsAddition = details == null ? '' : ': $details'; return 'MissingPlatformDirectoryException($message)$detailsAddition'; } } PathProviderPlatform get _platform => PathProviderPlatform.instance; /// Path to the temporary directory on the device that is not backed up and is /// suitable for storing caches of downloaded files. /// /// Files in this directory may be cleared at any time. This does *not* return /// a new temporary directory. Instead, the caller is responsible for creating /// (and cleaning up) files or directories within this directory. This /// directory is scoped to the calling application. /// /// Example implementations: /// - `NSCachesDirectory` on iOS and macOS. /// - `Context.getCacheDir` on Android. /// /// Throws a [MissingPlatformDirectoryException] if the system is unable to /// provide the directory. Future getTemporaryDirectory() async { final String? path = await _platform.getTemporaryPath(); if (path == null) { throw MissingPlatformDirectoryException( 'Unable to get temporary directory'); } return Directory(path); } /// 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. /// /// 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 /// provide the directory. Future getApplicationSupportDirectory() async { final String? path = await _platform.getApplicationSupportPath(); if (path == null) { throw MissingPlatformDirectoryException( 'Unable to get application support directory'); } return Directory(path); } /// Path to the directory where application can store files that are persistent, /// backed up, and not visible to the user, such as sqlite.db. /// /// 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 /// provide the directory on a supported platform. Future getLibraryDirectory() async { final String? path = await _platform.getLibraryPath(); if (path == null) { throw MissingPlatformDirectoryException('Unable to get library directory'); } return Directory(path); } /// Path to a directory where the application may place data that is /// user-generated, or that cannot otherwise be recreated by your application. /// /// Consider using another path, such as [getApplicationSupportDirectory] or /// [getExternalStorageDirectory], if the data is not user-generated. /// /// 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 /// provide the directory. Future getApplicationDocumentsDirectory() async { final String? path = await _platform.getApplicationDocumentsPath(); if (path == null) { throw MissingPlatformDirectoryException( 'Unable to get application documents directory'); } return Directory(path); } /// Path to a directory where the application may access top level storage. /// /// Example implementation: /// - `getExternalFilesDir(null)` on Android. /// /// 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) { return null; } return Directory(path); } /// Paths to directories where application specific cache 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. /// /// Example implementation: /// - Context.getExternalCacheDirs() on Android (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) { return null; } return paths.map((String path) => Directory(path)).toList(); } /// 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. /// /// Example implementation: /// - Context.getExternalFilesDirs(type) on Android (or /// Context.getExternalFilesDir(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. StorageDirectory? type, }) async { final List? paths = await _platform.getExternalStoragePaths(type: type); if (paths == null) { return null; } return paths.map((String path) => Directory(path)).toList(); } /// Path to the directory where downloaded files can be stored. /// /// 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) { return null; } return Directory(path); } ================================================ FILE: packages/path_provider/path_provider/pubspec.yaml ================================================ 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.12 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: platforms: android: default_package: path_provider_android ios: default_package: path_provider_foundation linux: default_package: path_provider_linux macos: default_package: path_provider_foundation windows: default_package: path_provider_windows dependencies: flutter: sdk: flutter path_provider_android: ^2.0.6 path_provider_foundation: ^2.1.0 path_provider_linux: ^2.0.1 path_provider_platform_interface: ^2.0.0 path_provider_windows: ^2.0.2 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter plugin_platform_interface: ^2.0.0 test: ^1.16.0 ================================================ FILE: packages/path_provider/path_provider/test/path_provider_test.dart ================================================ // Copyright 2013 The Flutter 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' show Directory; import 'package:flutter_test/flutter_test.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; const String kTemporaryPath = 'temporaryPath'; const String kApplicationSupportPath = 'applicationSupportPath'; const String kDownloadsPath = 'downloadsPath'; const String kLibraryPath = 'libraryPath'; const String kApplicationDocumentsPath = 'applicationDocumentsPath'; const String kExternalCachePath = 'externalCachePath'; const String kExternalStoragePath = 'externalStoragePath'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('PathProvider full implementation', () { setUp(() async { PathProviderPlatform.instance = FakePathProviderPlatform(); }); test('getTemporaryDirectory', () async { final Directory result = await getTemporaryDirectory(); expect(result.path, kTemporaryPath); }); test('getApplicationSupportDirectory', () async { final Directory result = await getApplicationSupportDirectory(); expect(result.path, kApplicationSupportPath); }); test('getLibraryDirectory', () async { final Directory result = await getLibraryDirectory(); expect(result.path, kLibraryPath); }); test('getApplicationDocumentsDirectory', () async { final Directory result = await getApplicationDocumentsDirectory(); expect(result.path, kApplicationDocumentsPath); }); test('getExternalStorageDirectory', () async { final Directory? result = await getExternalStorageDirectory(); expect(result?.path, kExternalStoragePath); }); test('getExternalCacheDirectories', () async { final List? result = await getExternalCacheDirectories(); expect(result?.length, 1); expect(result?.first.path, kExternalCachePath); }); test('getExternalStorageDirectories', () async { final List? result = await getExternalStorageDirectories(); expect(result?.length, 1); expect(result?.first.path, kExternalStoragePath); }); test('getDownloadsDirectory', () async { final Directory? result = await getDownloadsDirectory(); expect(result?.path, kDownloadsPath); }); }); group('PathProvider null implementation', () { setUp(() async { PathProviderPlatform.instance = AllNullFakePathProviderPlatform(); }); test('getTemporaryDirectory throws on null', () async { expect(getTemporaryDirectory(), throwsA(isA())); }); test('getApplicationSupportDirectory throws on null', () async { expect(getApplicationSupportDirectory(), throwsA(isA())); }); test('getLibraryDirectory throws on null', () async { expect(getLibraryDirectory(), throwsA(isA())); }); test('getApplicationDocumentsDirectory throws on null', () async { expect(getApplicationDocumentsDirectory(), throwsA(isA())); }); test('getExternalStorageDirectory passes null through', () async { final Directory? result = await getExternalStorageDirectory(); expect(result, isNull); }); test('getExternalCacheDirectories passes null through', () async { final List? result = await getExternalCacheDirectories(); expect(result, isNull); }); test('getExternalStorageDirectories passes null through', () async { final List? result = await getExternalStorageDirectories(); expect(result, isNull); }); test('getDownloadsDirectory passses null through', () async { final Directory? result = await getDownloadsDirectory(); expect(result, isNull); }); }); } class FakePathProviderPlatform extends Fake with MockPlatformInterfaceMixin implements PathProviderPlatform { @override Future getTemporaryPath() async { return kTemporaryPath; } @override Future getApplicationSupportPath() async { return kApplicationSupportPath; } @override Future getLibraryPath() async { return kLibraryPath; } @override Future getApplicationDocumentsPath() async { return kApplicationDocumentsPath; } @override Future getExternalStoragePath() async { return kExternalStoragePath; } @override Future?> getExternalCachePaths() async { return [kExternalCachePath]; } @override Future?> getExternalStoragePaths({ StorageDirectory? type, }) async { return [kExternalStoragePath]; } @override Future getDownloadsPath() async { return kDownloadsPath; } } class AllNullFakePathProviderPlatform extends Fake with MockPlatformInterfaceMixin implements PathProviderPlatform { @override Future getTemporaryPath() async { return null; } @override Future getApplicationSupportPath() async { return null; } @override Future getLibraryPath() async { return null; } @override Future getApplicationDocumentsPath() async { return null; } @override Future getExternalStoragePath() async { return null; } @override Future?> getExternalCachePaths() async { return null; } @override Future?> getExternalStoragePaths({ StorageDirectory? type, }) async { return null; } @override Future getDownloadsPath() async { return null; } } ================================================ FILE: packages/path_provider/path_provider_android/AUTHORS ================================================ # 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> ================================================ FILE: packages/path_provider/path_provider_android/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 2.0.22 * Removes unused Guava dependency. ## 2.0.21 * Updates code for `no_leading_underscores_for_local_identifiers` lint. * Updates minimum Flutter version to 2.10. * Upgrades `androidx.annotation` version to 1.5.0. * Upgrades Android Gradle plugin version to 7.3.1. ## 2.0.20 * Reverts changes in versions 2.0.18 and 2.0.19. ## 2.0.19 * Bumps kotlin to 1.7.10 ## 2.0.18 * Bumps `androidx.annotation:annotation` version to 1.4.0. * Bumps gradle version to 7.2.2. ## 2.0.17 * Lower minimim version back to 2.8.1. ## 2.0.16 * Fixes bug with `getExternalStoragePaths(null)`. ## 2.0.15 * Switches the medium from MethodChannels to Pigeon. ## 2.0.14 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 2.0.13 * Fixes typing build warning. ## 2.0.12 * Returns to using a different platform channel name, undoing the revert in 2.0.11, but updates the minimum Flutter version to 2.8 to avoid the issue that caused the revert. ## 2.0.11 * Temporarily reverts the platform channel name change from 2.0.10 in order to restore compatibility with Flutter versions earlier than 2.8. ## 2.0.10 * Switches to a package-internal implementation of the platform interface. ## 2.0.9 * Updates Android compileSdkVersion to 31. ## 2.0.8 * Updates example app Android compileSdkVersion to 31. * Fixes typing build warning. ## 2.0.7 * Fixes link in README. ## 2.0.6 * Split from `path_provider` as a federated implementation. ================================================ FILE: packages/path_provider/path_provider_android/LICENSE ================================================ 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. ================================================ FILE: packages/path_provider/path_provider_android/README.md ================================================ # path\_provider\_android The Android 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 ================================================ FILE: packages/path_provider/path_provider_android/android/build.gradle ================================================ group 'io.flutter.plugins.pathprovider' version '1.0-SNAPSHOT' buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.3.1' } } rootProject.allprojects { repositories { google() mavenCentral() } } apply plugin: 'com.android.library' android { compileSdkVersion 31 defaultConfig { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } testOptions { unitTests.includeAndroidResources = true unitTests.returnDefaultValues = true unitTests.all { testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" outputs.upToDateWhen {false} showStandardStreams = true } } } } dependencies { implementation 'androidx.annotation:annotation:1.5.0' testImplementation 'junit:junit:4.13.2' } ================================================ FILE: packages/path_provider/path_provider_android/android/settings.gradle ================================================ rootProject.name = 'path_provider_android' ================================================ FILE: packages/path_provider/path_provider_android/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/path_provider/path_provider_android/android/src/main/java/io/flutter/plugins/pathprovider/Messages.java ================================================ // Copyright 2013 The Flutter 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.2.0), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.pathprovider; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.plugin.common.BasicMessageChannel; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MessageCodec; import io.flutter.plugin.common.StandardMessageCodec; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** Generated class from Pigeon. */ @SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"}) public class Messages { public enum StorageDirectory { root(0), music(1), podcasts(2), ringtones(3), alarms(4), notifications(5), pictures(6), movies(7), downloads(8), dcim(9), documents(10); private int index; private StorageDirectory(final int index) { this.index = index; } } private static class PathProviderApiCodec extends StandardMessageCodec { public static final PathProviderApiCodec INSTANCE = new PathProviderApiCodec(); private PathProviderApiCodec() {} } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface PathProviderApi { @Nullable String getTemporaryPath(); @Nullable String getApplicationSupportPath(); @Nullable String getApplicationDocumentsPath(); @Nullable String getExternalStoragePath(); @NonNull List getExternalCachePaths(); @NonNull List getExternalStoragePaths(@NonNull StorageDirectory directory); /** The codec used by PathProviderApi. */ static MessageCodec getCodec() { return PathProviderApiCodec.INSTANCE; } /** * Sets up an instance of `PathProviderApi` to handle messages through the `binaryMessenger`. */ static void setup(BinaryMessenger binaryMessenger, PathProviderApi api) { { BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue(); BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.PathProviderApi.getTemporaryPath", getCodec(), taskQueue); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { String output = api.getTemporaryPath(); wrapped.put("result", output); } catch (Error | RuntimeException exception) { wrapped.put("error", wrapError(exception)); } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } { BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue(); BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.PathProviderApi.getApplicationSupportPath", getCodec(), taskQueue); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { String output = api.getApplicationSupportPath(); wrapped.put("result", output); } catch (Error | RuntimeException exception) { wrapped.put("error", wrapError(exception)); } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } { BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue(); BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.PathProviderApi.getApplicationDocumentsPath", getCodec(), taskQueue); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { String output = api.getApplicationDocumentsPath(); wrapped.put("result", output); } catch (Error | RuntimeException exception) { wrapped.put("error", wrapError(exception)); } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } { BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue(); BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.PathProviderApi.getExternalStoragePath", getCodec(), taskQueue); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { String output = api.getExternalStoragePath(); wrapped.put("result", output); } catch (Error | RuntimeException exception) { wrapped.put("error", wrapError(exception)); } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } { BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue(); BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.PathProviderApi.getExternalCachePaths", getCodec(), taskQueue); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { List output = api.getExternalCachePaths(); wrapped.put("result", output); } catch (Error | RuntimeException exception) { wrapped.put("error", wrapError(exception)); } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } { BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue(); BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.PathProviderApi.getExternalStoragePaths", getCodec(), taskQueue); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; StorageDirectory directoryArg = args.get(0) == null ? null : StorageDirectory.values()[(int) args.get(0)]; if (directoryArg == null) { throw new NullPointerException("directoryArg unexpectedly null."); } List output = api.getExternalStoragePaths(directoryArg); wrapped.put("result", output); } catch (Error | RuntimeException exception) { wrapped.put("error", wrapError(exception)); } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } } } private static Map wrapError(Throwable exception) { Map errorMap = new HashMap<>(); errorMap.put("message", exception.toString()); errorMap.put("code", exception.getClass().getSimpleName()); errorMap.put( "details", "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); return errorMap; } } ================================================ FILE: packages/path_provider/path_provider_android/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java ================================================ // Copyright 2013 The Flutter 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.pathprovider; import android.content.Context; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.BinaryMessenger.TaskQueue; import io.flutter.plugins.pathprovider.Messages.PathProviderApi; import io.flutter.util.PathUtils; import java.io.File; import java.util.ArrayList; import java.util.List; public class PathProviderPlugin implements FlutterPlugin, PathProviderApi { static final String TAG = "PathProviderPlugin"; private Context context; public PathProviderPlugin() {} private void setup(BinaryMessenger messenger, Context context) { TaskQueue taskQueue = messenger.makeBackgroundTaskQueue(); try { PathProviderApi.setup(messenger, this); } catch (Exception ex) { Log.e(TAG, "Received exception while setting up PathProviderPlugin", ex); } this.context = context; } @SuppressWarnings("deprecation") public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { PathProviderPlugin instance = new PathProviderPlugin(); instance.setup(registrar.messenger(), registrar.context()); } @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { setup(binding.getBinaryMessenger(), binding.getApplicationContext()); } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { PathProviderApi.setup(binding.getBinaryMessenger(), null); } @Override public @Nullable String getTemporaryPath() { return getPathProviderTemporaryDirectory(); } @Override public @Nullable String getApplicationSupportPath() { return getApplicationSupportDirectory(); } @Override public @Nullable String getApplicationDocumentsPath() { return getPathProviderApplicationDocumentsDirectory(); } @Override public @Nullable String getExternalStoragePath() { return getPathProviderStorageDirectory(); } @Override public @NonNull List getExternalCachePaths() { return getPathProviderExternalCacheDirectories(); } @Override public @NonNull List getExternalStoragePaths( @NonNull Messages.StorageDirectory directory) { return getPathProviderExternalStorageDirectories(directory); } private String getPathProviderTemporaryDirectory() { return context.getCacheDir().getPath(); } private String getApplicationSupportDirectory() { return PathUtils.getFilesDir(context); } private String getPathProviderApplicationDocumentsDirectory() { return PathUtils.getDataDirectory(context); } private String getPathProviderStorageDirectory() { final File dir = context.getExternalFilesDir(null); if (dir == null) { return null; } return dir.getAbsolutePath(); } private List getPathProviderExternalCacheDirectories() { final List paths = new ArrayList(); if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { for (File dir : context.getExternalCacheDirs()) { if (dir != null) { paths.add(dir.getAbsolutePath()); } } } else { File dir = context.getExternalCacheDir(); if (dir != null) { paths.add(dir.getAbsolutePath()); } } return paths; } private String getStorageDirectoryString(@NonNull Messages.StorageDirectory directory) { switch (directory) { case root: return null; case music: return "music"; case podcasts: return "podcasts"; case ringtones: return "ringtones"; case alarms: return "alarms"; case notifications: return "notifications"; case pictures: return "pictures"; case movies: return "movies"; case downloads: return "downloads"; case dcim: return "dcim"; case documents: return "documents"; default: throw new RuntimeException("Unrecognized directory: " + directory); } } private List getPathProviderExternalStorageDirectories( @NonNull Messages.StorageDirectory directory) { final List paths = new ArrayList(); if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { for (File dir : context.getExternalFilesDirs(getStorageDirectoryString(directory))) { if (dir != null) { paths.add(dir.getAbsolutePath()); } } } else { File dir = context.getExternalFilesDir(getStorageDirectoryString(directory)); if (dir != null) { paths.add(dir.getAbsolutePath()); } } return paths; } } ================================================ FILE: packages/path_provider/path_provider_android/android/src/main/java/io/flutter/plugins/pathprovider/StorageDirectoryMapper.java ================================================ // Copyright 2013 The Flutter 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.pathprovider; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Environment; /** Helps to map the Dart `StorageDirectory` enum to a Android system constant. */ class StorageDirectoryMapper { /** * Return a Android Environment constant for a Dart Index. * * @return The correct Android Environment constant or null, if the index is null. * @throws IllegalArgumentException If `dartIndex` is not null but also not matches any known * index. */ static String androidType(Integer dartIndex) throws IllegalArgumentException { if (dartIndex == null) { return null; } switch (dartIndex) { case 0: return Environment.DIRECTORY_MUSIC; case 1: return Environment.DIRECTORY_PODCASTS; case 2: return Environment.DIRECTORY_RINGTONES; case 3: return Environment.DIRECTORY_ALARMS; case 4: return Environment.DIRECTORY_NOTIFICATIONS; case 5: return Environment.DIRECTORY_PICTURES; case 6: return Environment.DIRECTORY_MOVIES; case 7: return Environment.DIRECTORY_DOWNLOADS; case 8: return Environment.DIRECTORY_DCIM; case 9: if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { return Environment.DIRECTORY_DOCUMENTS; } else { throw new IllegalArgumentException("Documents directory is unsupported."); } default: throw new IllegalArgumentException("Unknown index: " + dartIndex); } } } ================================================ FILE: packages/path_provider/path_provider_android/android/src/test/java/io/flutter/plugins/pathprovider/StorageDirectoryMapperTest.java ================================================ // Copyright 2013 The Flutter 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.pathprovider; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import android.os.Environment; import org.junit.Test; public class StorageDirectoryMapperTest { @org.junit.Test public void testAndroidType_null() { assertNull(StorageDirectoryMapper.androidType(null)); } @org.junit.Test public void testAndroidType_valid() { assertEquals(Environment.DIRECTORY_MUSIC, StorageDirectoryMapper.androidType(0)); assertEquals(Environment.DIRECTORY_PODCASTS, StorageDirectoryMapper.androidType(1)); assertEquals(Environment.DIRECTORY_RINGTONES, StorageDirectoryMapper.androidType(2)); assertEquals(Environment.DIRECTORY_ALARMS, StorageDirectoryMapper.androidType(3)); assertEquals(Environment.DIRECTORY_NOTIFICATIONS, StorageDirectoryMapper.androidType(4)); assertEquals(Environment.DIRECTORY_PICTURES, StorageDirectoryMapper.androidType(5)); assertEquals(Environment.DIRECTORY_MOVIES, StorageDirectoryMapper.androidType(6)); assertEquals(Environment.DIRECTORY_DOWNLOADS, StorageDirectoryMapper.androidType(7)); assertEquals(Environment.DIRECTORY_DCIM, StorageDirectoryMapper.androidType(8)); } @Test public void testAndroidType_invalid() { try { assertEquals(Environment.DIRECTORY_DCIM, StorageDirectoryMapper.androidType(10)); fail(); } catch (IllegalArgumentException e) { assertEquals("Unknown index: " + 10, e.getMessage()); } } } ================================================ FILE: packages/path_provider/path_provider_android/example/README.md ================================================ # 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. ================================================ FILE: packages/path_provider/path_provider_android/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 lintOptions { disable 'InvalidPackage' } defaultConfig { applicationId "io.flutter.plugins.pathproviderexample" minSdkVersion 16 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } ================================================ FILE: packages/path_provider/path_provider_android/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/path_provider/path_provider_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/path_provider/path_provider_android/example/android/app/src/androidTest/java/io/flutter/plugins/pathprovider/MainActivityTest.java ================================================ // Copyright 2013 The Flutter 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.pathprovider; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class MainActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/path_provider/path_provider_android/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/path_provider/path_provider_android/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/path_provider/path_provider_android/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/path_provider/path_provider_android/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true ================================================ FILE: packages/path_provider/path_provider_android/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withInputStream { stream -> plugins.load(stream) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/path_provider/path_provider_android/example/integration_test/path_provider_test.dart ================================================ // Copyright 2013 The Flutter 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; expect(() => provider.getLibraryPath(), throwsA(isInstanceOf())); }); testWidgets('getExternalStorageDirectory', (WidgetTester tester) async { final PathProviderPlatform provider = PathProviderPlatform.instance; final String? result = await provider.getExternalStoragePath(); _verifySampleFile(result, 'externalStorage'); }); testWidgets('getExternalCacheDirectories', (WidgetTester tester) async { final PathProviderPlatform provider = PathProviderPlatform.instance; final List? directories = await provider.getExternalCachePaths(); expect(directories, isNotNull); for (final String result in directories!) { _verifySampleFile(result, 'externalCache'); } }); final List allDirs = [ null, StorageDirectory.music, StorageDirectory.podcasts, StorageDirectory.ringtones, StorageDirectory.alarms, StorageDirectory.notifications, StorageDirectory.pictures, StorageDirectory.movies, ]; for (final StorageDirectory? type in allDirs) { testWidgets('getExternalStorageDirectories (type: $type)', (WidgetTester tester) async { final PathProviderPlatform provider = PathProviderPlatform.instance; final List? directories = await provider.getExternalStoragePaths(type: type); expect(directories, isNotNull); expect(directories, isNotEmpty); for (final String result in directories!) { _verifySampleFile(result, '$type'); } }); } } /// 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(); } ================================================ FILE: packages/path_provider/path_provider_android/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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? _appDocumentsDirectory; Future? _externalDocumentsDirectory; Future?>? _externalStorageDirectories; Future?>? _externalCacheDirectories; 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); } Widget _buildDirectories( 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) { final String combined = snapshot.data!.join(', '); text = Text('paths: $combined'); } 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 _requestExternalStorageDirectory() { setState(() { _externalDocumentsDirectory = provider.getExternalStoragePath(); }); } void _requestExternalStorageDirectories(StorageDirectory type) { setState(() { _externalStorageDirectories = provider.getExternalStoragePaths(type: type); }); } void _requestExternalCacheDirectories() { setState(() { _externalCacheDirectories = provider.getExternalCachePaths(); }); } @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: _requestExternalStorageDirectory, child: const Text('Get External Storage Directory'), ), ), FutureBuilder( future: _externalDocumentsDirectory, builder: _buildDirectory), Column(children: [ Padding( padding: const EdgeInsets.all(16.0), child: ElevatedButton( child: const Text('Get External Storage Directories'), onPressed: () { _requestExternalStorageDirectories( StorageDirectory.music, ); }, ), ), ]), FutureBuilder?>( future: _externalStorageDirectories, builder: _buildDirectories), Column(children: [ Padding( padding: const EdgeInsets.all(16.0), child: ElevatedButton( onPressed: _requestExternalCacheDirectories, child: const Text('Get External Cache Directories'), ), ), ]), FutureBuilder?>( future: _externalCacheDirectories, builder: _buildDirectories), ], ), ), ); } } ================================================ FILE: packages/path_provider/path_provider_android/example/pubspec.yaml ================================================ 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: ">=3.0.0" dependencies: flutter: sdk: flutter path_provider_android: # When depending on this package from a real application you should use: # path_provider: ^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 ================================================ FILE: packages/path_provider/path_provider_android/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/path_provider/path_provider_android/lib/messages.g.dart ================================================ // Copyright 2013 The Flutter 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.2.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 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'; enum StorageDirectory { root, music, podcasts, ringtones, alarms, notifications, pictures, movies, downloads, dcim, documents, } 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 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?); } } Future getExternalStoragePath() async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.PathProviderApi.getExternalStoragePath', 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> getExternalCachePaths() async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.PathProviderApi.getExternalCachePaths', 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 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 List?)!.cast(); } } Future> getExternalStoragePaths( StorageDirectory arg_directory) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.PathProviderApi.getExternalStoragePaths', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel .send([arg_directory.index]) 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 List?)!.cast(); } } } ================================================ FILE: packages/path_provider/path_provider_android/lib/path_provider_android.dart ================================================ // Copyright 2013 The Flutter 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:path_provider_platform_interface/path_provider_platform_interface.dart'; import 'messages.g.dart' as messages; messages.StorageDirectory _convertStorageDirectory( StorageDirectory? directory) { switch (directory) { case null: return messages.StorageDirectory.root; case StorageDirectory.music: return messages.StorageDirectory.music; case StorageDirectory.podcasts: return messages.StorageDirectory.podcasts; case StorageDirectory.ringtones: return messages.StorageDirectory.ringtones; case StorageDirectory.alarms: return messages.StorageDirectory.alarms; case StorageDirectory.notifications: return messages.StorageDirectory.notifications; case StorageDirectory.pictures: return messages.StorageDirectory.pictures; case StorageDirectory.movies: return messages.StorageDirectory.movies; case StorageDirectory.downloads: return messages.StorageDirectory.downloads; case StorageDirectory.dcim: return messages.StorageDirectory.dcim; case StorageDirectory.documents: return messages.StorageDirectory.documents; } } /// The Android implementation of [PathProviderPlatform]. class PathProviderAndroid extends PathProviderPlatform { final messages.PathProviderApi _api = messages.PathProviderApi(); /// Registers this class as the default instance of [PathProviderPlatform]. static void registerWith() { PathProviderPlatform.instance = PathProviderAndroid(); } @override Future getTemporaryPath() { return _api.getTemporaryPath(); } @override Future getApplicationSupportPath() { return _api.getApplicationSupportPath(); } @override Future getLibraryPath() { throw UnsupportedError('getLibraryPath is not supported on Android'); } @override Future getApplicationDocumentsPath() { return _api.getApplicationDocumentsPath(); } @override Future getExternalStoragePath() { return _api.getExternalStoragePath(); } @override Future?> getExternalCachePaths() async { return (await _api.getExternalCachePaths()).cast(); } @override Future?> getExternalStoragePaths({ StorageDirectory? type, }) async { return (await _api.getExternalStoragePaths(_convertStorageDirectory(type))) .cast(); } @override Future getDownloadsPath() { throw UnsupportedError('getDownloadsPath is not supported on Android'); } } ================================================ FILE: packages/path_provider/path_provider_android/pigeons/copyright.txt ================================================ Copyright 2013 The Flutter Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. ================================================ FILE: packages/path_provider/path_provider_android/pigeons/messages.dart ================================================ // Copyright 2013 The Flutter 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', javaOut: 'android/src/main/java/io/flutter/plugins/pathprovider/Messages.java', javaOptions: JavaOptions( className: 'Messages', package: 'io.flutter.plugins.pathprovider'), dartOut: 'lib/messages.g.dart', dartTestOut: 'test/messages_test.g.dart', copyrightHeader: 'pigeons/copyright.txt', )) enum StorageDirectory { root, music, podcasts, ringtones, alarms, notifications, pictures, movies, downloads, dcim, documents, } @HostApi(dartHostTestHandler: 'TestPathProviderApi') abstract class PathProviderApi { @TaskQueue(type: TaskQueueType.serialBackgroundThread) String? getTemporaryPath(); @TaskQueue(type: TaskQueueType.serialBackgroundThread) String? getApplicationSupportPath(); @TaskQueue(type: TaskQueueType.serialBackgroundThread) String? getApplicationDocumentsPath(); @TaskQueue(type: TaskQueueType.serialBackgroundThread) String? getExternalStoragePath(); @TaskQueue(type: TaskQueueType.serialBackgroundThread) List getExternalCachePaths(); @TaskQueue(type: TaskQueueType.serialBackgroundThread) List getExternalStoragePaths(StorageDirectory directory); } ================================================ FILE: packages/path_provider/path_provider_android/pubspec.yaml ================================================ name: path_provider_android description: Android implementation of the path_provider plugin. repository: https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22 version: 2.0.22 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: path_provider platforms: android: package: io.flutter.plugins.pathprovider pluginClass: PathProviderPlugin dartPluginClass: PathProviderAndroid dependencies: flutter: sdk: flutter path_provider_platform_interface: ^2.0.1 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter pigeon: ^3.1.5 test: ^1.16.0 ================================================ FILE: packages/path_provider/path_provider_android/test/messages_test.g.dart ================================================ // Copyright 2013 The Flutter 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'; // The following line is edited by hand to avoid confusing dart with overloaded types. import 'package:path_provider_android/messages.g.dart'; class _TestPathProviderApiCodec extends StandardMessageCodec { const _TestPathProviderApiCodec(); } abstract class TestPathProviderApi { static const MessageCodec codec = _TestPathProviderApiCodec(); String? getTemporaryPath(); String? getApplicationSupportPath(); String? getApplicationDocumentsPath(); String? getExternalStoragePath(); List getExternalCachePaths(); List getExternalStoragePaths(StorageDirectory directory); 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.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}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.PathProviderApi.getExternalStoragePath', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { // ignore message final String? output = api.getExternalStoragePath(); return {'result': output}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.PathProviderApi.getExternalCachePaths', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { // ignore message final List output = api.getExternalCachePaths(); return {'result': output}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.PathProviderApi.getExternalStoragePaths', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.PathProviderApi.getExternalStoragePaths was null.'); final List args = (message as List?)!; /// TODO(gaaclarke): The following line was tweaked by hand to address /// https://github.com/flutter/flutter/issues/105742. Alternatively /// the tests could be written with a mock BinaryMessenger but this is /// how we want to address it eventually. final StorageDirectory? arg_directory = StorageDirectory.values[args[0] as int]; assert(arg_directory != null, 'Argument for dev.flutter.pigeon.PathProviderApi.getExternalStoragePaths was null, expected non-null StorageDirectory.'); final List output = api.getExternalStoragePaths(arg_directory!); return {'result': output}; }); } } } } ================================================ FILE: packages/path_provider/path_provider_android/test/path_provider_android_test.dart ================================================ // Copyright 2013 The Flutter 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:path_provider_android/messages.g.dart' as messages; import 'package:path_provider_android/path_provider_android.dart'; import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; import 'messages_test.g.dart'; const String kTemporaryPath = 'temporaryPath'; const String kApplicationSupportPath = 'applicationSupportPath'; const String kLibraryPath = 'libraryPath'; const String kApplicationDocumentsPath = 'applicationDocumentsPath'; const String kExternalCachePaths = 'externalCachePaths'; const String kExternalStoragePaths = 'externalStoragePaths'; const String kDownloadsPath = 'downloadsPath'; class _Api implements TestPathProviderApi { @override String? getApplicationDocumentsPath() => kApplicationDocumentsPath; @override String? getApplicationSupportPath() => kApplicationSupportPath; @override List getExternalCachePaths() => [kExternalCachePaths]; @override String? getExternalStoragePath() => kExternalStoragePaths; @override List getExternalStoragePaths(messages.StorageDirectory directory) => [kExternalStoragePaths]; @override String? getTemporaryPath() => kTemporaryPath; } void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('PathProviderAndroid', () { late PathProviderAndroid pathProvider; setUp(() async { pathProvider = PathProviderAndroid(); TestPathProviderApi.setup(_Api()); }); test('getTemporaryPath', () async { final String? path = await pathProvider.getTemporaryPath(); expect(path, kTemporaryPath); }); test('getApplicationSupportPath', () async { final String? path = await pathProvider.getApplicationSupportPath(); expect(path, kApplicationSupportPath); }); test('getLibraryPath fails', () async { try { await pathProvider.getLibraryPath(); fail('should throw UnsupportedError'); } catch (e) { expect(e, isUnsupportedError); } }); test('getApplicationDocumentsPath', () async { final String? path = await pathProvider.getApplicationDocumentsPath(); expect(path, kApplicationDocumentsPath); }); test('getExternalCachePaths succeeds', () async { final List? result = await pathProvider.getExternalCachePaths(); expect(result!.length, 1); expect(result.first, kExternalCachePaths); }); for (final StorageDirectory? type in [ ...StorageDirectory.values ]) { test('getExternalStoragePaths (type: $type) android succeeds', () async { final List? result = await pathProvider.getExternalStoragePaths(type: type); expect(result!.length, 1); expect(result.first, kExternalStoragePaths); }); } // end of for-loop test('getDownloadsPath fails', () async { try { await pathProvider.getDownloadsPath(); fail('should throw UnsupportedError'); } catch (e) { expect(e, isUnsupportedError); } }); }); } ================================================ FILE: packages/path_provider/path_provider_foundation/.gitignore ================================================ .packages .flutter-plugins pubspec.lock ================================================ FILE: packages/path_provider/path_provider_foundation/AUTHORS ================================================ # 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> ================================================ FILE: packages/path_provider/path_provider_foundation/CHANGELOG.md ================================================ ## 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. ================================================ FILE: packages/path_provider/path_provider_foundation/LICENSE ================================================ 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. ================================================ FILE: packages/path_provider/path_provider_foundation/README.md ================================================ # path\_provider\_foundation The iOS and 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 ================================================ FILE: packages/path_provider/path_provider_foundation/darwin/Classes/PathProviderPlugin.swift ================================================ // Copyright 2013 The Flutter 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 } ================================================ FILE: packages/path_provider/path_provider_foundation/darwin/Classes/messages.g.swift ================================================ // Copyright 2013 The Flutter 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 ] } ================================================ FILE: packages/path_provider/path_provider_foundation/darwin/RunnerTests/RunnerTests.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import XCTest @testable import path_provider_foundation #if os(iOS) import Flutter #elseif os(macOS) import FlutterMacOS #endif class RunnerTests: XCTestCase { func testGetTemporaryDirectory() throws { let plugin = PathProviderPlugin() let path = plugin.getDirectoryPath(type: .temp) XCTAssertEqual( path, NSSearchPathForDirectoriesInDomains( FileManager.SearchPathDirectory.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true ).first) } func testGetApplicationDocumentsDirectory() throws { let plugin = PathProviderPlugin() let path = plugin.getDirectoryPath(type: .applicationDocuments) XCTAssertEqual( path, NSSearchPathForDirectoriesInDomains( FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true ).first) } func testGetApplicationSupportDirectory() throws { let plugin = PathProviderPlugin() 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( FileManager.SearchPathDirectory.applicationSupportDirectory, FileManager.SearchPathDomainMask.userDomainMask, true ).first!)) XCTAssert(path!.hasSuffix("Example")) #endif } func testGetLibraryDirectory() throws { let plugin = PathProviderPlugin() let path = plugin.getDirectoryPath(type: .library) XCTAssertEqual( path, NSSearchPathForDirectoriesInDomains( FileManager.SearchPathDirectory.libraryDirectory, FileManager.SearchPathDomainMask.userDomainMask, true ).first) } func testGetDownloadsDirectory() throws { let plugin = PathProviderPlugin() let path = plugin.getDirectoryPath(type: .downloads) XCTAssertEqual( path, NSSearchPathForDirectoriesInDomains( FileManager.SearchPathDirectory.downloadsDirectory, FileManager.SearchPathDomainMask.userDomainMask, true ).first) } } ================================================ FILE: packages/path_provider/path_provider_foundation/darwin/path_provider_foundation.podspec ================================================ # # 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 ================================================ FILE: packages/path_provider/path_provider_foundation/example/README.md ================================================ # 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. ================================================ FILE: packages/path_provider/path_provider_foundation/example/integration_test/path_provider_test.dart ================================================ // Copyright 2013 The Flutter 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('getDownloadsDirectory', (WidgetTester tester) async { final PathProviderPlatform provider = PathProviderPlatform.instance; final String? result = await provider.getDownloadsPath(); // _verifySampleFile causes hangs in driver for some reason, so just // validate that a non-empty path was returned. expect(result, isNotEmpty); }); } /// Verify a file called [name] in [directoryPath] by recreating it with test /// contents when necessary. /// /// If [createDirectory] is true, the directory will be created if missing. 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(); } ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/.gitignore ================================================ **/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 ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 11.0 ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Flutter/Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Flutter/Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Runner/AppDelegate.swift ================================================ // Copyright 2013 The Flutter 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) } } ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "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" } } ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName Path Provider Foundation CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName path_provider_foundation_example CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Runner/Runner-Bridging-Header.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "GeneratedPluginRegistrant.h" ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 51; objects = { /* Begin PBXBuildFile section */ 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 */; }; 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 */; }; D18DAAE2A3406D4789C8DAB2 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 988A82A3033B36B9EAF2782B /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 3380327829784D96002D32AE /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; 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; }; 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 = ""; }; 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 */ 3380327129784D96002D32AE /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( D18DAAE2A3406D4789C8DAB2 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 569E86265D93B926F433B2DF /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 33258D76298182CC006BAA98 /* RunnerTests */ = { isa = PBXGroup; children = ( 33258D7729818302006BAA98 /* RunnerTests.swift */, ); name = RunnerTests; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 33258D76298182CC006BAA98 /* RunnerTests */, 97C146EF1CF9000F007C117D /* Products */, E1C876D20454FC3A1ED7F7E5 /* Pods */, C72F144CE69E83C4574EB334 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, 3380327429784D96002D32AE /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; C72F144CE69E83C4574EB334 /* Frameworks */ = { isa = PBXGroup; children = ( 479D5DD53D431F6BBABA2E43 /* Pods_Runner.framework */, 988A82A3033B36B9EAF2782B /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; }; E1C876D20454FC3A1ED7F7E5 /* Pods */ = { isa = PBXGroup; children = ( 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 = ( 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 = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1400; LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 3380327329784D96002D32AE = { CreatedOnToolsVersion = 14.0; TestTargetID = 97C146ED1CF9000F007C117D; }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, 3380327329784D96002D32AE /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 3380327229784D96002D32AE /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 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", ); 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 = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 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 = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 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-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 */ 3380327029784D96002D32AE /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33258D7929818305006BAA98 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 3380327929784D96002D32AE /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = 3380327829784D96002D32AE /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; 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_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 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; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 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_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 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 = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 3380327D29784D96002D32AE /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 3380327A29784D96002D32AE /* Debug */, 3380327B29784D96002D32AE /* Release */, 3380327C29784D96002D32AE /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/path_provider/path_provider_foundation/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: packages/path_provider/path_provider_foundation/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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()); } /// Sample app class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { String? _tempDirectory = 'Unknown'; String? _downloadsDirectory = 'Unknown'; String? _libraryDirectory = 'Unknown'; String? _appSupportDirectory = 'Unknown'; String? _documentsDirectory = 'Unknown'; @override void initState() { super.initState(); initDirectories(); } // Platform messages are asynchronous, so we initialize in an async method. Future initDirectories() async { String? tempDirectory; String? downloadsDirectory; String? appSupportDirectory; String? libraryDirectory; String? documentsDirectory; final PathProviderPlatform provider = PathProviderPlatform.instance; try { tempDirectory = await provider.getTemporaryPath(); } catch (exception) { tempDirectory = 'Failed to get temp directory: $exception'; } try { downloadsDirectory = await provider.getDownloadsPath(); } catch (exception) { downloadsDirectory = 'Failed to get downloads directory: $exception'; } try { documentsDirectory = await provider.getApplicationDocumentsPath(); } catch (exception) { 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) { appSupportDirectory = 'Failed to get app support directory: $exception'; } setState(() { _tempDirectory = tempDirectory; _downloadsDirectory = downloadsDirectory; _libraryDirectory = libraryDirectory; _appSupportDirectory = appSupportDirectory; _documentsDirectory = documentsDirectory; }); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Path Provider example app'), ), body: Center( child: Column( children: [ 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'), ], ), ), ), ); } } ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/Flutter/Flutter-Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/Flutter/Flutter-Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/Podfile ================================================ platform :osx, '10.11' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_macos_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) end end ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/Runner/AppDelegate.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } } ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_64.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_1024.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/Runner/Base.lproj/MainMenu.xib ================================================ ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/Runner/Configs/AppInfo.xcconfig ================================================ // Application-level settings for the Runner target. // // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the // future. If not, the values below would default to using the project name when this becomes a // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. PRODUCT_NAME = path_provider_example // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.pathProviderExample // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2019 The Flutter Authors. All rights reserved. ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/Runner/Configs/Warnings.xcconfig ================================================ WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings GCC_WARN_UNDECLARED_SELECTOR = YES CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE CLANG_WARN__DUPLICATE_METHOD_MATCH = YES CLANG_WARN_PRAGMA_PACK = YES CLANG_WARN_STRICT_PROTOTYPES = YES CLANG_WARN_COMMA = YES GCC_WARN_STRICT_SELECTOR_MATCH = YES CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES GCC_WARN_SHADOW = YES CLANG_WARN_UNREACHABLE_CODE = YES ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/Runner/DebugProfile.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server com.apple.security.files.downloads.read-write ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/Runner/MainFlutterWindow.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { let flutterViewController = FlutterViewController.init() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } } ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.files.downloads.read-write ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 51; objects = { /* Begin PBXAggregateTarget section */ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { isa = PBXAggregateTarget; buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; buildPhases = ( 33CC111E2044C6BF0003C045 /* ShellScript */, ); dependencies = ( ); name = "Flutter Assemble"; productName = FLX; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 23F6FAA3AF82DFCF2B7DD79A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1523F64D34B952AB303BFFA8 /* Pods_Runner.framework */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 33EBD3AA26728EA70013E557 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33EBD3A926728EA70013E557 /* RunnerTests.swift */; }; FEE1C654F5DF2F210CC17B17 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BA0C143378C83246316BE4F7 /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC111A2044C6BA0003C045; remoteInfo = FLX; }; 33EBD3AC26728EA70013E557 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC10EC2044A3C60003C045; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 33CC110E2044A8840003C045 /* Bundle Framework */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Bundle Framework"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0A1A53CF00FD04D6ED0A8E4A /* 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 = ""; }; 0B41979101786837FC1ABC29 /* 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 = ""; }; 0B43E5DCF2F998ABCD395373 /* 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 = ""; }; 1523F64D34B952AB303BFFA8 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1C62AF358280E9A8FA10B127 /* 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 = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* path_provider_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = path_provider_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 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 = ../../../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 = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; BA0C143378C83246316BE4F7 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F4586DA69948E3A954A2FC9C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 23F6FAA3AF82DFCF2B7DD79A /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 33EBD3A426728EA70013E557 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( FEE1C654F5DF2F210CC17B17 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 30697CBF35C100C7DD4B4699 /* Pods */ = { isa = PBXGroup; children = ( 46139048DB9F59D473B61B5E /* Pods-Runner.debug.xcconfig */, F4586DA69948E3A954A2FC9C /* Pods-Runner.release.xcconfig */, 0A1A53CF00FD04D6ED0A8E4A /* Pods-Runner.profile.xcconfig */, 0B43E5DCF2F998ABCD395373 /* Pods-RunnerTests.debug.xcconfig */, 0B41979101786837FC1ABC29 /* Pods-RunnerTests.release.xcconfig */, 1C62AF358280E9A8FA10B127 /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( 33E5194F232828860026EE4D /* AppInfo.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, ); path = Configs; sourceTree = ""; }; 33CC10E42044A3C60003C045 = { isa = PBXGroup; children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, 33EBD3A826728EA70013E557 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, 30697CBF35C100C7DD4B4699 /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* path_provider_example.app */, 33EBD3A726728EA70013E557 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 33CC11242044D66E0003C045 /* Resources */ = { isa = PBXGroup; children = ( 33CC10F22044A3C60003C045 /* Assets.xcassets */, 33CC10F42044A3C60003C045 /* MainMenu.xib */, 33CC10F72044A3C60003C045 /* Info.plist */, ); name = Resources; path = ..; sourceTree = ""; }; 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, ); path = Flutter; sourceTree = ""; }; 33EBD3A826728EA70013E557 /* RunnerTests */ = { isa = PBXGroup; children = ( 33EBD3A926728EA70013E557 /* RunnerTests.swift */, 33EBD3AB26728EA70013E557 /* Info.plist */, ); path = RunnerTests; sourceTree = ""; }; 33FAB671232836740065AC1E /* Runner */ = { isa = PBXGroup; children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, 33CC11242044D66E0003C045 /* Resources */, 33BA886A226E78AF003329D5 /* Configs */, ); path = Runner; sourceTree = ""; }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( 1523F64D34B952AB303BFFA8 /* Pods_Runner.framework */, BA0C143378C83246316BE4F7 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 82C3ED26F2C350499338A54B /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, 7413A74A1ECFDFE67CD0521B /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* path_provider_example.app */; productType = "com.apple.product-type.application"; }; 33EBD3A626728EA70013E557 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = 33EBD3B126728EA70013E557 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( 74960BD2BEA7516F537D0F92 /* [CP] Check Pods Manifest.lock */, 33EBD3A326728EA70013E557 /* Sources */, 33EBD3A426728EA70013E557 /* Frameworks */, 33EBD3A526728EA70013E557 /* Resources */, ); buildRules = ( ); dependencies = ( 33EBD3AD26728EA70013E557 /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = 33EBD3A726728EA70013E557 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1250; LastUpgradeCheck = 0930; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; }; }; }; 33CC111A2044C6BA0003C045 = { CreatedOnToolsVersion = 9.2; ProvisioningStyle = Manual; }; 33EBD3A626728EA70013E557 = { CreatedOnToolsVersion = 12.5; TestTargetID = 33CC10EC2044A3C60003C045; }; }; }; buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 8.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 33CC10E42044A3C60003C045; productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, 33EBD3A626728EA70013E557 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 33EBD3A526728EA70013E557 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( Flutter/ephemeral/FlutterInputs.xcfilelist, ); inputPaths = ( Flutter/ephemeral/tripwire, ); outputFileListPaths = ( Flutter/ephemeral/FlutterOutputs.xcfilelist, ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh\ntouch Flutter/ephemeral/tripwire\n"; }; 7413A74A1ECFDFE67CD0521B /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", "${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 74960BD2BEA7516F537D0F92 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 82C3ED26F2C350499338A54B /* [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", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 33EBD3A326728EA70013E557 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33EBD3AA26728EA70013E557 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; }; 33EBD3AD26728EA70013E557 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC10EC2044A3C60003C045 /* Runner */; targetProxy = 33EBD3AC26728EA70013E557 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 33CC10F52044A3C60003C045 /* Base */, ); name = MainMenu.xib; path = Runner; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Profile; }; 338D0CEA231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter/ephemeral", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Profile; }; 338D0CEB231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Profile; }; 33CC10F92044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 33CC10FA2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; 33CC10FC2044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter/ephemeral", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; 33CC10FD2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter/ephemeral", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Release; }; 33CC111C2044C6BA0003C045 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 33CC111D2044C6BA0003C045 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 33EBD3AE26728EA70013E557 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 0B43E5DCF2F998ABCD395373 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COMBINE_HIDPI_IMAGES = YES; 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)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/path_provider_example.app/Contents/MacOS/path_provider_example"; }; name = Debug; }; 33EBD3AF26728EA70013E557 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 0B41979101786837FC1ABC29 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COMBINE_HIDPI_IMAGES = YES; 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)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/path_provider_example.app/Contents/MacOS/path_provider_example"; }; name = Release; }; 33EBD3B026728EA70013E557 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 1C62AF358280E9A8FA10B127 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COMBINE_HIDPI_IMAGES = YES; 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)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/path_provider_example.app/Contents/MacOS/path_provider_example"; }; name = Profile; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10F92044A3C60003C045 /* Debug */, 33CC10FA2044A3C60003C045 /* Release */, 338D0CE9231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10FC2044A3C60003C045 /* Debug */, 33CC10FD2044A3C60003C045 /* Release */, 338D0CEA231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC111C2044C6BA0003C045 /* Debug */, 33CC111D2044C6BA0003C045 /* Release */, 338D0CEB231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33EBD3B126728EA70013E557 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 33EBD3AE26728EA70013E557 /* Debug */, 33EBD3AF26728EA70013E557 /* Release */, 33EBD3B026728EA70013E557 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/path_provider/path_provider_foundation/example/macos/RunnerTests/Info.plist ================================================ 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 ================================================ FILE: packages/path_provider/path_provider_foundation/example/pubspec.yaml ================================================ name: path_provider_example description: Demonstrates how to use the path_provider plugin. publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter path_provider_foundation: # When depending on this package from a real application you should use: # 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. 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 ================================================ FILE: packages/path_provider/path_provider_foundation/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/path_provider/path_provider_foundation/ios/README.md ================================================ 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. ================================================ FILE: packages/path_provider/path_provider_foundation/lib/messages.g.dart ================================================ // Copyright 2013 The Flutter 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?); } } } ================================================ FILE: packages/path_provider/path_provider_foundation/lib/path_provider_foundation.dart ================================================ // Copyright 2013 The Flutter 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:path_provider_platform_interface/path_provider_platform_interface.dart'; import 'messages.g.dart'; /// 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 = PathProviderFoundation(); } @override Future getTemporaryPath() { return _pathProvider.getDirectoryPath(DirectoryType.temp); } @override Future getApplicationSupportPath() async { final String? path = await _pathProvider.getDirectoryPath(DirectoryType.applicationSupport); 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 _pathProvider.getDirectoryPath(DirectoryType.library); } @override Future getApplicationDocumentsPath() { return _pathProvider.getDirectoryPath(DirectoryType.applicationDocuments); } @override Future getExternalStoragePath() async { throw UnsupportedError( 'getExternalStoragePath is not supported on this platform'); } @override Future?> getExternalCachePaths() async { throw UnsupportedError( 'getExternalCachePaths is not supported on this platform'); } @override Future?> getExternalStoragePaths({ StorageDirectory? type, }) async { throw UnsupportedError( 'getExternalStoragePaths is not supported on this platform'); } @override Future getDownloadsPath() { return _pathProvider.getDirectoryPath(DirectoryType.downloads); } } ================================================ FILE: packages/path_provider/path_provider_foundation/macos/README.md ================================================ 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. ================================================ FILE: packages/path_provider/path_provider_foundation/pigeons/copyright.txt ================================================ Copyright 2013 The Flutter Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. ================================================ FILE: packages/path_provider/path_provider_foundation/pigeons/messages.dart ================================================ // Copyright 2013 The Flutter 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/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? getDirectoryPath(DirectoryType type); } ================================================ FILE: packages/path_provider/path_provider_foundation/pubspec.yaml ================================================ 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.1.1 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: path_provider platforms: ios: pluginClass: PathProviderPlugin dartPluginClass: PathProviderFoundation sharedDarwinSource: true macos: pluginClass: PathProviderPlugin dartPluginClass: PathProviderFoundation sharedDarwinSource: true dependencies: flutter: sdk: flutter 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 ================================================ FILE: packages/path_provider/path_provider_foundation/test/messages_test.g.dart ================================================ // Copyright 2013 The Flutter 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]; }); } } } } ================================================ FILE: packages/path_provider/path_provider_foundation/test/path_provider_foundation_test.dart ================================================ // Copyright 2013 The Flutter 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:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:path/path.dart' as p; 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('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; setUp(() async { testRoot = Directory.systemTemp.createTempSync(); pathProvider = PathProviderFoundation(); mockApi = MockTestPathProviderApi(); TestPathProviderApi.setup(mockApi); }); tearDown(() { testRoot.deleteSync(recursive: true); }); 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(); 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(); 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(); 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(); 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(); verify(mockApi.getDirectoryPath(DirectoryType.downloads)); expect(result, downloadsPath); }); 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)); }); }); } ================================================ FILE: packages/path_provider/path_provider_foundation/test/path_provider_foundation_test.mocks.dart ================================================ // 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?); } ================================================ FILE: packages/path_provider/path_provider_linux/.gitignore ================================================ .DS_Store .dart_tool/ .packages .pub/ build/ ================================================ FILE: packages/path_provider/path_provider_linux/.metadata ================================================ # 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: e491544588e8d34fdf31d5f840b4649850ef167a channel: master project_type: plugin ================================================ FILE: packages/path_provider/path_provider_linux/AUTHORS ================================================ # 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> ================================================ FILE: packages/path_provider/path_provider_linux/CHANGELOG.md ================================================ ## 2.1.8 * Adds compatibility with `xdg_directories` 1.0. * Updates minimum Flutter version to 3.0. ## 2.1.7 * Bumps ffi dependency to match path_provider_windows. ## 2.1.6 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 2.1.5 * Removes dependency on `meta`. ## 2.1.4 * Fixes `getApplicationSupportPath` handling of applications where the application ID is not set. ## 2.1.3 * Change getApplicationSupportPath from using executable name to application ID (if provided). * If the executable name based directory exists, continue to use that so existing applications continue with the same behaviour. ## 2.1.2 * Fixes link in README. ## 2.1.1 * Removed obsolete `pluginClass: none` from pubpsec. ## 2.1.0 * Now `getTemporaryPath` returns the value of the `TMPDIR` environment variable primarily. If `TMPDIR` is not set, `/tmp` is returned. ## 2.0.2 * Updated installation instructions in README. ## 2.0.1 * Add `implements` to pubspec.yaml. * Add `registerWith` method to the main Dart class. ## 2.0.0 * Migrate to null safety. ## 0.1.1+3 * Update Flutter SDK constraint. ## 0.1.1+2 * Log errors in the example when calls to the `path_provider` fail. ## 0.1.1+1 * Check in linux/ directory for example/ ## 0.1.1 - NOT PUBLISHED * Reverts changes on 0.1.0, which broke the tree. ## 0.1.0 - NOT PUBLISHED * This release updates getApplicationSupportPath to use the application ID instead of the executable name. * No migration is provided, so any older apps that were using this path will now have a different directory. ## 0.0.1+2 * This release updates the example to depend on the endorsed plugin rather than relative path ## 0.0.1+1 * This updates the readme and pubspec and example to reflect the endorsement of this implementation of `path_provider` ## 0.0.1 * The initial implementation of path\_provider for Linux * Implements getApplicationSupportPath, getApplicationDocumentsPath, getDownloadsPath, and getTemporaryPath ================================================ FILE: packages/path_provider/path_provider_linux/LICENSE ================================================ 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. ================================================ FILE: packages/path_provider/path_provider_linux/README.md ================================================ # path\_provider\_linux The linux 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 ================================================ FILE: packages/path_provider/path_provider_linux/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: packages/path_provider/path_provider_linux/example/.metadata ================================================ # 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: e491544588e8d34fdf31d5f840b4649850ef167a channel: master project_type: app ================================================ FILE: packages/path_provider/path_provider_linux/example/README.md ================================================ # 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. ================================================ FILE: packages/path_provider/path_provider_linux/example/integration_test/path_provider_test.dart ================================================ // Copyright 2013 The Flutter 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_linux/path_provider_linux.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('getTemporaryDirectory', (WidgetTester tester) async { final PathProviderLinux provider = PathProviderLinux(); final String? result = await provider.getTemporaryPath(); _verifySampleFile(result, 'temporaryDirectory'); }); testWidgets('getDownloadDirectory', (WidgetTester tester) async { if (!Platform.isLinux) { return; } final PathProviderLinux provider = PathProviderLinux(); final String? result = await provider.getDownloadsPath(); _verifySampleFile(result, 'downloadDirectory'); }); testWidgets('getApplicationDocumentsDirectory', (WidgetTester tester) async { final PathProviderLinux provider = PathProviderLinux(); final String? result = await provider.getApplicationDocumentsPath(); _verifySampleFile(result, 'applicationDocuments'); }); testWidgets('getApplicationSupportDirectory', (WidgetTester tester) async { final PathProviderLinux provider = PathProviderLinux(); final String? result = await provider.getApplicationSupportPath(); _verifySampleFile(result, 'applicationSupport'); }); } /// 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(); } ================================================ FILE: packages/path_provider/path_provider_linux/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:path_provider_linux/path_provider_linux.dart'; void main() { runApp(const MyApp()); } /// Sample app class MyApp extends StatefulWidget { /// Default Constructor const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { String? _tempDirectory = 'Unknown'; String? _downloadsDirectory = 'Unknown'; String? _appSupportDirectory = 'Unknown'; String? _documentsDirectory = 'Unknown'; final PathProviderLinux _provider = PathProviderLinux(); @override void initState() { super.initState(); initDirectories(); } // Platform messages are asynchronous, so we initialize in an async method. Future initDirectories() async { String? tempDirectory; String? downloadsDirectory; String? appSupportDirectory; String? documentsDirectory; // Platform messages may fail, so we use a try/catch PlatformException. try { tempDirectory = await _provider.getTemporaryPath(); } on PlatformException { tempDirectory = 'Failed to get temp directory.'; } try { downloadsDirectory = await _provider.getDownloadsPath(); } on PlatformException { downloadsDirectory = 'Failed to get downloads directory.'; } try { documentsDirectory = await _provider.getApplicationDocumentsPath(); } on PlatformException { documentsDirectory = 'Failed to get documents directory.'; } try { appSupportDirectory = await _provider.getApplicationSupportPath(); } on PlatformException { appSupportDirectory = 'Failed to get documents directory.'; } // If the widget was removed from the tree while the asynchronous platform // message was in flight, we want to discard the reply rather than calling // setState to update our non-existent appearance. if (!mounted) { return; } setState(() { _tempDirectory = tempDirectory; _downloadsDirectory = downloadsDirectory; _appSupportDirectory = appSupportDirectory; _documentsDirectory = documentsDirectory; }); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Path Provider Linux example app'), ), body: Center( child: Column( children: [ Text('Temp Directory: $_tempDirectory\n'), Text('Documents Directory: $_documentsDirectory\n'), Text('Downloads Directory: $_downloadsDirectory\n'), Text('Application Support Directory: $_appSupportDirectory\n'), ], ), ), ), ); } } ================================================ FILE: packages/path_provider/path_provider_linux/example/linux/.gitignore ================================================ flutter/ephemeral ================================================ FILE: packages/path_provider/path_provider_linux/example/linux/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(runner LANGUAGES CXX) set(BINARY_NAME "example") set(APPLICATION_ID "dev.flutter.plugins.path_provider_linux_example") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_14) target_compile_options(${TARGET} PRIVATE -Wall -Werror) target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") # Application build add_executable(${BINARY_NAME} "main.cc" "my_application.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" ) apply_standard_settings(${BINARY_NAME}) target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) add_dependencies(${BINARY_NAME} flutter_assemble) # Only the install-generated bundle's copy of the executable will launch # correctly, since the resources must in the right relative locations. To avoid # people trying to run the unbundled copy, put it in a subdirectory instead of # the default top-level location. set_target_properties(${BINARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" ) # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # By default, "installing" just makes a relocatable bundle in the build # directory. set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() # Start with a clean build bundle directory every time. install(CODE " file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") " COMPONENT Runtime) set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() ================================================ FILE: packages/path_provider/path_provider_linux/example/linux/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. # Serves the same purpose as list(TRANSFORM ... PREPEND ...), # which isn't available in 3.10. function(list_prepend LIST_NAME PREFIX) set(NEW_LIST "") foreach(element ${${LIST_NAME}}) list(APPEND NEW_LIST "${PREFIX}${element}") endforeach(element) set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) endfunction() # === Flutter Library === # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) pkg_check_modules(BLKID REQUIRED IMPORTED_TARGET blkid) set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "fl_basic_message_channel.h" "fl_binary_codec.h" "fl_binary_messenger.h" "fl_dart_project.h" "fl_engine.h" "fl_json_message_codec.h" "fl_json_method_codec.h" "fl_message_codec.h" "fl_method_call.h" "fl_method_channel.h" "fl_method_codec.h" "fl_method_response.h" "fl_plugin_registrar.h" "fl_plugin_registry.h" "fl_standard_message_codec.h" "fl_standard_method_codec.h" "fl_string_codec.h" "fl_value.h" "fl_view.h" "flutter_linux.h" ) list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") target_link_libraries(flutter INTERFACE PkgConfig::GTK PkgConfig::GLIB PkgConfig::GIO PkgConfig::BLKID ) add_dependencies(flutter flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/_phony_ COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" linux-x64 ${CMAKE_BUILD_TYPE} ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ) ================================================ FILE: packages/path_provider/path_provider_linux/example/linux/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/path_provider/path_provider_linux/example/linux/main.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "my_application.h" int main(int argc, char** argv) { // Only X11 is currently supported. // Wayland support is being developed: // https://github.com/flutter/flutter/issues/57932. gdk_set_allowed_backends("x11"); g_autoptr(MyApplication) app = my_application_new(); return g_application_run(G_APPLICATION(app), argc, argv); } ================================================ FILE: packages/path_provider/path_provider_linux/example/linux/my_application.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "my_application.h" #include #include "flutter/generated_plugin_registrant.h" struct _MyApplication { GtkApplication parent_instance; }; G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // Implements GApplication::activate. static void my_application_activate(GApplication* application) { GtkWindow* window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); gtk_header_bar_set_title(header_bar, "example"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); gtk_window_set_default_size(window, 1280, 720); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); FlView* view = fl_view_new(project); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view)); gtk_widget_grab_focus(GTK_WIDGET(view)); } static void my_application_class_init(MyApplicationClass* klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; } static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { return MY_APPLICATION(g_object_new( my_application_get_type(), "application-id", APPLICATION_ID, nullptr)); } ================================================ FILE: packages/path_provider/path_provider_linux/example/linux/my_application.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef FLUTTER_MY_APPLICATION_H_ #define FLUTTER_MY_APPLICATION_H_ #include G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, GtkApplication) /** * my_application_new: * * Creates a new Flutter-based application. * * Returns: a new #MyApplication. */ MyApplication* my_application_new(); #endif // FLUTTER_MY_APPLICATION_H_ ================================================ FILE: packages/path_provider/path_provider_linux/example/pubspec.yaml ================================================ name: pathproviderexample description: Demonstrates how to use the path_provider_linux plugin. publish_to: "none" environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter path_provider_linux: # When depending on this package from a real application you should use: # path_provider_linux: ^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: ../ dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/path_provider/path_provider_linux/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/path_provider/path_provider_linux/lib/path_provider_linux.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'src/path_provider_linux.dart'; ================================================ FILE: packages/path_provider/path_provider_linux/lib/src/get_application_id.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // getApplicationId() is implemented using FFI; export a stub for platforms // that don't support FFI (e.g., web) to avoid having transitive dependencies // break web compilation. export 'get_application_id_stub.dart' if (dart.library.ffi) 'get_application_id_real.dart'; ================================================ FILE: packages/path_provider/path_provider_linux/lib/src/get_application_id_real.dart ================================================ // Copyright 2013 The Flutter 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:ffi'; import 'package:ffi/ffi.dart'; import 'package:flutter/foundation.dart' show visibleForTesting; // GApplication* g_application_get_default(); typedef _GApplicationGetDefaultC = IntPtr Function(); typedef _GApplicationGetDefaultDart = int Function(); // const gchar* g_application_get_application_id(GApplication* application); typedef _GApplicationGetApplicationIdC = Pointer Function(IntPtr); typedef _GApplicationGetApplicationIdDart = Pointer Function(int); /// Interface for interacting with libgio. @visibleForTesting class GioUtils { /// Creates a default instance that uses the real libgio. GioUtils() { try { _gio = DynamicLibrary.open('libgio-2.0.so'); } on ArgumentError { _gio = null; } } DynamicLibrary? _gio; /// True if libgio was opened successfully. bool get libraryIsPresent => _gio != null; /// Wraps `g_application_get_default`. int gApplicationGetDefault() { if (_gio == null) { return 0; } final _GApplicationGetDefaultDart getDefault = _gio! .lookupFunction<_GApplicationGetDefaultC, _GApplicationGetDefaultDart>( 'g_application_get_default'); return getDefault(); } /// Wraps g_application_get_application_id. Pointer gApplicationGetApplicationId(int app) { if (_gio == null) { return nullptr; } final _GApplicationGetApplicationIdDart gApplicationGetApplicationId = _gio! .lookupFunction<_GApplicationGetApplicationIdC, _GApplicationGetApplicationIdDart>( 'g_application_get_application_id'); return gApplicationGetApplicationId(app); } } /// Allows overriding the default GioUtils instance with a fake for testing. @visibleForTesting GioUtils? gioUtilsOverride; /// Gets the application ID for this app. String? getApplicationId() { final GioUtils gio = gioUtilsOverride ?? GioUtils(); if (!gio.libraryIsPresent) { return null; } final int app = gio.gApplicationGetDefault(); if (app == 0) { return null; } final Pointer appId = gio.gApplicationGetApplicationId(app); if (appId == null || appId == nullptr) { return null; } return appId.toDartString(); } ================================================ FILE: packages/path_provider/path_provider_linux/lib/src/get_application_id_stub.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Gets the application ID for this app. String? getApplicationId() => null; ================================================ FILE: packages/path_provider/path_provider_linux/lib/src/path_provider_linux.dart ================================================ // Copyright 2013 The Flutter 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'; import 'package:path/path.dart' as path; import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; import 'package:xdg_directories/xdg_directories.dart' as xdg; import 'get_application_id.dart'; /// The linux implementation of [PathProviderPlatform] /// /// This class implements the `package:path_provider` functionality for Linux. class PathProviderLinux extends PathProviderPlatform { /// Constructs an instance of [PathProviderLinux] PathProviderLinux() : _environment = Platform.environment; /// Constructs an instance of [PathProviderLinux] with the given [environment] @visibleForTesting PathProviderLinux.private( {Map environment = const {}, String? executableName, String? applicationId}) : _environment = environment, _executableName = executableName, _applicationId = applicationId; final Map _environment; String? _executableName; String? _applicationId; /// Registers this class as the default instance of [PathProviderPlatform] static void registerWith() { PathProviderPlatform.instance = PathProviderLinux(); } @override Future getTemporaryPath() { final String environmentTmpDir = _environment['TMPDIR'] ?? ''; return Future.value( environmentTmpDir.isEmpty ? '/tmp' : environmentTmpDir, ); } @override Future getApplicationSupportPath() async { final Directory directory = Directory(path.join(xdg.dataHome.path, await _getId())); if (directory.existsSync()) { return directory.path; } // This plugin originally used the executable name as a directory. // Use that if it exists for backwards compatibility. final Directory legacyDirectory = Directory(path.join(xdg.dataHome.path, await _getExecutableName())); if (legacyDirectory.existsSync()) { return legacyDirectory.path; } // Create the directory, because mobile implementations assume the directory exists. await directory.create(recursive: true); return directory.path; } @override Future getApplicationDocumentsPath() { return Future.value(xdg.getUserDirectory('DOCUMENTS')?.path); } @override Future getDownloadsPath() { return Future.value(xdg.getUserDirectory('DOWNLOAD')?.path); } // Gets the name of this executable. Future _getExecutableName() async { _executableName ??= path.basenameWithoutExtension( await File('/proc/self/exe').resolveSymbolicLinks()); return _executableName!; } // Gets the unique ID for this application. Future _getId() async { _applicationId ??= getApplicationId(); // If no application ID then fall back to using the executable name. return _applicationId ?? await _getExecutableName(); } } ================================================ FILE: packages/path_provider/path_provider_linux/pubspec.yaml ================================================ name: path_provider_linux description: Linux implementation of the path_provider plugin repository: https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_linux issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22 version: 2.1.8 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: path_provider platforms: linux: dartPluginClass: PathProviderLinux dependencies: ffi: ">=1.1.2 <3.0.0" flutter: sdk: flutter path: ^1.8.0 path_provider_platform_interface: ^2.0.0 xdg_directories: ">=0.2.0 <2.0.0" dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/path_provider/path_provider_linux/test/get_application_id_test.dart ================================================ // Copyright 2013 The Flutter 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:ffi'; import 'package:ffi/ffi.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:path_provider_linux/src/get_application_id_real.dart'; class _FakeGioUtils implements GioUtils { int? application; Pointer? applicationId; @override bool libraryIsPresent = false; @override int gApplicationGetDefault() => application!; @override Pointer gApplicationGetApplicationId(int app) => applicationId!; } void main() { late _FakeGioUtils fakeGio; setUp(() { fakeGio = _FakeGioUtils(); gioUtilsOverride = fakeGio; }); tearDown(() { gioUtilsOverride = null; }); test('returns null if libgio is not available', () { expect(getApplicationId(), null); }); test('returns null if g_paplication_get_default returns 0', () { fakeGio.libraryIsPresent = true; fakeGio.application = 0; expect(getApplicationId(), null); }); test('returns null if g_application_get_application_id returns nullptr', () { fakeGio.libraryIsPresent = true; fakeGio.application = 1; fakeGio.applicationId = nullptr; expect(getApplicationId(), null); }); test('returns value if g_application_get_application_id returns a value', () { fakeGio.libraryIsPresent = true; fakeGio.application = 1; const String id = 'foo'; final Pointer idPtr = id.toNativeUtf8(); fakeGio.applicationId = idPtr; expect(getApplicationId(), id); calloc.free(idPtr); }); } ================================================ FILE: packages/path_provider/path_provider_linux/test/path_provider_linux_test.dart ================================================ // Copyright 2013 The Flutter 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:path_provider_linux/path_provider_linux.dart'; import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; import 'package:xdg_directories/xdg_directories.dart' as xdg; void main() { TestWidgetsFlutterBinding.ensureInitialized(); PathProviderLinux.registerWith(); test('registered instance', () { expect(PathProviderPlatform.instance, isA()); }); test('getTemporaryPath defaults to TMPDIR', () async { final PathProviderPlatform plugin = PathProviderLinux.private( environment: {'TMPDIR': '/run/user/0/tmp'}, ); expect(await plugin.getTemporaryPath(), '/run/user/0/tmp'); }); test('getTemporaryPath uses fallback if TMPDIR is empty', () async { final PathProviderPlatform plugin = PathProviderLinux.private( environment: {'TMPDIR': ''}, ); expect(await plugin.getTemporaryPath(), '/tmp'); }); test('getTemporaryPath uses fallback if TMPDIR is unset', () async { final PathProviderPlatform plugin = PathProviderLinux.private( environment: {}, ); expect(await plugin.getTemporaryPath(), '/tmp'); }); test('getApplicationSupportPath', () async { final PathProviderPlatform plugin = PathProviderLinux.private( executableName: 'path_provider_linux_test_binary', applicationId: 'com.example.Test'); // Note this will fail if ${xdg.dataHome.path}/path_provider_linux_test_binary exists on the local filesystem. expect(await plugin.getApplicationSupportPath(), '${xdg.dataHome.path}/com.example.Test'); }); test('getApplicationSupportPath uses executable name if no application Id', () async { final PathProviderPlatform plugin = PathProviderLinux.private( executableName: 'path_provider_linux_test_binary'); expect(await plugin.getApplicationSupportPath(), '${xdg.dataHome.path}/path_provider_linux_test_binary'); }); test('getApplicationDocumentsPath', () async { final PathProviderPlatform plugin = PathProviderPlatform.instance; expect(await plugin.getApplicationDocumentsPath(), startsWith('/')); }); test('getDownloadsPath', () async { final PathProviderPlatform plugin = PathProviderPlatform.instance; expect(await plugin.getDownloadsPath(), startsWith('/')); }); } ================================================ FILE: packages/path_provider/path_provider_platform_interface/AUTHORS ================================================ # 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> ================================================ FILE: packages/path_provider/path_provider_platform_interface/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 2.0.5 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. ## 2.0.4 * Minor fixes for new analysis options. * Removes unnecessary imports. ## 2.0.3 * Removes dependency on `meta`. ## 2.0.2 * Update to use the `verify` method introduced in plugin_platform_interface 2.1.0. ## 2.0.1 * Update platform_plugin_interface version requirement. ## 2.0.0 * Migrate to null safety. ## 1.0.5 * Update Flutter SDK constraint. ## 1.0.4 * Remove unused `test` dependency. ## 1.0.3 * Increase upper range of `package:platform` constraint to allow 3.X versions. ## 1.0.2 * Update lower bound of dart dependency to 2.1.0. ## 1.0.1 * Rename enum to StorageDirectory for backwards compatibility. ## 1.0.0 * Initial release. ================================================ FILE: packages/path_provider/path_provider_platform_interface/LICENSE ================================================ 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. ================================================ FILE: packages/path_provider/path_provider_platform_interface/README.md ================================================ # path_provider_platform_interface A common platform interface for the [`path_provider`][1] plugin. This interface allows platform-specific implementations of the `path_provider` plugin, as well as the plugin itself, to ensure they are supporting the same interface. # Usage To implement a new platform-specific implementation of `path_provider`, extend [`PathProviderPlatform`][2] with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `PathProviderPlatform` by calling `PathProviderPlatform.instance = MyPlatformPathProvider()`. # Note on breaking changes Strongly prefer non-breaking changes (such as adding a method to the interface) over breaking changes for this package. See https://flutter.dev/go/platform-interface-breaking-changes for a discussion on why a less-clean interface is preferable to a breaking change. [1]: ../ [2]: lib/path_provider_platform_interface.dart ================================================ FILE: packages/path_provider/path_provider_platform_interface/lib/path_provider_platform_interface.dart ================================================ // Copyright 2013 The Flutter 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:plugin_platform_interface/plugin_platform_interface.dart'; import 'src/enums.dart'; import 'src/method_channel_path_provider.dart'; export 'src/enums.dart'; /// The interface that implementations of path_provider must implement. /// /// Platform implementations should extend this class rather than implement it as `PathProvider` /// does not consider newly added methods to be breaking changes. Extending this class /// (using `extends`) ensures that the subclass will get the default implementation, while /// platform implementations that `implements` this interface will be broken by newly added /// [PathProviderPlatform] methods. abstract class PathProviderPlatform extends PlatformInterface { /// Constructs a PathProviderPlatform. PathProviderPlatform() : super(token: _token); static final Object _token = Object(); static PathProviderPlatform _instance = MethodChannelPathProvider(); /// The default instance of [PathProviderPlatform] to use. /// /// Defaults to [MethodChannelPathProvider]. static PathProviderPlatform get instance => _instance; /// Platform-specific plugins should set this with their own platform-specific /// class that extends [PathProviderPlatform] when they register themselves. static set instance(PathProviderPlatform instance) { PlatformInterface.verify(instance, _token); _instance = instance; } /// Path to the temporary directory on the device that is not backed up and is /// suitable for storing caches of downloaded files. Future getTemporaryPath() { throw UnimplementedError('getTemporaryPath() has not been implemented.'); } /// Path to a directory where the application may place application support /// files. Future getApplicationSupportPath() { throw UnimplementedError( 'getApplicationSupportPath() has not been implemented.'); } /// Path to the directory where application can store files that are persistent, /// backed up, and not visible to the user, such as sqlite.db. Future getLibraryPath() { throw UnimplementedError('getLibraryPath() has not been implemented.'); } /// Path to a directory where the application may place data that is /// user-generated, or that cannot otherwise be recreated by your application. Future getApplicationDocumentsPath() { throw UnimplementedError( 'getApplicationDocumentsPath() has not been implemented.'); } /// 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. Future getExternalStoragePath() { throw UnimplementedError( 'getExternalStoragePath() has not been implemented.'); } /// 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. Future?> getExternalCachePaths() { throw UnimplementedError( 'getExternalCachePaths() has not been implemented.'); } /// Paths to directories where application specific data can be stored. /// These paths typically reside on external storage like separate partitions /// or SD cards. Phones may have multiple storage directories available. Future?> getExternalStoragePaths({ /// Optional parameter. See [StorageDirectory] for more informations on /// how this type translates to Android storage directories. StorageDirectory? type, }) { throw UnimplementedError( 'getExternalStoragePaths() has not been implemented.'); } /// Path to the directory where downloaded files can be stored. /// This is typically only relevant on desktop operating systems. Future getDownloadsPath() { throw UnimplementedError('getDownloadsPath() has not been implemented.'); } } ================================================ FILE: packages/path_provider/path_provider_platform_interface/lib/src/enums.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Corresponds to constants defined in Androids `android.os.Environment` class. /// /// https://developer.android.com/reference/android/os/Environment.html#fields_1 enum StorageDirectory { /// Contains audio files that should be treated as music. /// /// See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_MUSIC. music, /// Contains audio files that should be treated as podcasts. /// /// See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_PODCASTS. podcasts, /// Contains audio files that should be treated as ringtones. /// /// See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_RINGTONES. ringtones, /// Contains audio files that should be treated as alarm sounds. /// /// See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_ALARMS. alarms, /// Contains audio files that should be treated as notification sounds. /// /// See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_NOTIFICATIONS. notifications, /// Contains images. See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_PICTURES. pictures, /// Contains movies. See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_MOVIES. movies, /// Contains files of any type that have been downloaded by the user. /// /// See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_DOWNLOADS. downloads, /// Used to hold both pictures and videos when the device filesystem is /// treated like a camera's. /// /// See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_DCIM. dcim, /// Holds user-created documents. See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_DOCUMENTS. documents, } ================================================ FILE: packages/path_provider/path_provider_platform_interface/lib/src/method_channel_path_provider.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart' show visibleForTesting; import 'package:flutter/services.dart'; import 'package:platform/platform.dart'; import '../path_provider_platform_interface.dart'; /// An implementation of [PathProviderPlatform] that uses method channels. class MethodChannelPathProvider extends PathProviderPlatform { /// The method channel used to interact with the native platform. @visibleForTesting MethodChannel methodChannel = const MethodChannel('plugins.flutter.io/path_provider'); // Ideally, this property shouldn't exist, and each platform should // just implement the supported methods. Once all the platforms are // federated, this property should be removed. Platform _platform = const LocalPlatform(); /// This API is only exposed for the unit tests. It should not be used by /// any code outside of the plugin itself. @visibleForTesting // ignore: use_setters_to_change_properties void setMockPathProviderPlatform(Platform platform) { _platform = platform; } @override Future getTemporaryPath() { return methodChannel.invokeMethod('getTemporaryDirectory'); } @override Future getApplicationSupportPath() { return methodChannel.invokeMethod('getApplicationSupportDirectory'); } @override Future getLibraryPath() { if (!_platform.isIOS && !_platform.isMacOS) { throw UnsupportedError('Functionality only available on iOS/macOS'); } return methodChannel.invokeMethod('getLibraryDirectory'); } @override Future getApplicationDocumentsPath() { return methodChannel .invokeMethod('getApplicationDocumentsDirectory'); } @override Future getExternalStoragePath() { if (!_platform.isAndroid) { throw UnsupportedError('Functionality only available on Android'); } return methodChannel.invokeMethod('getStorageDirectory'); } @override Future?> getExternalCachePaths() { if (!_platform.isAndroid) { throw UnsupportedError('Functionality only available on Android'); } return methodChannel .invokeListMethod('getExternalCacheDirectories'); } @override Future?> getExternalStoragePaths({ StorageDirectory? type, }) async { if (!_platform.isAndroid) { throw UnsupportedError('Functionality only available on Android'); } return methodChannel.invokeListMethod( 'getExternalStorageDirectories', {'type': type?.index}, ); } @override Future getDownloadsPath() { if (!_platform.isMacOS) { throw UnsupportedError('Functionality only available on macOS'); } return methodChannel.invokeMethod('getDownloadsDirectory'); } } ================================================ FILE: packages/path_provider/path_provider_platform_interface/pubspec.yaml ================================================ name: path_provider_platform_interface description: A common platform interface for the path_provider plugin. repository: https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_platform_interface issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%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.5 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter platform: ^3.0.0 plugin_platform_interface: ^2.1.0 dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/path_provider/path_provider_platform_interface/test/method_channel_path_provider_test.dart ================================================ // Copyright 2013 The Flutter 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:path_provider_platform_interface/src/enums.dart'; import 'package:path_provider_platform_interface/src/method_channel_path_provider.dart'; import 'package:platform/platform.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); const String kTemporaryPath = 'temporaryPath'; const String kApplicationSupportPath = 'applicationSupportPath'; const String kLibraryPath = 'libraryPath'; const String kApplicationDocumentsPath = 'applicationDocumentsPath'; const String kExternalCachePaths = 'externalCachePaths'; const String kExternalStoragePaths = 'externalStoragePaths'; const String kDownloadsPath = 'downloadsPath'; group('$MethodChannelPathProvider', () { late MethodChannelPathProvider methodChannelPathProvider; final List log = []; setUp(() async { methodChannelPathProvider = MethodChannelPathProvider(); _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(methodChannelPathProvider.methodChannel, (MethodCall methodCall) async { log.add(methodCall); switch (methodCall.method) { case 'getTemporaryDirectory': return kTemporaryPath; case 'getApplicationSupportDirectory': return kApplicationSupportPath; case 'getLibraryDirectory': return kLibraryPath; case 'getApplicationDocumentsDirectory': return kApplicationDocumentsPath; case 'getExternalStorageDirectories': return [kExternalStoragePaths]; case 'getExternalCacheDirectories': return [kExternalCachePaths]; case 'getDownloadsDirectory': return kDownloadsPath; default: return null; } }); }); setUp(() { methodChannelPathProvider.setMockPathProviderPlatform( FakePlatform(operatingSystem: 'android')); }); tearDown(() { log.clear(); }); test('getTemporaryPath', () async { final String? path = await methodChannelPathProvider.getTemporaryPath(); expect( log, [isMethodCall('getTemporaryDirectory', arguments: null)], ); expect(path, kTemporaryPath); }); test('getApplicationSupportPath', () async { final String? path = await methodChannelPathProvider.getApplicationSupportPath(); expect( log, [ isMethodCall('getApplicationSupportDirectory', arguments: null) ], ); expect(path, kApplicationSupportPath); }); test('getLibraryPath android fails', () async { try { await methodChannelPathProvider.getLibraryPath(); fail('should throw UnsupportedError'); } catch (e) { expect(e, isUnsupportedError); } }); test('getLibraryPath iOS succeeds', () async { methodChannelPathProvider .setMockPathProviderPlatform(FakePlatform(operatingSystem: 'ios')); final String? path = await methodChannelPathProvider.getLibraryPath(); expect( log, [isMethodCall('getLibraryDirectory', arguments: null)], ); expect(path, kLibraryPath); }); test('getLibraryPath macOS succeeds', () async { methodChannelPathProvider .setMockPathProviderPlatform(FakePlatform(operatingSystem: 'macos')); final String? path = await methodChannelPathProvider.getLibraryPath(); expect( log, [isMethodCall('getLibraryDirectory', arguments: null)], ); expect(path, kLibraryPath); }); test('getApplicationDocumentsPath', () async { final String? path = await methodChannelPathProvider.getApplicationDocumentsPath(); expect( log, [ isMethodCall('getApplicationDocumentsDirectory', arguments: null) ], ); expect(path, kApplicationDocumentsPath); }); test('getExternalCachePaths android succeeds', () async { final List? result = await methodChannelPathProvider.getExternalCachePaths(); expect( log, [isMethodCall('getExternalCacheDirectories', arguments: null)], ); expect(result!.length, 1); expect(result.first, kExternalCachePaths); }); test('getExternalCachePaths non-android fails', () async { methodChannelPathProvider .setMockPathProviderPlatform(FakePlatform(operatingSystem: 'ios')); try { await methodChannelPathProvider.getExternalCachePaths(); fail('should throw UnsupportedError'); } catch (e) { expect(e, isUnsupportedError); } }); for (final StorageDirectory? type in [ null, ...StorageDirectory.values ]) { test('getExternalStoragePaths (type: $type) android succeeds', () async { final List? result = await methodChannelPathProvider.getExternalStoragePaths(type: type); expect( log, [ isMethodCall( 'getExternalStorageDirectories', arguments: {'type': type?.index}, ) ], ); expect(result!.length, 1); expect(result.first, kExternalStoragePaths); }); test('getExternalStoragePaths (type: $type) non-android fails', () async { methodChannelPathProvider .setMockPathProviderPlatform(FakePlatform(operatingSystem: 'ios')); try { await methodChannelPathProvider.getExternalStoragePaths(); fail('should throw UnsupportedError'); } catch (e) { expect(e, isUnsupportedError); } }); } // end of for-loop test('getDownloadsPath macos succeeds', () async { methodChannelPathProvider .setMockPathProviderPlatform(FakePlatform(operatingSystem: 'macos')); final String? result = await methodChannelPathProvider.getDownloadsPath(); expect( log, [isMethodCall('getDownloadsDirectory', arguments: null)], ); expect(result, kDownloadsPath); }); test('getDownloadsPath non-macos fails', () async { methodChannelPathProvider.setMockPathProviderPlatform( FakePlatform(operatingSystem: 'android')); try { await methodChannelPathProvider.getDownloadsPath(); fail('should throw UnsupportedError'); } catch (e) { expect(e, isUnsupportedError); } }); }); } /// 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; ================================================ FILE: packages/path_provider/path_provider_windows/.gitignore ================================================ .packages .flutter-plugins pubspec.lock ================================================ FILE: packages/path_provider/path_provider_windows/AUTHORS ================================================ # 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> ================================================ FILE: packages/path_provider/path_provider_windows/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 2.1.3 * Updates minimum Flutter version to 2.10. * Adds compatibility with `package:win32` 3.x. ## 2.1.2 * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 2.1.1 * Updates dependency version of `package:win32` to 2.1.0. ## 2.1.0 * Upgrades `package:ffi` dependency to 2.0.0. * Added support for unicode encoded VERSIONINFO. * Minor fixes for new analysis options. ## 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 * Removed obsolete `pluginClass: none` from pubpsec. ## 2.0.3 * Updated installation instructions in README. ## 2.0.2 * Add `implements` to pubspec.yaml. * Add `registerWith()` to the Dart main class. ## 2.0.1 * Fix a crash when a known folder can't be located. ## 2.0.0 * Migrate to null safety ## 0.0.4+4 * Update Flutter SDK constraint. ## 0.0.4+3 * Remove unused `test` dependency. * Update Dart SDK constraint in example. ## 0.0.4+2 * Check in windows/ directory for example/ ## 0.0.4+1 * Add getPath to the stub, so that the analyzer won't complain about fakes that override it. * export 'folders.dart' rather than importing it, since it's intended to be public. ## 0.0.4 * Move the actual implementation behind a conditional import, exporting a stub for platforms that don't support FFI. Fixes web builds in projects with transitive dependencies on path_provider. ## 0.0.3 * Add missing `pluginClass: none` for compatibilty with stable channel. ## 0.0.2 * README update for endorsement. * Changed getApplicationSupportPath location. * Removed getLibraryPath. ## 0.0.1+2 * The initial implementation of path_provider for Windows * Implements getTemporaryPath, getApplicationSupportPath, getLibraryPath, getApplicationDocumentsPath and getDownloadsPath. ================================================ FILE: packages/path_provider/path_provider_windows/LICENSE ================================================ 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. ================================================ FILE: packages/path_provider/path_provider_windows/README.md ================================================ # path\_provider\_windows The Windows 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 ================================================ FILE: packages/path_provider/path_provider_windows/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: packages/path_provider/path_provider_windows/example/.metadata ================================================ # 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: f2320c3b7a42bc27e7f038212eed1b01f4269641 channel: master project_type: app ================================================ FILE: packages/path_provider/path_provider_windows/example/README.md ================================================ # 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. ================================================ FILE: packages/path_provider/path_provider_windows/example/integration_test/path_provider_test.dart ================================================ // Copyright 2013 The Flutter 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_windows/path_provider_windows.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('getTemporaryDirectory', (WidgetTester tester) async { final PathProviderWindows provider = PathProviderWindows(); final String? result = await provider.getTemporaryPath(); _verifySampleFile(result, 'temporaryDirectory'); }); testWidgets('getApplicationDocumentsDirectory', (WidgetTester tester) async { final PathProviderWindows provider = PathProviderWindows(); final String? result = await provider.getApplicationDocumentsPath(); _verifySampleFile(result, 'applicationDocuments'); }); testWidgets('getApplicationSupportDirectory', (WidgetTester tester) async { final PathProviderWindows provider = PathProviderWindows(); final String? result = await provider.getApplicationSupportPath(); _verifySampleFile(result, 'applicationSupport'); }); testWidgets('getDownloadsDirectory', (WidgetTester tester) async { final PathProviderWindows provider = PathProviderWindows(); final String? result = await provider.getDownloadsPath(); _verifySampleFile(result, 'downloads'); }); } /// 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(); } ================================================ FILE: packages/path_provider/path_provider_windows/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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_windows/path_provider_windows.dart'; void main() { runApp(const MyApp()); } /// Sample app class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { String? _tempDirectory = 'Unknown'; String? _downloadsDirectory = 'Unknown'; String? _appSupportDirectory = 'Unknown'; String? _documentsDirectory = 'Unknown'; @override void initState() { super.initState(); initDirectories(); } // Platform messages are asynchronous, so we initialize in an async method. Future initDirectories() async { String? tempDirectory; String? downloadsDirectory; String? appSupportDirectory; String? documentsDirectory; final PathProviderWindows provider = PathProviderWindows(); try { tempDirectory = await provider.getTemporaryPath(); } catch (exception) { tempDirectory = 'Failed to get temp directory: $exception'; } try { downloadsDirectory = await provider.getDownloadsPath(); } catch (exception) { downloadsDirectory = 'Failed to get downloads directory: $exception'; } try { documentsDirectory = await provider.getApplicationDocumentsPath(); } catch (exception) { documentsDirectory = 'Failed to get documents directory: $exception'; } try { appSupportDirectory = await provider.getApplicationSupportPath(); } catch (exception) { appSupportDirectory = 'Failed to get app support directory: $exception'; } setState(() { _tempDirectory = tempDirectory; _downloadsDirectory = downloadsDirectory; _appSupportDirectory = appSupportDirectory; _documentsDirectory = documentsDirectory; }); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Path Provider example app'), ), body: Center( child: Column( children: [ Text('Temp Directory: $_tempDirectory\n'), Text('Documents Directory: $_documentsDirectory\n'), Text('Downloads Directory: $_downloadsDirectory\n'), Text('Application Support Directory: $_appSupportDirectory\n'), ], ), ), ), ); } } ================================================ FILE: packages/path_provider/path_provider_windows/example/pubspec.yaml ================================================ name: path_provider_example description: Demonstrates how to use the path_provider plugin. publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter path_provider_windows: # When depending on this package from a real application you should use: # path_provider_windows: ^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: ../ dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/path_provider/path_provider_windows/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/path_provider/path_provider_windows/example/windows/.gitignore ================================================ flutter/ephemeral/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: packages/path_provider/path_provider_windows/example/windows/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(example LANGUAGES CXX) set(BINARY_NAME "example") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" CACHE STRING "" FORCE) else() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() endif() set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") # Use Unicode for all projects. add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") target_compile_options(${TARGET} PRIVATE /EHsc) target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build add_subdirectory("runner") # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # Support files are copied into place next to the executable, so that it can # run in place. This is done instead of making a separate bundle (as on Linux) # so that building and running from within Visual Studio will work. set(BUILD_BUNDLE_DIR "$") # Make the "install" step default, as it's required to run. set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" CONFIGURATIONS Profile;Release COMPONENT Runtime) ================================================ FILE: packages/path_provider/path_provider_windows/example/windows/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "flutter_export.h" "flutter_windows.h" "flutter_messenger.h" "flutter_plugin_registrar.h" ) list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") add_dependencies(flutter flutter_assemble) # === Wrapper === list(APPEND CPP_WRAPPER_SOURCES_CORE "core_implementations.cc" "standard_codec.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_PLUGIN "plugin_registrar.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_APP "flutter_engine.cc" "flutter_view_controller.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") # Wrapper sources needed for a plugin. add_library(flutter_wrapper_plugin STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ) apply_standard_settings(flutter_wrapper_plugin) set_target_properties(flutter_wrapper_plugin PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(flutter_wrapper_plugin PROPERTIES CXX_VISIBILITY_PRESET hidden) target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) target_include_directories(flutter_wrapper_plugin PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_plugin flutter_assemble) # Wrapper sources needed for the runner. add_library(flutter_wrapper_app STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_APP} ) apply_standard_settings(flutter_wrapper_app) target_link_libraries(flutter_wrapper_app PUBLIC flutter) target_include_directories(flutter_wrapper_app PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_app flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" windows-x64 $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ) ================================================ FILE: packages/path_provider/path_provider_windows/example/windows/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/path_provider/path_provider_windows/example/windows/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(runner LANGUAGES CXX) add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" "run_loop.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) apply_standard_settings(${BINARY_NAME}) target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") add_dependencies(${BINARY_NAME} flutter_assemble) ================================================ FILE: packages/path_provider/path_provider_windows/example/windows/runner/Runner.rc ================================================ // Microsoft Visual C++ generated resource script. // #pragma code_page(65001) #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APP_ICON ICON "resources\\app_icon.ico" ///////////////////////////////////////////////////////////////////////////// // // Version // #ifdef FLUTTER_BUILD_NUMBER #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER #else #define VERSION_AS_NUMBER 1,0,0 #endif #ifdef FLUTTER_BUILD_NAME #define VERSION_AS_STRING #FLUTTER_BUILD_NAME #else #define VERSION_AS_STRING "1.0.0" #endif VS_VERSION_INFO VERSIONINFO FILEVERSION VERSION_AS_NUMBER PRODUCTVERSION VERSION_AS_NUMBER FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "com.example" "\0" VALUE "FileDescription", "A new Flutter project." "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "example" "\0" VALUE "LegalCopyright", "Copyright (C) 2020 com.example. All rights reserved." "\0" VALUE "OriginalFilename", "example.exe" "\0" VALUE "ProductName", "example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: packages/path_provider/path_provider_windows/example/windows/runner/flutter_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "flutter_window.h" #include #include "flutter/generated_plugin_registrant.h" FlutterWindow::FlutterWindow(RunLoop* run_loop, const flutter::DartProject& project) : run_loop_(run_loop), project_(project) {} FlutterWindow::~FlutterWindow() {} bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opporutunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) { return *result; } } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); } ================================================ FILE: packages/path_provider/path_provider_windows/example/windows/runner/flutter_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_ #include #include #include #include "run_loop.h" #include "win32_window.h" // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { public: // Creates a new FlutterWindow driven by the |run_loop|, hosting a // Flutter view running |project|. explicit FlutterWindow(RunLoop* run_loop, const flutter::DartProject& project); virtual ~FlutterWindow(); protected: // Win32Window: bool OnCreate() override; void OnDestroy() override; LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; private: // The run loop driving events for this window. RunLoop* run_loop_; // The project to run. flutter::DartProject project_; // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; }; #endif // RUNNER_FLUTTER_WINDOW_H_ ================================================ FILE: packages/path_provider/path_provider_windows/example/windows/runner/main.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include "flutter_window.h" #include "run_loop.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t* command_line, _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { CreateAndAttachConsole(); } // Initialize COM, so that it is available for use in the library and/or // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); RunLoop run_loop; flutter::DartProject project(L"data"); FlutterWindow window(&run_loop, project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.CreateAndShow(L"example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); run_loop.Run(); ::CoUninitialize(); return EXIT_SUCCESS; } ================================================ FILE: packages/path_provider/path_provider_windows/example/windows/runner/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Runner.rc // #define IDI_APP_ICON 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: packages/path_provider/path_provider_windows/example/windows/runner/run_loop.cpp ================================================ // Copyright 2013 The Flutter 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 "run_loop.h" #include #include RunLoop::RunLoop() {} RunLoop::~RunLoop() {} void RunLoop::Run() { bool keep_running = true; TimePoint next_flutter_event_time = TimePoint::clock::now(); while (keep_running) { std::chrono::nanoseconds wait_duration = std::max(std::chrono::nanoseconds(0), next_flutter_event_time - TimePoint::clock::now()); ::MsgWaitForMultipleObjects( 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000), QS_ALLINPUT); bool processed_events = false; MSG message; // All pending Windows messages must be processed; MsgWaitForMultipleObjects // won't return again for items left in the queue after PeekMessage. while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) { processed_events = true; if (message.message == WM_QUIT) { keep_running = false; break; } ::TranslateMessage(&message); ::DispatchMessage(&message); // Allow Flutter to process messages each time a Windows message is // processed, to prevent starvation. next_flutter_event_time = std::min(next_flutter_event_time, ProcessFlutterMessages()); } // If the PeekMessage loop didn't run, process Flutter messages. if (!processed_events) { next_flutter_event_time = std::min(next_flutter_event_time, ProcessFlutterMessages()); } } } void RunLoop::RegisterFlutterInstance( flutter::FlutterEngine* flutter_instance) { flutter_instances_.insert(flutter_instance); } void RunLoop::UnregisterFlutterInstance( flutter::FlutterEngine* flutter_instance) { flutter_instances_.erase(flutter_instance); } RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { TimePoint next_event_time = TimePoint::max(); for (auto instance : flutter_instances_) { std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); if (wait_duration != std::chrono::nanoseconds::max()) { next_event_time = std::min(next_event_time, TimePoint::clock::now() + wait_duration); } } return next_event_time; } ================================================ FILE: packages/path_provider/path_provider_windows/example/windows/runner/run_loop.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_RUN_LOOP_H_ #define RUNNER_RUN_LOOP_H_ #include #include #include // A runloop that will service events for Flutter instances as well // as native messages. class RunLoop { public: RunLoop(); ~RunLoop(); // Prevent copying RunLoop(RunLoop const&) = delete; RunLoop& operator=(RunLoop const&) = delete; // Runs the run loop until the application quits. void Run(); // Registers the given Flutter instance for event servicing. void RegisterFlutterInstance(flutter::FlutterEngine* flutter_instance); // Unregisters the given Flutter instance from event servicing. void UnregisterFlutterInstance(flutter::FlutterEngine* flutter_instance); private: using TimePoint = std::chrono::steady_clock::time_point; // Processes all currently pending messages for registered Flutter instances. TimePoint ProcessFlutterMessages(); std::set flutter_instances_; }; #endif // RUNNER_RUN_LOOP_H_ ================================================ FILE: packages/path_provider/path_provider_windows/example/windows/runner/runner.exe.manifest ================================================ PerMonitorV2 ================================================ FILE: packages/path_provider/path_provider_windows/example/windows/runner/utils.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "utils.h" #include #include #include #include #include void CreateAndAttachConsole() { if (::AllocConsole()) { FILE* unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } if (freopen_s(&unused, "CONOUT$", "w", stderr)) { _dup2(_fileno(stdout), 2); } std::ios::sync_with_stdio(); FlutterDesktopResyncOutputStreams(); } } ================================================ FILE: packages/path_provider/path_provider_windows/example/windows/runner/utils.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_UTILS_H_ #define RUNNER_UTILS_H_ // Creates a console for the process, and redirects stdout and stderr to // it for both the runner and the Flutter library. void CreateAndAttachConsole(); #endif // RUNNER_UTILS_H_ ================================================ FILE: packages/path_provider/path_provider_windows/example/windows/runner/win32_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "win32_window.h" #include #include "resource.h" namespace { constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); // Scale helper to convert logical scaler values to physical using passed in // scale factor int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); if (!user32_module) { return; } auto enable_non_client_dpi_scaling = reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); FreeLibrary(user32_module); } } } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; // Returns the singleton registar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; } // Returns the name of the window class, registering the class if it hasn't // previously been registered. const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; bool class_registered_ = false; }; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; RegisterClass(&window_class); class_registered_ = true; } return kWindowClassName; } void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, const Size& size) { Destroy(); const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), static_cast(origin.y)}; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } return OnCreate(); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { case WM_DESTROY: window_handle_ = nullptr; Destroy(); if (quit_on_close_) { PostQuitMessage(0); } return 0; case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left; LONG newHeight = newRectSize->bottom - newRectSize->top; SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_SIZE: RECT rect = GetClientArea(); if (child_content_ != nullptr) { // Size and position the child window. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); } return 0; case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_); } return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } void Win32Window::Destroy() { OnDestroy(); if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, true); SetFocus(child_content_); } RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } void Win32Window::OnDestroy() { // No-op; provided for subclasses. } ================================================ FILE: packages/path_provider/path_provider_windows/example/windows/runner/win32_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_ #include #include #include #include // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { public: struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) : width(width), height(height) {} }; Win32Window(); virtual ~Win32Window(); // Creates and shows a win32 window with |title| and position and size using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a // consistent size to will treat the width height passed in to this function // as logical pixels and scale to appropriate for the default monitor. Returns // true if the window was created successfully. bool CreateAndShow(const std::wstring& title, const Point& origin, const Size& size); // Release OS resources associated with window. void Destroy(); // Inserts |content| into the window tree. void SetChildContent(HWND content); // Returns the backing Window handle to enable clients to set icon and other // window properties. Returns nullptr if the window has been destroyed. HWND GetHandle(); // If true, closing this window will quit the application. void SetQuitOnClose(bool quit_on_close); // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Called when CreateAndShow is called, allowing subclass window-related // setup. Subclasses should return false if setup fails. virtual bool OnCreate(); // Called when Destroy is called. virtual void OnDestroy(); private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically // responsponds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; bool quit_on_close_ = false; // window handle for top level window. HWND window_handle_ = nullptr; // window handle for hosted content. HWND child_content_ = nullptr; }; #endif // RUNNER_WIN32_WINDOW_H_ ================================================ FILE: packages/path_provider/path_provider_windows/lib/path_provider_windows.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // path_provider_windows is implemented using FFI; export a stub for platforms // that don't support FFI (e.g., web) to avoid having transitive dependencies // break web compilation. export 'src/folders_stub.dart' if (dart.library.ffi) 'src/folders.dart'; export 'src/path_provider_windows_stub.dart' if (dart.library.ffi) 'src/path_provider_windows_real.dart'; ================================================ FILE: packages/path_provider/path_provider_windows/lib/src/folders.dart ================================================ // Copyright 2013 The Flutter 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:win32/win32.dart'; // ignore_for_file: non_constant_identifier_names // ignore: avoid_classes_with_only_static_members /// A class containing the GUID references for each of the documented Windows /// known folders. A property of this class may be passed to the `getPath` /// method in the [PathProvidersWindows] class to retrieve a known folder from /// Windows. class WindowsKnownFolder { /// The file system directory that is used to store administrative tools for /// an individual user. The MMC will save customized consoles to this /// directory, and it will roam with the user. static String get AdminTools => FOLDERID_AdminTools; /// The file system directory that acts as a staging area for files waiting to /// be written to a CD. A typical path is C:\Documents and /// Settings\username\Local Settings\Application Data\Microsoft\CD Burning. static String get CDBurning => FOLDERID_CDBurning; /// The file system directory that contains administrative tools for all users /// of the computer. static String get CommonAdminTools => FOLDERID_CommonAdminTools; /// The file system directory that contains the directories for the common /// program groups that appear on the Start menu for all users. A typical path /// is C:\Documents and Settings\All Users\Start Menu\Programs. static String get CommonPrograms => FOLDERID_CommonPrograms; /// The file system directory that contains the programs and folders that /// appear on the Start menu for all users. A typical path is C:\Documents and /// Settings\All Users\Start Menu. static String get CommonStartMenu => FOLDERID_CommonStartMenu; /// The file system directory that contains the programs that appear in the /// Startup folder for all users. A typical path is C:\Documents and /// Settings\All Users\Start Menu\Programs\Startup. static String get CommonStartup => FOLDERID_CommonStartup; /// The file system directory that contains the templates that are available /// to all users. A typical path is C:\Documents and Settings\All /// Users\Templates. static String get CommonTemplates => FOLDERID_CommonTemplates; /// The virtual folder that represents My Computer, containing everything on /// the local computer: storage devices, printers, and Control Panel. The /// folder can also contain mapped network drives. static String get ComputerFolder => FOLDERID_ComputerFolder; /// The virtual folder that represents Network Connections, that contains /// network and dial-up connections. static String get ConnectionsFolder => FOLDERID_ConnectionsFolder; /// The virtual folder that contains icons for the Control Panel applications. static String get ControlPanelFolder => FOLDERID_ControlPanelFolder; /// The file system directory that serves as a common repository for Internet /// cookies. A typical path is C:\Documents and Settings\username\Cookies. static String get Cookies => FOLDERID_Cookies; /// The virtual folder that represents the Windows desktop, the root of the /// namespace. static String get Desktop => FOLDERID_Desktop; /// The virtual folder that represents the My Documents desktop item. static String get Documents => FOLDERID_Documents; /// The file system directory that serves as a repository for Internet /// downloads. static String get Downloads => FOLDERID_Downloads; /// The file system directory that serves as a common repository for the /// user's favorite items. A typical path is C:\Documents and /// Settings\username\Favorites. static String get Favorites => FOLDERID_Favorites; /// A virtual folder that contains fonts. A typical path is C:\Windows\Fonts. static String get Fonts => FOLDERID_Fonts; /// The file system directory that serves as a common repository for Internet /// history items. static String get History => FOLDERID_History; /// The file system directory that serves as a common repository for temporary /// Internet files. A typical path is C:\Documents and Settings\username\Local /// Settings\Temporary Internet Files. static String get InternetCache => FOLDERID_InternetCache; /// A virtual folder for Internet Explorer. static String get InternetFolder => FOLDERID_InternetFolder; /// The file system directory that serves as a data repository for local /// (nonroaming) applications. A typical path is C:\Documents and /// Settings\username\Local Settings\Application Data. static String get LocalAppData => FOLDERID_LocalAppData; /// The file system directory that serves as a common repository for music /// files. A typical path is C:\Documents and Settings\User\My Documents\My /// Music. static String get Music => FOLDERID_Music; /// A file system directory that contains the link objects that may exist in /// the My Network Places virtual folder. A typical path is C:\Documents and /// Settings\username\NetHood. static String get NetHood => FOLDERID_NetHood; /// The folder that represents other computers in your workgroup. static String get NetworkFolder => FOLDERID_NetworkFolder; /// The file system directory that serves as a common repository for image /// files. A typical path is C:\Documents and Settings\username\My /// Documents\My Pictures. static String get Pictures => FOLDERID_Pictures; /// The file system directory that contains the link objects that can exist in /// the Printers virtual folder. A typical path is C:\Documents and /// Settings\username\PrintHood. static String get PrintHood => FOLDERID_PrintHood; /// The virtual folder that contains installed printers. static String get PrintersFolder => FOLDERID_PrintersFolder; /// The user's profile folder. A typical path is C:\Users\username. /// Applications should not create files or folders at this level. static String get Profile => FOLDERID_Profile; /// The file system directory that contains application data for all users. A /// typical path is C:\Documents and Settings\All Users\Application Data. This /// folder is used for application data that is not user specific. For /// example, an application can store a spell-check dictionary, a database of /// clip art, or a log file in the CSIDL_COMMON_APPDATA folder. This /// information will not roam and is available to anyone using the computer. static String get ProgramData => FOLDERID_ProgramData; /// The Program Files folder. A typical path is C:\Program Files. static String get ProgramFiles => FOLDERID_ProgramFiles; /// The common Program Files folder. A typical path is C:\Program /// Files\Common. static String get ProgramFilesCommon => FOLDERID_ProgramFilesCommon; /// On 64-bit systems, a link to the common Program Files folder. A typical path is /// C:\Program Files\Common Files. static String get ProgramFilesCommonX64 => FOLDERID_ProgramFilesCommonX64; /// On 64-bit systems, a link to the 32-bit common Program Files folder. A /// typical path is C:\Program Files (x86)\Common Files. On 32-bit systems, a /// link to the Common Program Files folder. static String get ProgramFilesCommonX86 => FOLDERID_ProgramFilesCommonX86; /// On 64-bit systems, a link to the Program Files folder. A typical path is /// C:\Program Files. static String get ProgramFilesX64 => FOLDERID_ProgramFilesX64; /// On 64-bit systems, a link to the 32-bit Program Files folder. A typical /// path is C:\Program Files (x86). On 32-bit systems, a link to the Common /// Program Files folder. static String get ProgramFilesX86 => FOLDERID_ProgramFilesX86; /// The file system directory that contains the user's program groups (which /// are themselves file system directories). static String get Programs => FOLDERID_Programs; /// The file system directory that contains files and folders that appear on /// the desktop for all users. A typical path is C:\Documents and Settings\All /// Users\Desktop. static String get PublicDesktop => FOLDERID_PublicDesktop; /// The file system directory that contains documents that are common to all /// users. A typical path is C:\Documents and Settings\All Users\Documents. static String get PublicDocuments => FOLDERID_PublicDocuments; /// The file system directory that serves as a repository for music files /// common to all users. A typical path is C:\Documents and Settings\All /// Users\Documents\My Music. static String get PublicMusic => FOLDERID_PublicMusic; /// The file system directory that serves as a repository for image files /// common to all users. A typical path is C:\Documents and Settings\All /// Users\Documents\My Pictures. static String get PublicPictures => FOLDERID_PublicPictures; /// The file system directory that serves as a repository for video files /// common to all users. A typical path is C:\Documents and Settings\All /// Users\Documents\My Videos. static String get PublicVideos => FOLDERID_PublicVideos; /// The file system directory that contains shortcuts to the user's most /// recently used documents. A typical path is C:\Documents and /// Settings\username\My Recent Documents. static String get Recent => FOLDERID_Recent; /// The virtual folder that contains the objects in the user's Recycle Bin. static String get RecycleBinFolder => FOLDERID_RecycleBinFolder; /// The file system directory that contains resource data. A typical path is /// C:\Windows\Resources. static String get ResourceDir => FOLDERID_ResourceDir; /// The file system directory that serves as a common repository for /// application-specific data. A typical path is C:\Documents and /// Settings\username\Application Data. static String get RoamingAppData => FOLDERID_RoamingAppData; /// The file system directory that contains Send To menu items. A typical path /// is C:\Documents and Settings\username\SendTo. static String get SendTo => FOLDERID_SendTo; /// The file system directory that contains Start menu items. A typical path /// is C:\Documents and Settings\username\Start Menu. static String get StartMenu => FOLDERID_StartMenu; /// The file system directory that corresponds to the user's Startup program /// group. The system starts these programs whenever the associated user logs /// on. A typical path is C:\Documents and Settings\username\Start /// Menu\Programs\Startup. static String get Startup => FOLDERID_Startup; /// The Windows System folder. A typical path is C:\Windows\System32. static String get System => FOLDERID_System; /// The 32-bit Windows System folder. On 32-bit systems, this is typically /// C:\Windows\system32. On 64-bit systems, this is typically /// C:\Windows\syswow64. static String get SystemX86 => FOLDERID_SystemX86; /// The file system directory that serves as a common repository for document /// templates. A typical path is C:\Documents and Settings\username\Templates. static String get Templates => FOLDERID_Templates; /// The file system directory that serves as a common repository for video /// files. A typical path is C:\Documents and Settings\username\My /// Documents\My Videos. static String get Videos => FOLDERID_Videos; /// The Windows directory or SYSROOT. This corresponds to the %windir% or /// %SYSTEMROOT% environment variables. A typical path is C:\Windows. static String get Windows => FOLDERID_Windows; } ================================================ FILE: packages/path_provider/path_provider_windows/lib/src/folders_stub.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Stub version of the actual class. class WindowsKnownFolder {} ================================================ FILE: packages/path_provider/path_provider_windows/lib/src/path_provider_windows_real.dart ================================================ // Copyright 2013 The Flutter 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:ffi'; import 'dart:io'; import 'package:ffi/ffi.dart'; import 'package:flutter/foundation.dart' show visibleForTesting; import 'package:path/path.dart' as path; import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; import 'package:win32/win32.dart'; import 'folders.dart'; /// Constant for en-US language used in VersionInfo keys. @visibleForTesting const String languageEn = '0409'; /// Constant for CP1252 encoding used in VersionInfo keys @visibleForTesting const String encodingCP1252 = '04e4'; /// Constant for Unicode encoding used in VersionInfo keys @visibleForTesting const String encodingUnicode = '04b0'; /// Wraps the Win32 VerQueryValue API call. /// /// This class exists to allow injecting alternate metadata in tests without /// building multiple custom test binaries. @visibleForTesting class VersionInfoQuerier { /// Returns the value for [key] in [versionInfo]s in section with given /// language and encoding, or null if there is no such entry, /// or if versionInfo is null. /// /// See https://docs.microsoft.com/en-us/windows/win32/menurc/versioninfo-resource /// for list of possible language and encoding values. String? getStringValue( Pointer? versionInfo, String key, { required String language, required String encoding, }) { assert(language.isNotEmpty); assert(encoding.isNotEmpty); if (versionInfo == null) { return null; } final Pointer keyPath = TEXT('\\StringFileInfo\\$language$encoding\\$key'); final Pointer length = calloc(); final Pointer> valueAddress = calloc>(); try { if (VerQueryValue(versionInfo, keyPath, valueAddress, length) == 0) { return null; } return valueAddress.value.toDartString(); } finally { calloc.free(keyPath); calloc.free(length); calloc.free(valueAddress); } } } /// The Windows implementation of [PathProviderPlatform] /// /// This class implements the `package:path_provider` functionality for Windows. class PathProviderWindows extends PathProviderPlatform { /// Registers the Windows implementation. static void registerWith() { PathProviderPlatform.instance = PathProviderWindows(); } /// The object to use for performing VerQueryValue calls. @visibleForTesting VersionInfoQuerier versionInfoQuerier = VersionInfoQuerier(); /// This is typically the same as the TMP environment variable. @override Future getTemporaryPath() async { final Pointer buffer = calloc(MAX_PATH + 1).cast(); String path; try { final int length = GetTempPath(MAX_PATH, buffer); if (length == 0) { final int error = GetLastError(); throw WindowsException(error); } else { path = buffer.toDartString(); // GetTempPath adds a trailing backslash, but SHGetKnownFolderPath does // not. Strip off trailing backslash for consistency with other methods // here. if (path.endsWith(r'\')) { path = path.substring(0, path.length - 1); } } // Ensure that the directory exists, since GetTempPath doesn't. final Directory directory = Directory(path); if (!directory.existsSync()) { await directory.create(recursive: true); } return path; } finally { calloc.free(buffer); } } @override Future getApplicationSupportPath() async { final String? appDataRoot = await getPath(WindowsKnownFolder.RoamingAppData); if (appDataRoot == null) { return null; } final Directory directory = Directory( path.join(appDataRoot, _getApplicationSpecificSubdirectory())); // Ensure that the directory exists if possible, since it will on other // platforms. If the name is longer than MAXPATH, creating will fail, so // skip that step; it's up to the client to decide what to do with the path // in that case (e.g., using a short path). if (directory.path.length <= MAX_PATH) { if (!directory.existsSync()) { await directory.create(recursive: true); } } return directory.path; } @override Future getApplicationDocumentsPath() => getPath(WindowsKnownFolder.Documents); @override Future getDownloadsPath() => getPath(WindowsKnownFolder.Downloads); /// Retrieve any known folder from Windows. /// /// folderID is a GUID that represents a specific known folder ID, drawn from /// [WindowsKnownFolder]. Future getPath(String folderID) { final Pointer> pathPtrPtr = calloc>(); final Pointer knownFolderID = calloc()..ref.setGUID(folderID); try { final int hr = SHGetKnownFolderPath( knownFolderID, KF_FLAG_DEFAULT, NULL, pathPtrPtr, ); if (FAILED(hr)) { if (hr == E_INVALIDARG || hr == E_FAIL) { throw WindowsException(hr); } return Future.value(); } final String path = pathPtrPtr.value.toDartString(); return Future.value(path); } finally { calloc.free(pathPtrPtr); calloc.free(knownFolderID); } } String? _getStringValue(Pointer? infoBuffer, String key) => versionInfoQuerier.getStringValue(infoBuffer, key, language: languageEn, encoding: encodingCP1252) ?? versionInfoQuerier.getStringValue(infoBuffer, key, language: languageEn, encoding: encodingUnicode); /// Returns the relative path string to append to the root directory returned /// by Win32 APIs for application storage (such as RoamingAppDir) to get a /// directory that is unique to the application. /// /// The convention is to use company-name\product-name\. This will use that if /// possible, using the data in the VERSIONINFO resource, with the following /// fallbacks: /// - If the company name isn't there, that component will be dropped. /// - If the product name isn't there, it will use the exe's filename (without /// extension). String _getApplicationSpecificSubdirectory() { String? companyName; String? productName; final Pointer moduleNameBuffer = wsalloc(MAX_PATH + 1); final Pointer unused = calloc(); Pointer? infoBuffer; try { // Get the module name. final int moduleNameLength = GetModuleFileName(0, moduleNameBuffer, MAX_PATH); if (moduleNameLength == 0) { final int error = GetLastError(); throw WindowsException(error); } // From that, load the VERSIONINFO resource final int infoSize = GetFileVersionInfoSize(moduleNameBuffer, unused); if (infoSize != 0) { infoBuffer = calloc(infoSize); if (GetFileVersionInfo(moduleNameBuffer, 0, infoSize, infoBuffer) == 0) { calloc.free(infoBuffer); infoBuffer = null; } } companyName = _sanitizedDirectoryName(_getStringValue(infoBuffer, 'CompanyName')); productName = _sanitizedDirectoryName(_getStringValue(infoBuffer, 'ProductName')); // If there was no product name, use the executable name. productName ??= path.basenameWithoutExtension(moduleNameBuffer.toDartString()); return companyName != null ? path.join(companyName, productName) : productName; } finally { calloc.free(moduleNameBuffer); calloc.free(unused); if (infoBuffer != null) { calloc.free(infoBuffer); } } } /// Makes [rawString] safe as a directory component. See /// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions /// /// If after sanitizing the string is empty, returns null. String? _sanitizedDirectoryName(String? rawString) { if (rawString == null) { return null; } String sanitized = rawString // Replace banned characters. .replaceAll(RegExp(r'[<>:"/\\|?*]'), '_') // Remove trailing whitespace. .trimRight() // Ensure that it does not end with a '.'. .replaceAll(RegExp(r'[.]+$'), ''); const int kMaxComponentLength = 255; if (sanitized.length > kMaxComponentLength) { sanitized = sanitized.substring(0, kMaxComponentLength); } return sanitized.isEmpty ? null : sanitized; } } ================================================ FILE: packages/path_provider/path_provider_windows/lib/src/path_provider_windows_stub.dart ================================================ // Copyright 2013 The Flutter 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:path_provider_platform_interface/path_provider_platform_interface.dart'; /// A stub implementation to satisfy compilation of multi-platform packages that /// depend on path_provider_windows. This should never actually be created. /// /// Notably, because path_provider needs to manually register /// path_provider_windows, anything with a transitive dependency on /// path_provider will also depend on path_provider_windows, not just at the /// pubspec level but the code level. class PathProviderWindows extends PathProviderPlatform { /// Errors on attempted instantiation of the stub. It exists only to satisfy /// compile-time dependencies, and should never actually be created. PathProviderWindows() : assert(false); /// Registers the Windows implementation. static void registerWith() { PathProviderPlatform.instance = PathProviderWindows(); } /// Stub; see comment on VersionInfoQuerier. VersionInfoQuerier versionInfoQuerier = VersionInfoQuerier(); /// Match PathProviderWindows so that the analyzer won't report invalid /// overrides if tests provide fake PathProviderWindows implementations. Future getPath(String folderID) async => ''; } /// Stub to satisfy the analyzer, which doesn't seem to handle conditional /// exports correctly. class VersionInfoQuerier {} ================================================ FILE: packages/path_provider/path_provider_windows/pubspec.yaml ================================================ name: path_provider_windows description: Windows implementation of the path_provider plugin repository: https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22 version: 2.1.3 environment: sdk: ">=2.17.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: path_provider platforms: windows: dartPluginClass: PathProviderWindows dependencies: ffi: ^2.0.0 flutter: sdk: flutter path: ^1.8.0 path_provider_platform_interface: ^2.0.0 win32: ">=2.1.0 <4.0.0" dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/path_provider/path_provider_windows/test/path_provider_windows_test.dart ================================================ // Copyright 2013 The Flutter 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:ffi'; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; import 'package:path_provider_windows/path_provider_windows.dart'; import 'package:path_provider_windows/src/path_provider_windows_real.dart' show languageEn, encodingCP1252, encodingUnicode; // A fake VersionInfoQuerier that just returns preset responses. class FakeVersionInfoQuerier implements VersionInfoQuerier { FakeVersionInfoQuerier( this.responses, { this.language = languageEn, this.encoding = encodingUnicode, }); final String language; final String encoding; final Map responses; String? getStringValue( Pointer? versionInfo, String key, { required String language, required String encoding, }) { if (language == this.language && encoding == this.encoding) { return responses[key]; } else { return null; } } } void main() { test('registered instance', () { PathProviderWindows.registerWith(); expect(PathProviderPlatform.instance, isA()); }); test('getTemporaryPath', () async { final PathProviderWindows pathProvider = PathProviderWindows(); expect(await pathProvider.getTemporaryPath(), contains(r'C:\')); }, skip: !Platform.isWindows); test('getApplicationSupportPath with no version info', () async { final PathProviderWindows pathProvider = PathProviderWindows(); pathProvider.versionInfoQuerier = FakeVersionInfoQuerier({}); final String? path = await pathProvider.getApplicationSupportPath(); expect(path, contains(r'C:\')); expect(path, contains(r'AppData')); // The last path component should be the executable name. expect(path, endsWith(r'flutter_tester')); }, skip: !Platform.isWindows); test('getApplicationSupportPath with full version info in CP1252', () async { final PathProviderWindows pathProvider = PathProviderWindows(); pathProvider.versionInfoQuerier = FakeVersionInfoQuerier({ 'CompanyName': 'A Company', 'ProductName': 'Amazing App', }, encoding: encodingCP1252); final String? path = await pathProvider.getApplicationSupportPath(); expect(path, isNotNull); if (path != null) { expect(path, endsWith(r'AppData\Roaming\A Company\Amazing App')); expect(Directory(path).existsSync(), isTrue); } }, skip: !Platform.isWindows); test('getApplicationSupportPath with full version info in Unicode', () async { final PathProviderWindows pathProvider = PathProviderWindows(); pathProvider.versionInfoQuerier = FakeVersionInfoQuerier({ 'CompanyName': 'A Company', 'ProductName': 'Amazing App', }); final String? path = await pathProvider.getApplicationSupportPath(); expect(path, isNotNull); if (path != null) { expect(path, endsWith(r'AppData\Roaming\A Company\Amazing App')); expect(Directory(path).existsSync(), isTrue); } }, skip: !Platform.isWindows); test( 'getApplicationSupportPath with full version info in Unsupported Encoding', () async { final PathProviderWindows pathProvider = PathProviderWindows(); pathProvider.versionInfoQuerier = FakeVersionInfoQuerier({ 'CompanyName': 'A Company', 'ProductName': 'Amazing App', }, language: '0000', encoding: '0000'); final String? path = await pathProvider.getApplicationSupportPath(); expect(path, contains(r'C:\')); expect(path, contains(r'AppData')); // The last path component should be the executable name. expect(path, endsWith(r'flutter_tester')); }, skip: !Platform.isWindows); test('getApplicationSupportPath with missing company', () async { final PathProviderWindows pathProvider = PathProviderWindows(); pathProvider.versionInfoQuerier = FakeVersionInfoQuerier({ 'ProductName': 'Amazing App', }); final String? path = await pathProvider.getApplicationSupportPath(); expect(path, isNotNull); if (path != null) { expect(path, endsWith(r'AppData\Roaming\Amazing App')); expect(Directory(path).existsSync(), isTrue); } }, skip: !Platform.isWindows); test('getApplicationSupportPath with problematic values', () async { final PathProviderWindows pathProvider = PathProviderWindows(); pathProvider.versionInfoQuerier = FakeVersionInfoQuerier({ 'CompanyName': r'A Company: Name.', 'ProductName': r'A"/Terrible\|App?*Name', }); final String? path = await pathProvider.getApplicationSupportPath(); expect(path, isNotNull); if (path != null) { expect( path, endsWith( r'AppData\Roaming\A _Bad_ Company_ Name\A__Terrible__App__Name')); expect(Directory(path).existsSync(), isTrue); } }, skip: !Platform.isWindows); test('getApplicationSupportPath with a completely invalid company', () async { final PathProviderWindows pathProvider = PathProviderWindows(); pathProvider.versionInfoQuerier = FakeVersionInfoQuerier({ 'CompanyName': r'..', 'ProductName': r'Amazing App', }); final String? path = await pathProvider.getApplicationSupportPath(); expect(path, isNotNull); if (path != null) { expect(path, endsWith(r'AppData\Roaming\Amazing App')); expect(Directory(path).existsSync(), isTrue); } }, skip: !Platform.isWindows); test('getApplicationSupportPath with very long app name', () async { final PathProviderWindows pathProvider = PathProviderWindows(); final String truncatedName = 'A' * 255; pathProvider.versionInfoQuerier = FakeVersionInfoQuerier({ 'CompanyName': 'A Company', 'ProductName': truncatedName * 2, }); final String? path = await pathProvider.getApplicationSupportPath(); expect(path, endsWith('\\$truncatedName')); // The directory won't exist, since it's longer than MAXPATH, so don't check // that here. }, skip: !Platform.isWindows); test('getApplicationDocumentsPath', () async { final PathProviderWindows pathProvider = PathProviderWindows(); final String? path = await pathProvider.getApplicationDocumentsPath(); expect(path, contains(r'C:\')); expect(path, contains(r'Documents')); }, skip: !Platform.isWindows); test('getDownloadsPath', () async { final PathProviderWindows pathProvider = PathProviderWindows(); final String? path = await pathProvider.getDownloadsPath(); expect(path, contains(r'C:\')); expect(path, contains(r'Downloads')); }, skip: !Platform.isWindows); } ================================================ FILE: packages/plugin_platform_interface/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ build/ # Android related **/android/**/gradle-wrapper.jar **/android/.gradle **/android/captures/ **/android/gradlew **/android/gradlew.bat **/android/local.properties **/android/**/GeneratedPluginRegistrant.java # iOS/XCode related **/ios/**/*.mode1v3 **/ios/**/*.mode2v3 **/ios/**/*.moved-aside **/ios/**/*.pbxuser **/ios/**/*.perspectivev3 **/ios/**/*sync/ **/ios/**/.sconsign.dblite **/ios/**/.tags* **/ios/**/.vagrant/ **/ios/**/DerivedData/ **/ios/**/Icon? **/ios/**/Pods/ **/ios/**/.symlinks/ **/ios/**/profile **/ios/**/xcuserdata **/ios/.generated/ **/ios/Flutter/App.framework **/ios/Flutter/Flutter.framework **/ios/Flutter/Flutter.podspec **/ios/Flutter/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip **/ios/Flutter/flutter_assets/ **/ios/Flutter/flutter_export_environment.sh **/ios/ServiceDefinitions.json **/ios/Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !**/ios/**/default.mode1v3 !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: packages/plugin_platform_interface/.metadata ================================================ # 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: f213f92587b3f6f3b079a9cf356b2c5fcf00d20b channel: master project_type: package ================================================ FILE: packages/plugin_platform_interface/AUTHORS ================================================ # 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> ================================================ FILE: packages/plugin_platform_interface/CHANGELOG.md ================================================ ## NEXT * Updates minimum supported Dart version. ## 2.1.3 * Minor fixes for new analysis options. * Adds additional tests for `PlatformInterface` and `MockPlatformInterfaceMixin`. * Modifies `PlatformInterface` to use an expando for detecting if a customer tries to implement PlatformInterface using `implements` rather than `extends`. This ensures that `verify` will continue to work as advertized after https://github.com/dart-lang/language/issues/2020 is implemented. ## 2.1.2 * Updates README to demonstrate `verify` rather than `verifyToken`, and to note that the test mixin applies to fakes as well as mocks. * Adds an additional test for `verifyToken`. ## 2.1.1 * Fixes `verify` to work with fake objects, not just mocks. ## 2.1.0 * Introduce `verify`, which prevents use of `const Object()` as instance token. * Add a comment indicating that `verifyToken` will be deprecated in a future release. ## 2.0.2 * Update package description. ## 2.0.1 * Fix `federated flutter plugins` link in the README.md. ## 2.0.0 * Migrate to null safety. ## 1.0.3 * Fix homepage in `pubspec.yaml`. ## 1.0.2 * Make the pedantic dev_dependency explicit. ## 1.0.1 * Fixed a bug that made all platform interfaces appear as mocks in release builds (https://github.com/flutter/flutter/issues/46941). ## 1.0.0 - Initial release. * Provides `PlatformInterface` with common mechanism for enforcing that a platform interface is not implemented with `implements`. * Provides test only `MockPlatformInterface` to enable using Mockito to mock platform interfaces. ================================================ FILE: packages/plugin_platform_interface/LICENSE ================================================ 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. ================================================ FILE: packages/plugin_platform_interface/README.md ================================================ # plugin_platform_interface This package provides a base class for platform interfaces of [federated flutter plugins](https://flutter.dev/go/federated-plugins). Platform implementations should extend their platform interface classes rather than implement it as newly added methods to platform interfaces are not considered as breaking changes. Extending a platform interface ensures that subclasses will get the default implementations from the base class, while platform implementations that `implements` their platform interface will be broken by newly added methods. This class package provides common functionality for platform interface to enforce that they are extended and not implemented. ## Sample usage: ```dart abstract class UrlLauncherPlatform extends PlatformInterface { UrlLauncherPlatform() : super(token: _token); static UrlLauncherPlatform _instance = MethodChannelUrlLauncher(); static final Object _token = Object(); static UrlLauncherPlatform get instance => _instance; /// Platform-specific plugins should set this with their own platform-specific /// class that extends [UrlLauncherPlatform] when they register themselves. static set instance(UrlLauncherPlatform instance) { PlatformInterface.verify(instance, _token); _instance = instance; } } ``` This guarantees that UrlLauncherPlatform.instance cannot be set to an object that `implements` UrlLauncherPlatform (it can only be set to an object that `extends` UrlLauncherPlatform). ## Mocking or faking platform interfaces Test implementations of platform interfaces, such as those using `mockito`'s `Mock` or `test`'s `Fake`, will fail the verification done by `verify`. This package provides a `MockPlatformInterfaceMixin` which can be used in test code only to disable the `extends` enforcement. For example, a Mockito mock of a platform interface can be created with: ```dart class UrlLauncherPlatformMock extends Mock with MockPlatformInterfaceMixin implements UrlLauncherPlatform {} ``` ================================================ FILE: packages/plugin_platform_interface/lib/plugin_platform_interface.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. library plugin_platform_interface; import 'package:meta/meta.dart'; /// Base class for platform interfaces. /// /// Provides a static helper method for ensuring that platform interfaces are /// implemented using `extends` instead of `implements`. /// /// Platform interface classes are expected to have a private static token object which will be /// be passed to [verify] along with a platform interface object for verification. /// /// Sample usage: /// /// ```dart /// abstract class UrlLauncherPlatform extends PlatformInterface { /// UrlLauncherPlatform() : super(token: _token); /// /// static UrlLauncherPlatform _instance = MethodChannelUrlLauncher(); /// /// static final Object _token = Object(); /// /// static UrlLauncherPlatform get instance => _instance; /// /// /// Platform-specific plugins should set this with their own platform-specific /// /// class that extends [UrlLauncherPlatform] when they register themselves. /// static set instance(UrlLauncherPlatform instance) { /// PlatformInterface.verify(instance, _token); /// _instance = instance; /// } /// /// } /// ``` /// /// Mockito mocks of platform interfaces will fail the verification, in test code only it is possible /// to include the [MockPlatformInterfaceMixin] for the verification to be temporarily disabled. See /// [MockPlatformInterfaceMixin] for a sample of using Mockito to mock a platform interface. abstract class PlatformInterface { /// Constructs a PlatformInterface, for use only in constructors of abstract /// derived classes. /// /// @param token The same, non-`const` `Object` that will be passed to `verify`. PlatformInterface({required Object token}) { _instanceTokens[this] = token; } /// Expando mapping instances of PlatformInterface to their associated tokens. /// The reason this is not simply a private field of type `Object?` is because /// as of the implementation of field promotion in Dart /// (https://github.com/dart-lang/language/issues/2020), it is a runtime error /// to invoke a private member that is mocked in another library. The expando /// approach prevents [_verify] from triggering this runtime exception when /// encountering an implementation that uses `implements` rather than /// `extends`. This in turn allows [_verify] to throw an [AssertionError] (as /// documented). static final Expando _instanceTokens = Expando(); /// Ensures that the platform instance was constructed with a non-`const` token /// that matches the provided token and throws [AssertionError] if not. /// /// This is used to ensure that implementers are using `extends` rather than /// `implements`. /// /// Subclasses of [MockPlatformInterfaceMixin] are assumed to be valid in debug /// builds. /// /// This is implemented as a static method so that it cannot be overridden /// with `noSuchMethod`. static void verify(PlatformInterface instance, Object token) { _verify(instance, token, preventConstObject: true); } /// Performs the same checks as `verify` but without throwing an /// [AssertionError] if `const Object()` is used as the instance token. /// /// This method will be deprecated in a future release. static void verifyToken(PlatformInterface instance, Object token) { _verify(instance, token, preventConstObject: false); } static void _verify( PlatformInterface instance, Object token, { required bool preventConstObject, }) { if (instance is MockPlatformInterfaceMixin) { bool assertionsEnabled = false; assert(() { assertionsEnabled = true; return true; }()); if (!assertionsEnabled) { throw AssertionError( '`MockPlatformInterfaceMixin` is not intended for use in release builds.'); } return; } if (preventConstObject && identical(_instanceTokens[instance], const Object())) { throw AssertionError('`const Object()` cannot be used as the token.'); } if (!identical(token, _instanceTokens[instance])) { throw AssertionError( 'Platform interfaces must not be implemented with `implements`'); } } } /// A [PlatformInterface] mixin that can be combined with fake or mock objects, /// such as test's `Fake` or mockito's `Mock`. /// /// It passes the [PlatformInterface.verify] check even though it isn't /// using `extends`. /// /// This class is intended for use in tests only. /// /// Sample usage (assuming `UrlLauncherPlatform` extends [PlatformInterface]): /// /// ```dart /// class UrlLauncherPlatformMock extends Mock /// with MockPlatformInterfaceMixin /// implements UrlLauncherPlatform {} /// ``` @visibleForTesting abstract class MockPlatformInterfaceMixin implements PlatformInterface {} ================================================ FILE: packages/plugin_platform_interface/pubspec.yaml ================================================ name: plugin_platform_interface description: Reusable base class for platform interfaces of Flutter federated plugins, to help enforce best practices. repository: https://github.com/flutter/plugins/tree/main/packages/plugin_platform_interface issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+plugin_platform_interface%22 # DO NOT MAKE A BREAKING CHANGE TO THIS PACKAGE # DO NOT INCREASE THE MAJOR VERSION OF THIS PACKAGE # # This package is used as a second level dependency for many plugins, a major version bump here # is guaranteed to lead the ecosystem to a version lock (the first plugin that upgrades to version # 3 of this package cannot be used with any other plugin that have not yet migrated). # # Please consider carefully before bumping the major version of this package, ideally it should only # be done when absolutely necessary and after the ecosystem has already migrated to 2.X.Y version # that is forward compatible with 3.0.0 (ideally the ecosystem have migrated to depend on: # `plugin_platform_interface: >=2.X.Y <4.0.0`). version: 2.1.3 environment: sdk: ">=2.17.0 <3.0.0" dependencies: meta: ^1.3.0 dev_dependencies: mockito: ^5.0.0 test: ^1.16.0 ================================================ FILE: packages/plugin_platform_interface/test/plugin_platform_interface_test.dart ================================================ // Copyright 2013 The Flutter 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:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:test/test.dart'; class SamplePluginPlatform extends PlatformInterface { SamplePluginPlatform() : super(token: _token); static final Object _token = Object(); // ignore: avoid_setters_without_getters static set instance(SamplePluginPlatform instance) { PlatformInterface.verify(instance, _token); // A real implementation would set a static instance field here. } } class ImplementsSamplePluginPlatform extends Mock implements SamplePluginPlatform {} class ImplementsSamplePluginPlatformUsingNoSuchMethod implements SamplePluginPlatform { @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } class ImplementsSamplePluginPlatformUsingMockPlatformInterfaceMixin extends Mock with MockPlatformInterfaceMixin implements SamplePluginPlatform {} class ImplementsSamplePluginPlatformUsingFakePlatformInterfaceMixin extends Fake with MockPlatformInterfaceMixin implements SamplePluginPlatform {} class ExtendsSamplePluginPlatform extends SamplePluginPlatform {} class ConstTokenPluginPlatform extends PlatformInterface { ConstTokenPluginPlatform() : super(token: _token); static const Object _token = Object(); // invalid // ignore: avoid_setters_without_getters static set instance(ConstTokenPluginPlatform instance) { PlatformInterface.verify(instance, _token); } } class ExtendsConstTokenPluginPlatform extends ConstTokenPluginPlatform {} class VerifyTokenPluginPlatform extends PlatformInterface { VerifyTokenPluginPlatform() : super(token: _token); static final Object _token = Object(); // ignore: avoid_setters_without_getters static set instance(VerifyTokenPluginPlatform instance) { PlatformInterface.verifyToken(instance, _token); // A real implementation would set a static instance field here. } } class ImplementsVerifyTokenPluginPlatform extends Mock implements VerifyTokenPluginPlatform {} class ImplementsVerifyTokenPluginPlatformUsingMockPlatformInterfaceMixin extends Mock with MockPlatformInterfaceMixin implements VerifyTokenPluginPlatform {} class ExtendsVerifyTokenPluginPlatform extends VerifyTokenPluginPlatform {} class ConstVerifyTokenPluginPlatform extends PlatformInterface { ConstVerifyTokenPluginPlatform() : super(token: _token); static const Object _token = Object(); // invalid // ignore: avoid_setters_without_getters static set instance(ConstVerifyTokenPluginPlatform instance) { PlatformInterface.verifyToken(instance, _token); } } class ImplementsConstVerifyTokenPluginPlatform extends PlatformInterface implements ConstVerifyTokenPluginPlatform { ImplementsConstVerifyTokenPluginPlatform() : super(token: const Object()); } // Ensures that `PlatformInterface` has no instance methods. Adding an // instance method is discouraged and may be a breaking change if it // conflicts with instance methods in subclasses. class StaticMethodsOnlyPlatformInterfaceTest implements PlatformInterface {} class StaticMethodsOnlyMockPlatformInterfaceMixinTest implements MockPlatformInterfaceMixin {} void main() { group('`verify`', () { test('prevents implementation with `implements`', () { expect(() { SamplePluginPlatform.instance = ImplementsSamplePluginPlatform(); }, throwsA(isA())); }); test('prevents implmentation with `implements` and `noSuchMethod`', () { expect(() { SamplePluginPlatform.instance = ImplementsSamplePluginPlatformUsingNoSuchMethod(); }, throwsA(isA())); }); test('allows mocking with `implements`', () { final SamplePluginPlatform mock = ImplementsSamplePluginPlatformUsingMockPlatformInterfaceMixin(); SamplePluginPlatform.instance = mock; }); test('allows faking with `implements`', () { final SamplePluginPlatform fake = ImplementsSamplePluginPlatformUsingFakePlatformInterfaceMixin(); SamplePluginPlatform.instance = fake; }); test('allows extending', () { SamplePluginPlatform.instance = ExtendsSamplePluginPlatform(); }); test('prevents `const Object()` token', () { expect(() { ConstTokenPluginPlatform.instance = ExtendsConstTokenPluginPlatform(); }, throwsA(isA())); }); }); // Tests of the earlier, to-be-deprecated `verifyToken` method group('`verifyToken`', () { test('prevents implementation with `implements`', () { expect(() { VerifyTokenPluginPlatform.instance = ImplementsVerifyTokenPluginPlatform(); }, throwsA(isA())); }); test('allows mocking with `implements`', () { final VerifyTokenPluginPlatform mock = ImplementsVerifyTokenPluginPlatformUsingMockPlatformInterfaceMixin(); VerifyTokenPluginPlatform.instance = mock; }); test('allows extending', () { VerifyTokenPluginPlatform.instance = ExtendsVerifyTokenPluginPlatform(); }); test('does not prevent `const Object()` token', () { ConstVerifyTokenPluginPlatform.instance = ImplementsConstVerifyTokenPluginPlatform(); }); }); } ================================================ FILE: packages/quick_actions/quick_actions/AUTHORS ================================================ # 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> Daniel Roek ================================================ FILE: packages/quick_actions/quick_actions/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 1.0.1 * Updates implementaion package versions to current versions. ## 1.0.0 * Updates version to 1.0 to reflect current status. * Updates minimum Flutter version to 2.10. * Updates README to document that on Android, icons may need to be explicitly marked as used in the Android project for release builds. * Minor fixes for new analysis options. ## 0.6.0+11 * Removes unnecessary imports. * Updates minimum Flutter version to 2.8. * Adds OS version support information to README. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.6.0+10 * Moves Android and iOS implementations to federated packages. ## 0.6.0+9 * Updates Android compileSdkVersion to 31. * Updates code for analyzer changes. * Removes dependency on `meta`. ## 0.6.0+8 * Updates example app Android compileSdkVersion to 31. * Moves method call to background thread to fix CI failure. ## 0.6.0+7 * Update minimum Flutter SDK to 2.5 and iOS deployment target to 9.0. ## 0.6.0+6 * Updated Android lint settings. * Fix repository link in pubspec.yaml. ## 0.6.0+5 * Support only calling initialize once. ## 0.6.0+4 * Remove references to the Android V1 embedding. ## 0.6.0+3 * Added a `const` constructor for the `QuickActions` class, so the plugin will behave as documented in the sample code mentioned in the [README.md](https://github.com/flutter/plugins/blob/59e16a556e273c2d69189b2dcdfa92d101ea6408/packages/quick_actions/quick_actions/README.md). ## 0.6.0+2 * Migrate maven repository from jcenter to mavenCentral. ## 0.6.0+1 * Correctly handle iOS Application lifecycle events on cold start of the App. ## 0.6.0 * Migrate to federated architecture. ## 0.5.0+1 * Updated example app implementation. ## 0.5.0 * Migrate to null safety. * Fixes quick actions not working on iOS. ## 0.4.0+12 * Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) ## 0.4.0+11 * Update Flutter SDK constraint. ## 0.4.0+10 * Update android compileSdkVersion to 29. ## 0.4.0+9 * Keep handling deprecated Android v1 classes for backward compatibility. ## 0.4.0+8 * Update package:e2e -> package:integration_test ## 0.4.0+7 * Update package:e2e reference to use the local version in the flutter/plugins repository. ## 0.4.0+6 * Post-v2 Android embedding cleanup. ## 0.4.0+5 * Update lower bound of dart dependency to 2.1.0. ## 0.4.0+4 * Bump the minimum Flutter version to 1.12.13+hotfix.5. * Clean up various Android workarounds no longer needed after framework v1.12. * Complete v2 embedding support. * Fix UIApplicationShortcutItem availability warnings. * Fix CocoaPods podspec lint warnings. ## 0.4.0+3 * Replace deprecated `getFlutterEngine` call on Android. ## 0.4.0+2 * Make the pedantic dev_dependency explicit. ## 0.4.0+1 * Remove the deprecated `author:` field from pubspec.yaml * Migrate the plugin to the pubspec platforms manifest. * Require Flutter SDK 1.10.0 or greater. ## 0.4.0 - Added missing documentation. - **Breaking change**. `channel` and `withMethodChannel` are now `@visibleForTesting`. These methods are for plugin unit tests only and may be removed in the future. - **Breaking change**. Removed `runLaunchAction` from public API. This method was not meant to be used by consumers of the plugin. ## 0.3.3+1 * Update and migrate iOS example project by removing flutter_assets, change "English" to "en", remove extraneous xcconfigs, update to Xcode 11 build settings, and remove ARCHS and DEVELOPMENT_TEAM. ## 0.3.3 * Support Android V2 embedding. * Add e2e tests. * Migrate to using the new e2e test binding. ## 0.3.2+4 * Remove AndroidX warnings. ## 0.3.2+3 * Define clang module for iOS. ## 0.3.2+2 * Fix bug that would make the shortcut not open on Android. * Report shortcut used on Android. * Improves example. ## 0.3.2+1 * Update usage example in README. ## 0.3.2 * Fixed the quick actions launch on Android when the app is killed. ## 0.3.1 * Added unit tests. ## 0.3.0+2 * Add missing template type parameter to `invokeMethod` calls. * Bump minimum Flutter version to 1.5.0. * Replace invokeMethod with invokeMapMethod wherever necessary. ## 0.3.0+1 * Log a more detailed warning at build time about the previous AndroidX migration. ## 0.3.0 * **Breaking change**. Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to [also migrate](https://developer.android.com/jetpack/androidx/migrate) if they're using the original support library. ## 0.2.2 * Allow to register more than once. ## 0.2.1 * Updated Gradle tooling to match Android Studio 3.1.2. ## 0.2.0 * **Breaking change**. Set SDK constraints to match the Flutter beta release. ## 0.1.1 * Simplified and upgraded Android project template to Android SDK 27. * Updated package description. ## 0.1.0 * **Breaking change**. Upgraded to Gradle 4.1 and Android Studio Gradle plugin 3.0.1. Older Flutter projects need to upgrade their Gradle setup as well in order to use this version of the plugin. Instructions can be found [here](https://github.com/flutter/flutter/wiki/Updating-Flutter-projects-to-Gradle-4.1-and-Android-Studio-Gradle-plugin-3.0.1). ## 0.0.2 * Add FLT prefix to iOS types ## 0.0.1 * Initial release ================================================ FILE: packages/quick_actions/quick_actions/LICENSE ================================================ 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. ================================================ FILE: packages/quick_actions/quick_actions/README.md ================================================ # quick_actions This Flutter plugin allows you to manage and interact with the application's home screen quick actions. Quick actions refer to the [eponymous concept](https://developer.apple.com/design/human-interface-guidelines/ios/system-capabilities/home-screen-actions/) on iOS and to the [App Shortcuts](https://developer.android.com/guide/topics/ui/shortcuts.html) APIs on Android. | | Android | iOS | |-------------|-----------|------| | **Support** | SDK 16+\* | 9.0+ | ## Usage Initialize the library early in your application's lifecycle by providing a callback, which will then be called whenever the user launches the app via a quick action. ```dart final QuickActions quickActions = const QuickActions(); quickActions.initialize((shortcutType) { if (shortcutType == 'action_main') { print('The user tapped on the "Main view" action.'); } // More handling code... }); ``` Finally, manage the app's quick actions, for instance: ```dart quickActions.setShortcutItems([ const ShortcutItem(type: 'action_main', localizedTitle: 'Main view', icon: 'icon_main'), const ShortcutItem(type: 'action_help', localizedTitle: 'Help', icon: 'icon_help') ]); ``` Please note, that the `type` argument should be unique within your application (among all the registered shortcut items). The optional `icon` should be the name of the native resource (xcassets on iOS or drawable on Android) that the app will display for the quick action. ### Android \* The plugin will compile and run on SDK 16+, but will be a no-op below SDK 25 (Android 7.1). If the drawables used as icons are not referenced other than in your Dart code, you may need to [explicitly mark them to be kept](https://developer.android.com/studio/build/shrink-code#keep-resources) to ensure that they will be available for use in release builds. ================================================ FILE: packages/quick_actions/quick_actions/example/README.md ================================================ # quick_actions_example Demonstrates how to use the quick_actions plugin. ================================================ FILE: packages/quick_actions/quick_actions/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 lintOptions { disable 'InvalidPackage' } defaultConfig { applicationId "io.flutter.plugins.quickactionsexample" minSdkVersion 21 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' api 'androidx.test:core:1.2.0' } ================================================ FILE: packages/quick_actions/quick_actions/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/quick_actions/quick_actions/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/quick_actions/quick_actions/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/FlutterActivityTest.java ================================================ // Copyright 2013 The Flutter 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.quickactionsexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class FlutterActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/quick_actions/quick_actions/example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: packages/quick_actions/quick_actions/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/quick_actions/quick_actions/example/android/app/src/main/java/io/flutter/plugins/quickactionsexample/QuickActionsTestActivity.java ================================================ // Copyright 2013 The Flutter 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.quickactionsexample; import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; // Makes the FlutterEngine accessible for testing. public class QuickActionsTestActivity extends FlutterActivity { public FlutterEngine engine; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); engine = flutterEngine; } } ================================================ FILE: packages/quick_actions/quick_actions/example/android/app/src/main/res/drawable/ic_launcher_background.xml ================================================ ================================================ FILE: packages/quick_actions/quick_actions/example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: packages/quick_actions/quick_actions/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/quick_actions/quick_actions/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/quick_actions/quick_actions/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true ================================================ FILE: packages/quick_actions/quick_actions/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withInputStream { stream -> plugins.load(stream) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/quick_actions/quick_actions/example/integration_test/quick_actions_test.dart ================================================ // Copyright 2013 The Flutter 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:quick_actions/quick_actions.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('Can set shortcuts', (WidgetTester tester) async { const QuickActions quickActions = QuickActions(); await quickActions.initialize((String _) {}); const ShortcutItem shortCutItem = ShortcutItem( type: 'action_one', localizedTitle: 'Action one', icon: 'AppIcon', ); expect( quickActions.setShortcutItems([shortCutItem]), completes); }); } ================================================ FILE: packages/quick_actions/quick_actions/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 UIRequiredDeviceCapabilities arm64 MinimumOSVersion 9.0 ================================================ FILE: packages/quick_actions/quick_actions/example/ios/Flutter/Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/quick_actions/quick_actions/example/ios/Flutter/Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/quick_actions/quick_actions/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/quick_actions/quick_actions/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/quick_actions/quick_actions/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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]; // Override point for customization after application launch. [super application:application didFinishLaunchingWithOptions:launchOptions]; return NO; } @end ================================================ FILE: packages/quick_actions/quick_actions/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/quick_actions/quick_actions/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/quick_actions/quick_actions/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/quick_actions/quick_actions/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName quick_actions_example CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: packages/quick_actions/quick_actions/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/quick_actions/quick_actions/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 33E20B3526EFCDFC00A4A191 /* RunnerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33E20B3426EFCDFC00A4A191 /* RunnerTests.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 50EB54C1FE43DB743F5DEC7C /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D1A69703A518C37D73BF8B91 /* libPods-RunnerTests.a */; }; 686BE83025E58CCF00862533 /* RunnerUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 686BE82F25E58CCF00862533 /* RunnerUITests.m */; }; 83C36CAF23D629E5ABE75B2A /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CCC799F2B0AB50A9C34344F0 /* 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 */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 33E20B3726EFCDFC00A4A191 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; 686BE83225E58CCF00862533 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 33E20B3226EFCDFC00A4A191 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 33E20B3426EFCDFC00A4A191 /* RunnerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RunnerTests.m; sourceTree = ""; }; 33E20B3626EFCDFC00A4A191 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 5278439583922091276A37C9 /* 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 = ""; }; 686BE82D25E58CCF00862533 /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 686BE82F25E58CCF00862533 /* RunnerUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RunnerUITests.m; sourceTree = ""; }; 686BE83125E58CCF00862533 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 96F949A6B78E2DC62B93C4F8 /* 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 = ""; }; 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 = ""; }; 9D27FE1F0F21D4D47DDA16DE /* 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 = ""; }; CCC799F2B0AB50A9C34344F0 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; D1A69703A518C37D73BF8B91 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; F0609304FBCAEC2289164BD5 /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 33E20B2F26EFCDFC00A4A191 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 50EB54C1FE43DB743F5DEC7C /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 686BE82A25E58CCF00862533 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 83C36CAF23D629E5ABE75B2A /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 33E20B3326EFCDFC00A4A191 /* RunnerTests */ = { isa = PBXGroup; children = ( 33E20B3426EFCDFC00A4A191 /* RunnerTests.m */, 33E20B3626EFCDFC00A4A191 /* Info.plist */, ); path = RunnerTests; sourceTree = ""; }; 686BE82E25E58CCF00862533 /* RunnerUITests */ = { isa = PBXGroup; children = ( 686BE82F25E58CCF00862533 /* RunnerUITests.m */, 686BE83125E58CCF00862533 /* Info.plist */, ); path = RunnerUITests; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 686BE82E25E58CCF00862533 /* RunnerUITests */, 33E20B3326EFCDFC00A4A191 /* RunnerTests */, 97C146EF1CF9000F007C117D /* Products */, D0FE95BE2380323DD75CB891 /* Pods */, A44AD0D63DEF785A2A2DEE28 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, 686BE82D25E58CCF00862533 /* RunnerUITests.xctest */, 33E20B3226EFCDFC00A4A191 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; A44AD0D63DEF785A2A2DEE28 /* Frameworks */ = { isa = PBXGroup; children = ( CCC799F2B0AB50A9C34344F0 /* libPods-Runner.a */, D1A69703A518C37D73BF8B91 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; D0FE95BE2380323DD75CB891 /* Pods */ = { isa = PBXGroup; children = ( 5278439583922091276A37C9 /* Pods-Runner.debug.xcconfig */, F0609304FBCAEC2289164BD5 /* Pods-Runner.release.xcconfig */, 9D27FE1F0F21D4D47DDA16DE /* Pods-RunnerTests.debug.xcconfig */, 96F949A6B78E2DC62B93C4F8 /* Pods-RunnerTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 33E20B3126EFCDFC00A4A191 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = 33E20B3B26EFCDFC00A4A191 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( 3B2E8279C112D7129C8D23F1 /* [CP] Check Pods Manifest.lock */, 33E20B2E26EFCDFC00A4A191 /* Sources */, 33E20B2F26EFCDFC00A4A191 /* Frameworks */, 33E20B3026EFCDFC00A4A191 /* Resources */, ); buildRules = ( ); dependencies = ( 33E20B3826EFCDFC00A4A191 /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = 33E20B3226EFCDFC00A4A191 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 686BE82C25E58CCF00862533 /* RunnerUITests */ = { isa = PBXNativeTarget; buildConfigurationList = 686BE83625E58CCF00862533 /* Build configuration list for PBXNativeTarget "RunnerUITests" */; buildPhases = ( 686BE82925E58CCF00862533 /* Sources */, 686BE82A25E58CCF00862533 /* Frameworks */, 686BE82B25E58CCF00862533 /* Resources */, ); buildRules = ( ); dependencies = ( 686BE83325E58CCF00862533 /* PBXTargetDependency */, ); name = RunnerUITests; productName = RunnerUITests; productReference = 686BE82D25E58CCF00862533 /* RunnerUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( C6989ECD8FF0836301D734B4 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1100; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 33E20B3126EFCDFC00A4A191 = { CreatedOnToolsVersion = 12.5; TestTargetID = 97C146ED1CF9000F007C117D; }; 686BE82C25E58CCF00862533 = { CreatedOnToolsVersion = 12.4; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, 686BE82C25E58CCF00862533 /* RunnerUITests */, 33E20B3126EFCDFC00A4A191 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 33E20B3026EFCDFC00A4A191 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 686BE82B25E58CCF00862533 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 3B2E8279C112D7129C8D23F1 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 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"; }; C6989ECD8FF0836301D734B4 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 33E20B2E26EFCDFC00A4A191 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33E20B3526EFCDFC00A4A191 /* RunnerTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 686BE82925E58CCF00862533 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 686BE83025E58CCF00862533 /* RunnerUITests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 33E20B3826EFCDFC00A4A191 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = 33E20B3726EFCDFC00A4A191 /* PBXContainerItemProxy */; }; 686BE83325E58CCF00862533 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = 686BE83225E58CCF00862533 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 33E20B3926EFCDFC00A4A191 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9D27FE1F0F21D4D47DDA16DE /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; 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; }; 33E20B3A26EFCDFC00A4A191 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 96F949A6B78E2DC62B93C4F8 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; 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 = Release; }; 686BE83425E58CCF00862533 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = RunnerUITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.google.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = Runner; }; name = Debug; }; 686BE83525E58CCF00862533 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = RunnerUITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.google.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = Runner; }; name = Release; }; 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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 = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.quickActionsExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.quickActionsExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 33E20B3B26EFCDFC00A4A191 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 33E20B3926EFCDFC00A4A191 /* Debug */, 33E20B3A26EFCDFC00A4A191 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 686BE83625E58CCF00862533 /* Build configuration list for PBXNativeTarget "RunnerUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( 686BE83425E58CCF00862533 /* Debug */, 686BE83525E58CCF00862533 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/quick_actions/quick_actions/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/quick_actions/quick_actions/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/RunnerUITests.xcscheme ================================================ ================================================ FILE: packages/quick_actions/quick_actions/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/quick_actions/quick_actions/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/quick_actions/quick_actions/example/ios/RunnerTests/Info.plist ================================================ 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 ================================================ FILE: packages/quick_actions/quick_actions/example/ios/RunnerTests/RunnerTests.m ================================================ // Copyright 2013 The Flutter 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 quick_actions; @import XCTest; @interface QuickActionsTests : XCTestCase @end @implementation QuickActionsTests - (void)testPlugin { FLTQuickActionsPlugin *plugin = [[FLTQuickActionsPlugin alloc] init]; XCTAssertNotNil(plugin); } @end ================================================ FILE: packages/quick_actions/quick_actions/example/ios/RunnerUITests/Info.plist ================================================ 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 ================================================ FILE: packages/quick_actions/quick_actions/example/ios/RunnerUITests/RunnerUITests.m ================================================ // Copyright 2013 The Flutter 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 static const int kElementWaitingTime = 30; @interface RunnerUITests : XCTestCase @end @implementation RunnerUITests { XCUIApplication *_exampleApp; } - (void)setUp { [super setUp]; self.continueAfterFailure = NO; _exampleApp = [[XCUIApplication alloc] init]; } - (void)tearDown { [super tearDown]; [_exampleApp terminate]; _exampleApp = nil; } - (void)testQuickActionWithFreshStart { XCUIApplication *springboard = [[XCUIApplication alloc] initWithBundleIdentifier:@"com.apple.springboard"]; XCUIElement *quickActionsAppIcon = springboard.icons[@"quick_actions_example"]; if (![quickActionsAppIcon waitForExistenceWithTimeout:kElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", springboard.debugDescription); XCTFail(@"Failed due to not able to find the example app from springboard with %@ seconds", @(kElementWaitingTime)); } [quickActionsAppIcon pressForDuration:2]; XCUIElement *actionTwo = springboard.buttons[@"Action two"]; if (![actionTwo waitForExistenceWithTimeout:kElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", springboard.debugDescription); XCTFail(@"Failed due to not able to find the actionTwo button from springboard with %@ seconds", @(kElementWaitingTime)); } [actionTwo tap]; XCUIElement *actionTwoConfirmation = _exampleApp.otherElements[@"action_two"]; if (![actionTwoConfirmation waitForExistenceWithTimeout:kElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", springboard.debugDescription); XCTFail(@"Failed due to not able to find the actionTwoConfirmation in the app with %@ seconds", @(kElementWaitingTime)); } XCTAssertTrue(actionTwoConfirmation.exists); } - (void)testQuickActionWhenAppIsInBackground { [_exampleApp launch]; XCUIElement *actionsReady = _exampleApp.otherElements[@"actions ready"]; if (![actionsReady waitForExistenceWithTimeout:kElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", _exampleApp.debugDescription); XCTFail(@"Failed due to not able to find the actionsReady in the app with %@ seconds", @(kElementWaitingTime)); } [[XCUIDevice sharedDevice] pressButton:XCUIDeviceButtonHome]; XCUIApplication *springboard = [[XCUIApplication alloc] initWithBundleIdentifier:@"com.apple.springboard"]; XCUIElement *quickActionsAppIcon = springboard.icons[@"quick_actions_example"]; if (![quickActionsAppIcon waitForExistenceWithTimeout:kElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", springboard.debugDescription); XCTFail(@"Failed due to not able to find the example app from springboard with %@ seconds", @(kElementWaitingTime)); } [quickActionsAppIcon pressForDuration:2]; XCUIElement *actionOne = springboard.buttons[@"Action one"]; if (![actionOne waitForExistenceWithTimeout:kElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", springboard.debugDescription); XCTFail(@"Failed due to not able to find the actionOne button from springboard with %@ seconds", @(kElementWaitingTime)); } [actionOne tap]; XCUIElement *actionOneConfirmation = _exampleApp.otherElements[@"action_one"]; if (![actionOneConfirmation waitForExistenceWithTimeout:kElementWaitingTime]) { os_log_error(OS_LOG_DEFAULT, "%@", springboard.debugDescription); XCTFail(@"Failed due to not able to find the actionOneConfirmation in the app with %@ seconds", @(kElementWaitingTime)); } XCTAssertTrue(actionOneConfirmation.exists); } @end ================================================ FILE: packages/quick_actions/quick_actions/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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:quick_actions/quick_actions.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: 'Flutter Quick Actions Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({Key? key}) : super(key: key); @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { String shortcut = 'no action set'; @override void initState() { super.initState(); const QuickActions quickActions = QuickActions(); quickActions.initialize((String shortcutType) { setState(() { if (shortcutType != null) { shortcut = shortcutType; } }); }); quickActions.setShortcutItems([ // NOTE: This first action icon will only work on iOS. // In a real world project keep the same file name for both platforms. const ShortcutItem( type: 'action_one', localizedTitle: 'Action one', icon: 'AppIcon', ), // NOTE: This second action icon will only work on Android. // In a real world project keep the same file name for both platforms. const ShortcutItem( type: 'action_two', localizedTitle: 'Action two', icon: 'ic_launcher'), ]).then((void _) { setState(() { if (shortcut == 'no action set') { shortcut = 'actions ready'; } }); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(shortcut), ), body: const Center( child: Text('On home screen, long press the app icon to ' 'get Action one or Action two options. Tapping on that action should ' 'set the toolbar title.'), ), ); } } ================================================ FILE: packages/quick_actions/quick_actions/example/pubspec.yaml ================================================ name: quick_actions_example description: Demonstrates how to use the quick_actions plugin. publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter quick_actions: # When depending on this package from a real application you should use: # quick_actions: ^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: ../ dev_dependencies: espresso: ^0.2.0 flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/quick_actions/quick_actions/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/quick_actions/quick_actions/lib/quick_actions.dart ================================================ // Copyright 2013 The Flutter 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:quick_actions_platform_interface/platform_interface/quick_actions_platform.dart'; import 'package:quick_actions_platform_interface/types/types.dart'; export 'package:quick_actions_platform_interface/types/types.dart'; /// Quick actions plugin. class QuickActions { /// Creates a new instance of [QuickActions]. const QuickActions(); /// Initializes this plugin. /// /// Call this once before any further interaction with the plugin. Future initialize(QuickActionHandler handler) async => QuickActionsPlatform.instance.initialize(handler); /// Sets the [ShortcutItem]s to become the app's quick actions. Future setShortcutItems(List items) async => QuickActionsPlatform.instance.setShortcutItems(items); /// Removes all [ShortcutItem]s registered for the app. Future clearShortcutItems() => QuickActionsPlatform.instance.clearShortcutItems(); } ================================================ FILE: packages/quick_actions/quick_actions/pubspec.yaml ================================================ name: quick_actions description: Flutter plugin for creating shortcuts on home screen, also known as Quick Actions on iOS and App Shortcuts on Android. repository: https://github.com/flutter/plugins/tree/main/packages/quick_actions/quick_actions issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+quick_actions%22 version: 1.0.1 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: platforms: android: default_package: quick_actions_android ios: default_package: quick_actions_ios dependencies: flutter: sdk: flutter quick_actions_android: ^1.0.0 quick_actions_ios: ^1.0.0 quick_actions_platform_interface: ^1.0.0 dev_dependencies: flutter_test: sdk: flutter integration_test: sdk: flutter mockito: ^5.0.0 plugin_platform_interface: ^2.0.0 ================================================ FILE: packages/quick_actions/quick_actions/test/quick_actions_test.dart ================================================ // Copyright 2013 The Flutter 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:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:quick_actions/quick_actions.dart'; import 'package:quick_actions_platform_interface/quick_actions_platform_interface.dart'; void main() { group('$QuickActions', () { setUp(() { QuickActionsPlatform.instance = MockQuickActionsPlatform(); }); test('constructor() should return valid QuickActions instance', () { const QuickActions quickActions = QuickActions(); expect(quickActions, isNotNull); }); test('initialize() PlatformInterface', () async { const QuickActions quickActions = QuickActions(); void handler(String type) {} await quickActions.initialize(handler); verify(QuickActionsPlatform.instance.initialize(handler)).called(1); }); test('setShortcutItems() PlatformInterface', () { const QuickActions quickActions = QuickActions(); void handler(String type) {} quickActions.initialize(handler); quickActions.setShortcutItems([]); verify(QuickActionsPlatform.instance.initialize(handler)).called(1); verify(QuickActionsPlatform.instance.setShortcutItems([])) .called(1); }); test('clearShortcutItems() PlatformInterface', () { const QuickActions quickActions = QuickActions(); void handler(String type) {} quickActions.initialize(handler); quickActions.clearShortcutItems(); verify(QuickActionsPlatform.instance.initialize(handler)).called(1); verify(QuickActionsPlatform.instance.clearShortcutItems()).called(1); }); }); } class MockQuickActionsPlatform extends Mock with MockPlatformInterfaceMixin implements QuickActionsPlatform { @override Future clearShortcutItems() async => super.noSuchMethod(Invocation.method(#clearShortcutItems, [])); @override Future initialize(QuickActionHandler? handler) async => super.noSuchMethod(Invocation.method(#initialize, [handler])); @override Future setShortcutItems(List? items) async => super .noSuchMethod(Invocation.method(#setShortcutItems, [items])); } class MockQuickActions extends QuickActions {} ================================================ FILE: packages/quick_actions/quick_actions_android/AUTHORS ================================================ # 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> Daniel Roek Maurits van Beusekom ================================================ FILE: packages/quick_actions/quick_actions_android/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 1.0.0 * Updates version to 1.0 to reflect current status. * Updates minimum Flutter version to 2.10. * Updates mockito-core to 4.6.1. * Removes deprecated FieldSetter from QuickActionsTest. ## 0.6.2 * Updates gradle version to 7.2.1. ## 0.6.1 * Allows Android to trigger quick actions without restarting the app. ## 0.6.0+11 * Updates references to the obsolete master branch. ## 0.6.0+10 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.6.0+9 * Switches to a package-internal implementation of the platform interface. ================================================ FILE: packages/quick_actions/quick_actions_android/LICENSE ================================================ 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. ================================================ FILE: packages/quick_actions/quick_actions_android/README.md ================================================ # quick\_actions\_android The Android implementation of [`quick_actions`][1]. ## Usage This package is [endorsed][2], which means you can simply use `quick_actions` normally. This package will be automatically included in your app when you do. ## Contributing If you would like to contribute to the plugin, check out our [contribution guide][3]. [1]: https://pub.dev/packages/quick_actions [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin [3]: https://github.com/flutter/plugins/blob/main/CONTRIBUTING.md ================================================ FILE: packages/quick_actions/quick_actions_android/android/build.gradle ================================================ group 'io.flutter.plugins.quickactions' version '1.0-SNAPSHOT' buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.2.1' } } rootProject.allprojects { repositories { google() mavenCentral() } } apply plugin: 'com.android.library' android { compileSdkVersion 31 defaultConfig { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:5.0.0' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } testOptions { unitTests.includeAndroidResources = true unitTests.returnDefaultValues = true unitTests.all { testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" outputs.upToDateWhen {false} showStandardStreams = true } } } } ================================================ FILE: packages/quick_actions/quick_actions_android/android/settings.gradle ================================================ rootProject.name = 'quick_actions' ================================================ FILE: packages/quick_actions/quick_actions_android/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/MethodCallHandlerImpl.java ================================================ // Copyright 2013 The Flutter 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.quickactions; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.content.res.Resources; import android.graphics.drawable.Icon; import android.os.Build; import android.os.Handler; import android.os.Looper; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { protected static final String EXTRA_ACTION = "some unique action key"; private static final String CHANNEL_ID = "plugins.flutter.io/quick_actions_android"; private final Context context; private Activity activity; MethodCallHandlerImpl(Context context, Activity activity) { this.context = context; this.activity = activity; } void setActivity(Activity activity) { this.activity = activity; } @Override public void onMethodCall(MethodCall call, MethodChannel.Result result) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { // We already know that this functionality does not work for anything // lower than API 25 so we chose not to return error. Instead we do nothing. result.success(null); return; } ShortcutManager shortcutManager = (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE); switch (call.method) { case "setShortcutItems": if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) { List> serializedShortcuts = call.arguments(); List shortcuts = deserializeShortcuts(serializedShortcuts); Executor uiThreadExecutor = new UiThreadExecutor(); ThreadPoolExecutor executor = new ThreadPoolExecutor( 0, 1, 1, TimeUnit.SECONDS, new LinkedBlockingQueue()); executor.execute( () -> { boolean dynamicShortcutsSet = false; try { shortcutManager.setDynamicShortcuts(shortcuts); dynamicShortcutsSet = true; } catch (Exception e) { // Leave dynamicShortcutsSet as false } final boolean didSucceed = dynamicShortcutsSet; // TODO(camsim99): Move re-dispatch below to background thread when Flutter 2.8+ is // stable. uiThreadExecutor.execute( () -> { if (didSucceed) { result.success(null); } else { result.error( "quick_action_setshortcutitems_failure", "Exception thrown when setting dynamic shortcuts", null); } }); }); } return; case "clearShortcutItems": shortcutManager.removeAllDynamicShortcuts(); break; case "getLaunchAction": if (activity == null) { result.error( "quick_action_getlaunchaction_no_activity", "There is no activity available when launching action", null); return; } final Intent intent = activity.getIntent(); final String launchAction = intent.getStringExtra(EXTRA_ACTION); if (launchAction != null && !launchAction.isEmpty()) { shortcutManager.reportShortcutUsed(launchAction); intent.removeExtra(EXTRA_ACTION); } result.success(launchAction); return; default: result.notImplemented(); return; } result.success(null); } @TargetApi(Build.VERSION_CODES.N_MR1) private List deserializeShortcuts(List> shortcuts) { final List shortcutInfos = new ArrayList<>(); for (Map shortcut : shortcuts) { final String icon = shortcut.get("icon"); final String type = shortcut.get("type"); final String title = shortcut.get("localizedTitle"); final ShortcutInfo.Builder shortcutBuilder = new ShortcutInfo.Builder(context, type); final int resourceId = loadResourceId(context, icon); final Intent intent = getIntentToOpenMainActivity(type); if (resourceId > 0) { shortcutBuilder.setIcon(Icon.createWithResource(context, resourceId)); } final ShortcutInfo shortcutInfo = shortcutBuilder.setLongLabel(title).setShortLabel(title).setIntent(intent).build(); shortcutInfos.add(shortcutInfo); } return shortcutInfos; } private int loadResourceId(Context context, String icon) { if (icon == null) { return 0; } final String packageName = context.getPackageName(); final Resources res = context.getResources(); final int resourceId = res.getIdentifier(icon, "drawable", packageName); if (resourceId == 0) { return res.getIdentifier(icon, "mipmap", packageName); } else { return resourceId; } } private Intent getIntentToOpenMainActivity(String type) { final String packageName = context.getPackageName(); return context .getPackageManager() .getLaunchIntentForPackage(packageName) .setAction(Intent.ACTION_RUN) .putExtra(EXTRA_ACTION, type) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); } private static class UiThreadExecutor implements Executor { private final Handler handler = new Handler(Looper.getMainLooper()); @Override public void execute(Runnable command) { handler.post(command); } } } ================================================ FILE: packages/quick_actions/quick_actions_android/android/src/main/java/io/flutter/plugins/quickactions/QuickActionsPlugin.java ================================================ // Copyright 2013 The Flutter 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.quickactions; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.ShortcutManager; import android.os.Build; 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.plugin.common.MethodChannel; import io.flutter.plugin.common.PluginRegistry.NewIntentListener; /** QuickActionsPlugin */ public class QuickActionsPlugin implements FlutterPlugin, ActivityAware, NewIntentListener { private static final String CHANNEL_ID = "plugins.flutter.io/quick_actions_android"; private MethodChannel channel; private MethodCallHandlerImpl handler; private Activity activity; /** * Plugin registration. * *

Must be called when the application is created. */ @SuppressWarnings("deprecation") public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { final QuickActionsPlugin plugin = new QuickActionsPlugin(); plugin.setupChannel(registrar.messenger(), registrar.context(), registrar.activity()); } @Override public void onAttachedToEngine(FlutterPluginBinding binding) { setupChannel(binding.getBinaryMessenger(), binding.getApplicationContext(), null); } @Override public void onDetachedFromEngine(FlutterPluginBinding binding) { teardownChannel(); } @Override public void onAttachedToActivity(ActivityPluginBinding binding) { activity = binding.getActivity(); handler.setActivity(activity); binding.addOnNewIntentListener(this); onNewIntent(activity.getIntent()); } @Override public void onDetachedFromActivity() { handler.setActivity(null); } @Override public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { binding.removeOnNewIntentListener(this); onAttachedToActivity(binding); } @Override public void onDetachedFromActivityForConfigChanges() { onDetachedFromActivity(); } @Override public boolean onNewIntent(Intent intent) { // Do nothing for anything lower than API 25 as the functionality isn't supported. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { return false; } // Notify the Dart side if the launch intent has the intent extra relevant to quick actions. if (intent.hasExtra(MethodCallHandlerImpl.EXTRA_ACTION) && channel != null) { Context context = activity.getApplicationContext(); ShortcutManager shortcutManager = (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE); String shortcutId = intent.getStringExtra(MethodCallHandlerImpl.EXTRA_ACTION); channel.invokeMethod("launch", shortcutId); shortcutManager.reportShortcutUsed(shortcutId); } return false; } private void setupChannel(BinaryMessenger messenger, Context context, Activity activity) { channel = new MethodChannel(messenger, CHANNEL_ID); handler = new MethodCallHandlerImpl(context, activity); channel.setMethodCallHandler(handler); } private void teardownChannel() { channel.setMethodCallHandler(null); channel = null; handler = null; } } ================================================ FILE: packages/quick_actions/quick_actions_android/android/src/test/java/io/flutter/plugins/quickactions/QuickActionsTest.java ================================================ // Copyright 2013 The Flutter 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.quickactions; import static io.flutter.plugins.quickactions.MethodCallHandlerImpl.EXTRA_ACTION; 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.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.ShortcutManager; import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.StandardMethodCodec; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.nio.ByteBuffer; import org.junit.After; import org.junit.Test; public class QuickActionsTest { private static class TestBinaryMessenger implements BinaryMessenger { public MethodCall lastMethodCall; @Override public void send(@NonNull String channel, @Nullable ByteBuffer message) { send(channel, message, null); } @Override public void send( @NonNull String channel, @Nullable ByteBuffer message, @Nullable final BinaryReply callback) { if (channel.equals("plugins.flutter.io/quick_actions_android")) { lastMethodCall = StandardMethodCodec.INSTANCE.decodeMethodCall((ByteBuffer) message.position(0)); } } @Override public void setMessageHandler(@NonNull String channel, @Nullable BinaryMessageHandler handler) { // Do nothing. } } static final int SUPPORTED_BUILD = 25; static final int UNSUPPORTED_BUILD = 24; static final String SHORTCUT_TYPE = "action_one"; @Test public void canAttachToEngine() { final TestBinaryMessenger testBinaryMessenger = new TestBinaryMessenger(); final FlutterPluginBinding mockPluginBinding = mock(FlutterPluginBinding.class); when(mockPluginBinding.getBinaryMessenger()).thenReturn(testBinaryMessenger); final QuickActionsPlugin plugin = new QuickActionsPlugin(); plugin.onAttachedToEngine(mockPluginBinding); } @Test public void onAttachedToActivity_buildVersionSupported_invokesLaunchMethod() throws NoSuchFieldException, IllegalAccessException { // Arrange final TestBinaryMessenger testBinaryMessenger = new TestBinaryMessenger(); final QuickActionsPlugin plugin = new QuickActionsPlugin(); setUpMessengerAndFlutterPluginBinding(testBinaryMessenger, plugin); setBuildVersion(SUPPORTED_BUILD); Field handler = plugin.getClass().getDeclaredField("handler"); handler.setAccessible(true); handler.set(plugin, mock(MethodCallHandlerImpl.class)); final Intent mockIntent = createMockIntentWithQuickActionExtra(); final Activity mockMainActivity = mock(Activity.class); when(mockMainActivity.getIntent()).thenReturn(mockIntent); final ActivityPluginBinding mockActivityPluginBinding = mock(ActivityPluginBinding.class); when(mockActivityPluginBinding.getActivity()).thenReturn(mockMainActivity); final Context mockContext = mock(Context.class); when(mockMainActivity.getApplicationContext()).thenReturn(mockContext); final ShortcutManager mockShortcutManager = mock(ShortcutManager.class); when(mockContext.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(mockShortcutManager); plugin.onAttachedToActivity(mockActivityPluginBinding); // Act plugin.onAttachedToActivity(mockActivityPluginBinding); // Assert assertNotNull(testBinaryMessenger.lastMethodCall); assertEquals(testBinaryMessenger.lastMethodCall.method, "launch"); assertEquals(testBinaryMessenger.lastMethodCall.arguments, SHORTCUT_TYPE); } @Test public void onNewIntent_buildVersionUnsupported_doesNotInvokeMethod() throws NoSuchFieldException, IllegalAccessException { // Arrange final TestBinaryMessenger testBinaryMessenger = new TestBinaryMessenger(); final QuickActionsPlugin plugin = new QuickActionsPlugin(); setUpMessengerAndFlutterPluginBinding(testBinaryMessenger, plugin); setBuildVersion(UNSUPPORTED_BUILD); final Intent mockIntent = createMockIntentWithQuickActionExtra(); // Act final boolean onNewIntentReturn = plugin.onNewIntent(mockIntent); // Assert assertNull(testBinaryMessenger.lastMethodCall); assertFalse(onNewIntentReturn); } @Test public void onNewIntent_buildVersionSupported_invokesLaunchMethod() throws NoSuchFieldException, IllegalAccessException { // Arrange final TestBinaryMessenger testBinaryMessenger = new TestBinaryMessenger(); final QuickActionsPlugin plugin = new QuickActionsPlugin(); setUpMessengerAndFlutterPluginBinding(testBinaryMessenger, plugin); setBuildVersion(SUPPORTED_BUILD); final Intent mockIntent = createMockIntentWithQuickActionExtra(); final Activity mockMainActivity = mock(Activity.class); when(mockMainActivity.getIntent()).thenReturn(mockIntent); final ActivityPluginBinding mockActivityPluginBinding = mock(ActivityPluginBinding.class); when(mockActivityPluginBinding.getActivity()).thenReturn(mockMainActivity); final Context mockContext = mock(Context.class); when(mockMainActivity.getApplicationContext()).thenReturn(mockContext); final ShortcutManager mockShortcutManager = mock(ShortcutManager.class); when(mockContext.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(mockShortcutManager); plugin.onAttachedToActivity(mockActivityPluginBinding); // Act final boolean onNewIntentReturn = plugin.onNewIntent(mockIntent); // Assert assertNotNull(testBinaryMessenger.lastMethodCall); assertEquals(testBinaryMessenger.lastMethodCall.method, "launch"); assertEquals(testBinaryMessenger.lastMethodCall.arguments, SHORTCUT_TYPE); assertFalse(onNewIntentReturn); } private void setUpMessengerAndFlutterPluginBinding( TestBinaryMessenger testBinaryMessenger, QuickActionsPlugin plugin) { final FlutterPluginBinding mockPluginBinding = mock(FlutterPluginBinding.class); when(mockPluginBinding.getBinaryMessenger()).thenReturn(testBinaryMessenger); plugin.onAttachedToEngine(mockPluginBinding); } private Intent createMockIntentWithQuickActionExtra() { final Intent mockIntent = mock(Intent.class); when(mockIntent.hasExtra(EXTRA_ACTION)).thenReturn(true); when(mockIntent.getStringExtra(EXTRA_ACTION)).thenReturn(QuickActionsTest.SHORTCUT_TYPE); return mockIntent; } private void setBuildVersion(int buildVersion) throws NoSuchFieldException, IllegalAccessException { Field buildSdkField = Build.VERSION.class.getField("SDK_INT"); buildSdkField.setAccessible(true); final Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(buildSdkField, buildSdkField.getModifiers() & ~Modifier.FINAL); buildSdkField.set(null, buildVersion); } @After public void tearDown() throws NoSuchFieldException, IllegalAccessException { setBuildVersion(0); } } ================================================ FILE: packages/quick_actions/quick_actions_android/example/README.md ================================================ # 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. ================================================ FILE: packages/quick_actions/quick_actions_android/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" def androidXTestVersion = '1.2.0' android { compileSdkVersion 32 lintOptions { disable 'InvalidPackage' } defaultConfig { applicationId "io.flutter.plugins.quickactionsexample" minSdkVersion 21 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' api "androidx.test:core:$androidXTestVersion" 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:5.0.0' androidTestImplementation 'org.mockito:mockito-android:5.0.0' } ================================================ FILE: packages/quick_actions/quick_actions_android/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/FlutterActivityTest.java ================================================ // Copyright 2013 The Flutter 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.quickactionsexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class FlutterActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/quick_actions/quick_actions_android/example/android/app/src/androidTest/java/io/flutter/plugins/quickactionsexample/QuickActionsTest.java ================================================ // Copyright 2013 The Flutter 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.quickactionsexample; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.content.Context; import android.content.Intent; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.util.Log; import androidx.lifecycle.Lifecycle; import androidx.test.core.app.ActivityScenario; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.uiautomator.By; import androidx.test.uiautomator.UiDevice; import androidx.test.uiautomator.Until; import io.flutter.plugins.quickactions.QuickActionsPlugin; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class QuickActionsTest { private Context context; private UiDevice device; private ActivityScenario scenario; @Before public void setUp() { context = ApplicationProvider.getApplicationContext(); device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); scenario = ensureAppRunToView(); ensureAllAppShortcutsAreCreated(); } @After public void tearDown() { scenario.close(); Log.i(QuickActionsTest.class.getSimpleName(), "Run to completion"); } @Test public void quickActionPluginIsAdded() { final ActivityScenario scenario = ActivityScenario.launch(QuickActionsTestActivity.class); scenario.onActivity( activity -> { assertTrue(activity.engine.getPlugins().has(QuickActionsPlugin.class)); }); } @Test public void appShortcutsAreCreated() { List expectedShortcuts = createMockShortcuts(); ShortcutManager shortcutManager = (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE); List dynamicShortcuts = shortcutManager.getDynamicShortcuts(); // Assert the app shortcuts defined in ../lib/main.dart. assertFalse(dynamicShortcuts.isEmpty()); assertEquals(expectedShortcuts.size(), dynamicShortcuts.size()); for (ShortcutInfo expectedShortcut : expectedShortcuts) { ShortcutInfo dynamicShortcut = dynamicShortcuts .stream() .filter(s -> s.getId().equals(expectedShortcut.getId())) .findFirst() .get(); assertEquals(expectedShortcut.getShortLabel(), dynamicShortcut.getShortLabel()); assertEquals(expectedShortcut.getLongLabel(), dynamicShortcut.getLongLabel()); } } @Test public void appShortcutLaunchActivityAfterStarting() { // Arrange List shortcuts = createMockShortcuts(); ShortcutInfo firstShortcut = shortcuts.get(0); ShortcutManager shortcutManager = (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE); List dynamicShortcuts = shortcutManager.getDynamicShortcuts(); ShortcutInfo dynamicShortcut = dynamicShortcuts .stream() .filter(s -> s.getId().equals(firstShortcut.getId())) .findFirst() .get(); Intent dynamicShortcutIntent = dynamicShortcut.getIntent(); AtomicReference initialActivity = new AtomicReference<>(); scenario.onActivity(initialActivity::set); String appReadySentinel = " has launched"; // Act context.startActivity(dynamicShortcutIntent); device.wait(Until.hasObject(By.descContains(appReadySentinel)), 2000); AtomicReference currentActivity = new AtomicReference<>(); scenario.onActivity(currentActivity::set); // Assert Assert.assertTrue( "AppShortcut:" + firstShortcut.getId() + " does not launch the correct activity", // We can only find the shortcut type in content description while inspecting it in Ui // Automator Viewer. device.hasObject(By.descContains(firstShortcut.getId() + appReadySentinel))); // This is Android SingleTop behavior in which Android does not destroy the initial activity and // launch a new activity. Assert.assertEquals(initialActivity.get(), currentActivity.get()); } private void ensureAllAppShortcutsAreCreated() { device.wait(Until.hasObject(By.text("actions ready")), 1000); } private List createMockShortcuts() { List expectedShortcuts = new ArrayList<>(); String actionOneLocalizedTitle = "Action one"; expectedShortcuts.add( new ShortcutInfo.Builder(context, "action_one") .setShortLabel(actionOneLocalizedTitle) .setLongLabel(actionOneLocalizedTitle) .build()); String actionTwoLocalizedTitle = "Action two"; expectedShortcuts.add( new ShortcutInfo.Builder(context, "action_two") .setShortLabel(actionTwoLocalizedTitle) .setLongLabel(actionTwoLocalizedTitle) .build()); return expectedShortcuts; } private ActivityScenario ensureAppRunToView() { final ActivityScenario scenario = ActivityScenario.launch(QuickActionsTestActivity.class); scenario.moveToState(Lifecycle.State.STARTED); return scenario; } } ================================================ FILE: packages/quick_actions/quick_actions_android/example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: packages/quick_actions/quick_actions_android/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/quick_actions/quick_actions_android/example/android/app/src/main/java/io/flutter/plugins/quickactionsexample/QuickActionsTestActivity.java ================================================ // Copyright 2013 The Flutter 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.quickactionsexample; import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; // Makes the FlutterEngine accessible for testing. public class QuickActionsTestActivity extends FlutterActivity { public FlutterEngine engine; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); engine = flutterEngine; } } ================================================ FILE: packages/quick_actions/quick_actions_android/example/android/app/src/main/res/drawable/ic_launcher_background.xml ================================================ ================================================ FILE: packages/quick_actions/quick_actions_android/example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: packages/quick_actions/quick_actions_android/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/quick_actions/quick_actions_android/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/quick_actions/quick_actions_android/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.enableR8=true android.useAndroidX=true android.enableJetifier=true ================================================ FILE: packages/quick_actions/quick_actions_android/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withInputStream { stream -> plugins.load(stream) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/quick_actions/quick_actions_android/example/integration_test/quick_actions_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:quick_actions_example/main.dart' as app; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('Can run MyApp', (WidgetTester tester) async { app.main(); await tester.pumpAndSettle(); await tester.pump(const Duration(seconds: 1)); expect(find.byType(Text), findsWidgets); expect(find.byType(app.MyHomePage), findsOneWidget); }); } ================================================ FILE: packages/quick_actions/quick_actions_android/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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:quick_actions_android/quick_actions_android.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: 'Flutter Quick Actions Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({Key? key}) : super(key: key); @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { String shortcut = 'no action set'; @override void initState() { super.initState(); final QuickActionsAndroid quickActions = QuickActionsAndroid(); quickActions.initialize((String shortcutType) { setState(() { if (shortcutType != null) { shortcut = '$shortcutType has launched'; } }); }); quickActions.setShortcutItems([ const ShortcutItem( type: 'action_one', localizedTitle: 'Action one', icon: 'AppIcon', ), const ShortcutItem( type: 'action_two', localizedTitle: 'Action two', icon: 'ic_launcher'), ]).then((void _) { setState(() { if (shortcut == 'no action set') { shortcut = 'actions ready'; } }); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(shortcut), ), body: const Center( child: Text('On home screen, long press the app icon to ' 'get Action one or Action two options. Tapping on that action should ' 'set the toolbar title.'), ), ); } } ================================================ FILE: packages/quick_actions/quick_actions_android/example/pubspec.yaml ================================================ name: quick_actions_example description: Demonstrates how to use the quick_actions plugin. publish_to: none environment: sdk: ">=2.15.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter quick_actions_android: # When depending on this package from a real application you should use: # quick_actions_android: ^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: ../ dev_dependencies: espresso: ^0.2.0 flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/quick_actions/quick_actions_android/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/quick_actions/quick_actions_android/lib/quick_actions_android.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:quick_actions_platform_interface/quick_actions_platform_interface.dart'; export 'package:quick_actions_platform_interface/types/types.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/quick_actions_android'); /// An implementation of [QuickActionsPlatform] that for Android. class QuickActionsAndroid extends QuickActionsPlatform { /// Registers this class as the default instance of [QuickActionsPlatform]. static void registerWith() { QuickActionsPlatform.instance = QuickActionsAndroid(); } /// The MethodChannel that is being used by this implementation of the plugin. @visibleForTesting MethodChannel get channel => _channel; @override Future initialize(QuickActionHandler handler) async { channel.setMethodCallHandler((MethodCall call) async { assert(call.method == 'launch'); handler(call.arguments as String); }); final String? action = await channel.invokeMethod('getLaunchAction'); if (action != null) { handler(action); } } @override Future setShortcutItems(List items) async { final List> itemsList = items.map(_serializeItem).toList(); await channel.invokeMethod('setShortcutItems', itemsList); } @override Future clearShortcutItems() => channel.invokeMethod('clearShortcutItems'); Map _serializeItem(ShortcutItem item) { return { 'type': item.type, 'localizedTitle': item.localizedTitle, 'icon': item.icon, }; } } ================================================ FILE: packages/quick_actions/quick_actions_android/pubspec.yaml ================================================ name: quick_actions_android description: An implementation for the Android platform of the Flutter `quick_actions` plugin. repository: https://github.com/flutter/plugins/tree/main/packages/quick_actions/quick_actions_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 version: 1.0.0 environment: sdk: ">=2.15.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: quick_actions platforms: android: package: io.flutter.plugins.quickactions pluginClass: QuickActionsPlugin dartPluginClass: QuickActionsAndroid dependencies: flutter: sdk: flutter quick_actions_platform_interface: ^1.0.0 dev_dependencies: flutter_test: sdk: flutter integration_test: sdk: flutter plugin_platform_interface: ^2.1.2 ================================================ FILE: packages/quick_actions/quick_actions_android/test/quick_actions_android_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:quick_actions_android/quick_actions_android.dart'; import 'package:quick_actions_platform_interface/quick_actions_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$QuickActionsAndroid', () { late List log; setUp(() { log = []; }); QuickActionsAndroid buildQuickActionsPlugin() { final QuickActionsAndroid quickActions = QuickActionsAndroid(); _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(quickActions.channel, (MethodCall methodCall) async { log.add(methodCall); return ''; }); return quickActions; } test('registerWith() registers correct instance', () { QuickActionsAndroid.registerWith(); expect(QuickActionsPlatform.instance, isA()); }); group('#initialize', () { test('passes getLaunchAction on launch method', () { final QuickActionsAndroid quickActions = buildQuickActionsPlugin(); quickActions.initialize((String type) {}); expect( log, [ isMethodCall('getLaunchAction', arguments: null), ], ); }); test('initialize', () async { final QuickActionsAndroid quickActions = buildQuickActionsPlugin(); final Completer quickActionsHandler = Completer(); await quickActions .initialize((_) => quickActionsHandler.complete(true)); expect( log, [ isMethodCall('getLaunchAction', arguments: null), ], ); log.clear(); expect(quickActionsHandler.future, completion(isTrue)); }); }); group('#setShortCutItems', () { test('passes shortcutItem through channel', () { final QuickActionsAndroid quickActions = buildQuickActionsPlugin(); quickActions.initialize((String type) {}); quickActions.setShortcutItems([ const ShortcutItem( type: 'test', localizedTitle: 'title', icon: 'icon.svg') ]); expect( log, [ isMethodCall('getLaunchAction', arguments: null), isMethodCall('setShortcutItems', arguments: >[ { 'type': 'test', 'localizedTitle': 'title', 'icon': 'icon.svg', } ]), ], ); }); test('setShortcutItems with demo data', () async { const String type = 'type'; const String localizedTitle = 'localizedTitle'; const String icon = 'icon'; final QuickActionsAndroid quickActions = buildQuickActionsPlugin(); await quickActions.setShortcutItems( const [ ShortcutItem(type: type, localizedTitle: localizedTitle, icon: icon) ], ); expect( log, [ isMethodCall( 'setShortcutItems', arguments: >[ { 'type': type, 'localizedTitle': localizedTitle, 'icon': icon, } ], ), ], ); log.clear(); }); }); group('#clearShortCutItems', () { test('send clearShortcutItems through channel', () { final QuickActionsAndroid quickActions = buildQuickActionsPlugin(); quickActions.initialize((String type) {}); quickActions.clearShortcutItems(); expect( log, [ isMethodCall('getLaunchAction', arguments: null), isMethodCall('clearShortcutItems', arguments: null), ], ); }); test('clearShortcutItems', () { final QuickActionsAndroid quickActions = buildQuickActionsPlugin(); quickActions.clearShortcutItems(); expect( log, [ isMethodCall('clearShortcutItems', arguments: null), ], ); log.clear(); }); }); }); group('$ShortcutItem', () { test('Shortcut item can be constructed', () { const String type = 'type'; const String localizedTitle = 'title'; const String icon = 'foo'; const ShortcutItem item = ShortcutItem(type: type, localizedTitle: localizedTitle, icon: icon); expect(item.type, type); expect(item.localizedTitle, localizedTitle); expect(item.icon, icon); }); }); } /// 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; ================================================ FILE: packages/quick_actions/quick_actions_ios/AUTHORS ================================================ # 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> Daniel Roek Maurits van Beusekom ================================================ FILE: packages/quick_actions/quick_actions_ios/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 1.0.2 * Migrates remaining components to Swift and removes all Objective-C settings. * Migrates `RunnerUITests` to Swift. ## 1.0.1 * Removes custom modulemap file with "Test" submodule and private headers for Swift migration. * Migrates `FLTQuickActionsPlugin` class to Swift. ## 1.0.0 * Updates version to 1.0 to reflect current status. * Updates minimum Flutter version to 2.10. ## 0.6.0+14 * Refactors `FLTQuickActionsPlugin` class into multiple components. * Increases unit tests coverage to 100%. ## 0.6.0+13 * Adds some unit tests for `FLTQuickActionsPlugin` class. ## 0.6.0+12 * Adds a custom module map with a Test submodule for unit tests on iOS platform. ## 0.6.0+11 * Updates references to the obsolete master branch. ## 0.6.0+10 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.6.0+9 * Switches to a package-internal implementation of the platform interface. ================================================ FILE: packages/quick_actions/quick_actions_ios/LICENSE ================================================ 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. ================================================ FILE: packages/quick_actions/quick_actions_ios/README.md ================================================ # quick\_actions\_ios The iOS implementation of [`quick_actions`][1]. ## Usage This package is [endorsed][2], which means you can simply use `quick_actions` normally. This package will be automatically included in your app when you do. ## Contributing If you would like to contribute to the plugin, check out our [contribution guide][3]. [1]: https://pub.dev/packages/quick_actions [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin [3]: https://github.com/flutter/plugins/blob/main/CONTRIBUTING.md ================================================ FILE: packages/quick_actions/quick_actions_ios/example/README.md ================================================ # 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. ================================================ FILE: packages/quick_actions/quick_actions_ios/example/integration_test/quick_actions_test.dart ================================================ // Copyright 2013 The Flutter 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:quick_actions_ios/quick_actions_ios.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('Can set shortcuts', (WidgetTester tester) async { final QuickActionsIos quickActions = QuickActionsIos(); await quickActions.initialize((String value) {}); const ShortcutItem shortCutItem = ShortcutItem( type: 'action_one', localizedTitle: 'Action one', icon: 'AppIcon', ); expect( quickActions.setShortcutItems([shortCutItem]), completes); }); } ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 UIRequiredDeviceCapabilities arm64 MinimumOSVersion 9.0 ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/Flutter/Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/Flutter/Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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]; // Override point for customization after application launch. [super application:application didFinishLaunchingWithOptions:launchOptions]; return NO; } @end ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName quick_actions_example CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 2632072169FF635893D8EB4D /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 436668746754BEEA28B76E55 /* libPods-RunnerTests.a */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 6A841C2B6AED5CF8DB2A1894 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C35AD3650AB6BF850E016715 /* 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 */; }; 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 */; }; E092A7ED28D10802005C7F67 /* MockMethodChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */; }; E092A7EE28D10802005C7F67 /* QuickActionsPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */; }; E092A7F128D10890005C7F67 /* MockShortcutItemProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F028D10890005C7F67 /* MockShortcutItemProvider.swift */; }; E092A7F428D110B3005C7F67 /* DefaultShortcutItemParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */; }; E092A7F628D128EB005C7F67 /* RunnerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E092A7F528D128EB005C7F67 /* RunnerUITests.swift */; }; E0A075D529147FE200329BAE /* MockShortcutItemParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 33E20B3726EFCDFC00A4A191 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; 686BE83225E58CCF00862533 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 33E20B3226EFCDFC00A4A191 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 33E20B3626EFCDFC00A4A191 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 436668746754BEEA28B76E55 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 5278439583922091276A37C9 /* 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 = ""; }; 686BE82D25E58CCF00862533 /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 686BE83125E58CCF00862533 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 96F949A6B78E2DC62B93C4F8 /* 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 = ""; }; 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 = ""; }; 9D27FE1F0F21D4D47DDA16DE /* 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 = ""; }; C35AD3650AB6BF850E016715 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockMethodChannel.swift; sourceTree = ""; }; E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickActionsPluginTests.swift; sourceTree = ""; }; E092A7F028D10890005C7F67 /* MockShortcutItemProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockShortcutItemProvider.swift; sourceTree = ""; }; E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultShortcutItemParserTests.swift; sourceTree = ""; }; E092A7F528D128EB005C7F67 /* RunnerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerUITests.swift; sourceTree = ""; }; E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockShortcutItemParser.swift; sourceTree = ""; }; F0609304FBCAEC2289164BD5 /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 33E20B2F26EFCDFC00A4A191 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2632072169FF635893D8EB4D /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 686BE82A25E58CCF00862533 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 6A841C2B6AED5CF8DB2A1894 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 33E20B3326EFCDFC00A4A191 /* RunnerTests */ = { isa = PBXGroup; children = ( E092A7F228D10908005C7F67 /* Mocks */, 33E20B3626EFCDFC00A4A191 /* Info.plist */, E092A7EB28D10802005C7F67 /* QuickActionsPluginTests.swift */, E092A7F328D110B3005C7F67 /* DefaultShortcutItemParserTests.swift */, ); path = RunnerTests; sourceTree = ""; }; 686BE82E25E58CCF00862533 /* RunnerUITests */ = { isa = PBXGroup; children = ( 686BE83125E58CCF00862533 /* Info.plist */, E092A7F528D128EB005C7F67 /* RunnerUITests.swift */, ); path = RunnerUITests; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 686BE82E25E58CCF00862533 /* RunnerUITests */, 33E20B3326EFCDFC00A4A191 /* RunnerTests */, 97C146EF1CF9000F007C117D /* Products */, D0FE95BE2380323DD75CB891 /* Pods */, A44AD0D63DEF785A2A2DEE28 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, 686BE82D25E58CCF00862533 /* RunnerUITests.xctest */, 33E20B3226EFCDFC00A4A191 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; A44AD0D63DEF785A2A2DEE28 /* Frameworks */ = { isa = PBXGroup; children = ( C35AD3650AB6BF850E016715 /* libPods-Runner.a */, 436668746754BEEA28B76E55 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; D0FE95BE2380323DD75CB891 /* Pods */ = { isa = PBXGroup; children = ( 5278439583922091276A37C9 /* Pods-Runner.debug.xcconfig */, F0609304FBCAEC2289164BD5 /* Pods-Runner.release.xcconfig */, 9D27FE1F0F21D4D47DDA16DE /* Pods-RunnerTests.debug.xcconfig */, 96F949A6B78E2DC62B93C4F8 /* Pods-RunnerTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; E092A7F228D10908005C7F67 /* Mocks */ = { isa = PBXGroup; children = ( E092A7EA28D10801005C7F67 /* MockMethodChannel.swift */, E092A7F028D10890005C7F67 /* MockShortcutItemProvider.swift */, E0A075D429147FE200329BAE /* MockShortcutItemParser.swift */, ); path = Mocks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 33E20B3126EFCDFC00A4A191 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = 33E20B3B26EFCDFC00A4A191 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( 3B2E8279C112D7129C8D23F1 /* [CP] Check Pods Manifest.lock */, 33E20B2E26EFCDFC00A4A191 /* Sources */, 33E20B2F26EFCDFC00A4A191 /* Frameworks */, 33E20B3026EFCDFC00A4A191 /* Resources */, ); buildRules = ( ); dependencies = ( 33E20B3826EFCDFC00A4A191 /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = 33E20B3226EFCDFC00A4A191 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 686BE82C25E58CCF00862533 /* RunnerUITests */ = { isa = PBXNativeTarget; buildConfigurationList = 686BE83625E58CCF00862533 /* Build configuration list for PBXNativeTarget "RunnerUITests" */; buildPhases = ( 686BE82925E58CCF00862533 /* Sources */, 686BE82A25E58CCF00862533 /* Frameworks */, 686BE82B25E58CCF00862533 /* Resources */, ); buildRules = ( ); dependencies = ( 686BE83325E58CCF00862533 /* PBXTargetDependency */, ); name = RunnerUITests; productName = RunnerUITests; productReference = 686BE82D25E58CCF00862533 /* RunnerUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( C6989ECD8FF0836301D734B4 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 33E20B3126EFCDFC00A4A191 = { CreatedOnToolsVersion = 12.5; LastSwiftMigration = 1330; TestTargetID = 97C146ED1CF9000F007C117D; }; 686BE82C25E58CCF00862533 = { CreatedOnToolsVersion = 12.4; LastSwiftMigration = 1330; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, 686BE82C25E58CCF00862533 /* RunnerUITests */, 33E20B3126EFCDFC00A4A191 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 33E20B3026EFCDFC00A4A191 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 686BE82B25E58CCF00862533 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 3B2E8279C112D7129C8D23F1 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 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"; }; C6989ECD8FF0836301D734B4 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 33E20B2E26EFCDFC00A4A191 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E092A7EE28D10802005C7F67 /* QuickActionsPluginTests.swift in Sources */, E092A7ED28D10802005C7F67 /* MockMethodChannel.swift in Sources */, E092A7F128D10890005C7F67 /* MockShortcutItemProvider.swift in Sources */, E0A075D529147FE200329BAE /* MockShortcutItemParser.swift in Sources */, E092A7F428D110B3005C7F67 /* DefaultShortcutItemParserTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 686BE82925E58CCF00862533 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( E092A7F628D128EB005C7F67 /* RunnerUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 33E20B3826EFCDFC00A4A191 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = 33E20B3726EFCDFC00A4A191 /* PBXContainerItemProxy */; }; 686BE83325E58CCF00862533 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = 686BE83225E58CCF00862533 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 33E20B3926EFCDFC00A4A191 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9D27FE1F0F21D4D47DDA16DE /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; 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)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Debug; }; 33E20B3A26EFCDFC00A4A191 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 96F949A6B78E2DC62B93C4F8 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; 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)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; }; 686BE83425E58CCF00862533 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = RunnerUITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.google.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = Runner; }; name = Debug; }; 686BE83525E58CCF00862533 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = RunnerUITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.google.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = Runner; }; name = Release; }; 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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 = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.quickActionsExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.quickActionsExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 33E20B3B26EFCDFC00A4A191 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 33E20B3926EFCDFC00A4A191 /* Debug */, 33E20B3A26EFCDFC00A4A191 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 686BE83625E58CCF00862533 /* Build configuration list for PBXNativeTarget "RunnerUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( 686BE83425E58CCF00862533 /* Debug */, 686BE83525E58CCF00862533 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/RunnerUITests.xcscheme ================================================ ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/DefaultShortcutItemParserTests.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Flutter import XCTest @testable import quick_actions_ios class DefaultShortcutItemParserTests: XCTestCase { func testParseShortcutItems() { let rawItem = [ "type": "SearchTheThing", "localizedTitle": "Search the thing", "icon": "search_the_thing.png", ] let expectedItem = UIApplicationShortcutItem( type: "SearchTheThing", localizedTitle: "Search the thing", localizedSubtitle: nil, icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), userInfo: nil) let parser = DefaultShortcutItemParser() XCTAssertEqual(parser.parseShortcutItems([rawItem]), [expectedItem]) } func testParseShortcutItems_noIcon() { let rawItem: [String: Any] = [ "type": "SearchTheThing", "localizedTitle": "Search the thing", "icon": NSNull(), ] let expectedItem = UIApplicationShortcutItem( type: "SearchTheThing", localizedTitle: "Search the thing", localizedSubtitle: nil, icon: nil, userInfo: nil) let parser = DefaultShortcutItemParser() XCTAssertEqual(parser.parseShortcutItems([rawItem]), [expectedItem]) } func testParseShortcutItems_noType() { let rawItem = [ "localizedTitle": "Search the thing", "icon": "search_the_thing.png", ] let parser = DefaultShortcutItemParser() XCTAssertEqual(parser.parseShortcutItems([rawItem]), []) } func testParseShortcutItems_noLocalizedTitle() { let rawItem = [ "type": "SearchTheThing", "icon": "search_the_thing.png", ] let parser = DefaultShortcutItemParser() XCTAssertEqual(parser.parseShortcutItems([rawItem]), []) } } ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Info.plist ================================================ 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 ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockMethodChannel.swift ================================================ // Copyright 2013 The Flutter 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 @testable import quick_actions_ios final class MockMethodChannel: MethodChannel { var invokeMethodStub: ((_ methods: String, _ arguments: Any?) -> Void)? = nil func invokeMethod(_ method: String, arguments: Any?) { invokeMethodStub?(method, arguments) } } ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemParser.swift ================================================ // Copyright 2013 The Flutter 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 @testable import quick_actions_ios final class MockShortcutItemParser: ShortcutItemParser { var parseShortcutItemsStub: ((_ items: [[String: Any]]) -> [UIApplicationShortcutItem])? = nil func parseShortcutItems(_ items: [[String: Any]]) -> [UIApplicationShortcutItem] { return parseShortcutItemsStub?(items) ?? [] } } ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/Mocks/MockShortcutItemProvider.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @testable import quick_actions_ios final class MockShortcutItemProvider: ShortcutItemProviding { var shortcutItems: [UIApplicationShortcutItem]? = nil } ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/RunnerTests/QuickActionsPluginTests.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Flutter import XCTest @testable import quick_actions_ios class QuickActionsPluginTests: XCTestCase { func testHandleMethodCall_setShortcutItems() { let rawItem = [ "type": "SearchTheThing", "localizedTitle": "Search the thing", "icon": "search_the_thing.png", ] let item = UIApplicationShortcutItem( type: "SearchTheThing", localizedTitle: "Search the thing", localizedSubtitle: nil, icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), userInfo: nil) let call = FlutterMethodCall(methodName: "setShortcutItems", arguments: [rawItem]) let mockChannel = MockMethodChannel() let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( channel: mockChannel, shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) let parseShortcutItemsExpectation = expectation( description: "parseShortcutItems must be called.") mockShortcutItemParser.parseShortcutItemsStub = { items in XCTAssertEqual(items as? [[String: String]], [rawItem]) parseShortcutItemsExpectation.fulfill() return [item] } let resultExpectation = expectation(description: "result block must be called.") plugin.handle(call) { result in XCTAssertNil(result, "result block must be called with nil.") resultExpectation.fulfill() } XCTAssertEqual(mockShortcutItemProvider.shortcutItems, [item], "Must set shortcut items.") waitForExpectations(timeout: 1) } func testHandleMethodCall_clearShortcutItems() { let item = UIApplicationShortcutItem( type: "SearchTheThing", localizedTitle: "Search the thing", localizedSubtitle: nil, icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), userInfo: nil) let call = FlutterMethodCall(methodName: "clearShortcutItems", arguments: nil) let mockChannel = MockMethodChannel() let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() mockShortcutItemProvider.shortcutItems = [item] let plugin = QuickActionsPlugin( channel: mockChannel, shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) let resultExpectation = expectation(description: "result block must be called.") plugin.handle(call) { result in XCTAssertNil(result, "result block must be called with nil.") resultExpectation.fulfill() } XCTAssertEqual(mockShortcutItemProvider.shortcutItems, [], "Must clear shortcut items.") waitForExpectations(timeout: 1) } func testHandleMethodCall_getLaunchAction() { let call = FlutterMethodCall(methodName: "getLaunchAction", arguments: nil) let mockChannel = MockMethodChannel() let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( channel: mockChannel, shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) let resultExpectation = expectation(description: "result block must be called.") plugin.handle(call) { result in XCTAssertNil(result, "result block must be called with nil.") resultExpectation.fulfill() } waitForExpectations(timeout: 1) } func testHandleMethodCall_nonExistMethods() { let call = FlutterMethodCall(methodName: "nonExist", arguments: nil) let mockChannel = MockMethodChannel() let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( channel: mockChannel, shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) let resultExpectation = expectation(description: "result block must be called.") plugin.handle(call) { result in XCTAssertEqual( result as? NSObject, FlutterMethodNotImplemented, "result block must be called with FlutterMethodNotImplemented") resultExpectation.fulfill() } waitForExpectations(timeout: 1) } func testApplicationPerformActionForShortcutItem() { let mockChannel = MockMethodChannel() let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( channel: mockChannel, shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) let item = UIApplicationShortcutItem( type: "SearchTheThing", localizedTitle: "Search the thing", localizedSubtitle: nil, icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), userInfo: nil) let invokeMethodExpectation = expectation(description: "invokeMethod must be called.") mockChannel.invokeMethodStub = { method, arguments in XCTAssertEqual(method, "launch") XCTAssertEqual(arguments as? String, item.type) invokeMethodExpectation.fulfill() } let actionResult = plugin.application( UIApplication.shared, performActionFor: item ) { success in /* no-op */ } XCTAssert(actionResult, "performActionForShortcutItem must return true.") waitForExpectations(timeout: 1) } func testApplicationDidFinishLaunchingWithOptions_launchWithShortcut() { let mockChannel = MockMethodChannel() let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( channel: mockChannel, shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) let item = UIApplicationShortcutItem( type: "SearchTheThing", localizedTitle: "Search the thing", localizedSubtitle: nil, icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), userInfo: nil) let launchResult = plugin.application( UIApplication.shared, didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item]) XCTAssertFalse( launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.") } func testApplicationDidFinishLaunchingWithOptions_launchWithoutShortcut() { let mockChannel = MockMethodChannel() let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( channel: mockChannel, shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [:]) XCTAssert( launchResult, "didFinishLaunchingWithOptions must return true if not launched from shortcut.") } func testApplicationDidBecomeActive_launchWithoutShortcut() { let mockChannel = MockMethodChannel() let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( channel: mockChannel, shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) mockChannel.invokeMethodStub = { _, _ in XCTFail("invokeMethod should not be called if launch without shortcut.") } let launchResult = plugin.application(UIApplication.shared, didFinishLaunchingWithOptions: [:]) XCTAssert( launchResult, "didFinishLaunchingWithOptions must return true if not launched from shortcut.") plugin.applicationDidBecomeActive(UIApplication.shared) } func testApplicationDidBecomeActive_launchWithShortcut() { let item = UIApplicationShortcutItem( type: "SearchTheThing", localizedTitle: "Search the thing", localizedSubtitle: nil, icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), userInfo: nil) let mockChannel = MockMethodChannel() let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( channel: mockChannel, shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) let invokeMethodExpectation = expectation(description: "invokeMethod must be called.") mockChannel.invokeMethodStub = { method, arguments in XCTAssertEqual(method, "launch") XCTAssertEqual(arguments as? String, item.type) invokeMethodExpectation.fulfill() } let launchResult = plugin.application( UIApplication.shared, didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item]) XCTAssertFalse( launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.") plugin.applicationDidBecomeActive(UIApplication.shared) waitForExpectations(timeout: 1) } func testApplicationDidBecomeActive_launchWithShortcut_becomeActiveTwice() { let item = UIApplicationShortcutItem( type: "SearchTheThing", localizedTitle: "Search the thing", localizedSubtitle: nil, icon: UIApplicationShortcutIcon(templateImageName: "search_the_thing.png"), userInfo: nil) let mockChannel = MockMethodChannel() let mockShortcutItemProvider = MockShortcutItemProvider() let mockShortcutItemParser = MockShortcutItemParser() let plugin = QuickActionsPlugin( channel: mockChannel, shortcutItemProvider: mockShortcutItemProvider, shortcutItemParser: mockShortcutItemParser) let invokeMethodExpectation = expectation(description: "invokeMethod must be called.") var invokeMehtodCount = 0 mockChannel.invokeMethodStub = { method, arguments in invokeMehtodCount += 1 invokeMethodExpectation.fulfill() } let launchResult = plugin.application( UIApplication.shared, didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey.shortcutItem: item]) XCTAssertFalse( launchResult, "didFinishLaunchingWithOptions must return false if launched from shortcut.") plugin.applicationDidBecomeActive(UIApplication.shared) waitForExpectations(timeout: 1) XCTAssertEqual(invokeMehtodCount, 1, "shortcut should only be handled once per launch.") } } ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/RunnerUITests/Info.plist ================================================ 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 ================================================ FILE: packages/quick_actions/quick_actions_ios/example/ios/RunnerUITests/RunnerUITests.swift ================================================ // Copyright 2013 The Flutter 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 private let elementWaitingTime: TimeInterval = 30 class RunnerUITests: XCTestCase { private var exampleApp: XCUIApplication! override func setUp() { super.setUp() self.continueAfterFailure = false exampleApp = XCUIApplication() } override func tearDown() { super.tearDown() exampleApp.terminate() exampleApp = nil } func testQuickActionWithFreshStart() { let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") let quickActionsAppIcon = springboard.icons["quick_actions_example"] if !quickActionsAppIcon.waitForExistence(timeout: elementWaitingTime) { XCTFail( "Failed due to not able to find the example app from springboard with \(elementWaitingTime) seconds. Springboard debug description: \(springboard.debugDescription)" ) } quickActionsAppIcon.press(forDuration: 2) let actionTwo = springboard.buttons["Action two"] if !actionTwo.waitForExistence(timeout: elementWaitingTime) { XCTFail( "Failed due to not able to find the actionTwo button from springboard with \(elementWaitingTime) seconds. Springboard debug description: \(springboard.debugDescription)" ) } actionTwo.tap() let actionTwoConfirmation = exampleApp.otherElements["action_two"] if !actionTwoConfirmation.waitForExistence(timeout: elementWaitingTime) { XCTFail( "Failed due to not able to find the actionTwoConfirmation in the app with \(elementWaitingTime) seconds. Springboard debug description: \(springboard.debugDescription)" ) } XCTAssert(actionTwoConfirmation.exists) } func testQuickActionWhenAppIsInBackground() { exampleApp.launch() let actionsReady = exampleApp.otherElements["actions ready"] if !actionsReady.waitForExistence(timeout: elementWaitingTime) { XCTFail( "Failed due to not able to find the actionsReady in the app with \(elementWaitingTime) seconds. App debug description: \(exampleApp.debugDescription)" ) } XCUIDevice.shared.press(.home) let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") let quickActionsAppIcon = springboard.icons["quick_actions_example"] if !quickActionsAppIcon.waitForExistence(timeout: elementWaitingTime) { XCTFail( "Failed due to not able to find the example app from springboard with \(elementWaitingTime) seconds. Springboard debug description: \(springboard.debugDescription)" ) } quickActionsAppIcon.press(forDuration: 2) let actionOne = springboard.buttons["Action one"] if !actionOne.waitForExistence(timeout: elementWaitingTime) { XCTFail( "Failed due to not able to find the actionOne button from springboard with \(elementWaitingTime) seconds. Springboard debug description: \(springboard.debugDescription)" ) } actionOne.tap() let actionOneConfirmation = exampleApp.otherElements["action_one"] if !actionOneConfirmation.waitForExistence(timeout: elementWaitingTime) { XCTFail( "Failed due to not able to find the actionOneConfirmation in the app with \(elementWaitingTime) seconds. Springboard debug description: \(springboard.debugDescription)" ) } XCTAssert(actionOneConfirmation.exists) } } ================================================ FILE: packages/quick_actions/quick_actions_ios/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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:quick_actions_ios/quick_actions_ios.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: 'Flutter Quick Actions Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({Key? key}) : super(key: key); @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { String shortcut = 'no action set'; @override void initState() { super.initState(); final QuickActionsIos quickActions = QuickActionsIos(); quickActions.initialize((String shortcutType) { setState(() { if (shortcutType != null) { shortcut = shortcutType; } }); }); quickActions.setShortcutItems([ const ShortcutItem( type: 'action_one', localizedTitle: 'Action one', icon: 'AppIcon', ), const ShortcutItem( type: 'action_two', localizedTitle: 'Action two', icon: 'ic_launcher'), ]).then((void _) { setState(() { if (shortcut == 'no action set') { shortcut = 'actions ready'; } }); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(shortcut), ), body: const Center( child: Text('On home screen, long press the app icon to ' 'get Action one or Action two options. Tapping on that action should ' 'set the toolbar title.'), ), ); } } ================================================ FILE: packages/quick_actions/quick_actions_ios/example/pubspec.yaml ================================================ name: quick_actions_example description: Demonstrates how to use the quick_actions plugin. publish_to: none environment: sdk: ">=2.15.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter quick_actions_ios: # When depending on this package from a real application you should use: # quick_actions_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: ../ dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/quick_actions/quick_actions_ios/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/quick_actions/quick_actions_ios/ios/Assets/.gitkeep ================================================ ================================================ FILE: packages/quick_actions/quick_actions_ios/ios/Classes/MethodChannel.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Flutter /// A channel for platform code to communicate with the Dart code. protocol MethodChannel { /// Invokes a method in Dart code. /// - Parameter method the method name. /// - Parameter arguments the method arguments. func invokeMethod(_ method: String, arguments: Any?) } /// A default implementation of the `MethodChannel` protocol. extension FlutterMethodChannel: MethodChannel {} ================================================ FILE: packages/quick_actions/quick_actions_ios/ios/Classes/QuickActionsPlugin.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Flutter public final class QuickActionsPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel( name: "plugins.flutter.io/quick_actions_ios", binaryMessenger: registrar.messenger()) let instance = QuickActionsPlugin(channel: channel) registrar.addMethodCallDelegate(instance, channel: channel) registrar.addApplicationDelegate(instance) } private let channel: MethodChannel private let shortcutItemProvider: ShortcutItemProviding private let shortcutItemParser: ShortcutItemParser /// The type of the shortcut item selected when launching the app. private var launchingShortcutType: String? = nil init( channel: MethodChannel, shortcutItemProvider: ShortcutItemProviding = UIApplication.shared, shortcutItemParser: ShortcutItemParser = DefaultShortcutItemParser() ) { self.channel = channel self.shortcutItemProvider = shortcutItemProvider self.shortcutItemParser = shortcutItemParser } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case "setShortcutItems": // `arguments` must be an array of dictionaries let items = call.arguments as! [[String: Any]] shortcutItemProvider.shortcutItems = shortcutItemParser.parseShortcutItems(items) result(nil) case "clearShortcutItems": shortcutItemProvider.shortcutItems = [] result(nil) case "getLaunchAction": result(nil) case _: result(FlutterMethodNotImplemented) } } public func application( _ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void ) -> Bool { handleShortcut(shortcutItem.type) return true } public func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [AnyHashable: Any] = [:] ) -> Bool { if let shortcutItem = launchOptions[UIApplication.LaunchOptionsKey.shortcutItem] as? UIApplicationShortcutItem { // Keep hold of the shortcut type and handle it in the // `applicationDidBecomeActive:` method once the Dart MethodChannel // is initialized. launchingShortcutType = shortcutItem.type // Return false to indicate we handled the quick action to ensure // the `application:performActionFor:` method is not called (as // per Apple's documentation: // https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622935-application). return false } return true } public func applicationDidBecomeActive(_ application: UIApplication) { if let shortcutType = launchingShortcutType { handleShortcut(shortcutType) launchingShortcutType = nil } } private func handleShortcut(_ shortcut: String) { channel.invokeMethod("launch", arguments: shortcut) } } ================================================ FILE: packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemParser.swift ================================================ // Copyright 2013 The Flutter 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 /// A parser that parses an array of raw shortcut items. protocol ShortcutItemParser { /// Parses an array of raw shortcut items into an array of UIApplicationShortcutItems /// /// - Parameter items an array of raw shortcut items to be parsed. /// - Returns an array of parsed shortcut items to be set. /// func parseShortcutItems(_ items: [[String: Any]]) -> [UIApplicationShortcutItem] } /// A default implementation of the `ShortcutItemParser` protocol. final class DefaultShortcutItemParser: ShortcutItemParser { func parseShortcutItems(_ items: [[String: Any]]) -> [UIApplicationShortcutItem] { return items.compactMap { deserializeShortcutItem(with: $0) } } private func deserializeShortcutItem(with serialized: [String: Any]) -> UIApplicationShortcutItem? { guard let type = serialized["type"] as? String, let localizedTitle = serialized["localizedTitle"] as? String else { return nil } let icon = (serialized["icon"] as? String).map { UIApplicationShortcutIcon(templateImageName: $0) } // type and localizedTitle are required. return UIApplicationShortcutItem( type: type, localizedTitle: localizedTitle, localizedSubtitle: nil, icon: icon, userInfo: nil) } } ================================================ FILE: packages/quick_actions/quick_actions_ios/ios/Classes/ShortcutItemProviding.swift ================================================ // Copyright 2013 The Flutter 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 /// Provides the capability to get and set the app's home screen shortcut items. protocol ShortcutItemProviding: AnyObject { /// An array of shortcut items for home screen. var shortcutItems: [UIApplicationShortcutItem]? { get set } } /// A default implementation of the `ShortcutItemProviding` protocol. extension UIApplication: ShortcutItemProviding {} ================================================ FILE: packages/quick_actions/quick_actions_ios/ios/quick_actions_ios.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| s.name = 'quick_actions_ios' s.version = '0.0.1' s.summary = 'Flutter Quick Actions' s.description = <<-DESC This Flutter plugin allows you to manage and interact with the application's home screen quick actions. 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/quick_actions' } s.documentation_url = 'https://pub.dev/packages/quick_actions' s.swift_version = '5.0' s.source_files = 'Classes/**/*.swift' s.xcconfig = { 'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift', 'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift', } s.dependency 'Flutter' s.platform = :ios, '9.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } end ================================================ FILE: packages/quick_actions/quick_actions_ios/lib/quick_actions_ios.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:quick_actions_platform_interface/quick_actions_platform_interface.dart'; export 'package:quick_actions_platform_interface/types/types.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/quick_actions_ios'); /// An implementation of [QuickActionsPlatform] for iOS. class QuickActionsIos extends QuickActionsPlatform { /// Registers this class as the default instance of [QuickActionsPlatform]. static void registerWith() { QuickActionsPlatform.instance = QuickActionsIos(); } /// The MethodChannel that is being used by this implementation of the plugin. @visibleForTesting MethodChannel get channel => _channel; @override Future initialize(QuickActionHandler handler) async { channel.setMethodCallHandler((MethodCall call) async { assert(call.method == 'launch'); handler(call.arguments as String); }); final String? action = await channel.invokeMethod('getLaunchAction'); if (action != null) { handler(action); } } @override Future setShortcutItems(List items) async { final List> itemsList = items.map(_serializeItem).toList(); await channel.invokeMethod('setShortcutItems', itemsList); } @override Future clearShortcutItems() => channel.invokeMethod('clearShortcutItems'); Map _serializeItem(ShortcutItem item) { return { 'type': item.type, 'localizedTitle': item.localizedTitle, 'icon': item.icon, }; } } ================================================ FILE: packages/quick_actions/quick_actions_ios/pubspec.yaml ================================================ name: quick_actions_ios description: An implementation for the iOS platform of the Flutter `quick_actions` plugin. repository: https://github.com/flutter/plugins/tree/main/packages/quick_actions/quick_actions_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 version: 1.0.2 environment: sdk: ">=2.15.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: quick_actions platforms: ios: pluginClass: QuickActionsPlugin dartPluginClass: QuickActionsIos dependencies: flutter: sdk: flutter quick_actions_platform_interface: ^1.0.0 dev_dependencies: flutter_test: sdk: flutter integration_test: sdk: flutter plugin_platform_interface: ^2.1.2 ================================================ FILE: packages/quick_actions/quick_actions_ios/test/quick_actions_ios_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:quick_actions_ios/quick_actions_ios.dart'; import 'package:quick_actions_platform_interface/quick_actions_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$QuickActionsIos', () { late List log; setUp(() { log = []; }); QuickActionsIos buildQuickActionsPlugin() { final QuickActionsIos quickActions = QuickActionsIos(); _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(quickActions.channel, (MethodCall methodCall) async { log.add(methodCall); return ''; }); return quickActions; } test('registerWith() registers correct instance', () { QuickActionsIos.registerWith(); expect(QuickActionsPlatform.instance, isA()); }); group('#initialize', () { test('passes getLaunchAction on launch method', () { final QuickActionsIos quickActions = buildQuickActionsPlugin(); quickActions.initialize((String type) {}); expect( log, [ isMethodCall('getLaunchAction', arguments: null), ], ); }); test('initialize', () async { final QuickActionsIos quickActions = buildQuickActionsPlugin(); final Completer quickActionsHandler = Completer(); await quickActions .initialize((_) => quickActionsHandler.complete(true)); expect( log, [ isMethodCall('getLaunchAction', arguments: null), ], ); log.clear(); expect(quickActionsHandler.future, completion(isTrue)); }); }); group('#setShortCutItems', () { test('passes shortcutItem through channel', () { final QuickActionsIos quickActions = buildQuickActionsPlugin(); quickActions.initialize((String type) {}); quickActions.setShortcutItems([ const ShortcutItem( type: 'test', localizedTitle: 'title', icon: 'icon.svg') ]); expect( log, [ isMethodCall('getLaunchAction', arguments: null), isMethodCall('setShortcutItems', arguments: >[ { 'type': 'test', 'localizedTitle': 'title', 'icon': 'icon.svg', } ]), ], ); }); test('setShortcutItems with demo data', () async { const String type = 'type'; const String localizedTitle = 'localizedTitle'; const String icon = 'icon'; final QuickActionsIos quickActions = buildQuickActionsPlugin(); await quickActions.setShortcutItems( const [ ShortcutItem(type: type, localizedTitle: localizedTitle, icon: icon) ], ); expect( log, [ isMethodCall( 'setShortcutItems', arguments: >[ { 'type': type, 'localizedTitle': localizedTitle, 'icon': icon, } ], ), ], ); log.clear(); }); }); group('#clearShortCutItems', () { test('send clearShortcutItems through channel', () { final QuickActionsIos quickActions = buildQuickActionsPlugin(); quickActions.initialize((String type) {}); quickActions.clearShortcutItems(); expect( log, [ isMethodCall('getLaunchAction', arguments: null), isMethodCall('clearShortcutItems', arguments: null), ], ); }); test('clearShortcutItems', () { final QuickActionsIos quickActions = buildQuickActionsPlugin(); quickActions.clearShortcutItems(); expect( log, [ isMethodCall('clearShortcutItems', arguments: null), ], ); log.clear(); }); }); }); group('$ShortcutItem', () { test('Shortcut item can be constructed', () { const String type = 'type'; const String localizedTitle = 'title'; const String icon = 'foo'; const ShortcutItem item = ShortcutItem(type: type, localizedTitle: localizedTitle, icon: icon); expect(item.type, type); expect(item.localizedTitle, localizedTitle); expect(item.icon, icon); }); }); } /// 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; ================================================ FILE: packages/quick_actions/quick_actions_platform_interface/AUTHORS ================================================ # 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> Daniel Roek ================================================ FILE: packages/quick_actions/quick_actions_platform_interface/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 1.0.3 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. ## 1.0.2 * Removes dependency on `meta`. ## 1.0.1 * Updates code for analyzer changes. * Update to use the `verify` method introduced in plugin_platform_interface 2.1.0. ## 1.0.0 * Initial release of quick_actions_platform_interface ================================================ FILE: packages/quick_actions/quick_actions_platform_interface/LICENSE ================================================ 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. ================================================ FILE: packages/quick_actions/quick_actions_platform_interface/README.md ================================================ # quick_actions_platform_interface A common platform interface for the [`quick_actions`][1] plugin. This interface allows platform-specific implementations of the `quick_actions` plugin, as well as the plugin itself, to ensure they are supporting the same interface. # Usage To implement a new platform-specific implementation of `quick_actions`, extend [`QuickActionsPlatform`][2] with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `QuickActionsPlatform` by calling `QuickActionsPlatform.instance = MyPlatformQuickActions()`. # Note on breaking changes Strongly prefer non-breaking changes (such as adding a method to the interface) over breaking changes for this package. See https://flutter.dev/go/platform-interface-breaking-changes for a discussion on why a less-clean interface is preferable to a breaking change. [1]: ../quick_actions [2]: lib/quick_actions_platform_interface.dart ================================================ FILE: packages/quick_actions/quick_actions_platform_interface/lib/method_channel/method_channel_quick_actions.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import '../platform_interface/quick_actions_platform.dart'; import '../types/types.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/quick_actions'); /// An implementation of [QuickActionsPlatform] that uses method channels. class MethodChannelQuickActions extends QuickActionsPlatform { /// The MethodChannel that is being used by this implementation of the plugin. @visibleForTesting MethodChannel get channel => _channel; @override Future initialize(QuickActionHandler handler) async { channel.setMethodCallHandler((MethodCall call) async { assert(call.method == 'launch'); handler(call.arguments as String); }); final String? action = await channel.invokeMethod('getLaunchAction'); if (action != null) { handler(action); } } @override Future setShortcutItems(List items) async { final List> itemsList = items.map(_serializeItem).toList(); await channel.invokeMethod('setShortcutItems', itemsList); } @override Future clearShortcutItems() => channel.invokeMethod('clearShortcutItems'); Map _serializeItem(ShortcutItem item) { return { 'type': item.type, 'localizedTitle': item.localizedTitle, 'icon': item.icon, }; } } ================================================ FILE: packages/quick_actions/quick_actions_platform_interface/lib/platform_interface/quick_actions_platform.dart ================================================ // Copyright 2013 The Flutter 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:plugin_platform_interface/plugin_platform_interface.dart'; import '../method_channel/method_channel_quick_actions.dart'; import '../types/types.dart'; /// The interface that implementations of quick_actions must implement. /// /// Platform implementations should extend this class rather than implement it as `quick_actions` /// does not consider newly added methods to be breaking changes. Extending this class /// (using `extends`) ensures that the subclass will get the default implementation, while /// platform implementations that `implements` this interface will be broken by newly added /// [QuickActionsPlatform] methods. abstract class QuickActionsPlatform extends PlatformInterface { /// Constructs a QuickActionsPlatform. QuickActionsPlatform() : super(token: _token); static final Object _token = Object(); static QuickActionsPlatform _instance = MethodChannelQuickActions(); /// The default instance of [QuickActionsPlatform] to use. /// /// Defaults to [MethodChannelQuickActions]. static QuickActionsPlatform get instance => _instance; /// Platform-specific plugins should set this with their own platform-specific /// class that extends [QuickActionsPlatform] when they register themselves. // TODO(amirh): Extract common platform interface logic. // https://github.com/flutter/flutter/issues/43368 static set instance(QuickActionsPlatform instance) { PlatformInterface.verify(instance, _token); _instance = instance; } /// Initializes this plugin. /// /// Call this once before any further interaction with the plugin. Future initialize(QuickActionHandler handler) async { throw UnimplementedError('initialize() has not been implemented.'); } /// Sets the [ShortcutItem]s to become the app's quick actions. Future setShortcutItems(List items) async { throw UnimplementedError('setShortcutItems() has not been implemented.'); } /// Removes all [ShortcutItem]s registered for the app. Future clearShortcutItems() { throw UnimplementedError('clearShortcutItems() has not been implemented.'); } } ================================================ FILE: packages/quick_actions/quick_actions_platform_interface/lib/quick_actions_platform_interface.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'package:quick_actions_platform_interface/platform_interface/quick_actions_platform.dart'; export 'package:quick_actions_platform_interface/types/types.dart'; ================================================ FILE: packages/quick_actions/quick_actions_platform_interface/lib/types/quick_action_handler.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Handler for a quick action launch event. /// /// The argument [type] corresponds to the [ShortcutItem]'s field. typedef QuickActionHandler = void Function(String type); ================================================ FILE: packages/quick_actions/quick_actions_platform_interface/lib/types/shortcut_item.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Home screen quick-action shortcut item. class ShortcutItem { /// Constructs an instance with the given [type], [localizedTitle], and /// [icon]. /// /// Only [icon] should be nullable. It will remain `null` if unset. const ShortcutItem({ required this.type, required this.localizedTitle, this.icon, }); /// The identifier of this item; should be unique within the app. final String type; /// Localized title of the item. final String localizedTitle; /// Name of native resource (xcassets etc; NOT a Flutter asset) to be /// displayed as the icon for this item. final String? icon; } ================================================ FILE: packages/quick_actions/quick_actions_platform_interface/lib/types/types.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'quick_action_handler.dart'; export 'shortcut_item.dart'; ================================================ FILE: packages/quick_actions/quick_actions_platform_interface/pubspec.yaml ================================================ name: quick_actions_platform_interface description: A common platform interface for the quick_actions plugin. repository: https://github.com/flutter/plugins/tree/main/packages/quick_actions/quick_actions_platform_interface issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+quick_actions%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: 1.0.3 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.1.0 dev_dependencies: flutter_test: sdk: flutter mockito: ^5.0.1 ================================================ FILE: packages/quick_actions/quick_actions_platform_interface/test/method_channel_quick_actions_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:quick_actions_platform_interface/method_channel/method_channel_quick_actions.dart'; import 'package:quick_actions_platform_interface/types/shortcut_item.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$MethodChannelQuickActions', () { final MethodChannelQuickActions quickActions = MethodChannelQuickActions(); final List log = []; setUp(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(quickActions.channel, (MethodCall methodCall) async { log.add(methodCall); return ''; }); log.clear(); }); group('#initialize', () { test('passes getLaunchAction on launch method', () { quickActions.initialize((String type) {}); expect( log, [ isMethodCall('getLaunchAction', arguments: null), ], ); }); test('initialize', () async { final Completer quickActionsHandler = Completer(); await quickActions .initialize((_) => quickActionsHandler.complete(true)); expect( log, [ isMethodCall('getLaunchAction', arguments: null), ], ); log.clear(); expect(quickActionsHandler.future, completion(isTrue)); }); }); group('#setShortCutItems', () { test('passes shortcutItem through channel', () { quickActions.initialize((String type) {}); quickActions.setShortcutItems([ const ShortcutItem( type: 'test', localizedTitle: 'title', icon: 'icon.svg') ]); expect( log, [ isMethodCall('getLaunchAction', arguments: null), isMethodCall('setShortcutItems', arguments: >[ { 'type': 'test', 'localizedTitle': 'title', 'icon': 'icon.svg', } ]), ], ); }); test('setShortcutItems with demo data', () async { const String type = 'type'; const String localizedTitle = 'localizedTitle'; const String icon = 'icon'; await quickActions.setShortcutItems( const [ ShortcutItem(type: type, localizedTitle: localizedTitle, icon: icon) ], ); expect( log, [ isMethodCall( 'setShortcutItems', arguments: >[ { 'type': type, 'localizedTitle': localizedTitle, 'icon': icon, } ], ), ], ); log.clear(); }); }); group('#clearShortCutItems', () { test('send clearShortcutItems through channel', () { quickActions.initialize((String type) {}); quickActions.clearShortcutItems(); expect( log, [ isMethodCall('getLaunchAction', arguments: null), isMethodCall('clearShortcutItems', arguments: null), ], ); }); test('clearShortcutItems', () { quickActions.clearShortcutItems(); expect( log, [ isMethodCall('clearShortcutItems', arguments: null), ], ); log.clear(); }); }); }); group('$ShortcutItem', () { test('Shortcut item can be constructed', () { const String type = 'type'; const String localizedTitle = 'title'; const String icon = 'foo'; const ShortcutItem item = ShortcutItem(type: type, localizedTitle: localizedTitle, icon: icon); expect(item.type, type); expect(item.localizedTitle, localizedTitle); expect(item.icon, icon); }); }); } /// 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; ================================================ FILE: packages/quick_actions/quick_actions_platform_interface/test/quick_actions_platform_interface_test.dart ================================================ // Copyright 2013 The Flutter 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:quick_actions_platform_interface/method_channel/method_channel_quick_actions.dart'; import 'package:quick_actions_platform_interface/platform_interface/quick_actions_platform.dart'; import 'package:quick_actions_platform_interface/types/types.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); // Store the initial instance before any tests change it. final QuickActionsPlatform initialInstance = QuickActionsPlatform.instance; group('$QuickActionsPlatform', () { test('$MethodChannelQuickActions is the default instance', () { expect(initialInstance, isA()); }); test('Cannot be implemented with `implements`', () { expect(() { QuickActionsPlatform.instance = ImplementsQuickActionsPlatform(); // In versions of `package:plugin_platform_interface` prior to fixing // https://github.com/flutter/flutter/issues/109339, an attempt to // implement a platform interface using `implements` would sometimes // throw a `NoSuchMethodError` and other times throw an // `AssertionError`. After the issue is fixed, an `AssertionError` will // always be thrown. For the purpose of this test, we don't really care // what exception is thrown, so just allow any exception. }, throwsA(anything)); }); test('Can be extended', () { QuickActionsPlatform.instance = ExtendsQuickActionsPlatform(); }); test( 'Default implementation of initialize() should throw unimplemented error', () { // Arrange final ExtendsQuickActionsPlatform quickActionsPlatform = ExtendsQuickActionsPlatform(); // Act & Assert expect( () => quickActionsPlatform.initialize((String type) {}), throwsUnimplementedError, ); }); test( 'Default implementation of setShortcutItems() should throw unimplemented error', () { // Arrange final ExtendsQuickActionsPlatform quickActionsPlatform = ExtendsQuickActionsPlatform(); // Act & Assert expect( () => quickActionsPlatform.setShortcutItems([]), throwsUnimplementedError, ); }); test( 'Default implementation of clearShortcutItems() should throw unimplemented error', () { // Arrange final ExtendsQuickActionsPlatform quickActionsPlatform = ExtendsQuickActionsPlatform(); // Act & Assert expect( () => quickActionsPlatform.clearShortcutItems(), throwsUnimplementedError, ); }); }); } class ImplementsQuickActionsPlatform implements QuickActionsPlatform { @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } class ExtendsQuickActionsPlatform extends QuickActionsPlatform {} ================================================ FILE: packages/shared_preferences/shared_preferences/AUTHORS ================================================ # 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> ================================================ FILE: packages/shared_preferences/shared_preferences/CHANGELOG.md ================================================ ## 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. ## 2.0.15 * Minor fixes for new analysis options. ## 2.0.14 * Adds OS version support information to README. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 2.0.13 * Updates documentation on README.md. ## 2.0.12 * Removes dependency on `meta`. ## 2.0.11 * Corrects example for mocking in readme. ## 2.0.10 * Removes obsolete manual registration of Windows and Linux implementations. ## 2.0.9 * Fixes newly enabled analyzer options. * Updates example app Android compileSdkVersion to 31. * Moved Android and iOS implementations to federated packages. ## 2.0.8 * Update minimum Flutter SDK to 2.5 and iOS deployment target to 9.0. ## 2.0.7 * Add iOS unit test target. * Updated Android lint settings. * Fix string clash with double entries on Android ## 2.0.6 * Migrate maven repository from jcenter to mavenCentral. ## 2.0.5 * Fix missing declaration of windows' default_package ## 2.0.4 * Fix a regression with simultaneous writes on Android. ## 2.0.3 * Android: don't create additional Handler when method channel is called. ## 2.0.2 * Don't create additional thread pools when method channel is called. ## 2.0.1 * Removed deprecated [AsyncTask](https://developer.android.com/reference/android/os/AsyncTask) was deprecated in API level 30 ([#3481](https://github.com/flutter/plugins/pull/3481)) ## 2.0.0 * Migrate to null-safety. **Breaking changes**: * Setters no longer accept null to mean removing values. If you were previously using `set*(key, null)` for removing, use `remove(key)` instead. ## 0.5.13+2 * Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) ## 0.5.13+1 * Update Flutter SDK constraint. ## 0.5.13 * Update integration test examples to use `testWidgets` instead of `test`. ## 0.5.12+4 * Remove unused `test` dependency. ## 0.5.12+3 * Check in windows/ directory for example/ ## 0.5.12+2 * Update android compileSdkVersion to 29. ## 0.5.12+1 * Check in linux/ directory for example/ ## 0.5.12 * Keep handling deprecated Android v1 classes for backward compatibility. ## 0.5.11 * Support Windows by default. ## 0.5.10 * Update package:e2e -> package:integration_test ## 0.5.9 * Update package:e2e reference to use the local version in the flutter/plugins repository. ## 0.5.8 * Support Linux by default. ## 0.5.7+3 * Post-v2 Android embedding cleanup. ## 0.5.7+2 * Update lower bound of dart dependency to 2.1.0. ## 0.5.7+1 * 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.5.7 * Remove Android dependencies fallback. * Require Flutter SDK 1.12.13+hotfix.5 or greater. * Fix CocoaPods podspec lint warnings. ## 0.5.6+3 * Fix deprecated API usage warning. ## 0.5.6+2 * Make the pedantic dev_dependency explicit. ## 0.5.6+1 * Updated README ## 0.5.6 * Support `web` by default. * Require Flutter SDK 1.12.13+hotfix.4 or greater. ## 0.5.5 * Support macos by default. ## 0.5.4+10 * Adds a `shared_preferences_macos` package. ## 0.5.4+9 * Remove the deprecated `author:` field from pubspec.yaml * Migrate the plugin to the pubspec platforms manifest. * Require Flutter SDK 1.10.0 or greater. ## 0.5.4+8 * Switch `package:shared_preferences` to `package:shared_preferences_platform_interface`. No code changes are necessary in Flutter apps. This is not a breaking change. ## 0.5.4+7 * Restructure the project for Web support. ## 0.5.4+6 * Add missing documentation and a lint to prevent further undocumented APIs. ## 0.5.4+5 * Update and migrate iOS example project by removing flutter_assets, change "English" to "en", remove extraneous xcconfigs and framework outputs, update to Xcode 11 build settings, and remove ARCHS. ## 0.5.4+4 * `setMockInitialValues` needs to handle non-prefixed keys since that's an implementation detail. ## 0.5.4+3 * Android: Suppress casting warnings. ## 0.5.4+2 * Remove AndroidX warnings. ## 0.5.4+1 * Include lifecycle dependency as a compileOnly one on Android to resolve potential version conflicts with other transitive libraries. ## 0.5.4 * Support the v2 Android embedding. * Update to AndroidX. * Migrate to using the new e2e test binding. ## 0.5.3+5 * Define clang module for iOS. ## 0.5.3+4 * Copy `List` instances when reading and writing values to prevent mutations from propagating. ## 0.5.3+3 * `setMockInitialValues` can now be called multiple times and will `reload()` the singleton if necessary. ## 0.5.3+2 * Fix Gradle version. ## 0.5.3+1 * Add missing template type parameter to `invokeMethod` calls. * Bump minimum Flutter version to 1.5.0. * Replace invokeMethod with invokeMapMethod wherever necessary. ## 0.5.3 * Add reload method. ## 0.5.2+2 * Updated Gradle tooling to match Android Studio 3.4. ## 0.5.2+1 * .commit() calls are now run in an async background task on Android. ## 0.5.2 * Add containsKey method. ## 0.5.1+2 * Add a driver test ## 0.5.1+1 * Log a more detailed warning at build time about the previous AndroidX migration. ## 0.5.1 * Use String to save double in Android. ## 0.5.0 * **Breaking change**. Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to [also migrate](https://developer.android.com/jetpack/androidx/migrate) if they're using the original support library. ## 0.4.3 * Prevent strings that match special prefixes from being saved. This is a bugfix that prevents apps from accidentally setting special values that would be interpreted incorrectly. ## 0.4.2 * Updated Gradle tooling to match Android Studio 3.1.2. ## 0.4.1 * Added getKeys method. ## 0.4.0 * **Breaking change**. Set SDK constraints to match the Flutter beta release. ## 0.3.3 * Fixed Dart 2 issues. ## 0.3.2 * Added an getter that can retrieve values of any type ## 0.3.1 * Simplified and upgraded Android project template to Android SDK 27. * Updated package description. ## 0.3.0 * **Breaking change**. Upgraded to Gradle 4.1 and Android Studio Gradle plugin 3.0.1. Older Flutter projects need to upgrade their Gradle setup as well in order to use this version of the plugin. Instructions can be found [here](https://github.com/flutter/flutter/wiki/Updating-Flutter-projects-to-Gradle-4.1-and-Android-Studio-Gradle-plugin-3.0.1). ## 0.2.6 * Added FLT prefix to iOS types ## 0.2.5+1 * Aligned author name with rest of repo. ## 0.2.5 * Fixed crashes when setting null values. They now cause the key to be removed. * Added remove() method ## 0.2.4+1 * Fixed typo in changelog ## 0.2.4 * Added setMockInitialValues * Added a test * Updated README ## 0.2.3 * Suppress warning about unchecked operations when compiling for Android ## 0.2.2 * BREAKING CHANGE: setStringSet API changed to setStringList and plugin now supports ordered storage. ## 0.2.1 * Support arbitrary length integers for setInt. ## 0.2.0+1 * Updated README ## 0.2.0 * Upgrade to new plugin registration. (https://groups.google.com/forum/#!topic/flutter-dev/zba1Ynf2OKM) ## 0.1.1 * Upgrade Android SDK Build Tools to 25.0.3. ## 0.1.0 * Initial Open Source release. ================================================ FILE: packages/shared_preferences/shared_preferences/LICENSE ================================================ 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. ================================================ FILE: packages/shared_preferences/shared_preferences/README.md ================================================ # Shared preferences plugin [![pub package](https://img.shields.io/pub/v/shared_preferences.svg)](https://pub.dev/packages/shared_preferences) Wraps platform-specific persistent storage for simple data (NSUserDefaults on iOS and macOS, SharedPreferences on Android, etc.). Data may be persisted to disk asynchronously, and there is no guarantee that writes will be persisted to disk after returning, so this plugin must not be used for storing critical data. Supported data types are `int`, `double`, `bool`, `String` and `List`. | | Android | iOS | Linux | macOS | Web | Windows | |-------------|---------|------|-------|--------|-----|-------------| | **Support** | SDK 16+ | 9.0+ | Any | 10.11+ | Any | Any | ## Usage To use this plugin, add `shared_preferences` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels). ### Examples Here are small examples that show you how to use the API. #### Write data ```dart // Obtain shared preferences. final prefs = await SharedPreferences.getInstance(); // Save an integer value to 'counter' key. await prefs.setInt('counter', 10); // Save an boolean value to 'repeat' key. await prefs.setBool('repeat', true); // Save an double value to 'decimal' key. await prefs.setDouble('decimal', 1.5); // Save an String value to 'action' key. await prefs.setString('action', 'Start'); // Save an list of strings to 'items' key. await prefs.setStringList('items', ['Earth', 'Moon', 'Sun']); ``` #### Read data ```dart // Try reading data from the 'counter' key. If it doesn't exist, returns null. final int? counter = prefs.getInt('counter'); // Try reading data from the 'repeat' key. If it doesn't exist, returns null. final bool? repeat = prefs.getBool('repeat'); // Try reading data from the 'decimal' key. If it doesn't exist, returns null. final double? decimal = prefs.getDouble('decimal'); // Try reading data from the 'action' key. If it doesn't exist, returns null. final String? action = prefs.getString('action'); // Try reading data from the 'items' key. If it doesn't exist, returns null. final List? items = prefs.getStringList('items'); ``` #### Remove an entry ```dart // Remove data for the 'counter' key. final success = await prefs.remove('counter'); ``` ### Testing You can populate `SharedPreferences` with initial values in your tests by running this code: ```dart Map values = {'counter': 1}; SharedPreferences.setMockInitialValues(values); ``` ### Storage location by platform | Platform | Location | | :--- | :--- | | Android | SharedPreferences | | iOS | NSUserDefaults | | Linux | In the XDG_DATA_HOME directory | | macOS | NSUserDefaults | | Web | LocalStorage | | Windows | In the roaming AppData directory | ================================================ FILE: packages/shared_preferences/shared_preferences/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Android Studio will place build artifacts here /android/app/debug /android/app/profile /android/app/release ================================================ FILE: packages/shared_preferences/shared_preferences/example/.metadata ================================================ # 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 ================================================ FILE: packages/shared_preferences/shared_preferences/example/README.md ================================================ # shared_preferences_example Demonstrates how to use the shared_preferences plugin. ================================================ FILE: packages/shared_preferences/shared_preferences/example/android/.gitignore ================================================ gradle-wrapper.jar /.gradle /captures/ /gradlew /gradlew.bat /local.properties GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app key.properties ================================================ FILE: packages/shared_preferences/shared_preferences/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 sourceSets { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { applicationId "io.flutter.plugins.sharedpreferencesexample" minSdkVersion 16 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } ================================================ FILE: packages/shared_preferences/shared_preferences/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/shared_preferences/shared_preferences/example/android/app/src/androidTest/java/io/flutter/plugins/sharedpreferencesexample/FlutterActivityTest.java ================================================ // Copyright 2013 The Flutter 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.sharedpreferencesexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class FlutterActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/shared_preferences/shared_preferences/example/android/app/src/androidTest/java/io/flutter/plugins/sharedpreferencesexample/MainActivityTest.java ================================================ // Copyright 2013 The Flutter 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.sharedpreferencesexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class MainActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/shared_preferences/shared_preferences/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences/example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences/example/android/app/src/main/res/drawable-v21/launch_background.xml ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences/example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences/example/android/app/src/main/res/values-night/styles.xml ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences/example/android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences/example/android/build.gradle ================================================ buildscript { ext.kotlin_version = '1.3.50' repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/shared_preferences/shared_preferences/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/shared_preferences/shared_preferences/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true ================================================ FILE: packages/shared_preferences/shared_preferences/example/android/settings.gradle ================================================ include ':app' def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() assert localPropertiesFile.exists() localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" ================================================ FILE: packages/shared_preferences/shared_preferences/example/integration_test/shared_preferences_test.dart ================================================ // Copyright 2013 The Flutter 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/shared_preferences.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('$SharedPreferences', () { const String testString = 'hello world'; const bool testBool = true; const int testInt = 42; const double testDouble = 3.14159; const List testList = ['foo', 'bar']; const String testString2 = 'goodbye world'; const bool testBool2 = false; const int testInt2 = 1337; const double testDouble2 = 2.71828; const List testList2 = ['baz', 'quox']; late SharedPreferences preferences; setUp(() async { preferences = await SharedPreferences.getInstance(); }); tearDown(() { preferences.clear(); }); testWidgets('reading', (WidgetTester _) async { expect(preferences.get('String'), isNull); expect(preferences.get('bool'), isNull); expect(preferences.get('int'), isNull); expect(preferences.get('double'), isNull); expect(preferences.get('List'), isNull); expect(preferences.getString('String'), isNull); expect(preferences.getBool('bool'), isNull); expect(preferences.getInt('int'), isNull); expect(preferences.getDouble('double'), isNull); expect(preferences.getStringList('List'), isNull); }); testWidgets('writing', (WidgetTester _) async { await Future.wait(>[ preferences.setString('String', testString2), preferences.setBool('bool', testBool2), preferences.setInt('int', testInt2), preferences.setDouble('double', testDouble2), preferences.setStringList('List', testList2) ]); expect(preferences.getString('String'), testString2); expect(preferences.getBool('bool'), testBool2); expect(preferences.getInt('int'), testInt2); expect(preferences.getDouble('double'), testDouble2); expect(preferences.getStringList('List'), testList2); }); testWidgets('removing', (WidgetTester _) async { const String key = 'testKey'; await preferences.setString(key, testString); await preferences.setBool(key, testBool); await preferences.setInt(key, testInt); await preferences.setDouble(key, testDouble); await preferences.setStringList(key, testList); await preferences.remove(key); expect(preferences.get('testKey'), isNull); }); testWidgets('clearing', (WidgetTester _) async { await preferences.setString('String', testString); await preferences.setBool('bool', testBool); await preferences.setInt('int', testInt); await preferences.setDouble('double', testDouble); await preferences.setStringList('List', testList); await preferences.clear(); expect(preferences.getString('String'), null); expect(preferences.getBool('bool'), null); expect(preferences.getInt('int'), null); expect(preferences.getDouble('double'), null); expect(preferences.getStringList('List'), null); }); testWidgets('simultaneous writes', (WidgetTester _) async { final List> writes = >[]; const int writeCount = 100; for (int i = 1; i <= writeCount; i++) { writes.add(preferences.setInt('int', i)); } final List result = await Future.wait(writes, eagerError: true); // All writes should succeed. expect(result.where((bool element) => !element), isEmpty); // The last write should win. expect(preferences.getInt('int'), writeCount); }); }); } ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/.gitignore ================================================ *.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/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 ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 UIRequiredDeviceCapabilities arm64 MinimumOSVersion 9.0 ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Flutter/Debug.xcconfig ================================================ #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" ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Flutter/Release.xcconfig ================================================ #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" ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner/AppDelegate.swift ================================================ // Copyright 2013 The Flutter 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) } } ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "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" } } ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName shared_preferences_example CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner/Runner-Bridging-Header.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "GeneratedPluginRegistrant.h" ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 2D92224B1EC342E7007564B0 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D92224A1EC342E7007564B0 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 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 */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 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 = ""; }; 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; }; 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 = ""; }; 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 66F8BCECCEFF62F4071D2DFC /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 840012C8B5EDBCF56B0E4AC1 /* Pods */ = { isa = PBXGroup; children = ( 942E815CEF30E101E045B849 /* Pods-Runner.debug.xcconfig */, 081A3238A89B77A99B096D83 /* Pods-Runner.release.xcconfig */, A2FC4F1DC78D7C01312F877F /* Pods-RunnerTests.debug.xcconfig */, D896CE48B6CC2EB7D42CB6B6 /* Pods-RunnerTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 840012C8B5EDBCF56B0E4AC1 /* Pods */, CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; }; 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 */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = { isa = PBXGroup; children = ( 12E03CD14DABAA3AD3923183 /* libPods-Runner.a */, 556D8EFC85341B7D1FDF536D /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1100; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; 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"; }; AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 2D92224B1EC342E7007564B0 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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 = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.sharedPreferencesExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.sharedPreferencesExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/shared_preferences/shared_preferences/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: packages/shared_preferences/shared_preferences/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.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 Future _prefs = SharedPreferences.getInstance(); late Future _counter; Future _incrementCounter() async { final SharedPreferences prefs = await _prefs; final int counter = (prefs.getInt('counter') ?? 0) + 1; setState(() { _counter = prefs.setInt('counter', counter).then((bool success) { return counter; }); }); } @override void initState() { super.initState(); _counter = _prefs.then((SharedPreferences prefs) { return prefs.getInt('counter') ?? 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.none: case ConnectionState.waiting: return const CircularProgressIndicator(); case ConnectionState.active: case ConnectionState.done: 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), ), ); } } ================================================ FILE: packages/shared_preferences/shared_preferences/example/linux/.gitignore ================================================ flutter/ephemeral ================================================ FILE: packages/shared_preferences/shared_preferences/example/linux/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(runner LANGUAGES CXX) set(BINARY_NAME "example") set(APPLICATION_ID "dev.flutter.plugins.shared_preferences_example") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_14) target_compile_options(${TARGET} PRIVATE -Wall -Werror) target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") # Application build add_executable(${BINARY_NAME} "main.cc" "my_application.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" ) apply_standard_settings(${BINARY_NAME}) target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) add_dependencies(${BINARY_NAME} flutter_assemble) # Only the install-generated bundle's copy of the executable will launch # correctly, since the resources must in the right relative locations. To avoid # people trying to run the unbundled copy, put it in a subdirectory instead of # the default top-level location. set_target_properties(${BINARY_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" ) # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # By default, "installing" just makes a relocatable bundle in the build # directory. set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() # Start with a clean build bundle directory every time. install(CODE " file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") " COMPONENT Runtime) set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() ================================================ FILE: packages/shared_preferences/shared_preferences/example/linux/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. # Serves the same purpose as list(TRANSFORM ... PREPEND ...), # which isn't available in 3.10. function(list_prepend LIST_NAME PREFIX) set(NEW_LIST "") foreach(element ${${LIST_NAME}}) list(APPEND NEW_LIST "${PREFIX}${element}") endforeach(element) set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) endfunction() # === Flutter Library === # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) pkg_check_modules(BLKID REQUIRED IMPORTED_TARGET blkid) set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "fl_basic_message_channel.h" "fl_binary_codec.h" "fl_binary_messenger.h" "fl_dart_project.h" "fl_engine.h" "fl_json_message_codec.h" "fl_json_method_codec.h" "fl_message_codec.h" "fl_method_call.h" "fl_method_channel.h" "fl_method_codec.h" "fl_method_response.h" "fl_plugin_registrar.h" "fl_plugin_registry.h" "fl_standard_message_codec.h" "fl_standard_method_codec.h" "fl_string_codec.h" "fl_value.h" "fl_view.h" "flutter_linux.h" ) list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") target_link_libraries(flutter INTERFACE PkgConfig::GTK PkgConfig::GLIB PkgConfig::GIO PkgConfig::BLKID ) add_dependencies(flutter flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/_phony_ COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" linux-x64 ${CMAKE_BUILD_TYPE} ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ) ================================================ FILE: packages/shared_preferences/shared_preferences/example/linux/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/shared_preferences/shared_preferences/example/linux/main.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "my_application.h" int main(int argc, char** argv) { // Only X11 is currently supported. // Wayland support is being developed: // https://github.com/flutter/flutter/issues/57932. gdk_set_allowed_backends("x11"); g_autoptr(MyApplication) app = my_application_new(); return g_application_run(G_APPLICATION(app), argc, argv); } ================================================ FILE: packages/shared_preferences/shared_preferences/example/linux/my_application.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "my_application.h" #include #include "flutter/generated_plugin_registrant.h" struct _MyApplication { GtkApplication parent_instance; }; G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // Implements GApplication::activate. static void my_application_activate(GApplication* application) { GtkWindow* window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); gtk_header_bar_set_title(header_bar, "example"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); gtk_window_set_default_size(window, 1280, 720); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); FlView* view = fl_view_new(project); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view)); gtk_widget_grab_focus(GTK_WIDGET(view)); } static void my_application_class_init(MyApplicationClass* klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; } static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { return MY_APPLICATION(g_object_new( my_application_get_type(), "application-id", APPLICATION_ID, nullptr)); } ================================================ FILE: packages/shared_preferences/shared_preferences/example/linux/my_application.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef FLUTTER_MY_APPLICATION_H_ #define FLUTTER_MY_APPLICATION_H_ #include G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, GtkApplication) /** * my_application_new: * * Creates a new Flutter-based application. * * Returns: a new #MyApplication. */ MyApplication* my_application_new(); #endif // FLUTTER_MY_APPLICATION_H_ ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/.gitignore ================================================ # Flutter-related **/Flutter/ephemeral/ **/Pods/ # Xcode-related **/xcuserdata/ ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Flutter/Flutter-Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Flutter/Flutter-Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Podfile ================================================ platform :osx, '10.11' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_macos_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) end end ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Runner/AppDelegate.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } } ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_64.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_1024.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Runner/Base.lproj/MainMenu.xib ================================================

================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Runner/Configs/AppInfo.xcconfig ================================================ // Application-level settings for the Runner target. // // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the // future. If not, the values below would default to using the project name when this becomes a // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. PRODUCT_NAME = example // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.example // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2021 The Flutter Authors. All rights reserved. ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Runner/Configs/Warnings.xcconfig ================================================ WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings GCC_WARN_UNDECLARED_SELECTOR = YES CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE CLANG_WARN__DUPLICATE_METHOD_MATCH = YES CLANG_WARN_PRAGMA_PACK = YES CLANG_WARN_STRICT_PROTOTYPES = YES CLANG_WARN_COMMA = YES GCC_WARN_STRICT_SELECTOR_MATCH = YES CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES GCC_WARN_SHADOW = YES CLANG_WARN_UNREACHABLE_CODE = YES ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Runner/DebugProfile.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Runner/MainFlutterWindow.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { let flutterViewController = FlutterViewController.init() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } } ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 51; objects = { /* Begin PBXAggregateTarget section */ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { isa = PBXAggregateTarget; buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; buildPhases = ( 33CC111E2044C6BF0003C045 /* ShellScript */, ); dependencies = ( ); name = "Flutter Assemble"; productName = FLX; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC111A2044C6BA0003C045; remoteInfo = FLX; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 33CC110E2044A8840003C045 /* Bundle Framework */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Bundle Framework"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( 33E5194F232828860026EE4D /* AppInfo.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, ); path = Configs; sourceTree = ""; }; 33CC10E42044A3C60003C045 = { isa = PBXGroup; children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* example.app */, ); name = Products; sourceTree = ""; }; 33CC11242044D66E0003C045 /* Resources */ = { isa = PBXGroup; children = ( 33CC10F22044A3C60003C045 /* Assets.xcassets */, 33CC10F42044A3C60003C045 /* MainMenu.xib */, 33CC10F72044A3C60003C045 /* Info.plist */, ); name = Resources; path = ..; sourceTree = ""; }; 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, ); path = Flutter; sourceTree = ""; }; 33FAB671232836740065AC1E /* Runner */ = { isa = PBXGroup; children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, 33CC11242044D66E0003C045 /* Resources */, 33BA886A226E78AF003329D5 /* Configs */, ); path = Runner; sourceTree = ""; }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, ); buildRules = ( ); dependencies = ( 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; LastUpgradeCheck = 0930; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; }; }; }; 33CC111A2044C6BA0003C045 = { CreatedOnToolsVersion = 9.2; ProvisioningStyle = Manual; }; }; }; buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 33CC10E42044A3C60003C045; productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( Flutter/ephemeral/FlutterInputs.xcfilelist, ); inputPaths = ( Flutter/ephemeral/tripwire, ); outputFileListPaths = ( Flutter/ephemeral/FlutterOutputs.xcfilelist, ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 33CC10F52044A3C60003C045 /* Base */, ); name = MainMenu.xib; path = Runner; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Profile; }; 338D0CEA231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Profile; }; 338D0CEB231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Profile; }; 33CC10F92044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 33CC10FA2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; 33CC10FC2044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; 33CC10FD2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Release; }; 33CC111C2044C6BA0003C045 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 33CC111D2044C6BA0003C045 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10F92044A3C60003C045 /* Debug */, 33CC10FA2044A3C60003C045 /* Release */, 338D0CE9231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10FC2044A3C60003C045 /* Debug */, 33CC10FD2044A3C60003C045 /* Release */, 338D0CEA231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC111C2044C6BA0003C045 /* Debug */, 33CC111D2044C6BA0003C045 /* Release */, 338D0CEB231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/shared_preferences/shared_preferences/example/pubspec.yaml ================================================ 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: ">=3.0.0" dependencies: flutter: sdk: flutter shared_preferences: # When depending on this package from a real application you should use: # shared_preferences: ^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: ../ dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/shared_preferences/shared_preferences/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/shared_preferences/shared_preferences/example/web/index.html ================================================ example ================================================ FILE: packages/shared_preferences/shared_preferences/example/web/manifest.json ================================================ { "name": "example", "short_name": "example", "start_url": ".", "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", "description": "A new Flutter project.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [ { "src": "icons/Icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" } ] } ================================================ FILE: packages/shared_preferences/shared_preferences/example/windows/.gitignore ================================================ flutter/ephemeral/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: packages/shared_preferences/shared_preferences/example/windows/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(example LANGUAGES CXX) set(BINARY_NAME "example") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" CACHE STRING "" FORCE) else() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() endif() set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") # Use Unicode for all projects. add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") target_compile_options(${TARGET} PRIVATE /EHsc) target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build add_subdirectory("runner") # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # Support files are copied into place next to the executable, so that it can # run in place. This is done instead of making a separate bundle (as on Linux) # so that building and running from within Visual Studio will work. set(BUILD_BUNDLE_DIR "$") # Make the "install" step default, as it's required to run. set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" CONFIGURATIONS Profile;Release COMPONENT Runtime) ================================================ FILE: packages/shared_preferences/shared_preferences/example/windows/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "flutter_export.h" "flutter_windows.h" "flutter_messenger.h" "flutter_plugin_registrar.h" ) list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") add_dependencies(flutter flutter_assemble) # === Wrapper === list(APPEND CPP_WRAPPER_SOURCES_CORE "core_implementations.cc" "standard_codec.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_PLUGIN "plugin_registrar.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_APP "flutter_engine.cc" "flutter_view_controller.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") # Wrapper sources needed for a plugin. add_library(flutter_wrapper_plugin STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ) apply_standard_settings(flutter_wrapper_plugin) set_target_properties(flutter_wrapper_plugin PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(flutter_wrapper_plugin PROPERTIES CXX_VISIBILITY_PRESET hidden) target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) target_include_directories(flutter_wrapper_plugin PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_plugin flutter_assemble) # Wrapper sources needed for the runner. add_library(flutter_wrapper_app STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_APP} ) apply_standard_settings(flutter_wrapper_app) target_link_libraries(flutter_wrapper_app PUBLIC flutter) target_include_directories(flutter_wrapper_app PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_app flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" windows-x64 $ ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ) ================================================ FILE: packages/shared_preferences/shared_preferences/example/windows/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/shared_preferences/shared_preferences/example/windows/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(runner LANGUAGES CXX) add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" "run_loop.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) apply_standard_settings(${BINARY_NAME}) target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") add_dependencies(${BINARY_NAME} flutter_assemble) ================================================ FILE: packages/shared_preferences/shared_preferences/example/windows/runner/Runner.rc ================================================ // Microsoft Visual C++ generated resource script. // #pragma code_page(65001) #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APP_ICON ICON "resources\\app_icon.ico" ///////////////////////////////////////////////////////////////////////////// // // Version // #ifdef FLUTTER_BUILD_NUMBER #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER #else #define VERSION_AS_NUMBER 1,0,0 #endif #ifdef FLUTTER_BUILD_NAME #define VERSION_AS_STRING #FLUTTER_BUILD_NAME #else #define VERSION_AS_STRING "1.0.0" #endif VS_VERSION_INFO VERSIONINFO FILEVERSION VERSION_AS_NUMBER PRODUCTVERSION VERSION_AS_NUMBER FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "Flutter Dev" "\0" VALUE "FileDescription", "A new Flutter project." "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "example" "\0" VALUE "LegalCopyright", "Copyright (C) 2020 The Flutter Authors. All rights reserved." "\0" VALUE "OriginalFilename", "example.exe" "\0" VALUE "ProductName", "example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: packages/shared_preferences/shared_preferences/example/windows/runner/flutter_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "flutter_window.h" #include #include "flutter/generated_plugin_registrant.h" FlutterWindow::FlutterWindow(RunLoop* run_loop, const flutter::DartProject& project) : run_loop_(run_loop), project_(project) {} FlutterWindow::~FlutterWindow() {} bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opporutunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) { return *result; } } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); } ================================================ FILE: packages/shared_preferences/shared_preferences/example/windows/runner/flutter_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_ #include #include #include #include "run_loop.h" #include "win32_window.h" // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { public: // Creates a new FlutterWindow driven by the |run_loop|, hosting a // Flutter view running |project|. explicit FlutterWindow(RunLoop* run_loop, const flutter::DartProject& project); virtual ~FlutterWindow(); protected: // Win32Window: bool OnCreate() override; void OnDestroy() override; LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; private: // The run loop driving events for this window. RunLoop* run_loop_; // The project to run. flutter::DartProject project_; // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; }; #endif // RUNNER_FLUTTER_WINDOW_H_ ================================================ FILE: packages/shared_preferences/shared_preferences/example/windows/runner/main.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include "flutter_window.h" #include "run_loop.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t* command_line, _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { CreateAndAttachConsole(); } // Initialize COM, so that it is available for use in the library and/or // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); RunLoop run_loop; flutter::DartProject project(L"data"); FlutterWindow window(&run_loop, project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.CreateAndShow(L"example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); run_loop.Run(); ::CoUninitialize(); return EXIT_SUCCESS; } ================================================ FILE: packages/shared_preferences/shared_preferences/example/windows/runner/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Runner.rc // #define IDI_APP_ICON 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: packages/shared_preferences/shared_preferences/example/windows/runner/run_loop.cpp ================================================ // Copyright 2013 The Flutter 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 "run_loop.h" #include #include RunLoop::RunLoop() {} RunLoop::~RunLoop() {} void RunLoop::Run() { bool keep_running = true; TimePoint next_flutter_event_time = TimePoint::clock::now(); while (keep_running) { std::chrono::nanoseconds wait_duration = std::max(std::chrono::nanoseconds(0), next_flutter_event_time - TimePoint::clock::now()); ::MsgWaitForMultipleObjects( 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000), QS_ALLINPUT); bool processed_events = false; MSG message; // All pending Windows messages must be processed; MsgWaitForMultipleObjects // won't return again for items left in the queue after PeekMessage. while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) { processed_events = true; if (message.message == WM_QUIT) { keep_running = false; break; } ::TranslateMessage(&message); ::DispatchMessage(&message); // Allow Flutter to process messages each time a Windows message is // processed, to prevent starvation. next_flutter_event_time = std::min(next_flutter_event_time, ProcessFlutterMessages()); } // If the PeekMessage loop didn't run, process Flutter messages. if (!processed_events) { next_flutter_event_time = std::min(next_flutter_event_time, ProcessFlutterMessages()); } } } void RunLoop::RegisterFlutterInstance( flutter::FlutterEngine* flutter_instance) { flutter_instances_.insert(flutter_instance); } void RunLoop::UnregisterFlutterInstance( flutter::FlutterEngine* flutter_instance) { flutter_instances_.erase(flutter_instance); } RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { TimePoint next_event_time = TimePoint::max(); for (auto instance : flutter_instances_) { std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); if (wait_duration != std::chrono::nanoseconds::max()) { next_event_time = std::min(next_event_time, TimePoint::clock::now() + wait_duration); } } return next_event_time; } ================================================ FILE: packages/shared_preferences/shared_preferences/example/windows/runner/run_loop.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_RUN_LOOP_H_ #define RUNNER_RUN_LOOP_H_ #include #include #include // A runloop that will service events for Flutter instances as well // as native messages. class RunLoop { public: RunLoop(); ~RunLoop(); // Prevent copying RunLoop(RunLoop const&) = delete; RunLoop& operator=(RunLoop const&) = delete; // Runs the run loop until the application quits. void Run(); // Registers the given Flutter instance for event servicing. void RegisterFlutterInstance(flutter::FlutterEngine* flutter_instance); // Unregisters the given Flutter instance from event servicing. void UnregisterFlutterInstance(flutter::FlutterEngine* flutter_instance); private: using TimePoint = std::chrono::steady_clock::time_point; // Processes all currently pending messages for registered Flutter instances. TimePoint ProcessFlutterMessages(); std::set flutter_instances_; }; #endif // RUNNER_RUN_LOOP_H_ ================================================ FILE: packages/shared_preferences/shared_preferences/example/windows/runner/runner.exe.manifest ================================================ PerMonitorV2 ================================================ FILE: packages/shared_preferences/shared_preferences/example/windows/runner/utils.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "utils.h" #include #include #include #include #include void CreateAndAttachConsole() { if (::AllocConsole()) { FILE* unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } if (freopen_s(&unused, "CONOUT$", "w", stderr)) { _dup2(_fileno(stdout), 2); } std::ios::sync_with_stdio(); FlutterDesktopResyncOutputStreams(); } } ================================================ FILE: packages/shared_preferences/shared_preferences/example/windows/runner/utils.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_UTILS_H_ #define RUNNER_UTILS_H_ // Creates a console for the process, and redirects stdout and stderr to // it for both the runner and the Flutter library. void CreateAndAttachConsole(); #endif // RUNNER_UTILS_H_ ================================================ FILE: packages/shared_preferences/shared_preferences/example/windows/runner/win32_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "win32_window.h" #include #include "resource.h" namespace { constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); // Scale helper to convert logical scaler values to physical using passed in // scale factor int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); if (!user32_module) { return; } auto enable_non_client_dpi_scaling = reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); FreeLibrary(user32_module); } } } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; // Returns the singleton registar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; } // Returns the name of the window class, registering the class if it hasn't // previously been registered. const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; bool class_registered_ = false; }; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; RegisterClass(&window_class); class_registered_ = true; } return kWindowClassName; } void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, const Size& size) { Destroy(); const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), static_cast(origin.y)}; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } return OnCreate(); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { case WM_DESTROY: window_handle_ = nullptr; Destroy(); if (quit_on_close_) { PostQuitMessage(0); } return 0; case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left; LONG newHeight = newRectSize->bottom - newRectSize->top; SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_SIZE: RECT rect = GetClientArea(); if (child_content_ != nullptr) { // Size and position the child window. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); } return 0; case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_); } return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } void Win32Window::Destroy() { OnDestroy(); if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, true); SetFocus(child_content_); } RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } void Win32Window::OnDestroy() { // No-op; provided for subclasses. } ================================================ FILE: packages/shared_preferences/shared_preferences/example/windows/runner/win32_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_ #include #include #include #include // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { public: struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) : width(width), height(height) {} }; Win32Window(); virtual ~Win32Window(); // Creates and shows a win32 window with |title| and position and size using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a // consistent size to will treat the width height passed in to this function // as logical pixels and scale to appropriate for the default monitor. Returns // true if the window was created successfully. bool CreateAndShow(const std::wstring& title, const Point& origin, const Size& size); // Release OS resources associated with window. void Destroy(); // Inserts |content| into the window tree. void SetChildContent(HWND content); // Returns the backing Window handle to enable clients to set icon and other // window properties. Returns nullptr if the window has been destroyed. HWND GetHandle(); // If true, closing this window will quit the application. void SetQuitOnClose(bool quit_on_close); // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Called when CreateAndShow is called, allowing subclass window-related // setup. Subclasses should return false if setup fails. virtual bool OnCreate(); // Called when Destroy is called. virtual void OnDestroy(); private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically // responsponds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; bool quit_on_close_ = false; // window handle for top level window. HWND window_handle_ = nullptr; // window handle for hosted content. HWND child_content_ = nullptr; }; #endif // RUNNER_WIN32_WINDOW_H_ ================================================ FILE: packages/shared_preferences/shared_preferences/lib/shared_preferences.dart ================================================ // Copyright 2013 The Flutter 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' show visibleForTesting; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; /// Wraps NSUserDefaults (on iOS) and SharedPreferences (on Android), providing /// a persistent store for simple data. /// /// Data is persisted to disk asynchronously. class SharedPreferences { SharedPreferences._(this._preferenceCache); static const String _prefix = 'flutter.'; static Completer? _completer; static SharedPreferencesStorePlatform get _store => SharedPreferencesStorePlatform.instance; /// Loads and parses the [SharedPreferences] for this app from disk. /// /// Because this is reading from disk, it shouldn't be awaited in /// performance-sensitive blocks. static Future getInstance() async { if (_completer == null) { final Completer completer = Completer(); try { final Map preferencesMap = await _getSharedPreferencesMap(); completer.complete(SharedPreferences._(preferencesMap)); } on Exception catch (e) { // If there's an error, explicitly return the future with an error. // then set the completer to null so we can retry. completer.completeError(e); final Future sharedPrefsFuture = completer.future; _completer = null; return sharedPrefsFuture; } _completer = completer; } return _completer!.future; } /// The cache that holds all preferences. /// /// It is instantiated to the current state of the SharedPreferences or /// NSUserDefaults object and then kept in sync via setter methods in this /// class. /// /// It is NOT guaranteed that this cache and the device prefs will remain /// in sync since the setter method might fail for any reason. final Map _preferenceCache; /// Returns all keys in the persistent storage. Set getKeys() => Set.from(_preferenceCache.keys); /// Reads a value of any type from persistent storage. Object? get(String key) => _preferenceCache[key]; /// Reads a value from persistent storage, throwing an exception if it's not a /// bool. bool? getBool(String key) => _preferenceCache[key] as bool?; /// Reads a value from persistent storage, throwing an exception if it's not /// an int. int? getInt(String key) => _preferenceCache[key] as int?; /// Reads a value from persistent storage, throwing an exception if it's not a /// double. double? getDouble(String key) => _preferenceCache[key] as double?; /// Reads a value from persistent storage, throwing an exception if it's not a /// String. String? getString(String key) => _preferenceCache[key] as String?; /// Returns true if persistent storage the contains the given [key]. bool containsKey(String key) => _preferenceCache.containsKey(key); /// Reads a set of string values from persistent storage, throwing an /// exception if it's not a string set. List? getStringList(String key) { List? list = _preferenceCache[key] as List?; if (list != null && list is! List) { list = list.cast().toList(); _preferenceCache[key] = list; } // Make a copy of the list so that later mutations won't propagate return list?.toList() as List?; } /// Saves a boolean [value] to persistent storage in the background. Future setBool(String key, bool value) => _setValue('Bool', key, value); /// Saves an integer [value] to persistent storage in the background. Future setInt(String key, int value) => _setValue('Int', key, value); /// Saves a double [value] to persistent storage in the background. /// /// Android doesn't support storing doubles, so it will be stored as a float. Future setDouble(String key, double value) => _setValue('Double', key, value); /// Saves a string [value] to persistent storage in the background. /// /// Note: Due to limitations in Android's SharedPreferences, /// values cannot start with any one of the following: /// /// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu' /// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBCaWdJbnRlZ2Vy' /// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBEb3VibGUu' Future setString(String key, String value) => _setValue('String', key, value); /// Saves a list of strings [value] to persistent storage in the background. Future setStringList(String key, List value) => _setValue('StringList', key, value); /// Removes an entry from persistent storage. Future remove(String key) { final String prefixedKey = '$_prefix$key'; _preferenceCache.remove(key); return _store.remove(prefixedKey); } Future _setValue(String valueType, String key, Object value) { ArgumentError.checkNotNull(value, 'value'); final String prefixedKey = '$_prefix$key'; if (value is List) { // Make a copy of the list so that later mutations won't propagate _preferenceCache[key] = value.toList(); } else { _preferenceCache[key] = value; } return _store.setValue(valueType, prefixedKey, value); } /// Always returns true. /// On iOS, synchronize is marked deprecated. On Android, we commit every set. @Deprecated('This method is now a no-op, and should no longer be called.') Future commit() async => true; /// Completes with true once the user preferences for the app has been cleared. Future clear() { _preferenceCache.clear(); return _store.clear(); } /// Fetches the latest values from the host platform. /// /// Use this method to observe modifications that were made in native code /// (without using the plugin) while the app is running. Future reload() async { final Map preferences = await SharedPreferences._getSharedPreferencesMap(); _preferenceCache.clear(); _preferenceCache.addAll(preferences); } static Future> _getSharedPreferencesMap() async { final Map fromSystem = await _store.getAll(); assert(fromSystem != null); // Strip the flutter. prefix from the returned preferences. final Map preferencesMap = {}; for (final String key in fromSystem.keys) { assert(key.startsWith(_prefix)); preferencesMap[key.substring(_prefix.length)] = fromSystem[key]!; } return preferencesMap; } /// Initializes the shared preferences with mock values for testing. /// /// If the singleton instance has been initialized already, it is nullified. @visibleForTesting static void setMockInitialValues(Map values) { final Map newValues = values.map((String key, Object value) { String newKey = key; if (!key.startsWith(_prefix)) { newKey = '$_prefix$key'; } return MapEntry(newKey, value); }); SharedPreferencesStorePlatform.instance = InMemorySharedPreferencesStore.withData(newValues); _completer = null; } } ================================================ FILE: packages/shared_preferences/shared_preferences/pubspec.yaml ================================================ name: shared_preferences 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.17 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: platforms: android: default_package: shared_preferences_android ios: default_package: shared_preferences_foundation linux: default_package: shared_preferences_linux macos: default_package: shared_preferences_foundation web: default_package: shared_preferences_web windows: default_package: shared_preferences_windows dependencies: flutter: sdk: flutter shared_preferences_android: ^2.0.8 shared_preferences_foundation: ^2.1.0 shared_preferences_linux: ^2.0.1 shared_preferences_platform_interface: ^2.0.0 shared_preferences_web: ^2.0.0 shared_preferences_windows: ^2.0.1 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter ================================================ FILE: packages/shared_preferences/shared_preferences/test/shared_preferences_test.dart ================================================ // Copyright 2013 The Flutter 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/shared_preferences.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('SharedPreferences', () { const String testString = 'hello world'; const bool testBool = true; const int testInt = 42; const double testDouble = 3.14159; const List testList = ['foo', 'bar']; const Map testValues = { 'flutter.String': testString, 'flutter.bool': testBool, 'flutter.int': testInt, 'flutter.double': testDouble, 'flutter.List': testList, }; const String testString2 = 'goodbye world'; const bool testBool2 = false; const int testInt2 = 1337; const double testDouble2 = 2.71828; const List testList2 = ['baz', 'quox']; const Map testValues2 = { 'flutter.String': testString2, 'flutter.bool': testBool2, 'flutter.int': testInt2, 'flutter.double': testDouble2, 'flutter.List': testList2, }; late FakeSharedPreferencesStore store; late SharedPreferences preferences; setUp(() async { store = FakeSharedPreferencesStore(testValues); SharedPreferencesStorePlatform.instance = store; preferences = await SharedPreferences.getInstance(); store.log.clear(); }); test('reading', () async { expect(preferences.get('String'), testString); expect(preferences.get('bool'), testBool); expect(preferences.get('int'), testInt); expect(preferences.get('double'), testDouble); expect(preferences.get('List'), testList); expect(preferences.getString('String'), testString); expect(preferences.getBool('bool'), testBool); expect(preferences.getInt('int'), testInt); expect(preferences.getDouble('double'), testDouble); expect(preferences.getStringList('List'), testList); expect(store.log, []); }); test('writing', () async { await Future.wait(>[ preferences.setString('String', testString2), preferences.setBool('bool', testBool2), preferences.setInt('int', testInt2), preferences.setDouble('double', testDouble2), preferences.setStringList('List', testList2) ]); expect( store.log, [ isMethodCall('setValue', arguments: [ 'String', 'flutter.String', testString2, ]), isMethodCall('setValue', arguments: [ 'Bool', 'flutter.bool', testBool2, ]), isMethodCall('setValue', arguments: [ 'Int', 'flutter.int', testInt2, ]), isMethodCall('setValue', arguments: [ 'Double', 'flutter.double', testDouble2, ]), isMethodCall('setValue', arguments: [ 'StringList', 'flutter.List', testList2, ]), ], ); store.log.clear(); expect(preferences.getString('String'), testString2); expect(preferences.getBool('bool'), testBool2); expect(preferences.getInt('int'), testInt2); expect(preferences.getDouble('double'), testDouble2); expect(preferences.getStringList('List'), testList2); expect(store.log, equals([])); }); test('removing', () async { const String key = 'testKey'; await preferences.remove(key); expect( store.log, List.filled( 1, isMethodCall( 'remove', arguments: 'flutter.$key', ), growable: true, )); }); test('containsKey', () async { const String key = 'testKey'; expect(false, preferences.containsKey(key)); await preferences.setString(key, 'test'); expect(true, preferences.containsKey(key)); }); test('clearing', () async { await preferences.clear(); expect(preferences.getString('String'), null); expect(preferences.getBool('bool'), null); expect(preferences.getInt('int'), null); expect(preferences.getDouble('double'), null); expect(preferences.getStringList('List'), null); expect(store.log, [isMethodCall('clear', arguments: null)]); }); test('reloading', () async { await preferences.setString('String', testString); expect(preferences.getString('String'), testString); SharedPreferences.setMockInitialValues( testValues2.cast()); expect(preferences.getString('String'), testString); await preferences.reload(); expect(preferences.getString('String'), testString2); }); test('back to back calls should return same instance.', () async { final Future first = SharedPreferences.getInstance(); final Future second = SharedPreferences.getInstance(); expect(await first, await second); }); test('string list type is dynamic (usually from method channel)', () async { SharedPreferences.setMockInitialValues({ 'dynamic_list': ['1', '2'] }); final SharedPreferences prefs = await SharedPreferences.getInstance(); final List? value = prefs.getStringList('dynamic_list'); expect(value, ['1', '2']); }); group('mocking', () { const String key = 'dummy'; const String prefixedKey = 'flutter.$key'; test('test 1', () async { SharedPreferences.setMockInitialValues( {prefixedKey: 'my string'}); final SharedPreferences prefs = await SharedPreferences.getInstance(); final String? value = prefs.getString(key); expect(value, 'my string'); }); test('test 2', () async { SharedPreferences.setMockInitialValues( {prefixedKey: 'my other string'}); final SharedPreferences prefs = await SharedPreferences.getInstance(); final String? value = prefs.getString(key); expect(value, 'my other string'); }); }); test('writing copy of strings list', () async { final List myList = []; await preferences.setStringList('myList', myList); myList.add('foobar'); final List cachedList = preferences.getStringList('myList')!; expect(cachedList, []); cachedList.add('foobar2'); expect(preferences.getStringList('myList'), []); }); }); test('calling mock initial values with non-prefixed keys succeeds', () async { SharedPreferences.setMockInitialValues({ 'test': 'foo', }); final SharedPreferences prefs = await SharedPreferences.getInstance(); final String? value = prefs.getString('test'); expect(value, 'foo'); }); } class FakeSharedPreferencesStore implements SharedPreferencesStorePlatform { FakeSharedPreferencesStore(Map data) : backend = InMemorySharedPreferencesStore.withData(data); final InMemorySharedPreferencesStore backend; final List log = []; @override bool get isMock => true; @override Future clear() { log.add(const MethodCall('clear')); return backend.clear(); } @override Future> getAll() { log.add(const MethodCall('getAll')); return backend.getAll(); } @override Future remove(String key) { log.add(MethodCall('remove', key)); return backend.remove(key); } @override Future setValue(String valueType, String key, Object value) { log.add(MethodCall('setValue', [valueType, key, value])); return backend.setValue(valueType, key, value); } } ================================================ FILE: packages/shared_preferences/shared_preferences_android/AUTHORS ================================================ # 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> ================================================ FILE: packages/shared_preferences/shared_preferences_android/CHANGELOG.md ================================================ ## 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. * Updates code for `no_leading_underscores_for_local_identifiers` lint. ## 2.0.13 * Updates gradle to 7.2.2. * Updates minimum Flutter version to 2.10. ## 2.0.12 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 2.0.11 * Switches to an in-package method channel implementation. ## 2.0.10 * Removes dependency on `meta`. ## 2.0.9 * Updates compileSdkVersion to 31. ## 2.0.8 * Split from `shared_preferences` as a federated implementation. ================================================ FILE: packages/shared_preferences/shared_preferences_android/LICENSE ================================================ 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. ================================================ FILE: packages/shared_preferences/shared_preferences_android/README.md ================================================ # shared\_preferences\_android The Android 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 ================================================ FILE: packages/shared_preferences/shared_preferences_android/android/build.gradle ================================================ group 'io.flutter.plugins.sharedpreferences' version '1.0-SNAPSHOT' buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.2.2' } } rootProject.allprojects { repositories { google() mavenCentral() } } allprojects { gradle.projectsEvaluated { tasks.withType(JavaCompile) { options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" } } } apply plugin: 'com.android.library' android { compileSdkVersion 31 defaultConfig { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' baseline file("lint-baseline.xml") } dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-inline:5.0.0' } testOptions { unitTests.includeAndroidResources = true unitTests.returnDefaultValues = true unitTests.all { testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" outputs.upToDateWhen {false} showStandardStreams = true } } } } ================================================ FILE: packages/shared_preferences/shared_preferences_android/android/lint-baseline.xml ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences_android/android/settings.gradle ================================================ rootProject.name = 'shared_preferences_android' ================================================ FILE: packages/shared_preferences/shared_preferences_android/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/MethodCallHandlerImpl.java ================================================ // Copyright 2013 The Flutter 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.sharedpreferences; import android.content.Context; import android.content.SharedPreferences; import android.os.Handler; import android.os.Looper; import android.util.Base64; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.math.BigInteger; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * Implementation of the {@link MethodChannel.MethodCallHandler} for the plugin. It is also * responsible of managing the {@link android.content.SharedPreferences}. */ @SuppressWarnings("unchecked") class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { private static final String SHARED_PREFERENCES_NAME = "FlutterSharedPreferences"; // Fun fact: The following is a base64 encoding of the string "This is the prefix for a list." private static final String LIST_IDENTIFIER = "VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu"; private static final String BIG_INTEGER_PREFIX = "VGhpcyBpcyB0aGUgcHJlZml4IGZvciBCaWdJbnRlZ2Vy"; private static final String DOUBLE_PREFIX = "VGhpcyBpcyB0aGUgcHJlZml4IGZvciBEb3VibGUu"; private final android.content.SharedPreferences preferences; private final ExecutorService executor; private final Handler handler; /** * Constructs a {@link MethodCallHandlerImpl} instance. Creates a {@link * android.content.SharedPreferences} based on the {@code context}. */ MethodCallHandlerImpl(Context context) { preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); executor = new ThreadPoolExecutor(0, 1, 30L, TimeUnit.SECONDS, new LinkedBlockingQueue()); handler = new Handler(Looper.getMainLooper()); } @Override public void onMethodCall(MethodCall call, MethodChannel.Result result) { String key = call.argument("key"); try { switch (call.method) { case "setBool": commitAsync(preferences.edit().putBoolean(key, (boolean) call.argument("value")), result); break; case "setDouble": double doubleValue = ((Number) call.argument("value")).doubleValue(); String doubleValueStr = Double.toString(doubleValue); commitAsync(preferences.edit().putString(key, DOUBLE_PREFIX + doubleValueStr), result); break; case "setInt": Number number = call.argument("value"); if (number instanceof BigInteger) { BigInteger integerValue = (BigInteger) number; commitAsync( preferences .edit() .putString( key, BIG_INTEGER_PREFIX + integerValue.toString(Character.MAX_RADIX)), result); } else { commitAsync(preferences.edit().putLong(key, number.longValue()), result); } break; case "setString": String value = (String) call.argument("value"); if (value.startsWith(LIST_IDENTIFIER) || value.startsWith(BIG_INTEGER_PREFIX) || value.startsWith(DOUBLE_PREFIX)) { result.error( "StorageError", "This string cannot be stored as it clashes with special identifier prefixes.", null); return; } commitAsync(preferences.edit().putString(key, value), result); break; case "setStringList": List list = call.argument("value"); commitAsync( preferences.edit().putString(key, LIST_IDENTIFIER + encodeList(list)), result); break; case "commit": // We've been committing the whole time. result.success(true); break; case "getAll": result.success(getAllPrefs()); return; case "remove": commitAsync(preferences.edit().remove(key), result); break; case "clear": Set keySet = getAllPrefs().keySet(); SharedPreferences.Editor clearEditor = preferences.edit(); for (String keyToDelete : keySet) { clearEditor.remove(keyToDelete); } commitAsync(clearEditor, result); break; default: result.notImplemented(); break; } } catch (IOException e) { result.error("IOException encountered", call.method, e); } } public void teardown() { handler.removeCallbacksAndMessages(null); executor.shutdown(); } private void commitAsync( final SharedPreferences.Editor editor, final MethodChannel.Result result) { executor.execute( new Runnable() { @Override public void run() { final boolean response = editor.commit(); handler.post( new Runnable() { @Override public void run() { result.success(response); } }); } }); } private List decodeList(String encodedList) throws IOException { ObjectInputStream stream = null; try { stream = new ObjectInputStream(new ByteArrayInputStream(Base64.decode(encodedList, 0))); return (List) stream.readObject(); } catch (ClassNotFoundException e) { throw new IOException(e); } finally { if (stream != null) { stream.close(); } } } private String encodeList(List list) throws IOException { ObjectOutputStream stream = null; try { ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); stream = new ObjectOutputStream(byteStream); stream.writeObject(list); stream.flush(); return Base64.encodeToString(byteStream.toByteArray(), 0); } finally { if (stream != null) { stream.close(); } } } // Filter preferences to only those set by the flutter app. private Map getAllPrefs() throws IOException { Map allPrefs = preferences.getAll(); Map filteredPrefs = new HashMap<>(); for (String key : allPrefs.keySet()) { if (key.startsWith("flutter.")) { Object value = allPrefs.get(key); if (value instanceof String) { String stringValue = (String) value; if (stringValue.startsWith(LIST_IDENTIFIER)) { value = decodeList(stringValue.substring(LIST_IDENTIFIER.length())); } else if (stringValue.startsWith(BIG_INTEGER_PREFIX)) { String encoded = stringValue.substring(BIG_INTEGER_PREFIX.length()); value = new BigInteger(encoded, Character.MAX_RADIX); } else if (stringValue.startsWith(DOUBLE_PREFIX)) { String doubleStr = stringValue.substring(DOUBLE_PREFIX.length()); value = Double.valueOf(doubleStr); } } else if (value instanceof Set) { // This only happens for previous usage of setStringSet. The app expects a list. List listValue = new ArrayList<>((Set) value); // Let's migrate the value too while we are at it. boolean success = preferences .edit() .remove(key) .putString(key, LIST_IDENTIFIER + encodeList(listValue)) .commit(); if (!success) { // If we are unable to migrate the existing preferences, it means we potentially lost them. // In this case, an error from getAllPrefs() is appropriate since it will alert the app during plugin initialization. throw new IOException("Could not migrate set to list"); } value = listValue; } filteredPrefs.put(key, value); } } return filteredPrefs; } } ================================================ FILE: packages/shared_preferences/shared_preferences_android/android/src/main/java/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.java ================================================ // Copyright 2013 The Flutter 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.sharedpreferences; import android.content.Context; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodChannel; /** SharedPreferencesPlugin */ public class SharedPreferencesPlugin implements FlutterPlugin { private static final String CHANNEL_NAME = "plugins.flutter.io/shared_preferences_android"; private MethodChannel channel; private MethodCallHandlerImpl handler; @SuppressWarnings("deprecation") public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { final SharedPreferencesPlugin plugin = new SharedPreferencesPlugin(); plugin.setupChannel(registrar.messenger(), registrar.context()); } @Override public void onAttachedToEngine(FlutterPlugin.FlutterPluginBinding binding) { setupChannel(binding.getBinaryMessenger(), binding.getApplicationContext()); } @Override public void onDetachedFromEngine(FlutterPlugin.FlutterPluginBinding binding) { teardownChannel(); } private void setupChannel(BinaryMessenger messenger, Context context) { channel = new MethodChannel(messenger, CHANNEL_NAME); handler = new MethodCallHandlerImpl(context); channel.setMethodCallHandler(handler); } private void teardownChannel() { handler.teardown(); handler = null; channel.setMethodCallHandler(null); channel = null; } } ================================================ FILE: packages/shared_preferences/shared_preferences_android/android/src/test/java/io/flutter/plugins/sharedpreferences/SharedPreferencesTest.java ================================================ // Copyright 2013 The Flutter 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.sharedpreferences; import org.junit.Test; public class SharedPreferencesTest { // This is only a placeholder test and doesn't actually initialize the plugin. @Test public void initPluginDoesNotThrow() { final SharedPreferencesPlugin plugin = new SharedPreferencesPlugin(); } } ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Android Studio will place build artifacts here /android/app/debug /android/app/profile /android/app/release ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/.metadata ================================================ # 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 ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/README.md ================================================ # 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. ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/android/.gitignore ================================================ gradle-wrapper.jar /.gradle /captures/ /gradlew /gradlew.bat /local.properties GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app key.properties ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 sourceSets { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { applicationId "io.flutter.plugins.sharedpreferencesexample" minSdkVersion 16 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/android/app/src/androidTest/java/io/flutter/plugins/sharedpreferencesexample/MainActivityTest.java ================================================ // Copyright 2013 The Flutter 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.sharedpreferences; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class MainActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/android/app/src/main/res/drawable-v21/launch_background.xml ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/android/app/src/main/res/values-night/styles.xml ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/android/build.gradle ================================================ buildscript { ext.kotlin_version = '1.3.50' repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/android/settings.gradle ================================================ include ':app' def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() assert localPropertiesFile.exists() localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/integration_test/shared_preferences_test.dart ================================================ // Copyright 2013 The Flutter 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:integration_test/integration_test.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('SharedPreferencesAndroid', () { 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); }); testWidgets('simultaneous writes', (WidgetTester _) async { final List> writes = >[]; const int writeCount = 100; for (int i = 1; i <= writeCount; i++) { writes.add(preferences.setValue('Int', prefixedKey('int'), i)); } final List result = await Future.wait(writes, eagerError: true); // All writes should succeed. expect(result.where((bool element) => !element), isEmpty); // The last write should win. final Map values = await preferences.getAll(); expect(values[prefixedKey('int')], writeCount); }); testWidgets('string clash with lists, big integers and doubles', (WidgetTester _) async { final String key = prefixedKey('akey'); const String value = 'a string value'; await preferences.clear(); // Special prefixes used to store datatypes that can't be stored directly // in SharedPreferences as strings instead. const List specialPrefixes = [ // Prefix for lists: 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu', // Prefix for big integers: 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBCaWdJbnRlZ2Vy', // Prefix for doubles: 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBEb3VibGUu', ]; for (final String prefix in specialPrefixes) { expect(preferences.setValue('String', key, prefix + value), throwsA(isA())); final Map values = await preferences.getAll(); expect(values[key], null); } }); }); } ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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.none: case ConnectionState.waiting: return const CircularProgressIndicator(); case ConnectionState.active: case ConnectionState.done: 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), ), ); } } ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/pubspec.yaml ================================================ 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: ">=3.0.0" dependencies: flutter: sdk: flutter shared_preferences_android: # When depending on this package from a real application you should use: # shared_preferences_android: ^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 ================================================ FILE: packages/shared_preferences/shared_preferences_android/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/shared_preferences/shared_preferences_android/lib/shared_preferences_android.dart ================================================ // Copyright 2013 The Flutter 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_android'); /// The Android implementation of [SharedPreferencesStorePlatform]. /// /// This class implements the `package:shared_preferences` functionality for Android. class SharedPreferencesAndroid extends SharedPreferencesStorePlatform { /// Registers this class as the default instance of [SharedPreferencesStorePlatform]. static void registerWith() { SharedPreferencesStorePlatform.instance = SharedPreferencesAndroid(); } @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; } } ================================================ FILE: packages/shared_preferences/shared_preferences_android/pubspec.yaml ================================================ 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.15 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: shared_preferences platforms: android: package: io.flutter.plugins.sharedpreferences pluginClass: SharedPreferencesPlugin dartPluginClass: SharedPreferencesAndroid dependencies: flutter: sdk: flutter shared_preferences_platform_interface: ^2.0.0 dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/shared_preferences/shared_preferences_android/test/shared_preferences_android_test.dart ================================================ // Copyright 2013 The Flutter 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_android/shared_preferences_android.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_android', ); 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(); Map getArgumentDictionary(MethodCall call) { return (call.arguments as Map) .cast(); } _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, (MethodCall methodCall) async { log.add(methodCall); if (methodCall.method == 'getAll') { return testData.getAll(); } if (methodCall.method == 'remove') { final Map arguments = getArgumentDictionary(methodCall); final String key = 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 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}'); }); log.clear(); }); test('registered instance', () { SharedPreferencesAndroid.registerWith(); expect(SharedPreferencesStorePlatform.instance, isA()); }); test('getAll', () async { store = SharedPreferencesAndroid(); testData = InMemorySharedPreferencesStore.withData(kTestValues); expect(await store.getAll(), kTestValues); expect(log.single.method, 'getAll'); }); test('remove', () async { store = SharedPreferencesAndroid(); 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 = SharedPreferencesAndroid(); 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 = SharedPreferencesAndroid(); testData = InMemorySharedPreferencesStore.withData(kTestValues); expect(await testData.getAll(), isNotEmpty); expect(await store.clear(), true); expect(await testData.getAll(), isEmpty); expect(log.single.method, 'clear'); }); }); } /// 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; ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/AUTHORS ================================================ # 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> ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/CHANGELOG.md ================================================ ## 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. ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/LICENSE ================================================ 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. ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/README.md ================================================ # shared\_preferences\_foundation The iOS and 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 ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/darwin/Classes/SharedPreferencesPlugin.swift ================================================ // Copyright 2013 The Flutter 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 } ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/darwin/Classes/messages.g.swift ================================================ // Copyright 2013 The Flutter 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 ] } ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/darwin/Tests/RunnerTests.swift ================================================ // Copyright 2013 The Flutter 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?) } } ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/darwin/shared_preferences_foundation.podspec ================================================ # # 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' } 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 ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ migrate_working_dir/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Android Studio will place build artifacts here /android/app/debug /android/app/profile /android/app/release ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/README.md ================================================ # 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. ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/integration_test/shared_preferences_test.dart ================================================ // Copyright 2013 The Flutter 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('SharedPreferencesFoundation', () { 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); }); }); } ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/.gitignore ================================================ **/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 ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 11.0 ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Flutter/Debug.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Flutter/Release.xcconfig ================================================ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/AppDelegate.swift ================================================ // Copyright 2013 The Flutter 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) } } ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "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" } } ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName Shared Preferences Foundation CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName shared_preferences_foundation_example CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Runner/Runner-Bridging-Header.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import "GeneratedPluginRegistrant.h" ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 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 */ 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ../../../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 = ""; }; 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 = ""; }; 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; }; 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 = ""; }; 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 */ 3F94D8484CE6A0609BCE7680 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 9AF3A5CAB88B27F2BC36D686 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( B81650923B266CE1F32B75E4 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( 331C807B294A618700263BE5 /* RunnerTests.swift */, ); path = RunnerTests; sourceTree = ""; }; 4E1DD4374F34EBDF7F4214F0 /* Frameworks */ = { isa = PBXGroup; children = ( F3702C135032CE4599D8327B /* Pods_Runner.framework */, D4C443E7E16FB2AD978AC1D6 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, DA43E4FDD6392A0D5FBF1611 /* Pods */, 4E1DD4374F34EBDF7F4214F0 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; DA43E4FDD6392A0D5FBF1611 /* Pods */ = { isa = PBXGroup; children = ( 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 = 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 = ( 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 = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { CreatedOnToolsVersion = 14.0; TestTargetID = 97C146ED1CF9000F007C117D; }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 331C807F294A63A400263BE5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3AC0A86331B4FD70A0EF91D9 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); 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 = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 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", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; 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 = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 331C807D294A63A400263BE5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; 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_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 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; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 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_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 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 = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 331C8088294A63A400263BE5 /* Debug */, 331C8089294A63A400263BE5 /* Release */, 331C808A294A63A400263BE5 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; 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.none: case ConnectionState.waiting: return const CircularProgressIndicator(); case ConnectionState.active: case ConnectionState.done: 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), ), ); } } ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/Flutter/Flutter-Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/Flutter/Flutter-Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/Podfile ================================================ platform :osx, '10.11' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_macos_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) end end ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/AppDelegate.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } } ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_64.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_1024.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Base.lproj/MainMenu.xib ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Configs/AppInfo.xcconfig ================================================ // Application-level settings for the Runner target. // // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the // future. If not, the values below would default to using the project name when this becomes a // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. PRODUCT_NAME = url_launcher_example_example // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.urlLauncherExample // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2019 The Flutter Authors. All rights reserved. ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Configs/Warnings.xcconfig ================================================ WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings GCC_WARN_UNDECLARED_SELECTOR = YES CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE CLANG_WARN__DUPLICATE_METHOD_MATCH = YES CLANG_WARN_PRAGMA_PACK = YES CLANG_WARN_STRICT_PROTOTYPES = YES CLANG_WARN_COMMA = YES GCC_WARN_STRICT_SELECTOR_MATCH = YES CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES GCC_WARN_SHADOW = YES CLANG_WARN_UNREACHABLE_CODE = YES ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/DebugProfile.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/MainFlutterWindow.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { let flutterViewController = FlutterViewController.init() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } } ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { isa = PBXAggregateTarget; buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; buildPhases = ( 33CC111E2044C6BF0003C045 /* ShellScript */, ); dependencies = ( ); name = "Flutter Assemble"; productName = FLX; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 2664C8CF4F7C09B469256E8C /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FBE52A82BBDAFEA0EB8C219A /* Pods_RunnerTests.framework */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 33EBD39B26727BD10013E557 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33EBD39A26727BD10013E557 /* RunnerTests.swift */; }; DD4A1B9DEDBB72C87CD7AE27 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5067D74CB28D28AE3B3DD05B /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC111A2044C6BA0003C045; remoteInfo = FLX; }; 33EBD39D26727BD10013E557 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC10EC2044A3C60003C045; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 33CC110E2044A8840003C045 /* Bundle Framework */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Bundle Framework"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 2E4DBB55AB946A7F1AA7D737 /* 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 = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* url_launcher_example_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = url_launcher_example_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 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 = ../../../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 = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 899489AD6AA35AECA4E2BEA6 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; AD26B2A2C7409B621A8ADDA0 /* 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 = ""; }; B36FDC1D769C9045B8821207 /* 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 = ""; }; CC4DAF1C0735E2069209EED8 /* 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 = ""; }; FBE52A82BBDAFEA0EB8C219A /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( DD4A1B9DEDBB72C87CD7AE27 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 33EBD39526727BD10013E557 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2664C8CF4F7C09B469256E8C /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( 33E5194F232828860026EE4D /* AppInfo.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, ); path = Configs; sourceTree = ""; }; 33CC10E42044A3C60003C045 = { isa = PBXGroup; children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, 33EBD39926727BD10013E557 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, 96C1F6D923BD5787E8EBE8FC /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* url_launcher_example_example.app */, 33EBD39826727BD10013E557 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 33CC11242044D66E0003C045 /* Resources */ = { isa = PBXGroup; children = ( 33CC10F22044A3C60003C045 /* Assets.xcassets */, 33CC10F42044A3C60003C045 /* MainMenu.xib */, 33CC10F72044A3C60003C045 /* Info.plist */, ); name = Resources; path = ..; sourceTree = ""; }; 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, ); path = Flutter; sourceTree = ""; }; 33EBD39926727BD10013E557 /* RunnerTests */ = { isa = PBXGroup; children = ( 33EBD39A26727BD10013E557 /* RunnerTests.swift */, 33EBD39C26727BD10013E557 /* Info.plist */, ); path = RunnerTests; sourceTree = ""; }; 33FAB671232836740065AC1E /* Runner */ = { isa = PBXGroup; children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, 33CC11242044D66E0003C045 /* Resources */, 33BA886A226E78AF003329D5 /* Configs */, ); path = Runner; sourceTree = ""; }; 96C1F6D923BD5787E8EBE8FC /* Pods */ = { isa = PBXGroup; children = ( 899489AD6AA35AECA4E2BEA6 /* Pods-Runner.debug.xcconfig */, B36FDC1D769C9045B8821207 /* Pods-Runner.release.xcconfig */, 53F020549CA1E801ACA3428F /* Pods-Runner.profile.xcconfig */, AD26B2A2C7409B621A8ADDA0 /* Pods-RunnerTests.debug.xcconfig */, CC4DAF1C0735E2069209EED8 /* Pods-RunnerTests.release.xcconfig */, 2E4DBB55AB946A7F1AA7D737 /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( 5067D74CB28D28AE3B3DD05B /* Pods_Runner.framework */, FBE52A82BBDAFEA0EB8C219A /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( C318D59394D0E38099411848 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, 50C74DCD840D9B569BE3D48F /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* url_launcher_example_example.app */; productType = "com.apple.product-type.application"; }; 33EBD39726727BD10013E557 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = 33EBD3A226727BD10013E557 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( 057C8B472E54526F53651CE7 /* [CP] Check Pods Manifest.lock */, 33EBD39426727BD10013E557 /* Sources */, 33EBD39526727BD10013E557 /* Frameworks */, 33EBD39626727BD10013E557 /* Resources */, ); buildRules = ( ); dependencies = ( 33EBD39E26727BD10013E557 /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = 33EBD39826727BD10013E557 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1250; LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; }; }; }; 33CC111A2044C6BA0003C045 = { CreatedOnToolsVersion = 9.2; ProvisioningStyle = Manual; }; 33EBD39726727BD10013E557 = { CreatedOnToolsVersion = 12.5; TestTargetID = 33CC10EC2044A3C60003C045; }; }; }; buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 8.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 33CC10E42044A3C60003C045; productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, 33EBD39726727BD10013E557 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 33EBD39626727BD10013E557 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 057C8B472E54526F53651CE7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( Flutter/ephemeral/FlutterInputs.xcfilelist, ); inputPaths = ( Flutter/ephemeral/tripwire, ); outputFileListPaths = ( Flutter/ephemeral/FlutterOutputs.xcfilelist, ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh\ntouch Flutter/ephemeral/tripwire\n"; }; 50C74DCD840D9B569BE3D48F /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", "${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; C318D59394D0E38099411848 /* [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", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 33EBD39426727BD10013E557 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33EBD39B26727BD10013E557 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; }; 33EBD39E26727BD10013E557 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC10EC2044A3C60003C045 /* Runner */; targetProxy = 33EBD39D26727BD10013E557 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 33CC10F52044A3C60003C045 /* Base */, ); name = MainMenu.xib; path = Runner; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Profile; }; 338D0CEA231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter/ephemeral", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Profile; }; 338D0CEB231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Profile; }; 33CC10F92044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 33CC10FA2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; 33CC10FC2044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter/ephemeral", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; 33CC10FD2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter/ephemeral", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Release; }; 33CC111C2044C6BA0003C045 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 33CC111D2044C6BA0003C045 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 33EBD39F26727BD10013E557 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = AD26B2A2C7409B621A8ADDA0 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COMBINE_HIDPI_IMAGES = YES; 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)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/url_launcher_example_example.app/Contents/MacOS/url_launcher_example_example"; }; name = Debug; }; 33EBD3A026727BD10013E557 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = CC4DAF1C0735E2069209EED8 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COMBINE_HIDPI_IMAGES = YES; 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)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/url_launcher_example_example.app/Contents/MacOS/url_launcher_example_example"; }; name = Release; }; 33EBD3A126727BD10013E557 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 2E4DBB55AB946A7F1AA7D737 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COMBINE_HIDPI_IMAGES = YES; 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)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/url_launcher_example_example.app/Contents/MacOS/url_launcher_example_example"; }; name = Profile; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10F92044A3C60003C045 /* Debug */, 33CC10FA2044A3C60003C045 /* Release */, 338D0CE9231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10FC2044A3C60003C045 /* Debug */, 33CC10FD2044A3C60003C045 /* Release */, 338D0CEA231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC111C2044C6BA0003C045 /* Debug */, 33CC111D2044C6BA0003C045 /* Release */, 338D0CEB231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33EBD3A226727BD10013E557 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 33EBD39F26727BD10013E557 /* Debug */, 33EBD3A026727BD10013E557 /* Release */, 33EBD3A126727BD10013E557 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/macos/RunnerTests/Info.plist ================================================ 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 ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/pubspec.yaml ================================================ name: shared_preferences_example description: Testbed for the shared_preferences_foundation implementation. publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter shared_preferences_foundation: # When depending on this package from a real application you should use: # 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. 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 ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/ios/README.md ================================================ 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. ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/lib/messages.g.dart ================================================ // Copyright 2013 The Flutter 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'; 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 = StandardMessageCodec(); Future remove(String arg_key) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UserDefaultsApi.remove', codec, binaryMessenger: _binaryMessenger); 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.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future setBool(String arg_key, bool arg_value) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UserDefaultsApi.setBool', codec, binaryMessenger: _binaryMessenger); 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.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future setDouble(String arg_key, double arg_value) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UserDefaultsApi.setDouble', codec, binaryMessenger: _binaryMessenger); 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.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future setValue(String arg_key, Object arg_value) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UserDefaultsApi.setValue', codec, binaryMessenger: _binaryMessenger); 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.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future> getAll() async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UserDefaultsApi.getAll', 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 Map?)!.cast(); } } Future clear() async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UserDefaultsApi.clear', 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 { return; } } } ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/lib/shared_preferences_foundation.dart ================================================ // Copyright 2013 The Flutter 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:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; import 'messages.g.dart'; typedef _Setter = Future Function(String key, Object value); /// iOS and macOS implementation of shared_preferences. class SharedPreferencesFoundation extends SharedPreferencesStorePlatform { final UserDefaultsApi _api = UserDefaultsApi(); late final Map _setters = { 'Bool': (String key, Object value) { return _api.setBool(key, value as bool); }, 'Double': (String key, Object value) { return _api.setDouble(key, value as double); }, 'Int': (String key, Object value) { return _api.setValue(key, value as int); }, 'String': (String key, Object value) { return _api.setValue(key, value as String); }, 'StringList': (String key, Object value) { return _api.setValue(key, value as List); }, }; /// Registers this class as the default instance of /// [SharedPreferencesStorePlatform]. static void registerWith() { SharedPreferencesStorePlatform.instance = SharedPreferencesFoundation(); } @override Future clear() async { await _api.clear(); return true; } @override Future> getAll() async { final Map result = await _api.getAll(); return result.cast(); } @override Future remove(String key) async { await _api.remove(key); return true; } @override Future setValue(String valueType, String key, Object value) async { final _Setter? setter = _setters[valueType]; if (setter == null) { throw PlatformException( code: 'InvalidOperation', message: '"$valueType" is not a supported type.'); } await setter(key, value); return true; } } ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/macos/README.md ================================================ 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. ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/pigeons/copyright_header.txt ================================================ Copyright 2013 The Flutter Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/pigeons/messages.dart ================================================ // Copyright 2013 The Flutter 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/messages.g.dart', 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(); } ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/pubspec.yaml ================================================ 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.1.3 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: shared_preferences platforms: ios: pluginClass: SharedPreferencesPlugin dartPluginClass: SharedPreferencesFoundation sharedDarwinSource: true macos: pluginClass: SharedPreferencesPlugin dartPluginClass: SharedPreferencesFoundation sharedDarwinSource: true dependencies: flutter: sdk: flutter shared_preferences_platform_interface: ^2.0.0 dev_dependencies: flutter_test: sdk: flutter pigeon: ^5.0.0 ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/test/shared_preferences_foundation_test.dart ================================================ // Copyright 2013 The Flutter 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_foundation/shared_preferences_foundation.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; import 'test_api.g.dart'; class _MockSharedPreferencesApi implements TestUserDefaultsApi { final Map items = {}; @override Map getAll() { return items; } @override void remove(String key) { items.remove(key); } @override void setBool(String key, bool value) { items[key] = value; } @override void setDouble(String key, double value) { items[key] = value; } @override void setValue(String key, Object value) { items[key] = value; } @override void clear() { items.clear(); } } void main() { TestWidgetsFlutterBinding.ensureInitialized(); late _MockSharedPreferencesApi api; setUp(() { api = _MockSharedPreferencesApi(); TestUserDefaultsApi.setup(api); }); test('registerWith', () { 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 { 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, 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); expect(api.items['flutter.Double'], 1.5); expect(await plugin.setValue('Int', 'flutter.Int', 12), isTrue); expect(api.items['flutter.Int'], 12); expect(await plugin.setValue('String', 'flutter.String', 'hi'), isTrue); expect(api.items['flutter.String'], 'hi'); expect( await plugin .setValue('StringList', 'flutter.StringList', ['hi']), isTrue); expect(api.items['flutter.StringList'], ['hi']); }); test('setValue with unsupported type', () { final SharedPreferencesFoundation plugin = SharedPreferencesFoundation(); expect(() async { await plugin.setValue('Map', 'flutter.key', {}); }, throwsA(isA())); }); } ================================================ FILE: packages/shared_preferences/shared_preferences_foundation/test/test_api.g.dart ================================================ // Copyright 2013 The Flutter 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:shared_preferences_foundation/messages.g.dart'; abstract class TestUserDefaultsApi { 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}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UserDefaultsApi.remove', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.UserDefaultsApi.remove was null.'); final List args = (message as List?)!; final String? arg_key = (args[0] as String?); assert(arg_key != null, 'Argument for dev.flutter.pigeon.UserDefaultsApi.remove was null, expected non-null String.'); api.remove(arg_key!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UserDefaultsApi.setBool', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.UserDefaultsApi.setBool was null.'); final List args = (message as List?)!; final String? arg_key = (args[0] as String?); assert(arg_key != null, 'Argument for dev.flutter.pigeon.UserDefaultsApi.setBool was null, expected non-null String.'); final bool? arg_value = (args[1] as bool?); assert(arg_value != null, 'Argument for dev.flutter.pigeon.UserDefaultsApi.setBool was null, expected non-null bool.'); api.setBool(arg_key!, arg_value!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UserDefaultsApi.setDouble', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.UserDefaultsApi.setDouble was null.'); final List args = (message as List?)!; final String? arg_key = (args[0] as String?); assert(arg_key != null, 'Argument for dev.flutter.pigeon.UserDefaultsApi.setDouble was null, expected non-null String.'); final double? arg_value = (args[1] as double?); assert(arg_value != null, 'Argument for dev.flutter.pigeon.UserDefaultsApi.setDouble was null, expected non-null double.'); api.setDouble(arg_key!, arg_value!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UserDefaultsApi.setValue', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.UserDefaultsApi.setValue was null.'); final List args = (message as List?)!; final String? arg_key = (args[0] as String?); assert(arg_key != null, 'Argument for dev.flutter.pigeon.UserDefaultsApi.setValue was null, expected non-null String.'); final Object? arg_value = (args[1] as Object?); assert(arg_value != null, 'Argument for dev.flutter.pigeon.UserDefaultsApi.setValue was null, expected non-null Object.'); api.setValue(arg_key!, arg_value!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UserDefaultsApi.getAll', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { // ignore message final Map output = api.getAll(); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UserDefaultsApi.clear', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { // ignore message api.clear(); return []; }); } } } } ================================================ FILE: packages/shared_preferences/shared_preferences_linux/.gitignore ================================================ .DS_Store .dart_tool/ .packages .pub/ build/ ================================================ FILE: packages/shared_preferences/shared_preferences_linux/.metadata ================================================ # 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: e491544588e8d34fdf31d5f840b4649850ef167a channel: master project_type: plugin ================================================ FILE: packages/shared_preferences/shared_preferences_linux/AUTHORS ================================================ # 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> ================================================ FILE: packages/shared_preferences/shared_preferences_linux/CHANGELOG.md ================================================ ## 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. * Updates code for `no_leading_underscores_for_local_identifiers` lint. * Updates minimum Flutter version to 2.10. ## 2.1.1 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 2.1.0 * Deprecated `SharedPreferencesWindows.instance` in favor of `SharedPreferencesStorePlatform.instance`. ## 2.0.4 * Removes dependency on `meta`. ## 2.0.3 * Removed obsolete `pluginClass: none` from pubpsec. * Fixes newly enabled analyzer options. ## 2.0.2 * Updated installation instructions in README. ## 2.0.1 * Add `implements` to the pubspec. * Add `registerWith` to the Dart main class. ## 2.0.0 * Migrate to null-safety. ## 0.0.3+1 * Update Flutter SDK constraint. ## 0.0.3 * Update integration test examples to use `testWidgets` instead of `test`. ## 0.0.2+4 * Remove unused `test` dependency. * Update Dart SDK constraint in example. ## 0.0.2+3 * Check in linux/ directory for example/ ## 0.0.2+2 * Bump the `file` package dependency to resolve dep conflicts with `flutter_driver`. ## 0.0.2+1 * Replace path_provider dependency with path_provider_linux. ## 0.0.2 * Add iOS stub. ## 0.0.1 * Initial release to support shared_preferences on Linux. ================================================ FILE: packages/shared_preferences/shared_preferences_linux/LICENSE ================================================ 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. ================================================ FILE: packages/shared_preferences/shared_preferences_linux/README.md ================================================ # shared\_preferences\_linux The Linux 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 ================================================ FILE: packages/shared_preferences/shared_preferences_linux/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: packages/shared_preferences/shared_preferences_linux/example/.metadata ================================================ # 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: e491544588e8d34fdf31d5f840b4649850ef167a channel: master project_type: app ================================================ FILE: packages/shared_preferences/shared_preferences_linux/example/README.md ================================================ # 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. ================================================ FILE: packages/shared_preferences/shared_preferences_linux/example/integration_test/shared_preferences_test.dart ================================================ // Copyright 2013 The Flutter 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_linux/shared_preferences_linux.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('SharedPreferencesLinux', () { 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 SharedPreferencesLinux preferences; setUp(() async { preferences = SharedPreferencesLinux(); }); tearDown(() { preferences.clear(); }); testWidgets('reading', (WidgetTester _) async { final Map all = await preferences.getAll(); expect(all['String'], isNull); expect(all['bool'], isNull); expect(all['int'], isNull); expect(all['double'], isNull); expect(all['List'], isNull); }); testWidgets('writing', (WidgetTester _) async { await Future.wait(>[ preferences.setValue( 'String', 'String', kTestValues2['flutter.String']!), preferences.setValue('Bool', 'bool', kTestValues2['flutter.bool']!), preferences.setValue('Int', 'int', kTestValues2['flutter.int']!), preferences.setValue( 'Double', 'double', kTestValues2['flutter.double']!), preferences.setValue( 'StringList', 'List', kTestValues2['flutter.List']!) ]); final Map all = await preferences.getAll(); expect(all['String'], kTestValues2['flutter.String']); expect(all['bool'], kTestValues2['flutter.bool']); expect(all['int'], kTestValues2['flutter.int']); expect(all['double'], kTestValues2['flutter.double']); expect(all['List'], kTestValues2['flutter.List']); }); testWidgets('removing', (WidgetTester _) async { const String key = 'testKey'; await Future.wait(>[ preferences.setValue('String', key, kTestValues['flutter.String']!), preferences.setValue('Bool', key, kTestValues['flutter.bool']!), preferences.setValue('Int', key, kTestValues['flutter.int']!), preferences.setValue('Double', key, kTestValues['flutter.double']!), preferences.setValue('StringList', key, kTestValues['flutter.List']!) ]); await preferences.remove(key); final Map all = await preferences.getAll(); expect(all['testKey'], isNull); }); testWidgets('clearing', (WidgetTester _) async { await Future.wait(>[ preferences.setValue( 'String', 'String', kTestValues['flutter.String']!), preferences.setValue('Bool', 'bool', kTestValues['flutter.bool']!), preferences.setValue('Int', 'int', kTestValues['flutter.int']!), preferences.setValue( 'Double', 'double', kTestValues['flutter.double']!), preferences.setValue('StringList', 'List', kTestValues['flutter.List']!) ]); await preferences.clear(); final Map all = await preferences.getAll(); expect(all['String'], null); expect(all['bool'], null); expect(all['int'], null); expect(all['double'], null); expect(all['List'], null); }); }); } ================================================ FILE: packages/shared_preferences/shared_preferences_linux/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'package:flutter/material.dart'; import 'package:shared_preferences_linux/shared_preferences_linux.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 SharedPreferencesLinux prefs = SharedPreferencesLinux(); late Future _counter; Future _incrementCounter() async { final Map values = await prefs.getAll(); final int counter = (values['counter'] as int? ?? 0) + 1; setState(() { _counter = prefs.setValue('Int', 'counter', counter).then((bool success) { return counter; }); }); } @override void initState() { super.initState(); _counter = prefs.getAll().then((Map values) { return values['counter'] 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.none: case ConnectionState.waiting: return const CircularProgressIndicator(); case ConnectionState.active: case ConnectionState.done: 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), ), ); } } ================================================ FILE: packages/shared_preferences/shared_preferences_linux/example/linux/.gitignore ================================================ flutter/ephemeral ================================================ FILE: packages/shared_preferences/shared_preferences_linux/example/linux/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(runner LANGUAGES CXX) set(BINARY_NAME "example") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_14) target_compile_options(${TARGET} PRIVATE -Wall -Werror) target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) # Application build add_executable(${BINARY_NAME} "main.cc" "my_application.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" ) apply_standard_settings(${BINARY_NAME}) target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) add_dependencies(${BINARY_NAME} flutter_assemble) # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # By default, "installing" just makes a relocatable bundle in the build # directory. set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() # Start with a clean build bundle directory every time. install(CODE " file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") " COMPONENT Runtime) set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() ================================================ FILE: packages/shared_preferences/shared_preferences_linux/example/linux/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. # Serves the same purpose as list(TRANSFORM ... PREPEND ...), # which isn't available in 3.10. function(list_prepend LIST_NAME PREFIX) set(NEW_LIST "") foreach(element ${${LIST_NAME}}) list(APPEND NEW_LIST "${PREFIX}${element}") endforeach(element) set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) endfunction() # === Flutter Library === # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "fl_basic_message_channel.h" "fl_binary_codec.h" "fl_binary_messenger.h" "fl_dart_project.h" "fl_engine.h" "fl_json_message_codec.h" "fl_json_method_codec.h" "fl_message_codec.h" "fl_method_call.h" "fl_method_channel.h" "fl_method_codec.h" "fl_method_response.h" "fl_plugin_registrar.h" "fl_plugin_registry.h" "fl_standard_message_codec.h" "fl_standard_method_codec.h" "fl_string_codec.h" "fl_value.h" "fl_view.h" "flutter_linux.h" ) list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") target_link_libraries(flutter INTERFACE PkgConfig::GTK PkgConfig::GLIB PkgConfig::GIO ) add_dependencies(flutter flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/_phony_ COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" linux-x64 ${CMAKE_BUILD_TYPE} ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ) ================================================ FILE: packages/shared_preferences/shared_preferences_linux/example/linux/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/shared_preferences/shared_preferences_linux/example/linux/main.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "my_application.h" int main(int argc, char** argv) { g_autoptr(MyApplication) app = my_application_new(); return g_application_run(G_APPLICATION(app), argc, argv); } ================================================ FILE: packages/shared_preferences/shared_preferences_linux/example/linux/my_application.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "my_application.h" #include #include "flutter/generated_plugin_registrant.h" struct _MyApplication { GtkApplication parent_instance; }; G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // Implements GApplication::activate. static void my_application_activate(GApplication* application) { GtkWindow* window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); gtk_header_bar_set_title(header_bar, "example"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); gtk_window_set_default_size(window, 1280, 720); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); FlView* view = fl_view_new(project); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view)); gtk_widget_grab_focus(GTK_WIDGET(view)); } static void my_application_class_init(MyApplicationClass* klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; } static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { return MY_APPLICATION(g_object_new(my_application_get_type(), nullptr)); } ================================================ FILE: packages/shared_preferences/shared_preferences_linux/example/linux/my_application.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef FLUTTER_MY_APPLICATION_H_ #define FLUTTER_MY_APPLICATION_H_ #include G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, GtkApplication) /** * my_application_new: * * Creates a new Flutter-based application. * * Returns: a new #MyApplication. */ MyApplication* my_application_new(); #endif // FLUTTER_MY_APPLICATION_H_ ================================================ FILE: packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml ================================================ name: shared_preferences_linux_example description: Demonstrates how to use the shared_preferences_linux plugin. publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter shared_preferences_linux: # When depending on this package from a real application you should use: # shared_preferences_linux: ^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: ../ dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/shared_preferences/shared_preferences_linux/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/shared_preferences/shared_preferences_linux/lib/shared_preferences_linux.dart ================================================ // Copyright 2013 The Flutter 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 'dart:convert' show json; import 'package:file/file.dart'; import 'package:file/local.dart'; import 'package:flutter/foundation.dart' show debugPrint, visibleForTesting; import 'package:path/path.dart' as path; import 'package:path_provider_linux/path_provider_linux.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; /// The Linux implementation of [SharedPreferencesStorePlatform]. /// /// This class implements the `package:shared_preferences` functionality for Linux. class SharedPreferencesLinux extends SharedPreferencesStorePlatform { /// Deprecated instance of [SharedPreferencesLinux]. /// Use [SharedPreferencesStorePlatform.instance] instead. @Deprecated('Use `SharedPreferencesStorePlatform.instance` instead.') static SharedPreferencesLinux instance = SharedPreferencesLinux(); /// Registers the Linux implementation. static void registerWith() { SharedPreferencesStorePlatform.instance = SharedPreferencesLinux(); } /// Local copy of preferences Map? _cachedPreferences; /// File system used to store to disk. Exposed for testing only. @visibleForTesting FileSystem fs = const LocalFileSystem(); /// The path_provider_linux instance used to find the support directory. @visibleForTesting PathProviderLinux pathProvider = PathProviderLinux(); /// Gets the file where the preferences are stored. Future _getLocalDataFile() async { final String? directory = await pathProvider.getApplicationSupportPath(); if (directory == null) { return null; } return fs.file(path.join(directory, 'shared_preferences.json')); } /// Gets the preferences from the stored file. Once read, the preferences are /// maintained in memory. Future> _readPreferences() async { if (_cachedPreferences != null) { return _cachedPreferences!; } Map preferences = {}; final File? localDataFile = await _getLocalDataFile(); if (localDataFile != null && localDataFile.existsSync()) { final String stringMap = localDataFile.readAsStringSync(); if (stringMap.isNotEmpty) { final Object? data = json.decode(stringMap); if (data is Map) { preferences = data.cast(); } } } _cachedPreferences = preferences; return preferences; } /// Writes the cached preferences to disk. Returns [true] if the operation /// succeeded. Future _writePreferences(Map preferences) async { try { final File? localDataFile = await _getLocalDataFile(); if (localDataFile == null) { debugPrint('Unable to determine where to write preferences.'); return false; } if (!localDataFile.existsSync()) { localDataFile.createSync(recursive: true); } final String stringMap = json.encode(preferences); localDataFile.writeAsStringSync(stringMap); } catch (e) { debugPrint('Error saving preferences to disk: $e'); return false; } return true; } @override Future clear() async { final Map preferences = await _readPreferences(); preferences.clear(); return _writePreferences(preferences); } @override Future> getAll() async { return _readPreferences(); } @override Future remove(String key) async { final Map preferences = await _readPreferences(); preferences.remove(key); return _writePreferences(preferences); } @override Future setValue(String valueType, String key, Object value) async { final Map preferences = await _readPreferences(); preferences[key] = value; return _writePreferences(preferences); } } ================================================ FILE: packages/shared_preferences/shared_preferences_linux/pubspec.yaml ================================================ 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.3 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: shared_preferences platforms: linux: dartPluginClass: SharedPreferencesLinux dependencies: file: ^6.0.0 flutter: sdk: flutter path: ^1.8.0 path_provider_linux: ^2.0.0 path_provider_platform_interface: ^2.0.0 shared_preferences_platform_interface: ^2.0.0 dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/shared_preferences/shared_preferences_linux/test/shared_preferences_linux_test.dart ================================================ // Copyright 2013 The Flutter 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/memory.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:path/path.dart' as path; import 'package:path_provider_linux/path_provider_linux.dart'; import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; import 'package:shared_preferences_linux/shared_preferences_linux.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; void main() { late MemoryFileSystem fs; late PathProviderLinux pathProvider; SharedPreferencesLinux.registerWith(); setUp(() { fs = MemoryFileSystem.test(); pathProvider = FakePathProviderLinux(); }); Future getFilePath() async { final String? directory = await pathProvider.getApplicationSupportPath(); return path.join(directory!, 'shared_preferences.json'); } Future writeTestFile(String value) async { fs.file(await getFilePath()) ..createSync(recursive: true) ..writeAsStringSync(value); } Future readTestFile() async { return fs.file(await getFilePath()).readAsStringSync(); } SharedPreferencesLinux getPreferences() { final SharedPreferencesLinux prefs = SharedPreferencesLinux(); prefs.fs = fs; prefs.pathProvider = pathProvider; return prefs; } test('registered instance', () { SharedPreferencesLinux.registerWith(); expect( SharedPreferencesStorePlatform.instance, isA()); }); test('getAll', () async { await writeTestFile('{"key1": "one", "key2": 2}'); final SharedPreferencesLinux prefs = getPreferences(); final Map values = await prefs.getAll(); expect(values, hasLength(2)); expect(values['key1'], 'one'); expect(values['key2'], 2); }); test('remove', () async { await writeTestFile('{"key1":"one","key2":2}'); final SharedPreferencesLinux prefs = getPreferences(); await prefs.remove('key2'); expect(await readTestFile(), '{"key1":"one"}'); }); test('setValue', () async { await writeTestFile('{}'); final SharedPreferencesLinux prefs = getPreferences(); await prefs.setValue('', 'key1', 'one'); await prefs.setValue('', 'key2', 2); expect(await readTestFile(), '{"key1":"one","key2":2}'); }); test('clear', () async { await writeTestFile('{"key1":"one","key2":2}'); final SharedPreferencesLinux prefs = getPreferences(); await prefs.clear(); expect(await readTestFile(), '{}'); }); } /// Fake implementation of PathProviderLinux that returns hard-coded paths, /// allowing tests to run on any platform. /// /// Note that this should only be used with an in-memory filesystem, as the /// path it returns is a root path that does not actually exist on Linux. class FakePathProviderLinux extends PathProviderPlatform implements PathProviderLinux { @override Future getApplicationSupportPath() async => r'/appsupport'; @override Future getTemporaryPath() async => null; @override Future getLibraryPath() async => null; @override Future getApplicationDocumentsPath() async => null; @override Future getDownloadsPath() async => null; } ================================================ FILE: packages/shared_preferences/shared_preferences_platform_interface/AUTHORS ================================================ # 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> ================================================ FILE: packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 2.1.0 * Adopts `plugin_platform_interface`. As a result, `isMock` is deprecated in favor of the now-standard `MockPlatformInterfaceMixin`. ## 2.0.0 * Migrate to null safety. ## 1.0.5 * Update Flutter SDK constraint. ## 1.0.4 * Update lower bound of dart dependency to 2.1.0. ## 1.0.3 * Make the pedantic dev_dependency explicit. ## 1.0.2 * Adds a `shared_preferences_macos` package. ## 1.0.1 * Remove the deprecated `author:` field from pubspec.yaml ## 1.0.0 * Initial release. Contains the interface and an implementation based on method channels. ================================================ FILE: packages/shared_preferences/shared_preferences_platform_interface/LICENSE ================================================ 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. ================================================ FILE: packages/shared_preferences/shared_preferences_platform_interface/README.md ================================================ # shared_preferences_platform_interface A common platform interface for the [`shared_preferences`][1] plugin. This interface allows platform-specific implementations of the `shared_preferences` plugin, as well as the plugin itself, to ensure they are supporting the same interface. # Usage To implement a new platform-specific implementation of `shared_preferences`, extend [`SharedPreferencesPlatform`][2] with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `SharedPreferencesLoader` by calling the `SharedPreferencesPlatform.loader` setter. # Note on breaking changes Strongly prefer non-breaking changes (such as adding a method to the interface) over breaking changes for this package. See https://flutter.dev/go/platform-interface-breaking-changes for a discussion on why a less-clean interface is preferable to a breaking change. [1]: ../shared_preferences [2]: lib/shared_preferences_platform_interface.dart ================================================ FILE: packages/shared_preferences/shared_preferences_platform_interface/lib/method_channel_shared_preferences.dart ================================================ // Copyright 2013 The Flutter 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 'shared_preferences_platform_interface.dart'; const MethodChannel _kChannel = MethodChannel('plugins.flutter.io/shared_preferences'); /// Wraps NSUserDefaults (on iOS) and SharedPreferences (on Android), providing /// a persistent store for simple data. /// /// Data is persisted to disk asynchronously. class MethodChannelSharedPreferencesStore extends SharedPreferencesStorePlatform { @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; } } ================================================ FILE: packages/shared_preferences/shared_preferences_platform_interface/lib/shared_preferences_platform_interface.dart ================================================ // Copyright 2013 The Flutter 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:plugin_platform_interface/plugin_platform_interface.dart'; import 'method_channel_shared_preferences.dart'; /// The interface that implementations of shared_preferences must implement. /// /// Platform implementations should extend this class rather than implement it as `shared_preferences` /// does not consider newly added methods to be breaking changes. Extending this class /// (using `extends`) ensures that the subclass will get the default implementation, while /// platform implementations that `implements` this interface will be broken by newly added /// [SharedPreferencesStorePlatform] methods. abstract class SharedPreferencesStorePlatform extends PlatformInterface { /// Constructs a SharedPreferencesStorePlatform. SharedPreferencesStorePlatform() : super(token: _token); static final Object _token = Object(); /// The default instance of [SharedPreferencesStorePlatform] to use. /// /// Defaults to [MethodChannelSharedPreferencesStore]. static SharedPreferencesStorePlatform get instance => _instance; /// Platform-specific plugins should set this with their own platform-specific /// class that extends [SharedPreferencesStorePlatform] when they register themselves. static set instance(SharedPreferencesStorePlatform instance) { if (!instance.isMock) { PlatformInterface.verify(instance, _token); } _instance = instance; } static SharedPreferencesStorePlatform _instance = MethodChannelSharedPreferencesStore(); /// Only mock implementations should set this to true. /// /// Mockito mocks are implementing this class with `implements` which is forbidden for anything /// other than mocks (see class docs). This property provides a backdoor for mockito mocks to /// skip the verification that the class isn't implemented with `implements`. @visibleForTesting @Deprecated('Use MockPlatformInterfaceMixin instead') bool get isMock => false; /// Removes the value associated with the [key]. Future remove(String key); /// Stores the [value] associated with the [key]. /// /// The [valueType] must match the type of [value] as follows: /// /// * Value type "Bool" must be passed if the value is of type `bool`. /// * Value type "Double" must be passed if the value is of type `double`. /// * Value type "Int" must be passed if the value is of type `int`. /// * Value type "String" must be passed if the value is of type `String`. /// * Value type "StringList" must be passed if the value is of type `List`. Future setValue(String valueType, String key, Object value); /// Removes all keys and values in the store. Future clear(); /// Returns all key/value pairs persisted in this store. Future> getAll(); } /// Stores data in memory. /// /// Data does not persist across application restarts. This is useful in unit-tests. class InMemorySharedPreferencesStore extends SharedPreferencesStorePlatform { /// Instantiates an empty in-memory preferences store. InMemorySharedPreferencesStore.empty() : _data = {}; /// Instantiates an in-memory preferences store containing a copy of [data]. InMemorySharedPreferencesStore.withData(Map data) : _data = Map.from(data); final Map _data; @override Future clear() async { _data.clear(); return true; } @override Future> getAll() async { return Map.from(_data); } @override Future remove(String key) async { _data.remove(key); return true; } @override Future setValue(String valueType, String key, Object value) async { _data[key] = value; return true; } } ================================================ FILE: packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml ================================================ name: shared_preferences_platform_interface description: A common platform interface for the shared_preferences plugin. repository: https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_platform_interface issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 version: 2.1.0 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.1.0 dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/shared_preferences/shared_preferences_platform_interface/test/method_channel_shared_preferences_test.dart ================================================ // Copyright 2013 The Flutter 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_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', ); const Map kTestValues = { 'flutter.String': 'hello world', 'flutter.Bool': true, 'flutter.Int': 42, 'flutter.Double': 3.14159, 'flutter.StringList': ['foo', 'bar'], }; late InMemorySharedPreferencesStore testData; final List log = []; late MethodChannelSharedPreferencesStore store; setUp(() async { testData = InMemorySharedPreferencesStore.empty(); Map getArgumentDictionary(MethodCall call) { return (call.arguments as Map) .cast(); } _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, (MethodCall methodCall) async { log.add(methodCall); if (methodCall.method == 'getAll') { return testData.getAll(); } if (methodCall.method == 'remove') { final Map arguments = getArgumentDictionary(methodCall); final String key = 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 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}'); }); store = MethodChannelSharedPreferencesStore(); log.clear(); }); tearDown(() async { await testData.clear(); }); test('getAll', () async { testData = InMemorySharedPreferencesStore.withData(kTestValues); expect(await store.getAll(), kTestValues); expect(log.single.method, 'getAll'); }); test('remove', () async { 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 { 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 { testData = InMemorySharedPreferencesStore.withData(kTestValues); expect(await testData.getAll(), isNotEmpty); expect(await store.clear(), true); expect(await testData.getAll(), isEmpty); expect(log.single.method, 'clear'); }); }); } /// 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; ================================================ FILE: packages/shared_preferences/shared_preferences_platform_interface/test/shared_preferences_platform_interface_test.dart ================================================ // Copyright 2013 The Flutter 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:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group(SharedPreferencesStorePlatform, () { test('disallows implementing interface', () { expect(() { SharedPreferencesStorePlatform.instance = IllegalImplementation(); }, // In versions of `package:plugin_platform_interface` prior to fixing // https://github.com/flutter/flutter/issues/109339, an attempt to // implement a platform interface using `implements` would sometimes // throw a `NoSuchMethodError` and other times throw an // `AssertionError`. After the issue is fixed, an `AssertionError` will // always be thrown. For the purpose of this test, we don't really care // what exception is thrown, so just allow any exception. throwsA(anything)); }); test('supports MockPlatformInterfaceMixin', () { SharedPreferencesStorePlatform.instance = ModernMockImplementation(); }); test('still supports legacy isMock', () { SharedPreferencesStorePlatform.instance = LegacyIsMockImplementation(); }); }); } /// An implementation using `implements` that isn't a mock, which isn't allowed. class IllegalImplementation implements SharedPreferencesStorePlatform { // Intentionally declare self as not a mock to trigger the // compliance check. @override bool get isMock => false; @override Future clear() { throw UnimplementedError(); } @override Future> getAll() { throw UnimplementedError(); } @override Future remove(String key) { throw UnimplementedError(); } @override Future setValue(String valueType, String key, Object value) { throw UnimplementedError(); } } class LegacyIsMockImplementation implements SharedPreferencesStorePlatform { @override bool get isMock => true; @override Future clear() { throw UnimplementedError(); } @override Future> getAll() { throw UnimplementedError(); } @override Future remove(String key) { throw UnimplementedError(); } @override Future setValue(String valueType, String key, Object value) { throw UnimplementedError(); } } class ModernMockImplementation with MockPlatformInterfaceMixin implements SharedPreferencesStorePlatform { @override bool get isMock => false; @override Future clear() { throw UnimplementedError(); } @override Future> getAll() { throw UnimplementedError(); } @override Future remove(String key) { throw UnimplementedError(); } @override Future setValue(String valueType, String key, Object value) { throw UnimplementedError(); } } ================================================ FILE: packages/shared_preferences/shared_preferences_web/AUTHORS ================================================ # 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. 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> ================================================ FILE: packages/shared_preferences/shared_preferences_web/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 2.0.4 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 2.0.3 * Fixes newly enabled analyzer options. * Removes dependency on `meta`. ## 2.0.2 * Add `implements` to pubspec. ## 2.0.1 * Updated installation instructions in README. * Move tests to `example` directory, so they run as integration_tests with `flutter drive`. ## 2.0.0 * Migrate to null-safety. ## 0.1.2+8 * Update Flutter SDK constraint. ## 0.1.2+7 * Removed Android folder from `shared_preferences_web`. ## 0.1.2+6 * Update lower bound of dart dependency to 2.1.0. ## 0.1.2+5 * 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.1.2+4 * Make the pedantic dev_dependency explicit. ## 0.1.2+3 * Bump gradle version to avoid bugs with android projects # 0.1.2+2 * Remove unused onMethodCall method. # 0.1.2+1 * Add an android/ folder with no-op implementation to workaround https://github.com/flutter/flutter/issues/46898. # 0.1.2 * Bump lower constraint on Flutter version. * Add stub podspec file. # 0.1.1 * Adds a `shared_preferences_macos` package. # 0.1.0+1 - Remove the deprecated `author:` field from pubspec.yaml - Require Flutter SDK 1.10.0 or greater. # 0.1.0 - Initial release. ================================================ FILE: packages/shared_preferences/shared_preferences_web/LICENSE ================================================ 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. ================================================ FILE: packages/shared_preferences/shared_preferences_web/README.md ================================================ # shared\_preferences\_web The web 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 ================================================ FILE: packages/shared_preferences/shared_preferences_web/example/README.md ================================================ # 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. ## Testing This package uses `package:integration_test` to run its tests in a web browser. See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/wiki/Plugin-Tests#web-tests) in the Flutter wiki for instructions to setup and run the tests in this package. Check [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) for more info. ================================================ FILE: packages/shared_preferences/shared_preferences_web/example/integration_test/shared_preferences_web_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:convert' show json; import 'dart:html' as html; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:shared_preferences_platform_interface/method_channel_shared_preferences.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; import 'package:shared_preferences_web/shared_preferences_web.dart'; const Map kTestValues = { 'flutter.String': 'hello world', 'flutter.Bool': true, 'flutter.Int': 42, 'flutter.Double': 3.14159, 'flutter.StringList': ['foo', 'bar'], }; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('SharedPreferencesPlugin', () { setUp(() { html.window.localStorage.clear(); }); testWidgets('registers itself', (WidgetTester tester) async { SharedPreferencesStorePlatform.instance = MethodChannelSharedPreferencesStore(); expect(SharedPreferencesStorePlatform.instance, isNot(isA())); SharedPreferencesPlugin.registerWith(null); expect(SharedPreferencesStorePlatform.instance, isA()); }); testWidgets('getAll', (WidgetTester tester) async { final SharedPreferencesPlugin store = SharedPreferencesPlugin(); expect(await store.getAll(), isEmpty); html.window.localStorage['flutter.testKey'] = '"test value"'; html.window.localStorage['unprefixed_key'] = 'not a flutter value'; final Map allData = await store.getAll(); expect(allData, hasLength(1)); expect(allData['flutter.testKey'], 'test value'); }); testWidgets('remove', (WidgetTester tester) async { final SharedPreferencesPlugin store = SharedPreferencesPlugin(); html.window.localStorage['flutter.testKey'] = '"test value"'; expect(html.window.localStorage['flutter.testKey'], isNotNull); expect(await store.remove('flutter.testKey'), isTrue); expect(html.window.localStorage['flutter.testKey'], isNull); expect( () => store.remove('unprefixed'), throwsA(isA()), ); }); testWidgets('setValue', (WidgetTester tester) async { final SharedPreferencesPlugin store = SharedPreferencesPlugin(); for (final String key in kTestValues.keys) { final dynamic value = kTestValues[key]; expect(await store.setValue(key.split('.').last, key, value), true); } expect(html.window.localStorage.keys, hasLength(kTestValues.length)); for (final String key in html.window.localStorage.keys) { expect(html.window.localStorage[key], json.encode(kTestValues[key])); } // Check that generics are preserved. expect((await store.getAll())['flutter.StringList'], isA>()); // Invalid key format. expect( () => store.setValue('String', 'unprefixed', 'hello'), throwsA(isA()), ); }); testWidgets('clear', (WidgetTester tester) async { final SharedPreferencesPlugin store = SharedPreferencesPlugin(); html.window.localStorage['flutter.testKey1'] = '"test value"'; html.window.localStorage['flutter.testKey2'] = '42'; html.window.localStorage['unprefixed_key'] = 'not a flutter value'; expect(await store.clear(), isTrue); expect(html.window.localStorage.keys.single, 'unprefixed_key'); }); }); } ================================================ FILE: packages/shared_preferences/shared_preferences_web/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } /// App for testing class MyApp extends StatefulWidget { /// Default Constructor const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override Widget build(BuildContext context) { return const Directionality( textDirection: TextDirection.ltr, child: Text('Testing... Look at the console output for results!'), ); } } ================================================ FILE: packages/shared_preferences/shared_preferences_web/example/pubspec.yaml ================================================ name: shared_preferences_web_integration_tests publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter shared_preferences_platform_interface: ^2.0.0 shared_preferences_web: path: ../ dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter js: ^0.6.3 ================================================ FILE: packages/shared_preferences/shared_preferences_web/example/run_test.sh ================================================ #!/usr/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. if pgrep -lf chromedriver > /dev/null; then echo "chromedriver is running." if [ $# -eq 0 ]; then echo "No target specified, running all tests..." find integration_test/ -iname *_test.dart | xargs -n1 -i -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target='{}' else echo "Running test target: $1..." set -x flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target=$1 fi else echo "chromedriver is not running." echo "Please, check the README.md for instructions on how to use run_test.sh" fi ================================================ FILE: packages/shared_preferences/shared_preferences_web/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/shared_preferences/shared_preferences_web/example/web/index.html ================================================ example ================================================ FILE: packages/shared_preferences/shared_preferences_web/lib/shared_preferences_web.dart ================================================ // Copyright 2013 The Flutter 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 'dart:convert' show json; import 'dart:html' as html; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; /// The web implementation of [SharedPreferencesStorePlatform]. /// /// This class implements the `package:shared_preferences` functionality for the web. class SharedPreferencesPlugin extends SharedPreferencesStorePlatform { /// Registers this class as the default instance of [SharedPreferencesStorePlatform]. static void registerWith(Registrar? registrar) { SharedPreferencesStorePlatform.instance = SharedPreferencesPlugin(); } @override Future clear() async { // IMPORTANT: Do not use html.window.localStorage.clear() as that will // remove _all_ local data, not just the keys prefixed with // "flutter." _storedFlutterKeys.forEach(html.window.localStorage.remove); return true; } @override Future> getAll() async { final Map allData = {}; for (final String key in _storedFlutterKeys) { allData[key] = _decodeValue(html.window.localStorage[key]!); } return allData; } @override Future remove(String key) async { _checkPrefix(key); html.window.localStorage.remove(key); return true; } @override Future setValue(String valueType, String key, Object? value) async { _checkPrefix(key); html.window.localStorage[key] = _encodeValue(value); return true; } void _checkPrefix(String key) { if (!key.startsWith('flutter.')) { throw FormatException( 'Shared preferences keys must start with prefix "flutter.".', key, 0, ); } } Iterable get _storedFlutterKeys { return html.window.localStorage.keys .where((String key) => key.startsWith('flutter.')); } String _encodeValue(Object? value) { return json.encode(value); } Object _decodeValue(String encodedValue) { final Object? decodedValue = json.decode(encodedValue); if (decodedValue is List) { // JSON does not preserve generics. The encode/decode roundtrip is // `List` => JSON => `List`. We have to explicitly // restore the RTTI. return decodedValue.cast(); } return decodedValue!; } } ================================================ FILE: packages/shared_preferences/shared_preferences_web/pubspec.yaml ================================================ name: shared_preferences_web description: Web platform implementation of shared_preferences repository: https://github.com/flutter/plugins/tree/main/packages/shared_preferences/shared_preferences_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22 version: 2.0.4 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: shared_preferences platforms: web: pluginClass: SharedPreferencesPlugin fileName: shared_preferences_web.dart dependencies: flutter: sdk: flutter flutter_web_plugins: sdk: flutter shared_preferences_platform_interface: ^2.0.0 dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/shared_preferences/shared_preferences_web/test/README.md ================================================ ## test This package uses integration tests for testing. See `example/README.md` for more info. ================================================ FILE: packages/shared_preferences/shared_preferences_web/test/tests_exist_elsewhere_test.dart ================================================ // Copyright 2013 The Flutter 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: avoid_print import 'package:flutter_test/flutter_test.dart'; void main() { test('Tell the user where to find the real tests', () { print('---'); print('This package uses integration_test for its tests.'); print('See `example/README.md` for more info.'); print('---'); }); } ================================================ FILE: packages/shared_preferences/shared_preferences_windows/.metadata ================================================ # 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: df90bb5fd64e2066594151b9e311d45cd687a80c channel: master project_type: plugin ================================================ FILE: packages/shared_preferences/shared_preferences_windows/AUTHORS ================================================ # 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> ================================================ FILE: packages/shared_preferences/shared_preferences_windows/CHANGELOG.md ================================================ ## 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. * 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 * Deprecated `SharedPreferencesWindows.instance` in favor of `SharedPreferencesStorePlatform.instance`. ## 2.0.4 * Removes dependency on `meta`. ## 2.0.3 * Removed obsolete `pluginClass: none` from pubpsec. * Fixes newly enabled analyzer options. ## 2.0.2 * Updated installation instructions in README. ## 2.0.1 * Add `implements` to pubspec.yaml. * Add `registerWith` to the Dart main class. ## 2.0.0 * Migrate to null-safety. ## 0.0.2+3 * Remove 'ffi' dependency. ## 0.0.2+2 * Relax 'ffi' version constraint. ## 0.0.2+1 * Update Flutter SDK constraint. ## 0.0.2 * Update integration test examples to use `testWidgets` instead of `test`. ## 0.0.1+3 * Remove unused `test` dependency. ## 0.0.1+2 * Check in windows/ directory for example/ ## 0.0.1+1 * Add iOS stub for compatibility with 1.17 and earlier. ## 0.0.1 * Initial release to support shared_preferences on Windows. ================================================ FILE: packages/shared_preferences/shared_preferences_windows/LICENSE ================================================ 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. ================================================ FILE: packages/shared_preferences/shared_preferences_windows/README.md ================================================ # shared\_preferences\_windows The Windows 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 ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/.metadata ================================================ # 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: df90bb5fd64e2066594151b9e311d45cd687a80c channel: master project_type: app ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/AUTHORS ================================================ # 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. 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> ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/LICENSE ================================================ 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. ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/README.md ================================================ # 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. ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/integration_test/shared_preferences_test.dart ================================================ // Copyright 2013 The Flutter 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_windows/shared_preferences_windows.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('SharedPreferencesWindows', () { 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'], }; testWidgets('reading', (WidgetTester _) async { final SharedPreferencesWindows preferences = SharedPreferencesWindows(); preferences.clear(); final Map values = await preferences.getAll(); expect(values['String'], isNull); expect(values['bool'], isNull); expect(values['int'], isNull); expect(values['double'], isNull); expect(values['List'], isNull); }); testWidgets('writing', (WidgetTester _) async { final SharedPreferencesWindows preferences = SharedPreferencesWindows(); preferences.clear(); await preferences.setValue( 'String', 'String', kTestValues2['flutter.String']!); await preferences.setValue('Bool', 'bool', kTestValues2['flutter.bool']!); await preferences.setValue('Int', 'int', kTestValues2['flutter.int']!); await preferences.setValue( 'Double', 'double', kTestValues2['flutter.double']!); await preferences.setValue( 'StringList', 'List', kTestValues2['flutter.List']!); final Map values = await preferences.getAll(); expect(values['String'], kTestValues2['flutter.String']); expect(values['bool'], kTestValues2['flutter.bool']); expect(values['int'], kTestValues2['flutter.int']); expect(values['double'], kTestValues2['flutter.double']); expect(values['List'], kTestValues2['flutter.List']); }); testWidgets('removing', (WidgetTester _) async { final SharedPreferencesWindows preferences = SharedPreferencesWindows(); preferences.clear(); const String key = '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 { final SharedPreferencesWindows preferences = SharedPreferencesWindows(); preferences.clear(); 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); }); }); } ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'package:flutter/material.dart'; import 'package:shared_preferences_windows/shared_preferences_windows.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 SharedPreferencesWindows prefs = SharedPreferencesWindows(); late Future _counter; Future _incrementCounter() async { final Map values = await prefs.getAll(); final int counter = (values['counter'] as int? ?? 0) + 1; setState(() { _counter = prefs.setValue('Int', 'counter', counter).then((bool success) { return counter; }); }); } @override void initState() { super.initState(); _counter = prefs.getAll().then((Map values) { return values['counter'] 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.none: case ConnectionState.waiting: return const CircularProgressIndicator(); case ConnectionState.active: case ConnectionState.done: 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), ), ); } } ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml ================================================ name: shared_preferences_windows_example description: Demonstrates how to use the shared_preferences_windows plugin. publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter shared_preferences_windows: # When depending on this package from a real application you should use: # shared_preferences_windows: ^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: ../ dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/windows/.gitignore ================================================ flutter/ephemeral/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/windows/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(example LANGUAGES CXX) set(BINARY_NAME "example") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" CACHE STRING "" FORCE) else() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() endif() set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") # Use Unicode for all projects. add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") target_compile_options(${TARGET} PRIVATE /EHsc) target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build add_subdirectory("runner") # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # Support files are copied into place next to the executable, so that it can # run in place. This is done instead of making a separate bundle (as on Linux) # so that building and running from within Visual Studio will work. set(BUILD_BUNDLE_DIR "$") # Make the "install" step default, as it's required to run. set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" CONFIGURATIONS Profile;Release COMPONENT Runtime) ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/windows/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "flutter_export.h" "flutter_windows.h" "flutter_messenger.h" "flutter_plugin_registrar.h" ) list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") add_dependencies(flutter flutter_assemble) # === Wrapper === list(APPEND CPP_WRAPPER_SOURCES_CORE "core_implementations.cc" "standard_codec.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_PLUGIN "plugin_registrar.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_APP "flutter_engine.cc" "flutter_view_controller.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") # Wrapper sources needed for a plugin. add_library(flutter_wrapper_plugin STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ) apply_standard_settings(flutter_wrapper_plugin) set_target_properties(flutter_wrapper_plugin PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(flutter_wrapper_plugin PROPERTIES CXX_VISIBILITY_PRESET hidden) target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) target_include_directories(flutter_wrapper_plugin PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_plugin flutter_assemble) # Wrapper sources needed for the runner. add_library(flutter_wrapper_app STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_APP} ) apply_standard_settings(flutter_wrapper_app) target_link_libraries(flutter_wrapper_app PUBLIC flutter) target_include_directories(flutter_wrapper_app PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_app flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" windows-x64 $ ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ) ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/windows/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/windows/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(runner LANGUAGES CXX) add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" "run_loop.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) apply_standard_settings(${BINARY_NAME}) target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") add_dependencies(${BINARY_NAME} flutter_assemble) ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/windows/runner/Runner.rc ================================================ // Microsoft Visual C++ generated resource script. // #pragma code_page(65001) #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APP_ICON ICON "resources\\app_icon.ico" ///////////////////////////////////////////////////////////////////////////// // // Version // #ifdef FLUTTER_BUILD_NUMBER #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER #else #define VERSION_AS_NUMBER 1,0,0 #endif #ifdef FLUTTER_BUILD_NAME #define VERSION_AS_STRING #FLUTTER_BUILD_NAME #else #define VERSION_AS_STRING "1.0.0" #endif VS_VERSION_INFO VERSIONINFO FILEVERSION VERSION_AS_NUMBER PRODUCTVERSION VERSION_AS_NUMBER FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "com.example" "\0" VALUE "FileDescription", "A new Flutter project." "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "example" "\0" VALUE "LegalCopyright", "Copyright (C) 2020 com.example. All rights reserved." "\0" VALUE "OriginalFilename", "example.exe" "\0" VALUE "ProductName", "example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/windows/runner/flutter_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "flutter_window.h" #include #include "flutter/generated_plugin_registrant.h" FlutterWindow::FlutterWindow(RunLoop* run_loop, const flutter::DartProject& project) : run_loop_(run_loop), project_(project) {} FlutterWindow::~FlutterWindow() {} bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opporutunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) { return *result; } } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); } ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/windows/runner/flutter_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_ #include #include #include #include "run_loop.h" #include "win32_window.h" // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { public: // Creates a new FlutterWindow driven by the |run_loop|, hosting a // Flutter view running |project|. explicit FlutterWindow(RunLoop* run_loop, const flutter::DartProject& project); virtual ~FlutterWindow(); protected: // Win32Window: bool OnCreate() override; void OnDestroy() override; LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; private: // The run loop driving events for this window. RunLoop* run_loop_; // The project to run. flutter::DartProject project_; // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; }; #endif // RUNNER_FLUTTER_WINDOW_H_ ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/windows/runner/main.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include "flutter_window.h" #include "run_loop.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t* command_line, _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { CreateAndAttachConsole(); } // Initialize COM, so that it is available for use in the library and/or // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); RunLoop run_loop; flutter::DartProject project(L"data"); FlutterWindow window(&run_loop, project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.CreateAndShow(L"example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); run_loop.Run(); ::CoUninitialize(); return EXIT_SUCCESS; } ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/windows/runner/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Runner.rc // #define IDI_APP_ICON 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/windows/runner/run_loop.cpp ================================================ // Copyright 2013 The Flutter 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 "run_loop.h" #include #include RunLoop::RunLoop() {} RunLoop::~RunLoop() {} void RunLoop::Run() { bool keep_running = true; TimePoint next_flutter_event_time = TimePoint::clock::now(); while (keep_running) { std::chrono::nanoseconds wait_duration = std::max(std::chrono::nanoseconds(0), next_flutter_event_time - TimePoint::clock::now()); ::MsgWaitForMultipleObjects( 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000), QS_ALLINPUT); bool processed_events = false; MSG message; // All pending Windows messages must be processed; MsgWaitForMultipleObjects // won't return again for items left in the queue after PeekMessage. while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) { processed_events = true; if (message.message == WM_QUIT) { keep_running = false; break; } ::TranslateMessage(&message); ::DispatchMessage(&message); // Allow Flutter to process messages each time a Windows message is // processed, to prevent starvation. next_flutter_event_time = std::min(next_flutter_event_time, ProcessFlutterMessages()); } // If the PeekMessage loop didn't run, process Flutter messages. if (!processed_events) { next_flutter_event_time = std::min(next_flutter_event_time, ProcessFlutterMessages()); } } } void RunLoop::RegisterFlutterInstance( flutter::FlutterEngine* flutter_instance) { flutter_instances_.insert(flutter_instance); } void RunLoop::UnregisterFlutterInstance( flutter::FlutterEngine* flutter_instance) { flutter_instances_.erase(flutter_instance); } RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { TimePoint next_event_time = TimePoint::max(); for (auto instance : flutter_instances_) { std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); if (wait_duration != std::chrono::nanoseconds::max()) { next_event_time = std::min(next_event_time, TimePoint::clock::now() + wait_duration); } } return next_event_time; } ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/windows/runner/run_loop.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_RUN_LOOP_H_ #define RUNNER_RUN_LOOP_H_ #include #include #include // A runloop that will service events for Flutter instances as well // as native messages. class RunLoop { public: RunLoop(); ~RunLoop(); // Prevent copying RunLoop(RunLoop const&) = delete; RunLoop& operator=(RunLoop const&) = delete; // Runs the run loop until the application quits. void Run(); // Registers the given Flutter instance for event servicing. void RegisterFlutterInstance(flutter::FlutterEngine* flutter_instance); // Unregisters the given Flutter instance from event servicing. void UnregisterFlutterInstance(flutter::FlutterEngine* flutter_instance); private: using TimePoint = std::chrono::steady_clock::time_point; // Processes all currently pending messages for registered Flutter instances. TimePoint ProcessFlutterMessages(); std::set flutter_instances_; }; #endif // RUNNER_RUN_LOOP_H_ ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/windows/runner/runner.exe.manifest ================================================ PerMonitorV2 ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/windows/runner/utils.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "utils.h" #include #include #include #include #include void CreateAndAttachConsole() { if (::AllocConsole()) { FILE* unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } if (freopen_s(&unused, "CONOUT$", "w", stderr)) { _dup2(_fileno(stdout), 2); } std::ios::sync_with_stdio(); FlutterDesktopResyncOutputStreams(); } } ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/windows/runner/utils.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_UTILS_H_ #define RUNNER_UTILS_H_ // Creates a console for the process, and redirects stdout and stderr to // it for both the runner and the Flutter library. void CreateAndAttachConsole(); #endif // RUNNER_UTILS_H_ ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/windows/runner/win32_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "win32_window.h" #include #include "resource.h" namespace { constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); // Scale helper to convert logical scaler values to physical using passed in // scale factor int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); if (!user32_module) { return; } auto enable_non_client_dpi_scaling = reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); FreeLibrary(user32_module); } } } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; // Returns the singleton registar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; } // Returns the name of the window class, registering the class if it hasn't // previously been registered. const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; bool class_registered_ = false; }; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; RegisterClass(&window_class); class_registered_ = true; } return kWindowClassName; } void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, const Size& size) { Destroy(); const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), static_cast(origin.y)}; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } return OnCreate(); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { case WM_DESTROY: window_handle_ = nullptr; Destroy(); if (quit_on_close_) { PostQuitMessage(0); } return 0; case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left; LONG newHeight = newRectSize->bottom - newRectSize->top; SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_SIZE: RECT rect = GetClientArea(); if (child_content_ != nullptr) { // Size and position the child window. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); } return 0; case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_); } return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } void Win32Window::Destroy() { OnDestroy(); if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, true); SetFocus(child_content_); } RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } void Win32Window::OnDestroy() { // No-op; provided for subclasses. } ================================================ FILE: packages/shared_preferences/shared_preferences_windows/example/windows/runner/win32_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_ #include #include #include #include // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { public: struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) : width(width), height(height) {} }; Win32Window(); virtual ~Win32Window(); // Creates and shows a win32 window with |title| and position and size using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a // consistent size to will treat the width height passed in to this function // as logical pixels and scale to appropriate for the default monitor. Returns // true if the window was created successfully. bool CreateAndShow(const std::wstring& title, const Point& origin, const Size& size); // Release OS resources associated with window. void Destroy(); // Inserts |content| into the window tree. void SetChildContent(HWND content); // Returns the backing Window handle to enable clients to set icon and other // window properties. Returns nullptr if the window has been destroyed. HWND GetHandle(); // If true, closing this window will quit the application. void SetQuitOnClose(bool quit_on_close); // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Called when CreateAndShow is called, allowing subclass window-related // setup. Subclasses should return false if setup fails. virtual bool OnCreate(); // Called when Destroy is called. virtual void OnDestroy(); private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically // responsponds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; bool quit_on_close_ = false; // window handle for top level window. HWND window_handle_ = nullptr; // window handle for hosted content. HWND child_content_ = nullptr; }; #endif // RUNNER_WIN32_WINDOW_H_ ================================================ FILE: packages/shared_preferences/shared_preferences_windows/lib/shared_preferences_windows.dart ================================================ // Copyright 2013 The Flutter 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 'dart:convert' show json; import 'package:file/file.dart'; import 'package:file/local.dart'; import 'package:flutter/foundation.dart' show debugPrint, visibleForTesting; import 'package:path/path.dart' as path; import 'package:path_provider_windows/path_provider_windows.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; /// The Windows implementation of [SharedPreferencesStorePlatform]. /// /// This class implements the `package:shared_preferences` functionality for Windows. class SharedPreferencesWindows extends SharedPreferencesStorePlatform { /// Deprecated instance of [SharedPreferencesWindows]. /// Use [SharedPreferencesStorePlatform.instance] instead. @Deprecated('Use `SharedPreferencesStorePlatform.instance` instead.') static SharedPreferencesWindows instance = SharedPreferencesWindows(); /// Registers the Windows implementation. static void registerWith() { SharedPreferencesStorePlatform.instance = SharedPreferencesWindows(); } /// File system used to store to disk. Exposed for testing only. @visibleForTesting FileSystem fs = const LocalFileSystem(); /// The path_provider_windows instance used to find the support directory. @visibleForTesting PathProviderWindows pathProvider = PathProviderWindows(); /// Local copy of preferences Map? _cachedPreferences; /// Cached file for storing preferences. File? _localDataFilePath; /// Gets the file where the preferences are stored. Future _getLocalDataFile() async { if (_localDataFilePath != null) { return _localDataFilePath!; } final String? directory = await pathProvider.getApplicationSupportPath(); if (directory == null) { return null; } return _localDataFilePath = fs.file(path.join(directory, 'shared_preferences.json')); } /// Gets the preferences from the stored file. Once read, the preferences are /// maintained in memory. Future> _readPreferences() async { if (_cachedPreferences != null) { return _cachedPreferences!; } Map preferences = {}; final File? localDataFile = await _getLocalDataFile(); if (localDataFile != null && localDataFile.existsSync()) { final String stringMap = localDataFile.readAsStringSync(); if (stringMap.isNotEmpty) { final Object? data = json.decode(stringMap); if (data is Map) { preferences = data.cast(); } } } _cachedPreferences = preferences; return preferences; } /// Writes the cached preferences to disk. Returns [true] if the operation /// succeeded. Future _writePreferences(Map preferences) async { try { final File? localDataFile = await _getLocalDataFile(); if (localDataFile == null) { debugPrint('Unable to determine where to write preferences.'); return false; } if (!localDataFile.existsSync()) { localDataFile.createSync(recursive: true); } final String stringMap = json.encode(preferences); localDataFile.writeAsStringSync(stringMap); } catch (e) { debugPrint('Error saving preferences to disk: $e'); return false; } return true; } @override Future clear() async { final Map preferences = await _readPreferences(); preferences.clear(); return _writePreferences(preferences); } @override Future> getAll() async { return _readPreferences(); } @override Future remove(String key) async { final Map preferences = await _readPreferences(); preferences.remove(key); return _writePreferences(preferences); } @override Future setValue(String valueType, String key, Object value) async { final Map preferences = await _readPreferences(); preferences[key] = value; return _writePreferences(preferences); } } ================================================ FILE: packages/shared_preferences/shared_preferences_windows/pubspec.yaml ================================================ 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.3 environment: sdk: '>=2.12.0 <3.0.0' flutter: ">=3.0.0" flutter: plugin: implements: shared_preferences platforms: windows: dartPluginClass: SharedPreferencesWindows dependencies: file: ^6.0.0 flutter: sdk: flutter path: ^1.8.0 path_provider_platform_interface: ^2.0.0 path_provider_windows: ^2.0.0 shared_preferences_platform_interface: ^2.0.0 dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/shared_preferences/shared_preferences_windows/test/shared_preferences_windows_test.dart ================================================ // Copyright 2013 The Flutter 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/memory.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:path/path.dart' as path; import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; import 'package:path_provider_windows/path_provider_windows.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; import 'package:shared_preferences_windows/shared_preferences_windows.dart'; void main() { late MemoryFileSystem fileSystem; late PathProviderWindows pathProvider; setUp(() { fileSystem = MemoryFileSystem.test(); pathProvider = FakePathProviderWindows(); }); Future getFilePath() async { final String? directory = await pathProvider.getApplicationSupportPath(); return path.join(directory!, 'shared_preferences.json'); } Future writeTestFile(String value) async { fileSystem.file(await getFilePath()) ..createSync(recursive: true) ..writeAsStringSync(value); } Future readTestFile() async { return fileSystem.file(await getFilePath()).readAsStringSync(); } SharedPreferencesWindows getPreferences() { final SharedPreferencesWindows prefs = SharedPreferencesWindows(); prefs.fs = fileSystem; prefs.pathProvider = pathProvider; return prefs; } test('registered instance', () { SharedPreferencesWindows.registerWith(); expect(SharedPreferencesStorePlatform.instance, isA()); }); test('getAll', () async { await writeTestFile('{"key1": "one", "key2": 2}'); final SharedPreferencesWindows prefs = getPreferences(); final Map values = await prefs.getAll(); expect(values, hasLength(2)); expect(values['key1'], 'one'); expect(values['key2'], 2); }); test('remove', () async { await writeTestFile('{"key1":"one","key2":2}'); final SharedPreferencesWindows prefs = getPreferences(); await prefs.remove('key2'); expect(await readTestFile(), '{"key1":"one"}'); }); test('setValue', () async { await writeTestFile('{}'); final SharedPreferencesWindows prefs = getPreferences(); await prefs.setValue('', 'key1', 'one'); await prefs.setValue('', 'key2', 2); expect(await readTestFile(), '{"key1":"one","key2":2}'); }); test('clear', () async { await writeTestFile('{"key1":"one","key2":2}'); final SharedPreferencesWindows prefs = getPreferences(); await prefs.clear(); expect(await readTestFile(), '{}'); }); } /// Fake implementation of PathProviderWindows that returns hard-coded paths, /// allowing tests to run on any platform. /// /// Note that this should only be used with an in-memory filesystem, as the /// path it returns is a root path that does not actually exist on Windows. class FakePathProviderWindows extends PathProviderPlatform implements PathProviderWindows { @override late VersionInfoQuerier versionInfoQuerier; @override Future getApplicationSupportPath() async => r'C:\appsupport'; @override Future getTemporaryPath() async => null; @override Future getLibraryPath() async => null; @override Future getApplicationDocumentsPath() async => null; @override Future getDownloadsPath() async => null; @override Future getPath(String folderID) async => ''; } ================================================ FILE: packages/url_launcher/url_launcher/AUTHORS ================================================ # 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> ================================================ FILE: packages/url_launcher/url_launcher/CHANGELOG.md ================================================ ## 6.1.9 * Updates minimum Flutter version to 3.0. * Updates iOS minimum version in README. ## 6.1.8 * Updates code for stricter lint checks. ## 6.1.7 * Updates code for new analysis options. ## 6.1.6 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 6.1.5 * Migrates `README.md` examples to the [`code-excerpt` system](https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#readme-code). ## 6.1.4 * Adopts new platform interface method for launching URLs. * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/105648). ## 6.1.3 * Updates README section about query permissions to better reflect changes to `canLaunchUrl` recommendations. ## 6.1.2 * Minor fixes for new analysis options. ## 6.1.1 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 6.1.0 * Introduces new `launchUrl` and `canLaunchUrl` APIs; `launch` and `canLaunch` are now deprecated. These new APIs: * replace the `String` URL argument with a `Uri`, to prevent common issues with providing invalid URL strings. * replace `forceSafariVC` and `forceWebView` with `LaunchMode`, which makes the API platform-neutral, and standardizes the default behavior between Android and iOS. * move web view configuration options into a new `WebViewConfiguration` object. The default behavior for JavaScript and DOM storage is now enabled rather than disabled. * Also deprecates `closeWebView` in favor of `closeInAppWebView` to clarify that it is specific to the in-app web view launch option. * Adds OS version support information to README. * Reorganizes and clarifies README. ## 6.0.20 * Fixes a typo in `default_package` registration for Windows, macOS, and Linux. ## 6.0.19 * Updates README: * Adds description for `file` scheme usage. * Updates `Uri` class link to SDK documentation. ## 6.0.18 * Removes dependency on `meta`. ## 6.0.17 * Updates code for new analysis options. ## 6.0.16 * Moves Android and iOS implementations to federated packages. ## 6.0.15 * Updates README: * Improves organization. * Clarifies how `canLaunch` should be used. * Updates example application to demonstrate intended use of `canLaunch`. ## 6.0.14 * Updates readme to indicate that sending SMS messages on Android 11 requires to add a query to AndroidManifest.xml. * Fixes integration tests. * Updates example app Android compileSdkVersion to 31. ## 6.0.13 * Fixed extracting browser headers when they are null error. ## 6.0.12 * Fixed an error where 'launch' method of url_launcher would cause an error if the provided URL was not valid by RFC 3986. ## 6.0.11 * Update minimum Flutter SDK to 2.5 and iOS deployment target to 9.0. * Updated Android lint settings. ## 6.0.10 * Remove references to the Android v1 embedding. ## 6.0.9 * Silenced warnings that may occur during build when using a very recent version of Flutter relating to null safety. ## 6.0.8 * Adding API level 30 required package visibility configuration to the example's AndroidManifest.xml and README * Fix test button check for iOS 15. ## 6.0.7 * Update the README to describe a workaround to the `Uri` query encoding bug. ## 6.0.6 * Require `url_launcher_platform_interface` 2.0.3. This fixes an issue where 6.0.5 could fail to compile in some projects due to internal changes in that version that were not compatible with earlier versions of `url_launcher_platform_interface`. ## 6.0.5 * Add iOS unit and UI integration test targets. * Add a `Link` widget to the example app. ## 6.0.4 * Migrate maven repository from jcenter to mavenCentral. ## 6.0.3 * Update README notes about URL schemes on iOS ## 6.0.2 * Update platform_plugin_interface version requirement. ## 6.0.1 * Update result to `True` on iOS when the url was loaded successfully. * Added a README note about required applications. ## 6.0.0 * Migrate to null safety. * Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) * Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets. * Correct statement in description about which platforms url_launcher supports. ## 5.7.13 * Update Flutter SDK constraint. ## 5.7.12 * Updated code sample in `README.md` ## 5.7.11 * Update integration test examples to use `testWidgets` instead of `test`. ## 5.7.10 * Update Dart SDK constraint in example. ## 5.7.9 * Check in windows/ directory for example/ ## 5.7.8 * Fixed a situation where an app would crash if the url_launcher’s `launch` method can’t find an app to open the provided url. It will now throw a clear Dart PlatformException. ## 5.7.7 * Introduce the Link widget with an implementation for native platforms. ## 5.7.6 * Suppress deprecation warning on the `shouldOverrideUrlLoading` method on Android of the `FlutterWebChromeClient` class. ## 5.7.5 * Improved documentation of the `headers` parameter. ## 5.7.4 * Update android compileSdkVersion to 29. ## 5.7.3 * Check in linux/ directory for example/ ## 5.7.2 * Add API documentation explaining the [canLaunch] method returns `false` if package visibility (Android API 30) is not managed correctly. ## 5.7.1 * Keep handling deprecated Android v1 classes for backward compatibility. ## 5.7.0 * Handle WebView multi-window support. ## 5.6.0 * Support Windows by default. ## 5.5.3 * Suppress deprecation warning on the `shouldOverrideUrlLoading` method on Android. ## 5.5.2 * Depend explicitly on the `platform_interface` package that adds the `webOnlyWindowName` parameter. ## 5.5.1 * Added webOnlyWindowName parameter to launch() ## 5.5.0 * Support Linux by default. ## 5.4.11 * Add documentation in README suggesting how to properly encode urls with special characters. ## 5.4.10 * Post-v2 Android embedding cleanups. ## 5.4.9 * Update README. ## 5.4.8 * Initialize `previousAutomaticSystemUiAdjustment` in launch method. ## 5.4.7 * Update lower bound of dart dependency to 2.1.0. ## 5.4.6 * Add `web` to the example app. ## 5.4.5 * Remove Android dependencies fallback. * Require Flutter SDK 1.12.13+hotfix.5 or greater. * Fix CocoaPods podspec lint warnings. ## 5.4.4 * Replace deprecated `getFlutterEngine` call on Android. ## 5.4.3 * Fixed the launchUniversalLinkIos method. ## 5.4.2 * Make the pedantic dev_dependency explicit. ## 5.4.1 * Update unit tests to work with the PlatformInterface from package `plugin_platform_interface`. ## 5.4.0 * Support macOS by default. ## 5.3.0 * Support web by default. * Use the new plugins pubspec schema. ## 5.2.7 * Minor unit test changes and added a lint for public DartDocs. ## 5.2.6 * Remove AndroidX warnings. ## 5.2.5 * Include lifecycle dependency as a compileOnly one on Android to resolve potential version conflicts with other transitive libraries. ## 5.2.4 * Use `package:url_launcher_platform_interface` to get the platform-specific implementation. ## 5.2.3 * Android: Use android.arch.lifecycle instead of androidx.lifecycle:lifecycle in `build.gradle` to support apps that has not been migrated to AndroidX. ## 5.2.2 * Re-land embedder v2 support with correct Flutter SDK constraints. ## 5.2.1 * Revert the migration since the Flutter dependency was too low. ## 5.2.0 * Migrate the plugin to use the V2 Android engine embedding. This shouldn't affect existing functionality. Plugin authors who use the V2 embedding can now instantiate the plugin and expect that it correctly responds to app lifecycle changes. ## 5.1.7 * Define clang module for iOS. ## 5.1.6 * Fixes bug where androidx app won't build with this plugin by enabling androidx and jetifier in the android `gradle.properties`. ## 5.1.5 * Update homepage url after moving to federated directory. ## 5.1.4 * Update and migrate iOS example project. ## 5.1.3 * Always launch url from the top most UIViewController in iOS. ## 5.1.2 * Update AGP and gradle. * Split plugin and WebViewActivity class files. ## 5.1.1 * Suppress a handled deprecation warning on iOS ## 5.1.0 * Add `headers` field to enable headers in the Android implementation. ## 5.0.5 * Add `enableDomStorage` field to `launch` to enable DOM storage in Android WebView. ## 5.0.4 * Update Dart code to conform to current Dart formatter. ## 5.0.3 * Add missing template type parameter to `invokeMethod` calls. * Bump minimum Flutter version to 1.5.0. * Replace invokeMethod with invokeMapMethod wherever necessary. ## 5.0.2 * Fixes `closeWebView` failure on iOS. ## 5.0.1 * Log a more detailed warning at build time about the previous AndroidX migration. ## 5.0.0 * **Breaking change**. Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to [also migrate](https://developer.android.com/jetpack/androidx/migrate) if they're using the original support library. This was originally incorrectly pushed in the `4.2.0` update. ## 4.2.0+3 * **Revert the breaking 4.2.0 update**. 4.2.0 was known to be breaking and should have incremented the major version number instead of the minor. This revert is in and of itself breaking for anyone that has already migrated however. Anyone who has already migrated their app to AndroidX should immediately update to `5.0.0` instead. That's the correctly versioned new push of `4.2.0`. ## 4.2.0+2 * Updated `launch` to use async and await, fixed the incorrect return value by `launch` method. ## 4.2.0+1 * Refactored the Java and Objective-C code. Replaced instance variables with properties in Objective-C. ## 4.2.0 * **BAD**. This was a breaking change that was incorrectly published on a minor version upgrade, should never have happened. Reverted by 4.2.0+3. * **Breaking change**. Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to [also migrate](https://developer.android.com/jetpack/androidx/migrate) if they're using the original support library. ## 4.1.0+1 * This is just a version bump to republish as 4.1.0 was published with some dirty local state. ## 4.1.0 * Added `universalLinksOnly` setting. * Updated `launch` to return `Future`. ## 4.0.3 * Fixed launch url fail for Android: `launch` now assert activity not null and using activity to startActivity. * Fixed `WebViewActivity has leaked IntentReceiver` for Android. ## 4.0.2 * Added `closeWebView` function to programmatically close the current WebView. ## 4.0.1 * Added enableJavaScript field to `launch` to enable javascript in Android WebView. ## 4.0.0 * **Breaking change** Now requires a minimum Flutter version of 0.5.6. * Update to statusBarBrightness field so that the logic runs on the Flutter side. * **Breaking change** statusBarBrightness no longer has a default value. ## 3.0.3 * Added statusBarBrightness field to `launch` to set iOS status bar brightness. ## 3.0.2 * Updated Gradle tooling to match Android Studio 3.1.2. ## 3.0.1 * Fix a crash during Safari view controller dismiss. ## 3.0.0 * **Breaking change**. Set SDK constraints to match the Flutter beta release. ## 2.0.2 * Fixed Dart 2 issue: `launch` now returns `Future` instead of `Future`. ## 2.0.1 * Simplified and upgraded Android project template to Android SDK 27. * Updated package description. ## 2.0.0 * **Breaking change**. Upgraded to Gradle 4.1 and Android Studio Gradle plugin 3.0.1. Older Flutter projects need to upgrade their Gradle setup as well in order to use this version of the plugin. Instructions can be found [here](https://github.com/flutter/flutter/wiki/Updating-Flutter-projects-to-Gradle-4.1-and-Android-Studio-Gradle-plugin-3.0.1). ## 1.0.3 * Add FLT prefix to iOS types. ## 1.0.2 * Fix handling of URLs in Android WebView. ## 1.0.1 * Support option to launch default browser in iOS. * Parse incoming url and decide on what to open based on scheme. * Support WebView on Android. ## 1.0.0 * iOS plugin presents a Safari view controller instead of switching to the Safari app. ## 0.4.2+5 * Aligned author name with rest of repo. ## 0.4.2+2, 0.4.2+3, 0.4.2+4 * Updated README. ## 0.4.2+1 * Updated README. ## 0.4.2 * Change to README.md. ## 0.4.1 * Upgrade Android SDK Build Tools to 25.0.3. ## 0.4.0 * Upgrade to new plugin registration. ## 0.3.6 * Fix workaround for failing dynamic check in Xcode 7/sdk version 9. ## 0.3.5 * Workaround for failing dynamic check in Xcode 7/sdk version 9. ## 0.3.4 * Add test. ## 0.3.3 * Change to buildToolsVersion. ## 0.3.2 * Change to README.md. ## 0.3.1 * Change to README.md. ## 0.3.0 * Add `canLaunch` method. ## 0.2.0 * Change `launch` to a top-level method instead of a static method in a class. ## 0.1.1 * Change to README.md. ## 0.1.0 * Initial Open Source release. ================================================ FILE: packages/url_launcher/url_launcher/LICENSE ================================================ 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. ================================================ FILE: packages/url_launcher/url_launcher/README.md ================================================ # url_launcher [![pub package](https://img.shields.io/pub/v/url_launcher.svg)](https://pub.dev/packages/url_launcher) A Flutter plugin for launching a URL. | | Android | iOS | Linux | macOS | Web | Windows | |-------------|---------|-------|-------|--------|-----|-------------| | **Support** | SDK 16+ | 11.0+ | Any | 10.11+ | Any | Windows 10+ | ## Usage To use this plugin, add `url_launcher` as a [dependency in your pubspec.yaml file](https://flutter.dev/platform-plugins/). ### Example ``` dart import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; final Uri _url = Uri.parse('https://flutter.dev'); void main() => runApp( const MaterialApp( home: Material( child: Center( child: ElevatedButton( onPressed: _launchUrl, child: Text('Show Flutter homepage'), ), ), ), ), ); Future _launchUrl() async { if (!await launchUrl(_url)) { throw Exception('Could not launch $_url'); } } ``` See the example app for more complex examples. ## Configuration ### iOS Add any URL schemes passed to `canLaunchUrl` as `LSApplicationQueriesSchemes` entries in your Info.plist file, otherwise it will return false. Example: ```xml LSApplicationQueriesSchemes sms tel ``` See [`-[UIApplication canOpenURL:]`](https://developer.apple.com/documentation/uikit/uiapplication/1622952-canopenurl) for more details. ### Android Add any URL schemes passed to `canLaunchUrl` as `` entries in your `AndroidManifest.xml`, otherwise it will return false in most cases starting on Android 11 (API 30) or higher. A `` element must be added to your manifest as a child of the root element. Example: ``` xml ``` See [the Android documentation](https://developer.android.com/training/package-visibility/use-cases) for examples of other queries. ## Supported URL schemes The provided URL is passed directly to the host platform for handling. The supported URL schemes therefore depend on the platform and installed apps. Commonly used schemes include: | Scheme | Example | Action | |:---|:---|:---| | `https:` | `https://flutter.dev` | Open `` in the default browser | | `mailto:?subject=&body=` | `mailto:smith@example.org?subject=News&body=New%20plugin` | Create email to `` in the default email app | | `tel:` | `tel:+1-555-010-999` | Make a phone call to `` using the default phone app | | `sms:` | `sms:5550101234` | Send an SMS message to `` using the default messaging app | | `file:` | `file:/home` | Open file or folder using default app association, supported on desktop platforms | More details can be found here for [iOS](https://developer.apple.com/library/content/featuredarticles/iPhoneURLScheme_Reference/Introduction/Introduction.html) and [Android](https://developer.android.com/guide/components/intents-common.html) URL schemes are only supported if there are apps installed on the device that can support them. For example, iOS simulators don't have a default email or phone apps installed, so can't open `tel:` or `mailto:` links. ### Checking supported schemes If you need to know at runtime whether a scheme is guaranteed to work before using it (for instance, to adjust your UI based on what is available), you can check with [`canLaunchUrl`](https://pub.dev/documentation/url_launcher/latest/url_launcher/canLaunchUrl.html). However, `canLaunchUrl` can return false even if `launchUrl` would work in some circumstances (in web applications, on mobile without the necessary configuration as described above, etc.), so in cases where you can provide fallback behavior it is better to use `launchUrl` directly and handle failure. For example, a UI button that would have sent feedback email using a `mailto` URL might instead open a web-based feedback form using an `https` URL on failure, rather than disabling the button if `canLaunchUrl` returns false for `mailto`. ### Encoding URLs URLs must be properly encoded, especially when including spaces or other special characters. In general this is handled automatically by the [`Uri` class](https://api.dart.dev/dart-core/Uri-class.html). **However**, for any scheme other than `http` or `https`, you should use the `query` parameter and the `encodeQueryParameters` function shown below rather than `Uri`'s `queryParameters` constructor argument for any query parameters, due to [a bug](https://github.com/dart-lang/sdk/issues/43838) in the way `Uri` encodes query parameters. Using `queryParameters` will result in spaces being converted to `+` in many cases. ```dart String? encodeQueryParameters(Map params) { return params.entries .map((MapEntry e) => '${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}') .join('&'); } // ··· final Uri emailLaunchUri = Uri( scheme: 'mailto', path: 'smith@example.com', query: encodeQueryParameters({ 'subject': 'Example Subject & Symbols are allowed!', }), ); launchUrl(emailLaunchUri); ``` Encoding for `sms` is slightly different: ```dart final Uri smsLaunchUri = Uri( scheme: 'sms', path: '0118 999 881 999 119 7253', queryParameters: { 'body': Uri.encodeComponent('Example Subject & Symbols are allowed!'), }, ); ``` ### URLs not handled by `Uri` In rare cases, you may need to launch a URL that the host system considers valid, but cannot be expressed by `Uri`. For those cases, alternate APIs using strings are available by importing `url_launcher_string.dart`. Using these APIs in any other cases is **strongly discouraged**, as providing invalid URL strings was a very common source of errors with this plugin's original APIs. ### File scheme handling `file:` scheme can be used on desktop platforms: Windows, macOS, and Linux. We recommend checking first whether the directory or file exists before calling `launchUrl`. Example: ```dart final String filePath = testFile.absolute.path; final Uri uri = Uri.file(filePath); if (!File(uri.toFilePath()).existsSync()) { throw Exception('$uri does not exist!'); } if (!await launchUrl(uri)) { throw Exception('Could not launch $uri'); } ``` #### macOS file access configuration If you need to access files outside of your application's sandbox, you will need to have the necessary [entitlements](https://docs.flutter.dev/desktop#entitlements-and-the-app-sandbox). ## Browser vs in-app Handling On some platforms, web URLs can be launched either in an in-app web view, or in the default browser. The default behavior depends on the platform (see [`launchUrl`](https://pub.dev/documentation/url_launcher/latest/url_launcher/launchUrl.html) for details), but a specific mode can be used on supported platforms by passing a `LaunchMode`. ================================================ FILE: packages/url_launcher/url_launcher/example/README.md ================================================ # url_launcher_example Demonstrates how to use the url_launcher plugin. ================================================ FILE: packages/url_launcher/url_launcher/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 lintOptions { disable 'InvalidPackage' } defaultConfig { applicationId "io.flutter.plugins.urllauncherexample" minSdkVersion 16 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' } ================================================ FILE: packages/url_launcher/url_launcher/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/url_launcher/url_launcher/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/url_launcher/url_launcher/example/android/app/src/androidTest/java/io/flutter/plugins/urllauncherexample/FlutterActivityTest.java ================================================ // Copyright 2013 The Flutter 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.urllauncherexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class FlutterActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/url_launcher/url_launcher/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/url_launcher/url_launcher/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/url_launcher/url_launcher/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Wed Jul 31 20:16:04 BRT 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/url_launcher/url_launcher/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true android.enableR8=true ================================================ FILE: packages/url_launcher/url_launcher/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withInputStream { stream -> plugins.load(stream) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/url_launcher/url_launcher/example/build.excerpt.yaml ================================================ targets: $default: sources: include: - lib/** - android/app/src/main/** # Some default includes that aren't really used here but will prevent # false-negative warnings: - $package$ - lib/$lib$ exclude: - '**/.*/**' - '**/build/**' - 'android/app/src/main/res/**' builders: code_excerpter|code_excerpter: enabled: true generate_for: - '**/*.dart' - android/**/*.xml ================================================ FILE: packages/url_launcher/url_launcher/example/integration_test/url_launcher_test.dart ================================================ // Copyright 2013 The Flutter 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' show Platform; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:url_launcher/url_launcher.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('canLaunch', (WidgetTester _) async { expect( await canLaunchUrl(Uri(scheme: 'randomscheme', path: 'a_path')), false); // Generally all devices should have some default browser. expect(await canLaunchUrl(Uri(scheme: 'http', host: 'flutter.dev')), true); expect(await canLaunchUrl(Uri(scheme: 'https', host: 'flutter.dev')), true); // SMS handling is available by default on most platforms. if (kIsWeb || !(Platform.isLinux || Platform.isWindows)) { expect(await canLaunchUrl(Uri(scheme: 'sms', path: '5555555555')), true); } // Sanity-check legacy API. // ignore: deprecated_member_use expect(await canLaunch('randomstring'), false); // Generally all devices should have some default browser. // ignore: deprecated_member_use expect(await canLaunch('https://flutter.dev'), true); }); } ================================================ FILE: packages/url_launcher/url_launcher/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 UIRequiredDeviceCapabilities arm64 MinimumOSVersion 11.0 ================================================ FILE: packages/url_launcher/url_launcher/example/ios/Flutter/Debug.xcconfig ================================================ #include "Generated.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" ================================================ FILE: packages/url_launcher/url_launcher/example/ios/Flutter/Release.xcconfig ================================================ #include "Generated.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" ================================================ FILE: packages/url_launcher/url_launcher/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/url_launcher/url_launcher/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/url_launcher/url_launcher/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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]; [super application:application didFinishLaunchingWithOptions:launchOptions]; return YES; } @end ================================================ FILE: packages/url_launcher/url_launcher/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/url_launcher/url_launcher/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/url_launcher/url_launcher/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/url_launcher/url_launcher/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName url_launcher_example CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents ================================================ FILE: packages/url_launcher/url_launcher/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/url_launcher/url_launcher/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 2D92223F1EC1DA93007564B0 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D92223E1EC1DA93007564B0 /* GeneratedPluginRegistrant.m */; }; 2E37D9A274B2EACB147AC51B /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 856D0913184F79C678A42603 /* libPods-Runner.a */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 2D92223D1EC1DA93007564B0 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GeneratedPluginRegistrant.h; path = Runner/GeneratedPluginRegistrant.h; sourceTree = ""; }; 2D92223E1EC1DA93007564B0 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GeneratedPluginRegistrant.m; path = Runner/GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 487A1B5A2ECB3E406FD62FE3 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 666BCD7C181C34F8BE58929B /* 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 = ""; }; 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 = ""; }; 836316F9AEA584411312E29F /* 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 = ""; }; 856D0913184F79C678A42603 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; A84BFEE343F54B983D1B67EB /* 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 = ""; }; D25C434271ACF6555E002440 /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2E37D9A274B2EACB147AC51B /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 840012C8B5EDBCF56B0E4AC1 /* Pods */ = { isa = PBXGroup; children = ( 836316F9AEA584411312E29F /* Pods-Runner.debug.xcconfig */, A84BFEE343F54B983D1B67EB /* Pods-Runner.release.xcconfig */, 666BCD7C181C34F8BE58929B /* Pods-RunnerTests.debug.xcconfig */, D25C434271ACF6555E002440 /* Pods-RunnerTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 2D92223D1EC1DA93007564B0 /* GeneratedPluginRegistrant.h */, 2D92223E1EC1DA93007564B0 /* GeneratedPluginRegistrant.m */, 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 840012C8B5EDBCF56B0E4AC1 /* Pods */, CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = { isa = PBXGroup; children = ( 856D0913184F79C678A42603 /* libPods-Runner.a */, 487A1B5A2ECB3E406FD62FE3 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; DevelopmentTeam = S8QB4VV633; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 2D92223F1EC1DA93007564B0 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.urlLauncher; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.urlLauncher; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/url_launcher/url_launcher/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/url_launcher/url_launcher/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/url_launcher/url_launcher/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/url_launcher/url_launcher/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/url_launcher/url_launcher/example/lib/basic.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Run this example with: flutter run -t lib/basic.dart -d emulator // This file is used to extract code samples for the README.md file. // Run update-excerpts if you modify this file. // #docregion basic-example import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; final Uri _url = Uri.parse('https://flutter.dev'); void main() => runApp( const MaterialApp( home: Material( child: Center( child: ElevatedButton( onPressed: _launchUrl, child: Text('Show Flutter homepage'), ), ), ), ), ); Future _launchUrl() async { if (!await launchUrl(_url)) { throw Exception('Could not launch $_url'); } } // #enddocregion basic-example ================================================ FILE: packages/url_launcher/url_launcher/example/lib/encoding.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Run this example with: flutter run -t lib/encoding.dart -d emulator // This file is used to extract code samples for the README.md file. // Run update-excerpts if you modify this file. import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; /// Encode [params] so it produces a correct query string. /// Workaround for: https://github.com/dart-lang/sdk/issues/43838 // #docregion encode-query-parameters String? encodeQueryParameters(Map params) { return params.entries .map((MapEntry e) => '${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}') .join('&'); } // #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 [ ElevatedButton( onPressed: _composeMail, child: Text('Compose an email'), ), ElevatedButton( onPressed: _composeSms, child: Text('Compose a SMS'), ), ], ), ), ), ); void _composeMail() { // #docregion encode-query-parameters final Uri emailLaunchUri = Uri( scheme: 'mailto', path: 'smith@example.com', query: encodeQueryParameters({ 'subject': 'Example Subject & Symbols are allowed!', }), ); launchUrl(emailLaunchUri); // #enddocregion encode-query-parameters } void _composeSms() { // #docregion sms final Uri smsLaunchUri = Uri( scheme: 'sms', path: '0118 999 881 999 119 7253', queryParameters: { 'body': Uri.encodeComponent('Example Subject & Symbols are allowed!'), }, ); // #enddocregion sms launchUrl(smsLaunchUri); } ================================================ FILE: packages/url_launcher/url_launcher/example/lib/files.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Run this example with: flutter run -t lib/files.dart -d linux // This file is used to extract code samples for the README.md file. // Run update-excerpts if you modify this file. import 'dart:io'; import 'package:flutter/material.dart'; import 'package:path/path.dart' as p; import 'package:url_launcher/url_launcher.dart'; void main() => runApp( const MaterialApp( home: Material( child: Center( child: ElevatedButton( onPressed: _openFile, child: Text('Open File'), ), ), ), ), ); Future _openFile() async { // Prepare a file within tmp final String tempFilePath = p.joinAll([ ...p.split(Directory.systemTemp.path), 'flutter_url_launcher_example.txt' ]); final File testFile = File(tempFilePath); await testFile.writeAsString('Hello, world!'); // #docregion file final String filePath = testFile.absolute.path; final Uri uri = Uri.file(filePath); if (!File(uri.toFilePath()).existsSync()) { throw Exception('$uri does not exist!'); } if (!await launchUrl(uri)) { throw Exception('Could not launch $uri'); } // #enddocregion file } ================================================ FILE: packages/url_launcher/url_launcher/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'package:flutter/material.dart'; import 'package:url_launcher/link.dart'; import 'package:url_launcher/url_launcher.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: 'URL Launcher', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'URL Launcher'), ); } } 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 { bool _hasCallSupport = false; Future? _launched; String _phone = ''; @override void initState() { super.initState(); // Check for phone call support. canLaunchUrl(Uri(scheme: 'tel', path: '123')).then((bool result) { setState(() { _hasCallSupport = result; }); }); } Future _launchInBrowser(Uri url) async { if (!await launchUrl( url, mode: LaunchMode.externalApplication, )) { throw Exception('Could not launch $url'); } } Future _launchInWebViewOrVC(Uri url) async { if (!await launchUrl( url, mode: LaunchMode.inAppWebView, webViewConfiguration: const WebViewConfiguration( headers: {'my_header_key': 'my_header_value'}), )) { throw Exception('Could not launch $url'); } } Future _launchInWebViewWithoutJavaScript(Uri url) async { if (!await launchUrl( url, mode: LaunchMode.inAppWebView, webViewConfiguration: const WebViewConfiguration(enableJavaScript: false), )) { throw Exception('Could not launch $url'); } } Future _launchInWebViewWithoutDomStorage(Uri url) async { if (!await launchUrl( url, mode: LaunchMode.inAppWebView, webViewConfiguration: const WebViewConfiguration(enableDomStorage: false), )) { throw Exception('Could not launch $url'); } } Future _launchUniversalLinkIos(Uri url) async { final bool nativeAppLaunchSucceeded = await launchUrl( url, mode: LaunchMode.externalNonBrowserApplication, ); if (!nativeAppLaunchSucceeded) { await launchUrl( url, mode: LaunchMode.inAppWebView, ); } } Widget _launchStatus(BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { return const Text(''); } } Future _makePhoneCall(String phoneNumber) async { final Uri launchUri = Uri( scheme: 'tel', path: phoneNumber, ); await launchUrl(launchUri); } @override Widget build(BuildContext context) { // onPressed calls using this URL are not gated on a 'canLaunch' check // because the assumption is that every device can launch a web URL. final Uri toLaunch = Uri(scheme: 'https', host: 'www.cylog.org', path: 'headers/'); return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: ListView( children: [ Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.all(16.0), child: TextField( onChanged: (String text) => _phone = text, decoration: const InputDecoration( hintText: 'Input the phone number to launch')), ), ElevatedButton( onPressed: _hasCallSupport ? () => setState(() { _launched = _makePhoneCall(_phone); }) : null, child: _hasCallSupport ? const Text('Make phone call') : const Text('Calling not supported'), ), Padding( padding: const EdgeInsets.all(16.0), child: Text(toLaunch.toString()), ), ElevatedButton( onPressed: () => setState(() { _launched = _launchInBrowser(toLaunch); }), child: const Text('Launch in browser'), ), const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { _launched = _launchInWebViewOrVC(toLaunch); }), child: const Text('Launch in app'), ), ElevatedButton( onPressed: () => setState(() { _launched = _launchInWebViewWithoutJavaScript(toLaunch); }), child: const Text('Launch in app (JavaScript OFF)'), ), ElevatedButton( onPressed: () => setState(() { _launched = _launchInWebViewWithoutDomStorage(toLaunch); }), child: const Text('Launch in app (DOM storage OFF)'), ), const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { _launched = _launchUniversalLinkIos(toLaunch); }), child: const Text( 'Launch a universal link in a native app, fallback to Safari.(Youtube)'), ), const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { _launched = _launchInWebViewOrVC(toLaunch); Timer(const Duration(seconds: 5), () { closeInAppWebView(); }); }), child: const Text('Launch in app + close after 5 seconds'), ), const Padding(padding: EdgeInsets.all(16.0)), Link( uri: Uri.parse( 'https://pub.dev/documentation/url_launcher/latest/link/link-library.html'), target: LinkTarget.blank, builder: (BuildContext ctx, FollowLink? openLink) { return TextButton.icon( onPressed: openLink, label: const Text('Link Widget documentation'), icon: const Icon(Icons.read_more), ); }, ), const Padding(padding: EdgeInsets.all(16.0)), FutureBuilder(future: _launched, builder: _launchStatus), ], ), ], ), ); } } ================================================ FILE: packages/url_launcher/url_launcher/example/linux/.gitignore ================================================ flutter/ephemeral ================================================ FILE: packages/url_launcher/url_launcher/example/linux/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(runner LANGUAGES CXX) set(BINARY_NAME "example") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_14) target_compile_options(${TARGET} PRIVATE -Wall -Werror) target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) # Application build add_executable(${BINARY_NAME} "main.cc" "my_application.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" ) apply_standard_settings(${BINARY_NAME}) target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) add_dependencies(${BINARY_NAME} flutter_assemble) # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # By default, "installing" just makes a relocatable bundle in the build # directory. set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() # Start with a clean build bundle directory every time. install(CODE " file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") " COMPONENT Runtime) set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() ================================================ FILE: packages/url_launcher/url_launcher/example/linux/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. # Serves the same purpose as list(TRANSFORM ... PREPEND ...), # which isn't available in 3.10. function(list_prepend LIST_NAME PREFIX) set(NEW_LIST "") foreach(element ${${LIST_NAME}}) list(APPEND NEW_LIST "${PREFIX}${element}") endforeach(element) set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) endfunction() # === Flutter Library === # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "fl_basic_message_channel.h" "fl_binary_codec.h" "fl_binary_messenger.h" "fl_dart_project.h" "fl_engine.h" "fl_json_message_codec.h" "fl_json_method_codec.h" "fl_message_codec.h" "fl_method_call.h" "fl_method_channel.h" "fl_method_codec.h" "fl_method_response.h" "fl_plugin_registrar.h" "fl_plugin_registry.h" "fl_standard_message_codec.h" "fl_standard_method_codec.h" "fl_string_codec.h" "fl_value.h" "fl_view.h" "flutter_linux.h" ) list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") target_link_libraries(flutter INTERFACE PkgConfig::GTK PkgConfig::GLIB PkgConfig::GIO ) add_dependencies(flutter flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/_phony_ COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ) ================================================ FILE: packages/url_launcher/url_launcher/example/linux/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/url_launcher/url_launcher/example/linux/main.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "my_application.h" int main(int argc, char** argv) { g_autoptr(MyApplication) app = my_application_new(); return g_application_run(G_APPLICATION(app), argc, argv); } ================================================ FILE: packages/url_launcher/url_launcher/example/linux/my_application.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "my_application.h" #include #include "flutter/generated_plugin_registrant.h" struct _MyApplication { GtkApplication parent_instance; }; G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // Implements GApplication::activate. static void my_application_activate(GApplication* application) { GtkWindow* window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); gtk_header_bar_set_title(header_bar, "example"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); gtk_window_set_default_size(window, 1280, 720); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); FlView* view = fl_view_new(project); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view)); gtk_widget_grab_focus(GTK_WIDGET(view)); } static void my_application_class_init(MyApplicationClass* klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; } static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { return MY_APPLICATION(g_object_new(my_application_get_type(), nullptr)); } ================================================ FILE: packages/url_launcher/url_launcher/example/linux/my_application.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef FLUTTER_MY_APPLICATION_H_ #define FLUTTER_MY_APPLICATION_H_ #include G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, GtkApplication) /** * my_application_new: * * Creates a new Flutter-based application. * * Returns: a new #MyApplication. */ MyApplication* my_application_new(); #endif // FLUTTER_MY_APPLICATION_H_ ================================================ FILE: packages/url_launcher/url_launcher/example/macos/Flutter/Flutter-Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/url_launcher/url_launcher/example/macos/Flutter/Flutter-Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/url_launcher/url_launcher/example/macos/Podfile ================================================ platform :osx, '10.11' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_macos_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) end end ================================================ FILE: packages/url_launcher/url_launcher/example/macos/Runner/AppDelegate.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } } ================================================ FILE: packages/url_launcher/url_launcher/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_64.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_1024.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/url_launcher/url_launcher/example/macos/Runner/Base.lproj/MainMenu.xib ================================================ ================================================ FILE: packages/url_launcher/url_launcher/example/macos/Runner/Configs/AppInfo.xcconfig ================================================ // Application-level settings for the Runner target. // // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the // future. If not, the values below would default to using the project name when this becomes a // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. PRODUCT_NAME = url_launcher_example_example // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.urlLauncherExample // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2019 The Flutter Authors. All rights reserved. ================================================ FILE: packages/url_launcher/url_launcher/example/macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: packages/url_launcher/url_launcher/example/macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: packages/url_launcher/url_launcher/example/macos/Runner/Configs/Warnings.xcconfig ================================================ WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings GCC_WARN_UNDECLARED_SELECTOR = YES CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE CLANG_WARN__DUPLICATE_METHOD_MATCH = YES CLANG_WARN_PRAGMA_PACK = YES CLANG_WARN_STRICT_PROTOTYPES = YES CLANG_WARN_COMMA = YES GCC_WARN_STRICT_SELECTOR_MATCH = YES CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES GCC_WARN_SHADOW = YES CLANG_WARN_UNREACHABLE_CODE = YES ================================================ FILE: packages/url_launcher/url_launcher/example/macos/Runner/DebugProfile.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server ================================================ FILE: packages/url_launcher/url_launcher/example/macos/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: packages/url_launcher/url_launcher/example/macos/Runner/MainFlutterWindow.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { let flutterViewController = FlutterViewController.init() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } } ================================================ FILE: packages/url_launcher/url_launcher/example/macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox ================================================ FILE: packages/url_launcher/url_launcher/example/macos/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 51; objects = { /* Begin PBXAggregateTarget section */ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { isa = PBXAggregateTarget; buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; buildPhases = ( 33CC111E2044C6BF0003C045 /* ShellScript */, ); dependencies = ( ); name = "Flutter Assemble"; productName = FLX; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 33D1A10422148B71006C7A3E /* FlutterMacOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */; }; 33D1A10522148B93006C7A3E /* FlutterMacOS.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D73912F022F37F9E000D13A0 /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D73912EF22F37F9E000D13A0 /* App.framework */; }; D73912F222F3801D000D13A0 /* App.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = D73912EF22F37F9E000D13A0 /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DD4A1B9DEDBB72C87CD7AE27 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5067D74CB28D28AE3B3DD05B /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC111A2044C6BA0003C045; remoteInfo = FLX; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 33CC110E2044A8840003C045 /* Bundle Framework */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( D73912F222F3801D000D13A0 /* App.framework in Bundle Framework */, 33D1A10522148B93006C7A3E /* FlutterMacOS.framework in Bundle Framework */, ); name = "Bundle Framework"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* url_launcher_example_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = url_launcher_example_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FlutterMacOS.framework; path = Flutter/ephemeral/FlutterMacOS.framework; sourceTree = SOURCE_ROOT; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 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 = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 899489AD6AA35AECA4E2BEA6 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; B36FDC1D769C9045B8821207 /* 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 = ""; }; D73912EF22F37F9E000D13A0 /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/ephemeral/App.framework; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( D73912F022F37F9E000D13A0 /* App.framework in Frameworks */, 33D1A10422148B71006C7A3E /* FlutterMacOS.framework in Frameworks */, DD4A1B9DEDBB72C87CD7AE27 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( 33E5194F232828860026EE4D /* AppInfo.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, ); path = Configs; sourceTree = ""; }; 33CC10E42044A3C60003C045 = { isa = PBXGroup; children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, 96C1F6D923BD5787E8EBE8FC /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* url_launcher_example_example.app */, ); name = Products; sourceTree = ""; }; 33CC11242044D66E0003C045 /* Resources */ = { isa = PBXGroup; children = ( 33CC10F22044A3C60003C045 /* Assets.xcassets */, 33CC10F42044A3C60003C045 /* MainMenu.xib */, 33CC10F72044A3C60003C045 /* Info.plist */, ); name = Resources; path = ..; sourceTree = ""; }; 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, D73912EF22F37F9E000D13A0 /* App.framework */, 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */, ); path = Flutter; sourceTree = ""; }; 33FAB671232836740065AC1E /* Runner */ = { isa = PBXGroup; children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, 33CC11242044D66E0003C045 /* Resources */, 33BA886A226E78AF003329D5 /* Configs */, ); path = Runner; sourceTree = ""; }; 96C1F6D923BD5787E8EBE8FC /* Pods */ = { isa = PBXGroup; children = ( 899489AD6AA35AECA4E2BEA6 /* Pods-Runner.debug.xcconfig */, B36FDC1D769C9045B8821207 /* Pods-Runner.release.xcconfig */, 53F020549CA1E801ACA3428F /* Pods-Runner.profile.xcconfig */, ); name = Pods; path = Pods; sourceTree = ""; }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( 5067D74CB28D28AE3B3DD05B /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( C318D59394D0E38099411848 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, 50C74DCD840D9B569BE3D48F /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* url_launcher_example_example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; LastUpgradeCheck = 0930; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; }; }; }; 33CC111A2044C6BA0003C045 = { CreatedOnToolsVersion = 9.2; ProvisioningStyle = Manual; }; }; }; buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 8.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 33CC10E42044A3C60003C045; productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename\n"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( Flutter/ephemeral/FlutterInputs.xcfilelist, ); inputPaths = ( Flutter/ephemeral/tripwire, ); outputFileListPaths = ( Flutter/ephemeral/FlutterOutputs.xcfilelist, ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh\ntouch Flutter/ephemeral/tripwire\n"; }; 50C74DCD840D9B569BE3D48F /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; C318D59394D0E38099411848 /* [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", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 33CC10F52044A3C60003C045 /* Base */, ); name = MainMenu.xib; path = Runner; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Profile; }; 338D0CEA231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter/ephemeral", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Profile; }; 338D0CEB231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Profile; }; 33CC10F92044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 33CC10FA2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; 33CC10FC2044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter/ephemeral", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; 33CC10FD2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter/ephemeral", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Release; }; 33CC111C2044C6BA0003C045 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 33CC111D2044C6BA0003C045 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10F92044A3C60003C045 /* Debug */, 33CC10FA2044A3C60003C045 /* Release */, 338D0CE9231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10FC2044A3C60003C045 /* Debug */, 33CC10FD2044A3C60003C045 /* Release */, 338D0CEA231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC111C2044C6BA0003C045 /* Debug */, 33CC111D2044C6BA0003C045 /* Release */, 338D0CEB231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } ================================================ FILE: packages/url_launcher/url_launcher/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/url_launcher/url_launcher/example/macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/url_launcher/url_launcher/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/url_launcher/url_launcher/example/pubspec.yaml ================================================ name: url_launcher_example description: Demonstrates how to use the url_launcher plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter path: ^1.8.0 url_launcher: # When depending on this package from a real application you should use: # url_launcher: ^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: ../ dev_dependencies: build_runner: ^2.1.10 flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter mockito: ^5.0.0 plugin_platform_interface: ^2.0.0 flutter: uses-material-design: true ================================================ FILE: packages/url_launcher/url_launcher/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/url_launcher/url_launcher/example/web/index.html ================================================ url_launcher web example ================================================ FILE: packages/url_launcher/url_launcher/example/web/manifest.json ================================================ { "name": "url_launcher example", "short_name": "url_launcher", "start_url": ".", "display": "minimal-ui", "background_color": "#0175C2", "theme_color": "#0175C2", "description": "An example of the url_launcher on the web.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [ { "src": "icons/Icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" } ] } ================================================ FILE: packages/url_launcher/url_launcher/example/windows/.gitignore ================================================ flutter/ephemeral/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: packages/url_launcher/url_launcher/example/windows/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(example LANGUAGES CXX) set(BINARY_NAME "example") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" CACHE STRING "" FORCE) else() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() endif() set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") # Use Unicode for all projects. add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") target_compile_options(${TARGET} PRIVATE /EHsc) target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build add_subdirectory("runner") # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # Support files are copied into place next to the executable, so that it can # run in place. This is done instead of making a separate bundle (as on Linux) # so that building and running from within Visual Studio will work. set(BUILD_BUNDLE_DIR "$") # Make the "install" step default, as it's required to run. set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" CONFIGURATIONS Profile;Release COMPONENT Runtime) ================================================ FILE: packages/url_launcher/url_launcher/example/windows/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "flutter_export.h" "flutter_windows.h" "flutter_messenger.h" "flutter_plugin_registrar.h" ) list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") add_dependencies(flutter flutter_assemble) # === Wrapper === list(APPEND CPP_WRAPPER_SOURCES_CORE "core_implementations.cc" "standard_codec.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_PLUGIN "plugin_registrar.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_APP "flutter_engine.cc" "flutter_view_controller.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") # Wrapper sources needed for a plugin. add_library(flutter_wrapper_plugin STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ) apply_standard_settings(flutter_wrapper_plugin) set_target_properties(flutter_wrapper_plugin PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(flutter_wrapper_plugin PROPERTIES CXX_VISIBILITY_PRESET hidden) target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) target_include_directories(flutter_wrapper_plugin PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_plugin flutter_assemble) # Wrapper sources needed for the runner. add_library(flutter_wrapper_app STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_APP} ) apply_standard_settings(flutter_wrapper_app) target_link_libraries(flutter_wrapper_app PUBLIC flutter) target_include_directories(flutter_wrapper_app PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_app flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" windows-x64 $ ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ) ================================================ FILE: packages/url_launcher/url_launcher/example/windows/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/url_launcher/url_launcher/example/windows/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(runner LANGUAGES CXX) add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" "run_loop.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) apply_standard_settings(${BINARY_NAME}) target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") add_dependencies(${BINARY_NAME} flutter_assemble) ================================================ FILE: packages/url_launcher/url_launcher/example/windows/runner/Runner.rc ================================================ // Microsoft Visual C++ generated resource script. // #pragma code_page(65001) #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APP_ICON ICON "resources\\app_icon.ico" ///////////////////////////////////////////////////////////////////////////// // // Version // #ifdef FLUTTER_BUILD_NUMBER #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER #else #define VERSION_AS_NUMBER 1,0,0 #endif #ifdef FLUTTER_BUILD_NAME #define VERSION_AS_STRING #FLUTTER_BUILD_NAME #else #define VERSION_AS_STRING "1.0.0" #endif VS_VERSION_INFO VERSIONINFO FILEVERSION VERSION_AS_NUMBER PRODUCTVERSION VERSION_AS_NUMBER FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "Flutter Dev" "\0" VALUE "FileDescription", "A new Flutter project." "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "example" "\0" VALUE "LegalCopyright", "Copyright (C) 2020 The Flutter Authors. All rights reserved." "\0" VALUE "OriginalFilename", "example.exe" "\0" VALUE "ProductName", "example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: packages/url_launcher/url_launcher/example/windows/runner/flutter_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "flutter_window.h" #include #include "flutter/generated_plugin_registrant.h" FlutterWindow::FlutterWindow(RunLoop* run_loop, const flutter::DartProject& project) : run_loop_(run_loop), project_(project) {} FlutterWindow::~FlutterWindow() {} bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opporutunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) { return *result; } } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); } ================================================ FILE: packages/url_launcher/url_launcher/example/windows/runner/flutter_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_ #include #include #include #include "run_loop.h" #include "win32_window.h" // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { public: // Creates a new FlutterWindow driven by the |run_loop|, hosting a // Flutter view running |project|. explicit FlutterWindow(RunLoop* run_loop, const flutter::DartProject& project); virtual ~FlutterWindow(); protected: // Win32Window: bool OnCreate() override; void OnDestroy() override; LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; private: // The run loop driving events for this window. RunLoop* run_loop_; // The project to run. flutter::DartProject project_; // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; }; #endif // RUNNER_FLUTTER_WINDOW_H_ ================================================ FILE: packages/url_launcher/url_launcher/example/windows/runner/main.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include "flutter_window.h" #include "run_loop.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t* command_line, _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { CreateAndAttachConsole(); } // Initialize COM, so that it is available for use in the library and/or // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); RunLoop run_loop; flutter::DartProject project(L"data"); FlutterWindow window(&run_loop, project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.CreateAndShow(L"example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); run_loop.Run(); ::CoUninitialize(); return EXIT_SUCCESS; } ================================================ FILE: packages/url_launcher/url_launcher/example/windows/runner/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Runner.rc // #define IDI_APP_ICON 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: packages/url_launcher/url_launcher/example/windows/runner/run_loop.cpp ================================================ // Copyright 2013 The Flutter 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 "run_loop.h" #include #include RunLoop::RunLoop() {} RunLoop::~RunLoop() {} void RunLoop::Run() { bool keep_running = true; TimePoint next_flutter_event_time = TimePoint::clock::now(); while (keep_running) { std::chrono::nanoseconds wait_duration = std::max(std::chrono::nanoseconds(0), next_flutter_event_time - TimePoint::clock::now()); ::MsgWaitForMultipleObjects( 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000), QS_ALLINPUT); bool processed_events = false; MSG message; // All pending Windows messages must be processed; MsgWaitForMultipleObjects // won't return again for items left in the queue after PeekMessage. while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) { processed_events = true; if (message.message == WM_QUIT) { keep_running = false; break; } ::TranslateMessage(&message); ::DispatchMessage(&message); // Allow Flutter to process messages each time a Windows message is // processed, to prevent starvation. next_flutter_event_time = std::min(next_flutter_event_time, ProcessFlutterMessages()); } // If the PeekMessage loop didn't run, process Flutter messages. if (!processed_events) { next_flutter_event_time = std::min(next_flutter_event_time, ProcessFlutterMessages()); } } } void RunLoop::RegisterFlutterInstance( flutter::FlutterEngine* flutter_instance) { flutter_instances_.insert(flutter_instance); } void RunLoop::UnregisterFlutterInstance( flutter::FlutterEngine* flutter_instance) { flutter_instances_.erase(flutter_instance); } RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { TimePoint next_event_time = TimePoint::max(); for (auto instance : flutter_instances_) { std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); if (wait_duration != std::chrono::nanoseconds::max()) { next_event_time = std::min(next_event_time, TimePoint::clock::now() + wait_duration); } } return next_event_time; } ================================================ FILE: packages/url_launcher/url_launcher/example/windows/runner/run_loop.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_RUN_LOOP_H_ #define RUNNER_RUN_LOOP_H_ #include #include #include // A runloop that will service events for Flutter instances as well // as native messages. class RunLoop { public: RunLoop(); ~RunLoop(); // Prevent copying RunLoop(RunLoop const&) = delete; RunLoop& operator=(RunLoop const&) = delete; // Runs the run loop until the application quits. void Run(); // Registers the given Flutter instance for event servicing. void RegisterFlutterInstance(flutter::FlutterEngine* flutter_instance); // Unregisters the given Flutter instance from event servicing. void UnregisterFlutterInstance(flutter::FlutterEngine* flutter_instance); private: using TimePoint = std::chrono::steady_clock::time_point; // Processes all currently pending messages for registered Flutter instances. TimePoint ProcessFlutterMessages(); std::set flutter_instances_; }; #endif // RUNNER_RUN_LOOP_H_ ================================================ FILE: packages/url_launcher/url_launcher/example/windows/runner/runner.exe.manifest ================================================ PerMonitorV2 ================================================ FILE: packages/url_launcher/url_launcher/example/windows/runner/utils.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "utils.h" #include #include #include #include #include void CreateAndAttachConsole() { if (::AllocConsole()) { FILE* unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } if (freopen_s(&unused, "CONOUT$", "w", stderr)) { _dup2(_fileno(stdout), 2); } std::ios::sync_with_stdio(); FlutterDesktopResyncOutputStreams(); } } ================================================ FILE: packages/url_launcher/url_launcher/example/windows/runner/utils.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_UTILS_H_ #define RUNNER_UTILS_H_ // Creates a console for the process, and redirects stdout and stderr to // it for both the runner and the Flutter library. void CreateAndAttachConsole(); #endif // RUNNER_UTILS_H_ ================================================ FILE: packages/url_launcher/url_launcher/example/windows/runner/win32_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "win32_window.h" #include #include "resource.h" namespace { constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); // Scale helper to convert logical scaler values to physical using passed in // scale factor int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); if (!user32_module) { return; } auto enable_non_client_dpi_scaling = reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); FreeLibrary(user32_module); } } } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; // Returns the singleton registar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; } // Returns the name of the window class, registering the class if it hasn't // previously been registered. const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; bool class_registered_ = false; }; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; RegisterClass(&window_class); class_registered_ = true; } return kWindowClassName; } void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, const Size& size) { Destroy(); const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), static_cast(origin.y)}; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } return OnCreate(); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { case WM_DESTROY: window_handle_ = nullptr; Destroy(); if (quit_on_close_) { PostQuitMessage(0); } return 0; case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left; LONG newHeight = newRectSize->bottom - newRectSize->top; SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_SIZE: RECT rect = GetClientArea(); if (child_content_ != nullptr) { // Size and position the child window. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); } return 0; case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_); } return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } void Win32Window::Destroy() { OnDestroy(); if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, true); SetFocus(child_content_); } RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } void Win32Window::OnDestroy() { // No-op; provided for subclasses. } ================================================ FILE: packages/url_launcher/url_launcher/example/windows/runner/win32_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_ #include #include #include #include // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { public: struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) : width(width), height(height) {} }; Win32Window(); virtual ~Win32Window(); // Creates and shows a win32 window with |title| and position and size using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a // consistent size to will treat the width height passed in to this function // as logical pixels and scale to appropriate for the default monitor. Returns // true if the window was created successfully. bool CreateAndShow(const std::wstring& title, const Point& origin, const Size& size); // Release OS resources associated with window. void Destroy(); // Inserts |content| into the window tree. void SetChildContent(HWND content); // Returns the backing Window handle to enable clients to set icon and other // window properties. Returns nullptr if the window has been destroyed. HWND GetHandle(); // If true, closing this window will quit the application. void SetQuitOnClose(bool quit_on_close); // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Called when CreateAndShow is called, allowing subclass window-related // setup. Subclasses should return false if setup fails. virtual bool OnCreate(); // Called when Destroy is called. virtual void OnDestroy(); private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically // responsponds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; bool quit_on_close_ = false; // window handle for top level window. HWND window_handle_ = nullptr; // window handle for hosted content. HWND child_content_ = nullptr; }; #endif // RUNNER_WIN32_WINDOW_H_ ================================================ FILE: packages/url_launcher/url_launcher/lib/link.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'package:url_launcher_platform_interface/link.dart' show FollowLink, LinkTarget, LinkWidgetBuilder; export 'src/link.dart' show Link; ================================================ FILE: packages/url_launcher/url_launcher/lib/src/legacy_api.dart ================================================ // Copyright 2013 The Flutter 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:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; /// Parses the specified URL string and delegates handling of it to the /// underlying platform. /// /// The returned future completes with a [PlatformException] on invalid URLs and /// schemes which cannot be handled, that is when [canLaunch] would complete /// with false. /// /// By default when [forceSafariVC] is unset, the launcher /// opens web URLs in the Safari View Controller, anything else is opened /// using the default handler on the platform. If set to true, it opens the /// URL in the Safari View Controller. If false, the URL is opened in the /// default browser of the phone. Note that to work with universal links on iOS, /// this must be set to false to let the platform's system handle the URL. /// Set this to false if you want to use the cookies/context of the main browser /// of the app (such as SSO flows). This setting will nullify [universalLinksOnly] /// and will always launch a web content in the built-in Safari View Controller regardless /// if the url is a universal link or not. /// /// [universalLinksOnly] is only used in iOS with iOS version >= 10.0. This setting is only validated /// when [forceSafariVC] is set to false. The default value of this setting is false. /// By default (when unset), the launcher will either launch the url in a browser (when the /// url is not a universal link), or launch the respective native app content (when /// the url is a universal link). When set to true, the launcher will only launch /// the content if the url is a universal link and the respective app for the universal /// link is installed on the user's device; otherwise throw a [PlatformException]. /// /// [forceWebView] is an Android only setting. If null or false, the URL is /// always launched with the default browser on device. If set to true, the URL /// is launched in a WebView. Unlike iOS, browser context is shared across /// WebViews. /// [enableJavaScript] is an Android only setting. If true, WebView enable /// javascript. /// [enableDomStorage] is an Android only setting. If true, WebView enable /// DOM storage. /// [headers] is an Android only setting that adds headers to the WebView. /// When not using a WebView, the header information is passed to the browser, /// some Android browsers do not support the [Browser.EXTRA_HEADERS](https://developer.android.com/reference/android/provider/Browser#EXTRA_HEADERS) /// intent extra and the header information will be lost. /// [webOnlyWindowName] is an Web only setting . _blank opens the new url in new tab , /// _self opens the new url in current tab. /// Default behaviour is to open the url in new tab. /// /// Note that if any of the above are set to true but the URL is not a web URL, /// this will throw a [PlatformException]. /// /// [statusBarBrightness] Sets the status bar brightness of the application /// after opening a link on iOS. Does nothing if no value is passed. This does /// not handle resetting the previous status bar style. /// /// Returns true if launch url is successful; false is only returned when [universalLinksOnly] /// is set to true and the universal link failed to launch. @Deprecated('Use launchUrl instead') Future launch( String urlString, { bool? forceSafariVC, bool forceWebView = false, bool enableJavaScript = false, bool enableDomStorage = false, bool universalLinksOnly = false, Map headers = const {}, Brightness? statusBarBrightness, String? webOnlyWindowName, }) async { final Uri? url = Uri.tryParse(urlString.trimLeft()); final bool isWebURL = url != null && (url.scheme == 'http' || url.scheme == 'https'); if ((forceSafariVC ?? false || forceWebView) && !isWebURL) { throw PlatformException( code: 'NOT_A_WEB_SCHEME', message: 'To use webview or safariVC, you need to pass ' 'in a web URL. This $urlString is not a web URL.'); } /// [true] so that ui is automatically computed if [statusBarBrightness] is set. bool previousAutomaticSystemUiAdjustment = true; if (statusBarBrightness != null && defaultTargetPlatform == TargetPlatform.iOS && _ambiguate(WidgetsBinding.instance) != null) { previousAutomaticSystemUiAdjustment = _ambiguate(WidgetsBinding.instance)! .renderView .automaticSystemUiAdjustment; _ambiguate(WidgetsBinding.instance)! .renderView .automaticSystemUiAdjustment = false; SystemChrome.setSystemUIOverlayStyle(statusBarBrightness == Brightness.light ? SystemUiOverlayStyle.dark : SystemUiOverlayStyle.light); } final bool result = await UrlLauncherPlatform.instance.launch( urlString, useSafariVC: forceSafariVC ?? isWebURL, useWebView: forceWebView, enableJavaScript: enableJavaScript, enableDomStorage: enableDomStorage, universalLinksOnly: universalLinksOnly, headers: headers, webOnlyWindowName: webOnlyWindowName, ); if (statusBarBrightness != null && _ambiguate(WidgetsBinding.instance) != null) { _ambiguate(WidgetsBinding.instance)! .renderView .automaticSystemUiAdjustment = previousAutomaticSystemUiAdjustment; } return result; } /// Checks whether the specified URL can be handled by some app installed on the /// device. /// /// On some systems, such as recent versions of Android and iOS, this will /// always return false unless the application has been configuration to allow /// querying the system for launch support. See /// [the README](https://pub.dev/packages/url_launcher#configuration) for /// details. @Deprecated('Use canLaunchUrl instead') Future canLaunch(String urlString) async { return UrlLauncherPlatform.instance.canLaunch(urlString); } /// Closes the current WebView, if one was previously opened via a call to [launch]. /// /// If [launch] was never called, then this call will not have any effect. /// /// On Android systems, if [launch] was called without `forceWebView` being set to `true` /// Or on IOS systems, if [launch] was called without `forceSafariVC` being set to `true`, /// this call will not do anything either, simply because there is no /// WebView/SafariViewController available to be closed. @Deprecated('Use closeInAppWebView instead') Future closeWebView() async { return UrlLauncherPlatform.instance.closeWebView(); } /// This allows a value of type T or T? to be treated as a value of type T?. /// /// 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; ================================================ FILE: packages/url_launcher/url_launcher/lib/src/link.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:url_launcher_platform_interface/link.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'types.dart'; import 'url_launcher_uri.dart'; /// The function used to push routes to the Flutter framework. @visibleForTesting Future Function(Object?, String) pushRouteToFrameworkFunction = pushRouteNameToFramework; /// A widget that renders a real link on the web, and uses WebViews in native /// platforms to open links. /// /// Example link to an external URL: /// /// ```dart /// Link( /// uri: Uri.parse('https://flutter.dev'), /// builder: (BuildContext context, FollowLink followLink) => ElevatedButton( /// onPressed: followLink, /// // ... other properties here ... /// )}, /// ); /// ``` /// /// Example link to a route name within the app: /// /// ```dart /// Link( /// uri: Uri.parse('/home'), /// builder: (BuildContext context, FollowLink followLink) => ElevatedButton( /// onPressed: followLink, /// // ... other properties here ... /// )}, /// ); /// ``` class Link extends StatelessWidget implements LinkInfo { /// Creates a widget that renders a real link on the web, and uses WebViews in /// native platforms to open links. const Link({ Key? key, required this.uri, this.target = LinkTarget.defaultTarget, required this.builder, }) : super(key: key); /// Called at build time to construct the widget tree under the link. @override final LinkWidgetBuilder builder; /// The destination that this link leads to. @override final Uri? uri; /// The target indicating where to open the link. @override final LinkTarget target; /// Whether the link is disabled or not. @override bool get isDisabled => uri == null; LinkDelegate get _effectiveDelegate { return UrlLauncherPlatform.instance.linkDelegate ?? DefaultLinkDelegate.create; } @override Widget build(BuildContext context) { return _effectiveDelegate(this); } } /// The default delegate used on non-web platforms. /// /// For external URIs, it uses url_launche APIs. For app route names, it uses /// event channel messages to instruct the framework to push the route name. class DefaultLinkDelegate extends StatelessWidget { /// Creates a delegate for the given [link]. const DefaultLinkDelegate(this.link, {Key? key}) : super(key: key); /// Given a [link], creates an instance of [DefaultLinkDelegate]. /// /// This is a static method so it can be used as a tear-off. static DefaultLinkDelegate create(LinkInfo link) { return DefaultLinkDelegate(link); } /// Information about the link built by the app. final LinkInfo link; bool get _useWebView { if (link.target == LinkTarget.self) { return true; } if (link.target == LinkTarget.blank) { return false; } return false; } Future _followLink(BuildContext context) async { final Uri url = link.uri!; if (!url.hasScheme) { // A uri that doesn't have a scheme is an internal route name. In this // case, we push it via Flutter's navigation system instead of letting the // browser handle it. final String routeName = link.uri.toString(); await pushRouteToFrameworkFunction(context, routeName); return; } // At this point, we know that the link is external. So we use the // `launchUrl` API to open the link. if (await canLaunchUrl(url)) { await launchUrl( url, mode: _useWebView ? LaunchMode.inAppWebView : LaunchMode.externalApplication, ); } else { FlutterError.reportError(FlutterErrorDetails( exception: 'Could not launch link $url', stack: StackTrace.current, library: 'url_launcher', context: ErrorDescription('during launching a link'), )); } } @override Widget build(BuildContext context) { return link.builder( context, link.isDisabled ? null : () => _followLink(context), ); } } ================================================ FILE: packages/url_launcher/url_launcher/lib/src/type_conversion.dart ================================================ // Copyright 2013 The Flutter 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:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'types.dart'; /// Converts an (app-facing) [WebViewConfiguration] to a (platform interface) /// [InAppWebViewConfiguration]. InAppWebViewConfiguration convertConfiguration(WebViewConfiguration config) { return InAppWebViewConfiguration( enableJavaScript: config.enableJavaScript, enableDomStorage: config.enableDomStorage, headers: config.headers, ); } /// Converts an (app-facing) [LaunchMode] to a (platform interface) /// [PreferredLaunchMode]. PreferredLaunchMode convertLaunchMode(LaunchMode mode) { switch (mode) { case LaunchMode.platformDefault: return PreferredLaunchMode.platformDefault; case LaunchMode.inAppWebView: return PreferredLaunchMode.inAppWebView; case LaunchMode.externalApplication: return PreferredLaunchMode.externalApplication; case LaunchMode.externalNonBrowserApplication: return PreferredLaunchMode.externalNonBrowserApplication; } } ================================================ FILE: packages/url_launcher/url_launcher/lib/src/types.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; /// The desired mode to launch a URL. /// /// Support for these modes varies by platform. Platforms that do not support /// the requested mode may substitute another mode. See [launchUrl] for more /// details. enum LaunchMode { /// Leaves the decision of how to launch the URL to the platform /// implementation. platformDefault, /// Loads the URL in an in-app web view (e.g., Safari View Controller). inAppWebView, /// Passes the URL to the OS to be handled by another application. externalApplication, /// Passes the URL to the OS to be handled by another non-browser application. externalNonBrowserApplication, } /// Additional configuration options for [LaunchMode.inAppWebView]. @immutable class WebViewConfiguration { /// Creates a new WebViewConfiguration with the given settings. const WebViewConfiguration({ this.enableJavaScript = true, this.enableDomStorage = true, this.headers = const {}, }); /// Whether or not JavaScript is enabled for the web content. /// /// Disabling this may not be supported on all platforms. final bool enableJavaScript; /// Whether or not DOM storage is enabled for the web content. /// /// Disabling this may not be supported on all platforms. final bool enableDomStorage; /// Additional headers to pass in the load request. /// /// On Android, this may work even when not loading in an in-app web view. /// When loading in an external browsers, this sets /// [Browser.EXTRA_HEADERS](https://developer.android.com/reference/android/provider/Browser#EXTRA_HEADERS) /// Not all browsers support this, so it is not guaranteed to be honored. final Map headers; } ================================================ FILE: packages/url_launcher/url_launcher/lib/src/url_launcher_string.dart ================================================ // Copyright 2013 The Flutter 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:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'type_conversion.dart'; import 'types.dart'; /// String version of [launchUrl]. /// /// This should be used only in the very rare case of needing to launch a URL /// that is considered valid by the host platform, but not by Dart's [Uri] /// class. In all other cases, use [launchUrl] instead, as that will ensure /// that you are providing a valid URL. /// /// The behavior of this method when passing an invalid URL is entirely /// platform-specific; no effort is made by the plugin to make the URL valid. /// Some platforms may provide best-effort interpretation of an invalid URL, /// others will immediately fail if the URL can't be parsed according to the /// official standards that define URL formats. Future launchUrlString( String urlString, { LaunchMode mode = LaunchMode.platformDefault, WebViewConfiguration webViewConfiguration = const WebViewConfiguration(), String? webOnlyWindowName, }) async { if (mode == LaunchMode.inAppWebView && !(urlString.startsWith('https:') || urlString.startsWith('http:'))) { throw ArgumentError.value(urlString, 'urlString', 'To use an in-app web view, you must provide an http(s) URL.'); } return UrlLauncherPlatform.instance.launchUrl( urlString, LaunchOptions( mode: convertLaunchMode(mode), webViewConfiguration: convertConfiguration(webViewConfiguration), webOnlyWindowName: webOnlyWindowName, ), ); } /// String version of [canLaunchUrl]. /// /// This should be used only in the very rare case of needing to check a URL /// that is considered valid by the host platform, but not by Dart's [Uri] /// class. In all other cases, use [canLaunchUrl] instead, as that will ensure /// that you are providing a valid URL. /// /// The behavior of this method when passing an invalid URL is entirely /// platform-specific; no effort is made by the plugin to make the URL valid. /// Some platforms may provide best-effort interpretation of an invalid URL, /// others will immediately fail if the URL can't be parsed according to the /// official standards that define URL formats. Future canLaunchUrlString(String urlString) async { return UrlLauncherPlatform.instance.canLaunch(urlString); } ================================================ FILE: packages/url_launcher/url_launcher/lib/src/url_launcher_uri.dart ================================================ // Copyright 2013 The Flutter 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:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import '../url_launcher_string.dart'; import 'type_conversion.dart'; /// Passes [url] to the underlying platform for handling. /// /// [mode] support varies significantly by platform: /// - [LaunchMode.platformDefault] is supported on all platforms: /// - On iOS and Android, this treats web URLs as /// [LaunchMode.inAppWebView], and all other URLs as /// [LaunchMode.externalApplication]. /// - On Windows, macOS, and Linux this behaves like /// [LaunchMode.externalApplication]. /// - On web, this uses `webOnlyWindowName` for web URLs, and behaves like /// [LaunchMode.externalApplication] for any other content. /// - [LaunchMode.inAppWebView] is currently only supported on iOS and /// Android. If a non-web URL is passed with this mode, an [ArgumentError] /// will be thrown. /// - [LaunchMode.externalApplication] is supported on all platforms. /// On iOS, this should be used in cases where sharing the cookies of the /// user's browser is important, such as SSO flows, since Safari View /// Controller does not share the browser's context. /// - [LaunchMode.externalNonBrowserApplication] is supported on iOS 10+. /// This setting is used to require universal links to open in a non-browser /// application. /// /// For web, [webOnlyWindowName] specifies a target for the launch. This /// supports the standard special link target names. For example: /// - "_blank" opens the new URL in a new tab. /// - "_self" opens the new URL in the current tab. /// Default behaviour when unset is to open the url in a new tab. /// /// Returns true if the URL was launched successful, otherwise either returns /// false or throws a [PlatformException] depending on the failure. Future launchUrl( Uri url, { LaunchMode mode = LaunchMode.platformDefault, WebViewConfiguration webViewConfiguration = const WebViewConfiguration(), String? webOnlyWindowName, }) async { if (mode == LaunchMode.inAppWebView && !(url.scheme == 'https' || url.scheme == 'http')) { throw ArgumentError.value(url, 'url', 'To use an in-app web view, you must provide an http(s) URL.'); } return UrlLauncherPlatform.instance.launchUrl( url.toString(), LaunchOptions( mode: convertLaunchMode(mode), webViewConfiguration: convertConfiguration(webViewConfiguration), webOnlyWindowName: webOnlyWindowName, ), ); } /// Checks whether the specified URL can be handled by some app installed on the /// device. /// /// Returns true if it is possible to verify that there is a handler available. /// A false return value can indicate either that there is no handler available, /// or that the application does not have permission to check. For example: /// - On recent versions of Android and iOS, this will always return false /// unless the application has been configuration to allow /// querying the system for launch support. See /// [the README](https://pub.dev/packages/url_launcher#configuration) for /// details. /// - On web, this will always return false except for a few specific schemes /// that are always assumed to be supported (such as http(s)), as web pages /// are never allowed to query installed applications. Future canLaunchUrl(Uri url) async { return UrlLauncherPlatform.instance.canLaunch(url.toString()); } /// Closes the current in-app web view, if one was previously opened by /// [launchUrl]. /// /// If [launchUrl] was never called with [LaunchMode.inAppWebView], then this /// call will have no effect. Future closeInAppWebView() async { return UrlLauncherPlatform.instance.closeWebView(); } ================================================ FILE: packages/url_launcher/url_launcher/lib/url_launcher.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'src/legacy_api.dart'; export 'src/types.dart'; export 'src/url_launcher_uri.dart'; ================================================ FILE: packages/url_launcher/url_launcher/lib/url_launcher_string.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Provides a String-based alterantive to the Uri-based primary API. // // This is provided as a separate import because it's much easier to use // incorrectly, so should require explicit opt-in (to avoid issues such as // IDE auto-complete to the more error-prone APIs just by importing the // main API). export 'src/types.dart'; export 'src/url_launcher_string.dart'; ================================================ FILE: packages/url_launcher/url_launcher/pubspec.yaml ================================================ name: url_launcher 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.9 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: platforms: android: default_package: url_launcher_android ios: default_package: url_launcher_ios linux: default_package: url_launcher_linux macos: default_package: url_launcher_macos web: default_package: url_launcher_web windows: default_package: url_launcher_windows dependencies: flutter: sdk: flutter url_launcher_android: ^6.0.13 url_launcher_ios: ^6.0.13 # Allow either the pure-native or Dart/native hybrid versions of the desktop # implementations, as both are compatible. url_launcher_linux: ">=2.0.0 <4.0.0" url_launcher_macos: ">=2.0.0 <4.0.0" url_launcher_platform_interface: ^2.1.0 url_launcher_web: ^2.0.0 url_launcher_windows: ">=2.0.0 <4.0.0" dev_dependencies: flutter_test: sdk: flutter mockito: ^5.0.0 plugin_platform_interface: ^2.0.0 test: ^1.16.3 ================================================ FILE: packages/url_launcher/url_launcher/test/link_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:url_launcher/link.dart'; import 'package:url_launcher/src/link.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'mocks/mock_url_launcher_platform.dart'; void main() { late MockUrlLauncher mock; setUp(() { mock = MockUrlLauncher(); UrlLauncherPlatform.instance = mock; }); group('Link', () { testWidgets('handles null uri correctly', (WidgetTester tester) async { bool isBuilt = false; FollowLink? followLink; final Link link = Link( uri: null, builder: (BuildContext context, FollowLink? followLink2) { isBuilt = true; followLink = followLink2; return Container(); }, ); await tester.pumpWidget(link); expect(link.isDisabled, isTrue); expect(isBuilt, isTrue); expect(followLink, isNull); }); testWidgets('calls url_launcher for external URLs with blank target', (WidgetTester tester) async { FollowLink? followLink; await tester.pumpWidget(Link( uri: Uri.parse('http://example.com/foobar'), target: LinkTarget.blank, builder: (BuildContext context, FollowLink? followLink2) { followLink = followLink2; return Container(); }, )); mock ..setLaunchExpectations( url: 'http://example.com/foobar', launchMode: PreferredLaunchMode.externalApplication, universalLinksOnly: false, enableJavaScript: true, enableDomStorage: true, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); await followLink!(); expect(mock.canLaunchCalled, isTrue); expect(mock.launchCalled, isTrue); }); testWidgets('calls url_launcher for external URLs with self target', (WidgetTester tester) async { FollowLink? followLink; await tester.pumpWidget(Link( uri: Uri.parse('http://example.com/foobar'), target: LinkTarget.self, builder: (BuildContext context, FollowLink? followLink2) { followLink = followLink2; return Container(); }, )); mock ..setLaunchExpectations( url: 'http://example.com/foobar', launchMode: PreferredLaunchMode.inAppWebView, universalLinksOnly: false, enableJavaScript: true, enableDomStorage: true, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); await followLink!(); expect(mock.canLaunchCalled, isTrue); expect(mock.launchCalled, isTrue); }); testWidgets('pushes to framework for internal route names', (WidgetTester tester) async { final Uri uri = Uri.parse('/foo/bar'); FollowLink? followLink; await tester.pumpWidget(MaterialApp( routes: { '/': (BuildContext context) => Link( uri: uri, builder: (BuildContext context, FollowLink? followLink2) { followLink = followLink2; return Container(); }, ), '/foo/bar': (BuildContext context) => Container(), }, )); bool frameworkCalled = false; final Future Function(Object?, String) originalPushFunction = pushRouteToFrameworkFunction; pushRouteToFrameworkFunction = (Object? _, String __) { frameworkCalled = true; return Future.value(ByteData(0)); }; await followLink!(); // Shouldn't use url_launcher when uri is an internal route name. expect(mock.canLaunchCalled, isFalse); expect(mock.launchCalled, isFalse); // A route should have been pushed to the framework. expect(frameworkCalled, true); // Restore the original function. pushRouteToFrameworkFunction = originalPushFunction; }); }); } ================================================ FILE: packages/url_launcher/url_launcher/test/mocks/mock_url_launcher_platform.dart ================================================ // Copyright 2013 The Flutter 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:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:url_launcher_platform_interface/link.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; class MockUrlLauncher extends Fake with MockPlatformInterfaceMixin implements UrlLauncherPlatform { String? url; PreferredLaunchMode? launchMode; bool? useSafariVC; bool? useWebView; bool? enableJavaScript; bool? enableDomStorage; bool? universalLinksOnly; Map? headers; String? webOnlyWindowName; bool? response; bool closeWebViewCalled = false; bool canLaunchCalled = false; bool launchCalled = false; // ignore: use_setters_to_change_properties void setCanLaunchExpectations(String url) { this.url = url; } void setLaunchExpectations({ required String url, PreferredLaunchMode? launchMode, bool? useSafariVC, bool? useWebView, required bool enableJavaScript, required bool enableDomStorage, required bool universalLinksOnly, required Map headers, required String? webOnlyWindowName, }) { this.url = url; this.launchMode = launchMode; this.useSafariVC = useSafariVC; this.useWebView = useWebView; this.enableJavaScript = enableJavaScript; this.enableDomStorage = enableDomStorage; this.universalLinksOnly = universalLinksOnly; this.headers = headers; this.webOnlyWindowName = webOnlyWindowName; } // ignore: use_setters_to_change_properties void setResponse(bool response) { this.response = response; } @override LinkDelegate? get linkDelegate => null; @override Future canLaunch(String url) async { expect(url, this.url); canLaunchCalled = true; return response!; } @override Future launch( String url, { required bool useSafariVC, required bool useWebView, required bool enableJavaScript, required bool enableDomStorage, required bool universalLinksOnly, required Map headers, String? webOnlyWindowName, }) async { expect(url, this.url); expect(useSafariVC, this.useSafariVC); expect(useWebView, this.useWebView); expect(enableJavaScript, this.enableJavaScript); expect(enableDomStorage, this.enableDomStorage); expect(universalLinksOnly, this.universalLinksOnly); expect(headers, this.headers); expect(webOnlyWindowName, this.webOnlyWindowName); launchCalled = true; return response!; } @override Future launchUrl(String url, LaunchOptions options) async { expect(url, this.url); expect(options.mode, launchMode); expect(options.webViewConfiguration.enableJavaScript, enableJavaScript); expect(options.webViewConfiguration.enableDomStorage, enableDomStorage); expect(options.webViewConfiguration.headers, headers); expect(options.webOnlyWindowName, webOnlyWindowName); launchCalled = true; return response!; } @override Future closeWebView() async { closeWebViewCalled = true; } } ================================================ FILE: packages/url_launcher/url_launcher/test/src/legacy_api_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#105648) // ignore: unnecessary_import import 'dart:ui' show Brightness; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart' show PlatformException; import 'package:flutter_test/flutter_test.dart'; import 'package:url_launcher/src/legacy_api.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import '../mocks/mock_url_launcher_platform.dart'; void main() { final MockUrlLauncher mock = MockUrlLauncher(); UrlLauncherPlatform.instance = mock; test('closeWebView default behavior', () async { await closeWebView(); expect(mock.closeWebViewCalled, isTrue); }); group('canLaunch', () { test('returns true', () async { mock ..setCanLaunchExpectations('foo') ..setResponse(true); final bool result = await canLaunch('foo'); expect(result, isTrue); }); test('returns false', () async { mock ..setCanLaunchExpectations('foo') ..setResponse(false); final bool result = await canLaunch('foo'); expect(result, isFalse); }); }); group('launch', () { test('default behavior', () async { mock ..setLaunchExpectations( url: 'http://flutter.dev/', useSafariVC: true, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect(await launch('http://flutter.dev/'), isTrue); }); test('with headers', () async { mock ..setLaunchExpectations( url: 'http://flutter.dev/', useSafariVC: true, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: {'key': 'value'}, webOnlyWindowName: null, ) ..setResponse(true); expect( await launch( 'http://flutter.dev/', headers: {'key': 'value'}, ), isTrue); }); test('force SafariVC', () async { mock ..setLaunchExpectations( url: 'http://flutter.dev/', useSafariVC: true, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect(await launch('http://flutter.dev/', forceSafariVC: true), isTrue); }); test('universal links only', () async { mock ..setLaunchExpectations( url: 'http://flutter.dev/', useSafariVC: false, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: true, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect( await launch('http://flutter.dev/', forceSafariVC: false, universalLinksOnly: true), isTrue); }); test('force WebView', () async { mock ..setLaunchExpectations( url: 'http://flutter.dev/', useSafariVC: true, useWebView: true, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect(await launch('http://flutter.dev/', forceWebView: true), isTrue); }); test('force WebView enable javascript', () async { mock ..setLaunchExpectations( url: 'http://flutter.dev/', useSafariVC: true, useWebView: true, enableJavaScript: true, enableDomStorage: false, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect( await launch('http://flutter.dev/', forceWebView: true, enableJavaScript: true), isTrue); }); test('force WebView enable DOM storage', () async { mock ..setLaunchExpectations( url: 'http://flutter.dev/', useSafariVC: true, useWebView: true, enableJavaScript: false, enableDomStorage: true, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect( await launch('http://flutter.dev/', forceWebView: true, enableDomStorage: true), isTrue); }); test('force SafariVC to false', () async { mock ..setLaunchExpectations( url: 'http://flutter.dev/', useSafariVC: false, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect(await launch('http://flutter.dev/', forceSafariVC: false), isTrue); }); test('cannot launch a non-web in webview', () async { expect(() async => launch('tel:555-555-5555', forceWebView: true), throwsA(isA())); }); test('send e-mail', () async { mock ..setLaunchExpectations( url: 'mailto:gmail-noreply@google.com?subject=Hello', useSafariVC: false, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect(await launch('mailto:gmail-noreply@google.com?subject=Hello'), isTrue); }); test('cannot send e-mail with forceSafariVC: true', () async { expect( () async => launch('mailto:gmail-noreply@google.com?subject=Hello', forceSafariVC: true), throwsA(isA())); }); test('cannot send e-mail with forceWebView: true', () async { expect( () async => launch('mailto:gmail-noreply@google.com?subject=Hello', forceWebView: true), throwsA(isA())); }); test('controls system UI when changing statusBarBrightness', () async { mock ..setLaunchExpectations( url: 'http://flutter.dev/', useSafariVC: true, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); final TestWidgetsFlutterBinding binding = _anonymize(TestWidgetsFlutterBinding.ensureInitialized())! as TestWidgetsFlutterBinding; debugDefaultTargetPlatformOverride = TargetPlatform.iOS; binding.renderView.automaticSystemUiAdjustment = true; final Future launchResult = launch('http://flutter.dev/', statusBarBrightness: Brightness.dark); // Should take over control of the automaticSystemUiAdjustment while it's // pending, then restore it back to normal after the launch finishes. expect(binding.renderView.automaticSystemUiAdjustment, isFalse); await launchResult; expect(binding.renderView.automaticSystemUiAdjustment, isTrue); }); test('sets automaticSystemUiAdjustment to not be null', () async { mock ..setLaunchExpectations( url: 'http://flutter.dev/', useSafariVC: true, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); final TestWidgetsFlutterBinding binding = _anonymize(TestWidgetsFlutterBinding.ensureInitialized())! as TestWidgetsFlutterBinding; debugDefaultTargetPlatformOverride = TargetPlatform.android; expect(binding.renderView.automaticSystemUiAdjustment, true); final Future launchResult = launch('http://flutter.dev/', statusBarBrightness: Brightness.dark); // The automaticSystemUiAdjustment should be set before the launch // and equal to true after the launch result is complete. expect(binding.renderView.automaticSystemUiAdjustment, true); await launchResult; expect(binding.renderView.automaticSystemUiAdjustment, true); }); test('open non-parseable url', () async { mock ..setLaunchExpectations( url: 'rdp://full%20address=s:mypc:3389&audiomode=i:2&disable%20themes=i:1', useSafariVC: false, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect( await launch( 'rdp://full%20address=s:mypc:3389&audiomode=i:2&disable%20themes=i:1'), isTrue); }); test('cannot open non-parseable url with forceSafariVC: true', () async { expect( () async => launch( 'rdp://full%20address=s:mypc:3389&audiomode=i:2&disable%20themes=i:1', forceSafariVC: true), throwsA(isA())); }); test('cannot open non-parseable url with forceWebView: true', () async { expect( () async => launch( 'rdp://full%20address=s:mypc:3389&audiomode=i:2&disable%20themes=i:1', forceWebView: true), throwsA(isA())); }); }); } /// This removes the type information from a value so that it can be cast /// to another type even if that cast is redundant. /// We use this so that APIs whose type have become more descriptive can still /// be used on the stable branch where they require a cast. Object? _anonymize(T? value) => value; ================================================ FILE: packages/url_launcher/url_launcher/test/src/url_launcher_string_test.dart ================================================ // Copyright 2013 The Flutter 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:url_launcher/src/types.dart'; import 'package:url_launcher/src/url_launcher_string.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import '../mocks/mock_url_launcher_platform.dart'; void main() { final MockUrlLauncher mock = MockUrlLauncher(); UrlLauncherPlatform.instance = mock; group('canLaunchUrlString', () { test('handles returning true', () async { const String urlString = 'https://flutter.dev'; mock ..setCanLaunchExpectations(urlString) ..setResponse(true); final bool result = await canLaunchUrlString(urlString); expect(result, isTrue); }); test('handles returning false', () async { const String urlString = 'https://flutter.dev'; mock ..setCanLaunchExpectations(urlString) ..setResponse(false); final bool result = await canLaunchUrlString(urlString); expect(result, isFalse); }); }); group('launchUrlString', () { test('default behavior with web URL', () async { const String urlString = 'https://flutter.dev'; mock ..setLaunchExpectations( url: urlString, launchMode: PreferredLaunchMode.platformDefault, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect(await launchUrlString(urlString), isTrue); }); test('default behavior with non-web URL', () async { const String urlString = 'customscheme:foo'; mock ..setLaunchExpectations( url: urlString, launchMode: PreferredLaunchMode.platformDefault, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect(await launchUrlString(urlString), isTrue); }); test('explicit default launch mode with web URL', () async { const String urlString = 'https://flutter.dev'; mock ..setLaunchExpectations( url: urlString, launchMode: PreferredLaunchMode.platformDefault, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect(await launchUrlString(urlString), isTrue); }); test('explicit default launch mode with non-web URL', () async { const String urlString = 'customscheme:foo'; mock ..setLaunchExpectations( url: urlString, launchMode: PreferredLaunchMode.platformDefault, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect(await launchUrlString(urlString), isTrue); }); test('in-app webview', () async { const String urlString = 'https://flutter.dev'; mock ..setLaunchExpectations( url: urlString, launchMode: PreferredLaunchMode.inAppWebView, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect(await launchUrlString(urlString, mode: LaunchMode.inAppWebView), isTrue); }); test('external browser', () async { const String urlString = 'https://flutter.dev'; mock ..setLaunchExpectations( url: urlString, launchMode: PreferredLaunchMode.externalApplication, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect( await launchUrlString(urlString, mode: LaunchMode.externalApplication), isTrue); }); test('external non-browser only', () async { const String urlString = 'https://flutter.dev'; mock ..setLaunchExpectations( url: urlString, launchMode: PreferredLaunchMode.externalNonBrowserApplication, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: true, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect( await launchUrlString(urlString, mode: LaunchMode.externalNonBrowserApplication), isTrue); }); test('in-app webview without javascript', () async { const String urlString = 'https://flutter.dev'; mock ..setLaunchExpectations( url: urlString, launchMode: PreferredLaunchMode.inAppWebView, enableJavaScript: false, enableDomStorage: true, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect( await launchUrlString(urlString, mode: LaunchMode.inAppWebView, webViewConfiguration: const WebViewConfiguration(enableJavaScript: false)), isTrue); }); test('in-app webview without DOM storage', () async { const String urlString = 'https://flutter.dev'; mock ..setLaunchExpectations( url: urlString, launchMode: PreferredLaunchMode.inAppWebView, enableJavaScript: true, enableDomStorage: false, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect( await launchUrlString(urlString, mode: LaunchMode.inAppWebView, webViewConfiguration: const WebViewConfiguration(enableDomStorage: false)), isTrue); }); test('in-app webview with headers', () async { const String urlString = 'https://flutter.dev'; mock ..setLaunchExpectations( url: urlString, launchMode: PreferredLaunchMode.inAppWebView, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: false, headers: {'key': 'value'}, webOnlyWindowName: null, ) ..setResponse(true); expect( await launchUrlString(urlString, mode: LaunchMode.inAppWebView, webViewConfiguration: const WebViewConfiguration( headers: {'key': 'value'})), isTrue); }); test('cannot launch a non-web URL in a webview', () async { expect( () async => launchUrlString('tel:555-555-5555', mode: LaunchMode.inAppWebView), throwsA(isA())); }); test('non-web URL with default options', () async { const String emailLaunchUrlString = 'mailto:smith@example.com?subject=Hello'; mock ..setLaunchExpectations( url: emailLaunchUrlString, launchMode: PreferredLaunchMode.platformDefault, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect(await launchUrlString(emailLaunchUrlString), isTrue); }); test('allows non-parsable url', () async { // Not a valid Dart [Uri], but a valid URL on at least some platforms. const String urlString = 'rdp://full%20address=s:mypc:3389&audiomode=i:2&disable%20themes=i:1'; mock ..setLaunchExpectations( url: urlString, launchMode: PreferredLaunchMode.platformDefault, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect(await launchUrlString(urlString), isTrue); }); }); } ================================================ FILE: packages/url_launcher/url_launcher/test/src/url_launcher_uri_test.dart ================================================ // Copyright 2013 The Flutter 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:url_launcher/src/types.dart'; import 'package:url_launcher/src/url_launcher_uri.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import '../mocks/mock_url_launcher_platform.dart'; void main() { final MockUrlLauncher mock = MockUrlLauncher(); UrlLauncherPlatform.instance = mock; test('closeInAppWebView', () async { await closeInAppWebView(); expect(mock.closeWebViewCalled, isTrue); }); group('canLaunchUrl', () { test('handles returning true', () async { final Uri url = Uri.parse('https://flutter.dev'); mock ..setCanLaunchExpectations(url.toString()) ..setResponse(true); final bool result = await canLaunchUrl(url); expect(result, isTrue); }); test('handles returning false', () async { final Uri url = Uri.parse('https://flutter.dev'); mock ..setCanLaunchExpectations(url.toString()) ..setResponse(false); final bool result = await canLaunchUrl(url); expect(result, isFalse); }); }); group('launchUrl', () { test('default behavior with web URL', () async { final Uri url = Uri.parse('https://flutter.dev'); mock ..setLaunchExpectations( url: url.toString(), launchMode: PreferredLaunchMode.platformDefault, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect(await launchUrl(url), isTrue); }); test('default behavior with non-web URL', () async { final Uri url = Uri.parse('customscheme:foo'); mock ..setLaunchExpectations( url: url.toString(), launchMode: PreferredLaunchMode.platformDefault, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect(await launchUrl(url), isTrue); }); test('explicit default launch mode with web URL', () async { final Uri url = Uri.parse('https://flutter.dev'); mock ..setLaunchExpectations( url: url.toString(), launchMode: PreferredLaunchMode.platformDefault, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect(await launchUrl(url), isTrue); }); test('explicit default launch mode with non-web URL', () async { final Uri url = Uri.parse('customscheme:foo'); mock ..setLaunchExpectations( url: url.toString(), launchMode: PreferredLaunchMode.platformDefault, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect(await launchUrl(url), isTrue); }); test('in-app webview', () async { final Uri url = Uri.parse('https://flutter.dev'); mock ..setLaunchExpectations( url: url.toString(), launchMode: PreferredLaunchMode.inAppWebView, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect(await launchUrl(url, mode: LaunchMode.inAppWebView), isTrue); }); test('external browser', () async { final Uri url = Uri.parse('https://flutter.dev'); mock ..setLaunchExpectations( url: url.toString(), launchMode: PreferredLaunchMode.externalApplication, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect( await launchUrl(url, mode: LaunchMode.externalApplication), isTrue); }); test('external non-browser only', () async { final Uri url = Uri.parse('https://flutter.dev'); mock ..setLaunchExpectations( url: url.toString(), launchMode: PreferredLaunchMode.externalNonBrowserApplication, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: true, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect( await launchUrl(url, mode: LaunchMode.externalNonBrowserApplication), isTrue); }); test('in-app webview without javascript', () async { final Uri url = Uri.parse('https://flutter.dev'); mock ..setLaunchExpectations( url: url.toString(), launchMode: PreferredLaunchMode.inAppWebView, enableJavaScript: false, enableDomStorage: true, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect( await launchUrl(url, mode: LaunchMode.inAppWebView, webViewConfiguration: const WebViewConfiguration(enableJavaScript: false)), isTrue); }); test('in-app webview without DOM storage', () async { final Uri url = Uri.parse('https://flutter.dev'); mock ..setLaunchExpectations( url: url.toString(), launchMode: PreferredLaunchMode.inAppWebView, enableJavaScript: true, enableDomStorage: false, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect( await launchUrl(url, mode: LaunchMode.inAppWebView, webViewConfiguration: const WebViewConfiguration(enableDomStorage: false)), isTrue); }); test('in-app webview with headers', () async { final Uri url = Uri.parse('https://flutter.dev'); mock ..setLaunchExpectations( url: url.toString(), launchMode: PreferredLaunchMode.inAppWebView, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: false, headers: {'key': 'value'}, webOnlyWindowName: null, ) ..setResponse(true); expect( await launchUrl(url, mode: LaunchMode.inAppWebView, webViewConfiguration: const WebViewConfiguration( headers: {'key': 'value'})), isTrue); }); test('cannot launch a non-web URL in a webview', () async { expect( () async => launchUrl(Uri(scheme: 'tel', path: '555-555-5555'), mode: LaunchMode.inAppWebView), throwsA(isA())); }); test('non-web URL with default options', () async { final Uri emailLaunchUrl = Uri( scheme: 'mailto', path: 'smith@example.com', queryParameters: {'subject': 'Hello'}, ); mock ..setLaunchExpectations( url: emailLaunchUrl.toString(), launchMode: PreferredLaunchMode.platformDefault, enableJavaScript: true, enableDomStorage: true, universalLinksOnly: false, headers: {}, webOnlyWindowName: null, ) ..setResponse(true); expect(await launchUrl(emailLaunchUrl), isTrue); }); }); } ================================================ FILE: packages/url_launcher/url_launcher_android/AUTHORS ================================================ # 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> ================================================ FILE: packages/url_launcher/url_launcher_android/CHANGELOG.md ================================================ ## 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. ## 6.0.21 * Updates androidx.annotation to 1.2.0. ## 6.0.20 * Updates android gradle plugin to 4.2.0. ## 6.0.19 * Revert gradle back to 3.4.2. ## 6.0.18 * Updates gradle to 7.2.2. * Updates minimum Flutter version to 2.10. ## 6.0.17 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 6.0.16 * Adds fallback querying for `canLaunch` with web URLs, to avoid false negatives when there is a custom scheme handler. ## 6.0.15 * Switches to an in-package method channel implementation. ## 6.0.14 * Updates code for new analysis options. * Removes dependency on `meta`. ## 6.0.13 * Splits from `shared_preferences` as a federated implementation. ================================================ FILE: packages/url_launcher/url_launcher_android/LICENSE ================================================ 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. ================================================ FILE: packages/url_launcher/url_launcher_android/README.md ================================================ # url\_launcher\_android The Android implementation of [`url_launcher`][1]. ## Usage This package is [endorsed][2], which means you can simply use `url_launcher` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/url_launcher [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/url_launcher/url_launcher_android/android/build.gradle ================================================ group 'io.flutter.plugins.urllauncher' version '1.0-SNAPSHOT' buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:4.2.0' } } rootProject.allprojects { repositories { google() mavenCentral() } } apply plugin: 'com.android.library' android { compileSdkVersion 33 defaultConfig { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } testOptions { unitTests.includeAndroidResources = true unitTests.returnDefaultValues = true unitTests.all { testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" outputs.upToDateWhen {false} showStandardStreams = true } } } } dependencies { compileOnly 'androidx.annotation:annotation:1.2.0' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:5.1.1' testImplementation 'androidx.test:core:1.0.0' testImplementation 'org.robolectric:robolectric:4.3' } ================================================ FILE: packages/url_launcher/url_launcher_android/android/settings.gradle ================================================ rootProject.name = 'url_launcher_android' ================================================ FILE: packages/url_launcher/url_launcher_android/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/MethodCallHandlerImpl.java ================================================ // Copyright 2013 The Flutter 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.urllauncher; import android.os.Bundle; import android.util.Log; import androidx.annotation.Nullable; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugins.urllauncher.UrlLauncher.LaunchStatus; import java.util.Map; /** * Translates incoming UrlLauncher MethodCalls into well formed Java function calls for {@link * UrlLauncher}. */ final class MethodCallHandlerImpl implements MethodCallHandler { private static final String TAG = "MethodCallHandlerImpl"; private final UrlLauncher urlLauncher; @Nullable private MethodChannel channel; /** Forwards all incoming MethodChannel calls to the given {@code urlLauncher}. */ MethodCallHandlerImpl(UrlLauncher urlLauncher) { this.urlLauncher = urlLauncher; } @Override public void onMethodCall(MethodCall call, Result result) { final String url = call.argument("url"); switch (call.method) { case "canLaunch": onCanLaunch(result, url); break; case "launch": onLaunch(call, result, url); break; case "closeWebView": onCloseWebView(result); break; default: result.notImplemented(); break; } } /** * Registers this instance as a method call handler on the given {@code messenger}. * *

Stops any previously started and unstopped calls. * *

This should be cleaned with {@link #stopListening} once the messenger is disposed of. */ void startListening(BinaryMessenger messenger) { if (channel != null) { Log.wtf(TAG, "Setting a method call handler before the last was disposed."); stopListening(); } channel = new MethodChannel(messenger, "plugins.flutter.io/url_launcher_android"); channel.setMethodCallHandler(this); } /** * Clears this instance from listening to method calls. * *

Does nothing if {@link #startListening} hasn't been called, or if we're already stopped. */ void stopListening() { if (channel == null) { Log.d(TAG, "Tried to stop listening when no MethodChannel had been initialized."); return; } channel.setMethodCallHandler(null); channel = null; } private void onCanLaunch(Result result, String url) { result.success(urlLauncher.canLaunch(url)); } private void onLaunch(MethodCall call, Result result, String url) { final boolean useWebView = call.argument("useWebView"); final boolean enableJavaScript = call.argument("enableJavaScript"); final boolean enableDomStorage = call.argument("enableDomStorage"); final Map headersMap = call.argument("headers"); final Bundle headersBundle = extractBundle(headersMap); LaunchStatus launchStatus = urlLauncher.launch(url, headersBundle, useWebView, enableJavaScript, enableDomStorage); if (launchStatus == LaunchStatus.NO_ACTIVITY) { result.error("NO_ACTIVITY", "Launching a URL requires a foreground activity.", null); } else if (launchStatus == LaunchStatus.ACTIVITY_NOT_FOUND) { result.error( "ACTIVITY_NOT_FOUND", String.format("No Activity found to handle intent { %s }", url), null); } else { result.success(true); } } private void onCloseWebView(Result result) { urlLauncher.closeWebView(); result.success(null); } private static Bundle extractBundle(Map headersMap) { final Bundle headersBundle = new Bundle(); for (String key : headersMap.keySet()) { final String value = headersMap.get(key); headersBundle.putString(key, value); } return headersBundle; } } ================================================ FILE: packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncher.java ================================================ // Copyright 2013 The Flutter 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.urllauncher; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.provider.Browser; import android.util.Log; import androidx.annotation.Nullable; /** Launches components for URLs. */ class UrlLauncher { private static final String TAG = "UrlLauncher"; private final Context applicationContext; @Nullable private Activity activity; /** * Uses the given {@code applicationContext} for launching intents. * *

It may be null initially, but should be set before calling {@link #launch}. */ UrlLauncher(Context applicationContext, @Nullable Activity activity) { this.applicationContext = applicationContext; this.activity = activity; } void setActivity(@Nullable Activity activity) { this.activity = activity; } /** Returns whether the given {@code url} resolves into an existing component. */ boolean canLaunch(String url) { Intent launchIntent = new Intent(Intent.ACTION_VIEW); launchIntent.setData(Uri.parse(url)); ComponentName componentName = launchIntent.resolveActivity(applicationContext.getPackageManager()); if (componentName == null) { Log.i(TAG, "component name for " + url + " is null"); return false; } else { Log.i(TAG, "component name for " + url + " is " + componentName.toShortString()); return !"{com.android.fallback/com.android.fallback.Fallback}" .equals(componentName.toShortString()); } } /** * Attempts to launch the given {@code url}. * * @param headersBundle forwarded to the intent as {@code Browser.EXTRA_HEADERS}. * @param useWebView when true, the URL is launched inside of {@link WebViewActivity}. * @param enableJavaScript Only used if {@param useWebView} is true. Enables JS in the WebView. * @param enableDomStorage Only used if {@param useWebView} is true. Enables DOM storage in the * @return {@link LaunchStatus#NO_ACTIVITY} if there's no available {@code applicationContext}. * {@link LaunchStatus#ACTIVITY_NOT_FOUND} if there's no activity found to handle {@code * launchIntent}. {@link LaunchStatus#OK} otherwise. */ LaunchStatus launch( String url, Bundle headersBundle, boolean useWebView, boolean enableJavaScript, boolean enableDomStorage) { if (activity == null) { return LaunchStatus.NO_ACTIVITY; } Intent launchIntent; if (useWebView) { launchIntent = WebViewActivity.createIntent( activity, url, enableJavaScript, enableDomStorage, headersBundle); } else { launchIntent = new Intent(Intent.ACTION_VIEW) .setData(Uri.parse(url)) .putExtra(Browser.EXTRA_HEADERS, headersBundle); } try { activity.startActivity(launchIntent); } catch (ActivityNotFoundException e) { return LaunchStatus.ACTIVITY_NOT_FOUND; } return LaunchStatus.OK; } /** Closes any activities started with {@link #launch} {@code useWebView=true}. */ void closeWebView() { applicationContext.sendBroadcast(new Intent(WebViewActivity.ACTION_CLOSE)); } /** Result of a {@link #launch} call. */ enum LaunchStatus { /** The intent was well formed. */ OK, /** No activity was found to launch. */ NO_ACTIVITY, /** No Activity found that can handle given intent. */ ACTIVITY_NOT_FOUND, } } ================================================ FILE: packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/UrlLauncherPlugin.java ================================================ // Copyright 2013 The Flutter 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.urllauncher; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; /** * Plugin implementation that uses the new {@code io.flutter.embedding} package. * *

Instantiate this in an add to app scenario to gracefully handle activity and context changes. */ public final class UrlLauncherPlugin implements FlutterPlugin, ActivityAware { private static final String TAG = "UrlLauncherPlugin"; @Nullable private MethodCallHandlerImpl methodCallHandler; @Nullable private UrlLauncher urlLauncher; /** * Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common} * package. * *

Calling this automatically initializes the plugin. However plugins initialized this way * won't react to changes in activity or context, unlike {@link UrlLauncherPlugin}. */ @SuppressWarnings("deprecation") public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { MethodCallHandlerImpl handler = new MethodCallHandlerImpl(new UrlLauncher(registrar.context(), registrar.activity())); handler.startListening(registrar.messenger()); } @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { urlLauncher = new UrlLauncher(binding.getApplicationContext(), /*activity=*/ null); methodCallHandler = new MethodCallHandlerImpl(urlLauncher); methodCallHandler.startListening(binding.getBinaryMessenger()); } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { if (methodCallHandler == null) { Log.wtf(TAG, "Already detached from the engine."); return; } methodCallHandler.stopListening(); methodCallHandler = null; urlLauncher = null; } @Override public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { if (methodCallHandler == null) { Log.wtf(TAG, "urlLauncher was never set."); return; } urlLauncher.setActivity(binding.getActivity()); } @Override public void onDetachedFromActivity() { if (methodCallHandler == null) { Log.wtf(TAG, "urlLauncher was never set."); return; } urlLauncher.setActivity(null); } @Override public void onDetachedFromActivityForConfigChanges() { onDetachedFromActivity(); } @Override public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { onAttachedToActivity(binding); } } ================================================ FILE: packages/url_launcher/url_launcher_android/android/src/main/java/io/flutter/plugins/urllauncher/WebViewActivity.java ================================================ // Copyright 2013 The Flutter 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.urllauncher; import android.annotation.TargetApi; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Build; import android.os.Bundle; import android.os.Message; import android.provider.Browser; import android.view.KeyEvent; import android.webkit.WebChromeClient; import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import java.util.Collections; import java.util.HashMap; import java.util.Map; /* Launches WebView activity */ public class WebViewActivity extends Activity { /* * Use this to trigger a BroadcastReceiver inside WebViewActivity * that will request the current instance to finish. * */ public static String ACTION_CLOSE = "close action"; private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (ACTION_CLOSE.equals(action)) { finish(); } } }; private final WebViewClient webViewClient = new WebViewClient() { /* * This method is deprecated in API 24. Still overridden to support * earlier Android versions. */ @SuppressWarnings("deprecation") @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { view.loadUrl(url); return false; } return super.shouldOverrideUrlLoading(view, url); } @RequiresApi(Build.VERSION_CODES.N) @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { view.loadUrl(request.getUrl().toString()); } return false; } }; private WebView webview; private IntentFilter closeIntentFilter = new IntentFilter(ACTION_CLOSE); // Verifies that a url opened by `Window.open` has a secure url. private class FlutterWebChromeClient extends WebChromeClient { @Override public boolean onCreateWindow( final WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { final WebViewClient webViewClient = new WebViewClient() { @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public boolean shouldOverrideUrlLoading( @NonNull WebView view, @NonNull WebResourceRequest request) { webview.loadUrl(request.getUrl().toString()); return true; } /* * This method is deprecated in API 24. Still overridden to support * earlier Android versions. */ @SuppressWarnings("deprecation") @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { webview.loadUrl(url); return true; } }; final WebView newWebView = new WebView(webview.getContext()); newWebView.setWebViewClient(webViewClient); final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; transport.setWebView(newWebView); resultMsg.sendToTarget(); return true; } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); webview = new WebView(this); setContentView(webview); // Get the Intent that started this activity and extract the string final Intent intent = getIntent(); final String url = intent.getStringExtra(URL_EXTRA); final boolean enableJavaScript = intent.getBooleanExtra(ENABLE_JS_EXTRA, false); final boolean enableDomStorage = intent.getBooleanExtra(ENABLE_DOM_EXTRA, false); final Bundle headersBundle = intent.getBundleExtra(Browser.EXTRA_HEADERS); final Map headersMap = extractHeaders(headersBundle); webview.loadUrl(url, headersMap); webview.getSettings().setJavaScriptEnabled(enableJavaScript); webview.getSettings().setDomStorageEnabled(enableDomStorage); // Open new urls inside the webview itself. webview.setWebViewClient(webViewClient); // Multi windows is set with FlutterWebChromeClient by default to handle internal bug: b/159892679. webview.getSettings().setSupportMultipleWindows(true); webview.setWebChromeClient(new FlutterWebChromeClient()); // Register receiver that may finish this Activity. registerReceiver(broadcastReceiver, closeIntentFilter); } @VisibleForTesting public static Map extractHeaders(@Nullable Bundle headersBundle) { if (headersBundle == null) { return Collections.emptyMap(); } final Map headersMap = new HashMap<>(); for (String key : headersBundle.keySet()) { final String value = headersBundle.getString(key); headersMap.put(key, value); } return headersMap; } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(broadcastReceiver); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && webview.canGoBack()) { webview.goBack(); return true; } return super.onKeyDown(keyCode, event); } private static String URL_EXTRA = "url"; private static String ENABLE_JS_EXTRA = "enableJavaScript"; private static String ENABLE_DOM_EXTRA = "enableDomStorage"; /* Hides the constants used to forward data to the Activity instance. */ public static Intent createIntent( Context context, String url, boolean enableJavaScript, boolean enableDomStorage, Bundle headersBundle) { return new Intent(context, WebViewActivity.class) .putExtra(URL_EXTRA, url) .putExtra(ENABLE_JS_EXTRA, enableJavaScript) .putExtra(ENABLE_DOM_EXTRA, enableDomStorage) .putExtra(Browser.EXTRA_HEADERS, headersBundle); } } ================================================ FILE: packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/MethodCallHandlerImplTest.java ================================================ // Copyright 2013 The Flutter 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.urllauncher; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; 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.os.Bundle; import androidx.test.core.app.ApplicationProvider; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel.Result; import java.util.HashMap; import java.util.Map; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class MethodCallHandlerImplTest { private static final String CHANNEL_NAME = "plugins.flutter.io/url_launcher_android"; private UrlLauncher urlLauncher; private MethodCallHandlerImpl methodCallHandler; @Before public void setUp() { urlLauncher = new UrlLauncher(ApplicationProvider.getApplicationContext(), /*activity=*/ null); methodCallHandler = new MethodCallHandlerImpl(urlLauncher); } @Test public void startListening_registersChannel() { BinaryMessenger messenger = mock(BinaryMessenger.class); methodCallHandler.startListening(messenger); verify(messenger, times(1)) .setMessageHandler(eq(CHANNEL_NAME), any(BinaryMessageHandler.class)); } @Test public void startListening_unregistersExistingChannel() { BinaryMessenger firstMessenger = mock(BinaryMessenger.class); BinaryMessenger secondMessenger = mock(BinaryMessenger.class); methodCallHandler.startListening(firstMessenger); methodCallHandler.startListening(secondMessenger); // Unregisters the first and then registers the second. verify(firstMessenger, times(1)).setMessageHandler(CHANNEL_NAME, null); verify(secondMessenger, times(1)) .setMessageHandler(eq(CHANNEL_NAME), any(BinaryMessageHandler.class)); } @Test public void stopListening_unregistersExistingChannel() { BinaryMessenger messenger = mock(BinaryMessenger.class); methodCallHandler.startListening(messenger); methodCallHandler.stopListening(); verify(messenger, times(1)).setMessageHandler(CHANNEL_NAME, null); } @Test public void stopListening_doesNothingWhenUnset() { BinaryMessenger messenger = mock(BinaryMessenger.class); methodCallHandler.stopListening(); verify(messenger, never()).setMessageHandler(CHANNEL_NAME, null); } @Test public void onMethodCall_canLaunchReturnsTrue() { urlLauncher = mock(UrlLauncher.class); methodCallHandler = new MethodCallHandlerImpl(urlLauncher); String url = "foo"; when(urlLauncher.canLaunch(url)).thenReturn(true); Result result = mock(Result.class); Map args = new HashMap<>(); args.put("url", url); methodCallHandler.onMethodCall(new MethodCall("canLaunch", args), result); verify(result, times(1)).success(true); } @Test public void onMethodCall_canLaunchReturnsFalse() { urlLauncher = mock(UrlLauncher.class); methodCallHandler = new MethodCallHandlerImpl(urlLauncher); String url = "foo"; when(urlLauncher.canLaunch(url)).thenReturn(false); Result result = mock(Result.class); Map args = new HashMap<>(); args.put("url", url); methodCallHandler.onMethodCall(new MethodCall("canLaunch", args), result); verify(result, times(1)).success(false); } @Test public void onMethodCall_launchReturnsNoActivityError() { // Setup mock objects urlLauncher = mock(UrlLauncher.class); Result result = mock(Result.class); // Setup expected values String url = "foo"; boolean useWebView = false; boolean enableJavaScript = false; boolean enableDomStorage = false; // Setup arguments map send on the method channel Map args = new HashMap<>(); args.put("url", url); args.put("useWebView", useWebView); args.put("enableJavaScript", enableJavaScript); args.put("enableDomStorage", enableDomStorage); args.put("headers", new HashMap<>()); // Mock the launch method on the urlLauncher class when(urlLauncher.launch( eq(url), any(Bundle.class), eq(useWebView), eq(enableJavaScript), eq(enableDomStorage))) .thenReturn(UrlLauncher.LaunchStatus.NO_ACTIVITY); // Act by calling the "launch" method on the method channel methodCallHandler = new MethodCallHandlerImpl(urlLauncher); methodCallHandler.onMethodCall(new MethodCall("launch", args), result); // Verify the results and assert verify(result, times(1)) .error("NO_ACTIVITY", "Launching a URL requires a foreground activity.", null); } @Test public void onMethodCall_launchReturnsActivityNotFoundError() { // Setup mock objects urlLauncher = mock(UrlLauncher.class); Result result = mock(Result.class); // Setup expected values String url = "foo"; boolean useWebView = false; boolean enableJavaScript = false; boolean enableDomStorage = false; // Setup arguments map send on the method channel Map args = new HashMap<>(); args.put("url", url); args.put("useWebView", useWebView); args.put("enableJavaScript", enableJavaScript); args.put("enableDomStorage", enableDomStorage); args.put("headers", new HashMap<>()); // Mock the launch method on the urlLauncher class when(urlLauncher.launch( eq(url), any(Bundle.class), eq(useWebView), eq(enableJavaScript), eq(enableDomStorage))) .thenReturn(UrlLauncher.LaunchStatus.ACTIVITY_NOT_FOUND); // Act by calling the "launch" method on the method channel methodCallHandler = new MethodCallHandlerImpl(urlLauncher); methodCallHandler.onMethodCall(new MethodCall("launch", args), result); // Verify the results and assert verify(result, times(1)) .error( "ACTIVITY_NOT_FOUND", String.format("No Activity found to handle intent { %s }", url), null); } @Test public void onMethodCall_launchReturnsTrue() { // Setup mock objects urlLauncher = mock(UrlLauncher.class); Result result = mock(Result.class); // Setup expected values String url = "foo"; boolean useWebView = false; boolean enableJavaScript = false; boolean enableDomStorage = false; // Setup arguments map send on the method channel Map args = new HashMap<>(); args.put("url", url); args.put("useWebView", useWebView); args.put("enableJavaScript", enableJavaScript); args.put("enableDomStorage", enableDomStorage); args.put("headers", new HashMap<>()); // Mock the launch method on the urlLauncher class when(urlLauncher.launch( eq(url), any(Bundle.class), eq(useWebView), eq(enableJavaScript), eq(enableDomStorage))) .thenReturn(UrlLauncher.LaunchStatus.OK); // Act by calling the "launch" method on the method channel methodCallHandler = new MethodCallHandlerImpl(urlLauncher); methodCallHandler.onMethodCall(new MethodCall("launch", args), result); // Verify the results and assert verify(result, times(1)).success(true); } @Test public void onMethodCall_closeWebView() { urlLauncher = mock(UrlLauncher.class); methodCallHandler = new MethodCallHandlerImpl(urlLauncher); String url = "foo"; when(urlLauncher.canLaunch(url)).thenReturn(true); Result result = mock(Result.class); Map args = new HashMap<>(); args.put("url", url); methodCallHandler.onMethodCall(new MethodCall("closeWebView", args), result); verify(urlLauncher, times(1)).closeWebView(); verify(result, times(1)).success(null); } } ================================================ FILE: packages/url_launcher/url_launcher_android/android/src/test/java/io/flutter/plugins/urllauncher/WebViewActivityTest.java ================================================ // Copyright 2013 The Flutter 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.urllauncher; import static org.junit.Assert.assertEquals; import java.util.Collections; import org.junit.Test; public class WebViewActivityTest { @Test public void extractHeaders_returnsEmptyMapWhenHeadersBundleNull() { assertEquals(WebViewActivity.extractHeaders(null), Collections.emptyMap()); } } ================================================ FILE: packages/url_launcher/url_launcher_android/example/README.md ================================================ # 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. ================================================ FILE: packages/url_launcher/url_launcher_android/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 lintOptions { disable 'InvalidPackage' } defaultConfig { applicationId "io.flutter.plugins.urllauncherexample" minSdkVersion 16 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' } ================================================ FILE: packages/url_launcher/url_launcher_android/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/url_launcher/url_launcher_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/url_launcher/url_launcher_android/example/android/app/src/androidTest/java/io/flutter/plugins/urllauncherexample/FlutterActivityTest.java ================================================ // Copyright 2013 The Flutter 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.urllauncherexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class FlutterActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/url_launcher/url_launcher_android/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/url_launcher/url_launcher_android/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/url_launcher/url_launcher_android/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Wed Jul 31 20:16:04 BRT 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/url_launcher/url_launcher_android/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true android.enableR8=true ================================================ FILE: packages/url_launcher/url_launcher_android/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withInputStream { stream -> plugins.load(stream) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/url_launcher/url_launcher_android/example/integration_test/url_launcher_test.dart ================================================ // Copyright 2013 The Flutter 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:url_launcher_platform_interface/url_launcher_platform_interface.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('canLaunch', (WidgetTester _) async { final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; expect(await launcher.canLaunch('randomstring'), false); // Generally all devices should have some default browser. expect(await launcher.canLaunch('http://flutter.dev'), true); // sms:, tel:, and mailto: links may not be openable on every device, so // aren't tested here. }); } ================================================ FILE: packages/url_launcher/url_launcher_android/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'package:flutter/material.dart'; import 'package:url_launcher_platform_interface/url_launcher_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: 'URL Launcher', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'URL Launcher'), ); } } 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 UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; bool _hasCallSupport = false; Future? _launched; String _phone = ''; @override void initState() { super.initState(); // Check for phone call support. launcher.canLaunch('tel:123').then((bool result) { setState(() { _hasCallSupport = result; }); }); } Future _launchInBrowser(String url) async { if (!await launcher.launch( url, useSafariVC: false, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: {}, )) { throw Exception('Could not launch $url'); } } Future _launchInWebView(String url) async { if (!await launcher.launch( url, useSafariVC: true, useWebView: true, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: {'my_header_key': 'my_header_value'}, )) { throw Exception('Could not launch $url'); } } Future _launchInWebViewWithJavaScript(String url) async { if (!await launcher.launch( url, useSafariVC: true, useWebView: true, enableJavaScript: true, enableDomStorage: false, universalLinksOnly: false, headers: {}, )) { throw Exception('Could not launch $url'); } } Future _launchInWebViewWithDomStorage(String url) async { if (!await launcher.launch( url, useSafariVC: true, useWebView: true, enableJavaScript: false, enableDomStorage: true, universalLinksOnly: false, headers: {}, )) { throw Exception('Could not launch $url'); } } Widget _launchStatus(BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { return const Text(''); } } Future _makePhoneCall(String phoneNumber) async { // Use `Uri` to ensure that `phoneNumber` is properly URL-encoded. // Just using 'tel:$phoneNumber' would create invalid URLs in some cases, // such as spaces in the input, which would cause `launch` to fail on some // platforms. final Uri launchUri = Uri( scheme: 'tel', path: phoneNumber, ); await launcher.launch( launchUri.toString(), useSafariVC: false, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: true, headers: {}, ); } @override Widget build(BuildContext context) { // onPressed calls using this URL are not gated on a 'canLaunch' check // because the assumption is that every device can launch a web URL. const String toLaunch = 'https://www.cylog.org/headers/'; return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: ListView( children: [ Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.all(16.0), child: TextField( onChanged: (String text) => _phone = text, decoration: const InputDecoration( hintText: 'Input the phone number to launch')), ), ElevatedButton( onPressed: _hasCallSupport ? () => setState(() { _launched = _makePhoneCall(_phone); }) : null, child: _hasCallSupport ? const Text('Make phone call') : const Text('Calling not supported'), ), const Padding( padding: EdgeInsets.all(16.0), child: Text(toLaunch), ), ElevatedButton( onPressed: () => setState(() { _launched = _launchInBrowser(toLaunch); }), child: const Text('Launch in browser'), ), const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { _launched = _launchInWebView(toLaunch); }), child: const Text('Launch in app'), ), ElevatedButton( onPressed: () => setState(() { _launched = _launchInWebViewWithJavaScript(toLaunch); }), child: const Text('Launch in app (JavaScript ON)'), ), ElevatedButton( onPressed: () => setState(() { _launched = _launchInWebViewWithDomStorage(toLaunch); }), child: const Text('Launch in app (DOM storage ON)'), ), const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { _launched = _launchInWebView(toLaunch); Timer(const Duration(seconds: 5), () { launcher.closeWebView(); }); }), child: const Text('Launch in app + close after 5 seconds'), ), const Padding(padding: EdgeInsets.all(16.0)), FutureBuilder(future: _launched, builder: _launchStatus), ], ), ], ), ); } } ================================================ FILE: packages/url_launcher/url_launcher_android/example/pubspec.yaml ================================================ name: url_launcher_example description: Demonstrates how to use the url_launcher plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter url_launcher_android: # When depending on this package from a real application you should use: # url_launcher_android: ^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: ../ url_launcher_platform_interface: ^2.0.3 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter mockito: ^5.0.0 plugin_platform_interface: ^2.0.0 flutter: uses-material-design: true ================================================ FILE: packages/url_launcher/url_launcher_android/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/url_launcher/url_launcher_android/lib/url_launcher_android.dart ================================================ // Copyright 2013 The Flutter 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: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_android'); /// An implementation of [UrlLauncherPlatform] for Android. class UrlLauncherAndroid extends UrlLauncherPlatform { /// Registers this class as the default instance of [UrlLauncherPlatform]. static void registerWith() { UrlLauncherPlatform.instance = UrlLauncherAndroid(); } @override final LinkDelegate? linkDelegate = null; @override Future canLaunch(String url) async { final bool canLaunchSpecificUrl = await _canLaunchUrl(url); if (!canLaunchSpecificUrl) { final String scheme = _getUrlScheme(url); // canLaunch can return false when a custom application is registered to // handle a web URL, but the caller doesn't have permission to see what // that handler is. If that happens, try a web URL (with the same scheme // variant, to be safe) that should not have a custom handler. If that // returns true, then there is a browser, which means that there is // at least one handler for the original URL. if (scheme == 'http' || scheme == 'https') { return _canLaunchUrl('$scheme://flutter.dev'); } } return canLaunchSpecificUrl; } Future _canLaunchUrl(String url) { return _channel.invokeMethod( 'canLaunch', {'url': url}, ).then((bool? value) => value ?? false); } @override Future closeWebView() { return _channel.invokeMethod('closeWebView'); } @override Future launch( String url, { required bool useSafariVC, required bool useWebView, required bool enableJavaScript, required bool enableDomStorage, required bool universalLinksOnly, required Map headers, String? webOnlyWindowName, }) { return _channel.invokeMethod( 'launch', { 'url': url, 'useWebView': useWebView, 'enableJavaScript': enableJavaScript, 'enableDomStorage': enableDomStorage, 'universalLinksOnly': universalLinksOnly, 'headers': headers, }, ).then((bool? value) => value ?? false); } // Returns the part of [url] up to the first ':', or an empty string if there // is no ':'. This deliberately does not use [Uri] to extract the scheme // so that it works on strings that aren't actually valid URLs, since Android // is very lenient about what it accepts for launching. String _getUrlScheme(String url) { final int schemeEnd = url.indexOf(':'); if (schemeEnd == -1) { return ''; } return url.substring(0, schemeEnd); } } ================================================ FILE: packages/url_launcher/url_launcher_android/pubspec.yaml ================================================ 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.23 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: url_launcher platforms: android: package: io.flutter.plugins.urllauncher pluginClass: UrlLauncherPlugin dartPluginClass: UrlLauncherAndroid dependencies: flutter: sdk: flutter url_launcher_platform_interface: ^2.0.3 dev_dependencies: flutter_test: sdk: flutter mockito: ^5.0.0 plugin_platform_interface: ^2.0.0 test: ^1.16.3 ================================================ FILE: packages/url_launcher/url_launcher_android/test/url_launcher_android_test.dart ================================================ // Copyright 2013 The Flutter 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:url_launcher_android/url_launcher_android.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); const MethodChannel channel = MethodChannel('plugins.flutter.io/url_launcher_android'); late List log; setUp(() { log = []; _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, (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; }); }); test('registers instance', () { UrlLauncherAndroid.registerWith(); expect(UrlLauncherPlatform.instance, isA()); }); group('canLaunch', () { test('calls through', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, (MethodCall methodCall) async { log.add(methodCall); return true; }); final UrlLauncherAndroid launcher = UrlLauncherAndroid(); final bool canLaunch = await launcher.canLaunch('http://example.com/'); expect( log, [ isMethodCall('canLaunch', arguments: { 'url': 'http://example.com/', }) ], ); expect(canLaunch, true); }); test('returns false if platform returns null', () async { final UrlLauncherAndroid launcher = UrlLauncherAndroid(); final bool canLaunch = await launcher.canLaunch('http://example.com/'); expect(canLaunch, false); }); test('checks a generic URL if an http URL returns false', () async { const String specificUrl = 'http://example.com/'; const String genericUrl = 'http://flutter.dev'; _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, (MethodCall methodCall) async { log.add(methodCall); return (methodCall.arguments as Map)['url'] != specificUrl; }); final UrlLauncherAndroid launcher = UrlLauncherAndroid(); final bool canLaunch = await launcher.canLaunch(specificUrl); expect(canLaunch, true); expect(log.length, 2); expect((log[1].arguments as Map)['url'], genericUrl); }); test('checks a generic URL if an https URL returns false', () async { const String specificUrl = 'https://example.com/'; const String genericUrl = 'https://flutter.dev'; _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, (MethodCall methodCall) async { log.add(methodCall); return (methodCall.arguments as Map)['url'] != specificUrl; }); final UrlLauncherAndroid launcher = UrlLauncherAndroid(); final bool canLaunch = await launcher.canLaunch(specificUrl); expect(canLaunch, true); expect(log.length, 2); expect((log[1].arguments as Map)['url'], genericUrl); }); test('does not a generic URL if a non-web URL returns false', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, (MethodCall methodCall) async { log.add(methodCall); return false; }); final UrlLauncherAndroid launcher = UrlLauncherAndroid(); final bool canLaunch = await launcher.canLaunch('sms:12345'); expect(canLaunch, false); expect(log.length, 1); }); }); group('launch', () { test('calls through', () async { final UrlLauncherAndroid launcher = UrlLauncherAndroid(); 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/', 'useWebView': false, 'enableJavaScript': false, 'enableDomStorage': false, 'universalLinksOnly': false, 'headers': {}, }) ], ); }); test('passes headers', () async { final UrlLauncherAndroid launcher = UrlLauncherAndroid(); 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/', 'useWebView': false, 'enableJavaScript': false, 'enableDomStorage': false, 'universalLinksOnly': false, 'headers': {'key': 'value'}, }) ], ); }); test('handles universal links only', () async { final UrlLauncherAndroid launcher = UrlLauncherAndroid(); 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/', 'useWebView': false, 'enableJavaScript': false, 'enableDomStorage': false, 'universalLinksOnly': true, 'headers': {}, }) ], ); }); test('handles force WebView', () async { final UrlLauncherAndroid launcher = UrlLauncherAndroid(); await launcher.launch( 'http://example.com/', useSafariVC: true, useWebView: true, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: const {}, ); expect( log, [ isMethodCall('launch', arguments: { 'url': 'http://example.com/', 'useWebView': true, 'enableJavaScript': false, 'enableDomStorage': false, 'universalLinksOnly': false, 'headers': {}, }) ], ); }); test('handles force WebView with javascript', () async { final UrlLauncherAndroid launcher = UrlLauncherAndroid(); await launcher.launch( 'http://example.com/', useSafariVC: true, useWebView: true, enableJavaScript: true, enableDomStorage: false, universalLinksOnly: false, headers: const {}, ); expect( log, [ isMethodCall('launch', arguments: { 'url': 'http://example.com/', 'useWebView': true, 'enableJavaScript': true, 'enableDomStorage': false, 'universalLinksOnly': false, 'headers': {}, }) ], ); }); test('handles force WebView with DOM storage', () async { final UrlLauncherAndroid launcher = UrlLauncherAndroid(); await launcher.launch( 'http://example.com/', useSafariVC: true, useWebView: true, enableJavaScript: false, enableDomStorage: true, universalLinksOnly: false, headers: const {}, ); expect( log, [ isMethodCall('launch', arguments: { 'url': 'http://example.com/', 'useWebView': true, 'enableJavaScript': false, 'enableDomStorage': true, 'universalLinksOnly': false, 'headers': {}, }) ], ); }); test('returns false if platform returns null', () async { final UrlLauncherAndroid launcher = UrlLauncherAndroid(); final bool launched = await launcher.launch( 'http://example.com/', useSafariVC: true, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: const {}, ); expect(launched, false); }); }); group('closeWebView', () { test('calls through', () async { final UrlLauncherAndroid launcher = UrlLauncherAndroid(); await launcher.closeWebView(); expect( log, [isMethodCall('closeWebView', arguments: null)], ); }); }); } /// 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; ================================================ FILE: packages/url_launcher/url_launcher_ios/AUTHORS ================================================ # 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> ================================================ FILE: packages/url_launcher/url_launcher_ios/CHANGELOG.md ================================================ ## 6.1.0 * Updates minimum Flutter version to 3.3 and iOS 11. ## 6.0.18 * Updates code for stricter lint checks. * Updates minimum Flutter version to 2.10. ## 6.0.17 * Suppresses warnings for pre-iOS-13 codepaths. ## 6.0.16 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 6.0.15 * Switches to an in-package method channel implementation. ## 6.0.14 * Updates code for new analysis options. * Removes dependency on `meta`. ## 6.0.13 * Splits from `url_launcher` as a federated implementation. ================================================ FILE: packages/url_launcher/url_launcher_ios/LICENSE ================================================ 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. ================================================ FILE: packages/url_launcher/url_launcher_ios/README.md ================================================ # url\_launcher\_ios The iOS implementation of [`url_launcher`][1]. ## Usage This package is [endorsed][2], which means you can simply use `url_launcher` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/url_launcher [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/url_launcher/url_launcher_ios/example/README.md ================================================ # 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. ================================================ FILE: packages/url_launcher/url_launcher_ios/example/integration_test/url_launcher_test.dart ================================================ // Copyright 2013 The Flutter 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:url_launcher_platform_interface/url_launcher_platform_interface.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('canLaunch', (WidgetTester _) async { final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; expect(await launcher.canLaunch('randomstring'), false); // Generally all devices should have some default browser. expect(await launcher.canLaunch('http://flutter.dev'), true); // SMS handling is available by default on test devices. expect(await launcher.canLaunch('sms:5555555555'), true); // tel: and mailto: links may not be openable on every device. iOS // simulators notably can't open these link types. }); } ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 UIRequiredDeviceCapabilities arm64 MinimumOSVersion 11.0 ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/Flutter/Debug.xcconfig ================================================ #include "Generated.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/Flutter/Release.xcconfig ================================================ #include "Generated.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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]; [super application:application didFinishLaunchingWithOptions:launchOptions]; return YES; } @end ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName url_launcher_example CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 2D92223F1EC1DA93007564B0 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D92223E1EC1DA93007564B0 /* GeneratedPluginRegistrant.m */; }; 2E37D9A274B2EACB147AC51B /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 856D0913184F79C678A42603 /* libPods-Runner.a */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; B8140773523F70A044426500 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 487A1B5A2ECB3E406FD62FE3 /* libPods-RunnerTests.a */; }; F7151F4B26604CFB0028CB91 /* URLLauncherTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F4A26604CFB0028CB91 /* URLLauncherTests.m */; }; F7151F5926604D060028CB91 /* URLLauncherUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F5826604D060028CB91 /* URLLauncherUITests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ F7151F4D26604CFB0028CB91 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; F7151F5B26604D060028CB91 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 2D92223D1EC1DA93007564B0 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GeneratedPluginRegistrant.h; path = Runner/GeneratedPluginRegistrant.h; sourceTree = ""; }; 2D92223E1EC1DA93007564B0 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GeneratedPluginRegistrant.m; path = Runner/GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 487A1B5A2ECB3E406FD62FE3 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 666BCD7C181C34F8BE58929B /* 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 = ""; }; 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 = ""; }; 836316F9AEA584411312E29F /* 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 = ""; }; 856D0913184F79C678A42603 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; A84BFEE343F54B983D1B67EB /* 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 = ""; }; D25C434271ACF6555E002440 /* 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 = ""; }; F7151F4826604CFB0028CB91 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F7151F4A26604CFB0028CB91 /* URLLauncherTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = URLLauncherTests.m; sourceTree = ""; }; F7151F4C26604CFB0028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F7151F5626604D060028CB91 /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F7151F5826604D060028CB91 /* URLLauncherUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = URLLauncherUITests.m; sourceTree = ""; }; F7151F5A26604D060028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 2E37D9A274B2EACB147AC51B /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F4526604CFB0028CB91 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( B8140773523F70A044426500 /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F5326604D060028CB91 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 840012C8B5EDBCF56B0E4AC1 /* Pods */ = { isa = PBXGroup; children = ( 836316F9AEA584411312E29F /* Pods-Runner.debug.xcconfig */, A84BFEE343F54B983D1B67EB /* Pods-Runner.release.xcconfig */, 666BCD7C181C34F8BE58929B /* Pods-RunnerTests.debug.xcconfig */, D25C434271ACF6555E002440 /* Pods-RunnerTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 2D92223D1EC1DA93007564B0 /* GeneratedPluginRegistrant.h */, 2D92223E1EC1DA93007564B0 /* GeneratedPluginRegistrant.m */, 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, F7151F4926604CFB0028CB91 /* RunnerTests */, F7151F5726604D060028CB91 /* RunnerUITests */, 97C146EF1CF9000F007C117D /* Products */, 840012C8B5EDBCF56B0E4AC1 /* Pods */, CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, F7151F4826604CFB0028CB91 /* RunnerTests.xctest */, F7151F5626604D060028CB91 /* RunnerUITests.xctest */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = { isa = PBXGroup; children = ( 856D0913184F79C678A42603 /* libPods-Runner.a */, 487A1B5A2ECB3E406FD62FE3 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; F7151F4926604CFB0028CB91 /* RunnerTests */ = { isa = PBXGroup; children = ( F7151F4A26604CFB0028CB91 /* URLLauncherTests.m */, F7151F4C26604CFB0028CB91 /* Info.plist */, ); path = RunnerTests; sourceTree = ""; }; F7151F5726604D060028CB91 /* RunnerUITests */ = { isa = PBXGroup; children = ( F7151F5826604D060028CB91 /* URLLauncherUITests.m */, F7151F5A26604D060028CB91 /* Info.plist */, ); path = RunnerUITests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; F7151F4726604CFB0028CB91 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = F7151F5126604CFB0028CB91 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( DD4687403C4F35FCD2994FDE /* [CP] Check Pods Manifest.lock */, F7151F4426604CFB0028CB91 /* Sources */, F7151F4526604CFB0028CB91 /* Frameworks */, F7151F4626604CFB0028CB91 /* Resources */, ); buildRules = ( ); dependencies = ( F7151F4E26604CFB0028CB91 /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = F7151F4826604CFB0028CB91 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; F7151F5526604D060028CB91 /* RunnerUITests */ = { isa = PBXNativeTarget; buildConfigurationList = F7151F5D26604D060028CB91 /* Build configuration list for PBXNativeTarget "RunnerUITests" */; buildPhases = ( F7151F5226604D060028CB91 /* Sources */, F7151F5326604D060028CB91 /* Frameworks */, F7151F5426604D060028CB91 /* Resources */, ); buildRules = ( ); dependencies = ( F7151F5C26604D060028CB91 /* PBXTargetDependency */, ); name = RunnerUITests; productName = RunnerUITests; productReference = F7151F5626604D060028CB91 /* RunnerUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; DevelopmentTeam = S8QB4VV633; }; F7151F4726604CFB0028CB91 = { CreatedOnToolsVersion = 12.5; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; F7151F5526604D060028CB91 = { CreatedOnToolsVersion = 12.5; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, F7151F4726604CFB0028CB91 /* RunnerTests */, F7151F5526604D060028CB91 /* RunnerUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F4626604CFB0028CB91 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; F7151F5426604D060028CB91 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; DD4687403C4F35FCD2994FDE /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 2D92223F1EC1DA93007564B0 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F4426604CFB0028CB91 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F7151F4B26604CFB0028CB91 /* URLLauncherTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F5226604D060028CB91 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F7151F5926604D060028CB91 /* URLLauncherUITests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ F7151F4E26604CFB0028CB91 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = F7151F4D26604CFB0028CB91 /* PBXContainerItemProxy */; }; F7151F5C26604D060028CB91 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = F7151F5B26604D060028CB91 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.urlLauncher; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.urlLauncher; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; F7151F4F26604CFB0028CB91 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 666BCD7C181C34F8BE58929B /* 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"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Debug; }; F7151F5026604CFB0028CB91 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = D25C434271ACF6555E002440 /* 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"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; }; F7151F5E26604D060028CB91 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = Runner; }; name = Debug; }; F7151F5F26604D060028CB91 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = Runner; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F7151F5126604CFB0028CB91 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( F7151F4F26604CFB0028CB91 /* Debug */, F7151F5026604CFB0028CB91 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F7151F5D26604D060028CB91 /* Build configuration list for PBXNativeTarget "RunnerUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( F7151F5E26604D060028CB91 /* Debug */, F7151F5F26604D060028CB91 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/RunnerTests/Info.plist ================================================ 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 ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/RunnerTests/URLLauncherTests.m ================================================ // Copyright 2013 The Flutter 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 url_launcher_ios; @import XCTest; @interface URLLauncherTests : XCTestCase @end @implementation URLLauncherTests - (void)testPlugin { FLTURLLauncherPlugin *plugin = [[FLTURLLauncherPlugin alloc] init]; XCTAssertNotNil(plugin); } @end ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/RunnerUITests/Info.plist ================================================ 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 ================================================ FILE: packages/url_launcher/url_launcher_ios/example/ios/RunnerUITests/URLLauncherUITests.m ================================================ // Copyright 2013 The Flutter 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; @import os.log; @interface URLLauncherUITests : XCTestCase @property(nonatomic, strong) XCUIApplication *app; @end @implementation URLLauncherUITests - (void)setUp { self.continueAfterFailure = NO; self.app = [[XCUIApplication alloc] init]; [self.app launch]; } - (void)testLaunch { XCUIApplication *app = self.app; NSArray *buttonNames = @[ @"Launch in app", @"Launch in app(JavaScript ON)", @"Launch in app(DOM storage ON)", @"Launch a universal link in a native app, fallback to Safari.(Youtube)" ]; for (NSString *buttonName in buttonNames) { XCUIElement *button = app.buttons[buttonName]; XCTAssertTrue([button waitForExistenceWithTimeout:30.0]); XCTAssertEqual(app.webViews.count, 0); [button tap]; XCUIElement *webView = app.webViews.firstMatch; XCTAssertTrue([webView waitForExistenceWithTimeout:30.0]); XCTAssertTrue([app.buttons[@"ForwardButton"] waitForExistenceWithTimeout:30.0]); XCTAssertTrue(app.buttons[@"Share"].exists); XCTAssertTrue(app.buttons[@"OpenInSafariButton"].exists); [app.buttons[@"Done"] tap]; } } @end ================================================ FILE: packages/url_launcher/url_launcher_ios/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'package:flutter/material.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'URL Launcher', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'URL Launcher'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { Future? _launched; String _phone = ''; Future _launchInBrowser(String url) async { final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; if (await launcher.canLaunch(url)) { await launcher.launch( url, useSafariVC: false, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: {'my_header_key': 'my_header_value'}, ); } else { throw Exception('Could not launch $url'); } } Future _launchInWebViewOrVC(String url) async { final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; if (await launcher.canLaunch(url)) { await launcher.launch( url, useSafariVC: true, useWebView: true, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: {'my_header_key': 'my_header_value'}, ); } else { throw Exception('Could not launch $url'); } } Future _launchInWebViewWithJavaScript(String url) async { final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; if (await launcher.canLaunch(url)) { await launcher.launch( url, useSafariVC: true, useWebView: true, enableJavaScript: true, enableDomStorage: false, universalLinksOnly: false, headers: {}, ); } else { throw Exception('Could not launch $url'); } } Future _launchInWebViewWithDomStorage(String url) async { final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; if (await launcher.canLaunch(url)) { await launcher.launch( url, useSafariVC: true, useWebView: true, enableJavaScript: false, enableDomStorage: true, universalLinksOnly: false, headers: {}, ); } else { throw Exception('Could not launch $url'); } } Future _launchUniversalLinkIos(String url) async { final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; if (await launcher.canLaunch(url)) { final bool nativeAppLaunchSucceeded = await launcher.launch( url, useSafariVC: false, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: true, headers: {}, ); if (!nativeAppLaunchSucceeded) { await launcher.launch( url, useSafariVC: true, useWebView: true, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: true, headers: {}, ); } } } Widget _launchStatus(BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { return const Text(''); } } Future _makePhoneCall(String url) async { final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; if (await launcher.canLaunch(url)) { await launcher.launch( url, useSafariVC: false, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: true, headers: {}, ); } else { throw Exception('Could not launch $url'); } } @override Widget build(BuildContext context) { const String toLaunch = 'https://www.cylog.org/headers/'; return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: ListView( children: [ Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.all(16.0), child: TextField( onChanged: (String text) => _phone = text, decoration: const InputDecoration( hintText: 'Input the phone number to launch')), ), ElevatedButton( onPressed: () => setState(() { _launched = _makePhoneCall('tel:$_phone'); }), child: const Text('Make phone call'), ), const Padding( padding: EdgeInsets.all(16.0), child: Text(toLaunch), ), ElevatedButton( onPressed: () => setState(() { _launched = _launchInBrowser(toLaunch); }), child: const Text('Launch in browser'), ), const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { _launched = _launchInWebViewOrVC(toLaunch); }), child: const Text('Launch in app'), ), ElevatedButton( onPressed: () => setState(() { _launched = _launchInWebViewWithJavaScript(toLaunch); }), child: const Text('Launch in app(JavaScript ON)'), ), ElevatedButton( onPressed: () => setState(() { _launched = _launchInWebViewWithDomStorage(toLaunch); }), child: const Text('Launch in app(DOM storage ON)'), ), const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { _launched = _launchUniversalLinkIos(toLaunch); }), child: const Text( 'Launch a universal link in a native app, fallback to Safari.(Youtube)'), ), const Padding(padding: EdgeInsets.all(16.0)), ElevatedButton( onPressed: () => setState(() { _launched = _launchInWebViewOrVC(toLaunch); Timer(const Duration(seconds: 5), () { UrlLauncherPlatform.instance.closeWebView(); }); }), child: const Text('Launch in app + close after 5 seconds'), ), const Padding(padding: EdgeInsets.all(16.0)), FutureBuilder(future: _launched, builder: _launchStatus), ], ), ], ), ); } } ================================================ FILE: packages/url_launcher/url_launcher_ios/example/pubspec.yaml ================================================ name: url_launcher_example description: Demonstrates how to use the url_launcher plugin. publish_to: none environment: sdk: '>=2.18.0 <3.0.0' flutter: ">=3.3.0" dependencies: flutter: sdk: flutter url_launcher_ios: # When depending on this package from a real application you should use: # url_launcher_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: ../ url_launcher_platform_interface: ^2.0.3 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter mockito: ^5.0.0 plugin_platform_interface: ^2.0.0 flutter: uses-material-design: true ================================================ FILE: packages/url_launcher/url_launcher_ios/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/url_launcher/url_launcher_ios/ios/Assets/.gitkeep ================================================ ================================================ FILE: packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.h ================================================ // Copyright 2013 The Flutter 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 FLTURLLauncherPlugin : NSObject @end ================================================ FILE: packages/url_launcher/url_launcher_ios/ios/Classes/FLTURLLauncherPlugin.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import #import "FLTURLLauncherPlugin.h" @interface FLTURLLaunchSession : NSObject @property(copy, nonatomic) FlutterResult flutterResult; @property(strong, nonatomic) NSURL *url; @property(strong, nonatomic) SFSafariViewController *safari; @property(nonatomic, copy) void (^didFinish)(void); @end @implementation FLTURLLaunchSession - (instancetype)initWithUrl:url withFlutterResult:result { self = [super init]; if (self) { self.url = url; self.flutterResult = result; self.safari = [[SFSafariViewController alloc] initWithURL:url]; self.safari.delegate = self; } return self; } - (void)safariViewController:(SFSafariViewController *)controller didCompleteInitialLoad:(BOOL)didLoadSuccessfully { if (didLoadSuccessfully) { self.flutterResult(@YES); } else { self.flutterResult([FlutterError errorWithCode:@"Error" message:[NSString stringWithFormat:@"Error while launching %@", self.url] details:nil]); } } - (void)safariViewControllerDidFinish:(SFSafariViewController *)controller { [controller dismissViewControllerAnimated:YES completion:nil]; self.didFinish(); } - (void)close { [self safariViewControllerDidFinish:self.safari]; } @end @interface FLTURLLauncherPlugin () @property(strong, nonatomic) FLTURLLaunchSession *currentSession; @end @implementation FLTURLLauncherPlugin + (void)registerWithRegistrar:(NSObject *)registrar { FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/url_launcher_ios" binaryMessenger:registrar.messenger]; FLTURLLauncherPlugin *plugin = [[FLTURLLauncherPlugin alloc] init]; [registrar addMethodCallDelegate:plugin channel:channel]; } - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { NSString *url = call.arguments[@"url"]; if ([@"canLaunch" isEqualToString:call.method]) { result(@([self canLaunchURL:url])); } else if ([@"launch" isEqualToString:call.method]) { NSNumber *useSafariVC = call.arguments[@"useSafariVC"]; if (useSafariVC.boolValue) { [self launchURLInVC:url result:result]; } else { [self launchURL:url call:call result:result]; } } else if ([@"closeWebView" isEqualToString:call.method]) { [self closeWebViewWithResult:result]; } else { result(FlutterMethodNotImplemented); } } - (BOOL)canLaunchURL:(NSString *)urlString { NSURL *url = [NSURL URLWithString:urlString]; UIApplication *application = [UIApplication sharedApplication]; return [application canOpenURL:url]; } - (void)launchURL:(NSString *)urlString call:(FlutterMethodCall *)call result:(FlutterResult)result { NSURL *url = [NSURL URLWithString:urlString]; UIApplication *application = [UIApplication sharedApplication]; NSNumber *universalLinksOnly = call.arguments[@"universalLinksOnly"] ?: @0; NSDictionary *options = @{UIApplicationOpenURLOptionUniversalLinksOnly : universalLinksOnly}; [application openURL:url options:options completionHandler:^(BOOL success) { result(@(success)); }]; } - (void)launchURLInVC:(NSString *)urlString result:(FlutterResult)result { NSURL *url = [NSURL URLWithString:urlString]; self.currentSession = [[FLTURLLaunchSession alloc] initWithUrl:url withFlutterResult:result]; __weak typeof(self) weakSelf = self; self.currentSession.didFinish = ^(void) { weakSelf.currentSession = nil; }; [self.topViewController presentViewController:self.currentSession.safari animated:YES completion:nil]; } - (void)closeWebViewWithResult:(FlutterResult)result { if (self.currentSession != nil) { [self.currentSession close]; } result(nil); } - (UIViewController *)topViewController { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" // TODO(stuartmorgan) Provide a non-deprecated codepath. See // https://github.com/flutter/flutter/issues/104117 return [self topViewControllerFromViewController:[UIApplication sharedApplication] .keyWindow.rootViewController]; #pragma clang diagnostic pop } /** * This method recursively iterate through the view hierarchy * to return the top most view controller. * * It supports the following scenarios: * * - The view controller is presenting another view. * - The view controller is a UINavigationController. * - The view controller is a UITabBarController. * * @return The top most view controller. */ - (UIViewController *)topViewControllerFromViewController:(UIViewController *)viewController { if ([viewController isKindOfClass:[UINavigationController class]]) { UINavigationController *navigationController = (UINavigationController *)viewController; return [self topViewControllerFromViewController:[navigationController.viewControllers lastObject]]; } if ([viewController isKindOfClass:[UITabBarController class]]) { UITabBarController *tabController = (UITabBarController *)viewController; return [self topViewControllerFromViewController:tabController.selectedViewController]; } if (viewController.presentedViewController) { return [self topViewControllerFromViewController:viewController.presentedViewController]; } return viewController; } @end ================================================ FILE: packages/url_launcher/url_launcher_ios/ios/url_launcher_ios.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| s.name = 'url_launcher_ios' s.version = '0.0.1' s.summary = 'Flutter plugin for launching a URL.' s.description = <<-DESC A Flutter plugin for making the underlying platform (Android or iOS) launch a URL. DESC s.homepage = 'https://github.com/flutter/plugins/tree/main/packages/url_launcher' 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/url_launcher/url_launcher_ios' } s.documentation_url = 'https://pub.dev/packages/url_launcher' s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' s.platform = :ios, '11.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } end ================================================ FILE: packages/url_launcher/url_launcher_ios/lib/url_launcher_ios.dart ================================================ // Copyright 2013 The Flutter 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: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_ios'); /// An implementation of [UrlLauncherPlatform] for iOS. class UrlLauncherIOS extends UrlLauncherPlatform { /// Registers this class as the default instance of [UrlLauncherPlatform]. static void registerWith() { UrlLauncherPlatform.instance = UrlLauncherIOS(); } @override final LinkDelegate? linkDelegate = null; @override Future canLaunch(String url) { return _channel.invokeMethod( 'canLaunch', {'url': url}, ).then((bool? value) => value ?? false); } @override Future closeWebView() { return _channel.invokeMethod('closeWebView'); } @override Future launch( String url, { required bool useSafariVC, required bool useWebView, required bool enableJavaScript, required bool enableDomStorage, required bool universalLinksOnly, required Map headers, String? webOnlyWindowName, }) { return _channel.invokeMethod( 'launch', { 'url': url, 'useSafariVC': useSafariVC, 'enableJavaScript': enableJavaScript, 'enableDomStorage': enableDomStorage, 'universalLinksOnly': universalLinksOnly, 'headers': headers, }, ).then((bool? value) => value ?? false); } } ================================================ FILE: packages/url_launcher/url_launcher_ios/pubspec.yaml ================================================ 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.1.0 environment: sdk: '>=2.18.0 <3.0.0' flutter: ">=3.3.0" flutter: plugin: implements: url_launcher platforms: ios: pluginClass: FLTURLLauncherPlugin dartPluginClass: UrlLauncherIOS dependencies: flutter: sdk: flutter url_launcher_platform_interface: ^2.0.3 dev_dependencies: flutter_test: sdk: flutter mockito: ^5.0.0 plugin_platform_interface: ^2.0.0 test: ^1.16.3 ================================================ FILE: packages/url_launcher/url_launcher_ios/test/url_launcher_ios_test.dart ================================================ // Copyright 2013 The Flutter 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:url_launcher_ios/url_launcher_ios.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$UrlLauncherIOS', () { const MethodChannel channel = MethodChannel('plugins.flutter.io/url_launcher_ios'); final List log = []; _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, (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; }); tearDown(() { log.clear(); }); test('registers instance', () { UrlLauncherIOS.registerWith(); expect(UrlLauncherPlatform.instance, isA()); }); test('canLaunch', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(); await launcher.canLaunch('http://example.com/'); expect( log, [ isMethodCall('canLaunch', arguments: { 'url': 'http://example.com/', }) ], ); }); test('canLaunch should return false if platform returns null', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(); final bool canLaunch = await launcher.canLaunch('http://example.com/'); expect(canLaunch, false); }); test('launch', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(); 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/', 'useSafariVC': true, 'enableJavaScript': false, 'enableDomStorage': false, 'universalLinksOnly': false, 'headers': {}, }) ], ); }); test('launch with headers', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(); 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/', 'useSafariVC': true, 'enableJavaScript': false, 'enableDomStorage': false, 'universalLinksOnly': false, 'headers': {'key': 'value'}, }) ], ); }); test('launch force SafariVC', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(); 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/', 'useSafariVC': true, 'enableJavaScript': false, 'enableDomStorage': false, 'universalLinksOnly': false, 'headers': {}, }) ], ); }); test('launch universal links only', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(); 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/', 'useSafariVC': false, 'enableJavaScript': false, 'enableDomStorage': false, 'universalLinksOnly': true, 'headers': {}, }) ], ); }); test('launch force SafariVC to false', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(); await launcher.launch( 'http://example.com/', useSafariVC: false, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: const {}, ); expect( log, [ isMethodCall('launch', arguments: { 'url': 'http://example.com/', 'useSafariVC': false, 'enableJavaScript': false, 'enableDomStorage': false, 'universalLinksOnly': false, 'headers': {}, }) ], ); }); test('launch should return false if platform returns null', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(); 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('closeWebView default behavior', () async { final UrlLauncherIOS launcher = UrlLauncherIOS(); await launcher.closeWebView(); expect( log, [isMethodCall('closeWebView', arguments: null)], ); }); }); } /// 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; ================================================ FILE: packages/url_launcher/url_launcher_linux/.gitignore ================================================ .packages .flutter-plugins pubspec.lock ================================================ FILE: packages/url_launcher/url_launcher_linux/.metadata ================================================ # 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: 4b12050112afd581ddf53df848275fa681f908f3 channel: master project_type: plugin ================================================ FILE: packages/url_launcher/url_launcher_linux/AUTHORS ================================================ # 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> ================================================ FILE: packages/url_launcher/url_launcher_linux/CHANGELOG.md ================================================ ## 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 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 3.0.0 * Changes the major version since, due to a typo in `default_package` in existing versions of `url_launcher`, requiring Dart registration in this package is in practice a breaking change. * Does not include any API changes; clients can allow both 2.x or 3.x. ## 2.0.4 * **\[Retracted\]** Switches to an in-package method channel implementation. ## 2.0.3 * Updates code for new analysis options. * Fix minor memory leak in Linux url_launcher tests. * Fixes canLaunch detection for URIs addressing on local or network file systems ## 2.0.2 * Replaced reference to `shared_preferences` plugin with the `url_launcher` in the README. ## 2.0.1 * Updated installation instructions in README. ## 2.0.0 * Migrate to null safety. * Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets. * Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) * Set `implementation` in pubspec.yaml ## 0.0.2+1 * Update Flutter SDK constraint. ## 0.0.2 * Update integration test examples to use `testWidgets` instead of `test`. ## 0.0.1+4 * Update Dart SDK constraint in example. ## 0.0.1+3 * Add a missing include. ## 0.0.1+2 * Check in linux/ directory for example/ # 0.0.1+1 * README update for endorsement by url_launcher. # 0.0.1 * The initial implementation of url_launcher for Linux ================================================ FILE: packages/url_launcher/url_launcher_linux/LICENSE ================================================ 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. ================================================ FILE: packages/url_launcher/url_launcher_linux/README.md ================================================ # url\_launcher\_linux The Linux implementation of [`url_launcher`][1]. ## Usage This package is [endorsed][2], which means you can simply use `url_launcher` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/url_launcher [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/url_launcher/url_launcher_linux/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: packages/url_launcher/url_launcher_linux/example/.metadata ================================================ # 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: 4b12050112afd581ddf53df848275fa681f908f3 channel: master project_type: app ================================================ FILE: packages/url_launcher/url_launcher_linux/example/README.md ================================================ # 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. ================================================ FILE: packages/url_launcher/url_launcher_linux/example/integration_test/url_launcher_test.dart ================================================ // Copyright 2013 The Flutter 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:url_launcher_platform_interface/url_launcher_platform_interface.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('canLaunch', (WidgetTester _) async { final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; expect(await launcher.canLaunch('randomstring'), false); // Generally all devices should have some default browser. expect(await launcher.canLaunch('http://flutter.dev'), true); }); } ================================================ FILE: packages/url_launcher/url_launcher_linux/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'package:flutter/material.dart'; import 'package:url_launcher_platform_interface/url_launcher_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: 'URL Launcher', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'URL Launcher'), ); } } 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 { Future? _launched; Future _launchInBrowser(String url) async { if (await UrlLauncherPlatform.instance.canLaunch(url)) { await UrlLauncherPlatform.instance.launch( url, useSafariVC: false, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: {}, ); } else { throw Exception('Could not launch $url'); } } Widget _launchStatus(BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { return const Text(''); } } @override Widget build(BuildContext context) { const String toLaunch = 'https://www.cylog.org/headers/'; return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: ListView( children: [ Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Padding( padding: EdgeInsets.all(16.0), child: Text(toLaunch), ), ElevatedButton( onPressed: () => setState(() { _launched = _launchInBrowser(toLaunch); }), child: const Text('Launch in browser'), ), const Padding(padding: EdgeInsets.all(16.0)), FutureBuilder(future: _launched, builder: _launchStatus), ], ), ], ), ); } } ================================================ FILE: packages/url_launcher/url_launcher_linux/example/linux/.gitignore ================================================ flutter/ephemeral ================================================ FILE: packages/url_launcher/url_launcher_linux/example/linux/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(runner LANGUAGES CXX) set(BINARY_NAME "example") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_14) target_compile_options(${TARGET} PRIVATE -Wall -Werror) target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) # Application build add_executable(${BINARY_NAME} "main.cc" "my_application.cc" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" ) apply_standard_settings(${BINARY_NAME}) target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) add_dependencies(${BINARY_NAME} flutter_assemble) # Enable the test target. set(include_url_launcher_linux_tests TRUE) # Provide an alias for the test target using the name expected by repo tooling. add_custom_target(unit_tests DEPENDS url_launcher_linux_test) # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # By default, "installing" just makes a relocatable bundle in the build # directory. set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() # Start with a clean build bundle directory every time. install(CODE " file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") " COMPONENT Runtime) set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() ================================================ FILE: packages/url_launcher/url_launcher_linux/example/linux/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. # Serves the same purpose as list(TRANSFORM ... PREPEND ...), # which isn't available in 3.10. function(list_prepend LIST_NAME PREFIX) set(NEW_LIST "") foreach(element ${${LIST_NAME}}) list(APPEND NEW_LIST "${PREFIX}${element}") endforeach(element) set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) endfunction() # === Flutter Library === # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "fl_basic_message_channel.h" "fl_binary_codec.h" "fl_binary_messenger.h" "fl_dart_project.h" "fl_engine.h" "fl_json_message_codec.h" "fl_json_method_codec.h" "fl_message_codec.h" "fl_method_call.h" "fl_method_channel.h" "fl_method_codec.h" "fl_method_response.h" "fl_plugin_registrar.h" "fl_plugin_registry.h" "fl_standard_message_codec.h" "fl_standard_method_codec.h" "fl_string_codec.h" "fl_value.h" "fl_view.h" "flutter_linux.h" ) list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") target_link_libraries(flutter INTERFACE PkgConfig::GTK PkgConfig::GLIB PkgConfig::GIO ) add_dependencies(flutter flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/_phony_ COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ) ================================================ FILE: packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/url_launcher/url_launcher_linux/example/linux/main.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "my_application.h" int main(int argc, char** argv) { g_autoptr(MyApplication) app = my_application_new(); return g_application_run(G_APPLICATION(app), argc, argv); } ================================================ FILE: packages/url_launcher/url_launcher_linux/example/linux/my_application.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "my_application.h" #include #include "flutter/generated_plugin_registrant.h" struct _MyApplication { GtkApplication parent_instance; }; G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // Implements GApplication::activate. static void my_application_activate(GApplication* application) { GtkWindow* window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); gtk_header_bar_set_title(header_bar, "example"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); gtk_window_set_default_size(window, 1280, 720); gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); FlView* view = fl_view_new(project); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view)); gtk_widget_grab_focus(GTK_WIDGET(view)); } static void my_application_class_init(MyApplicationClass* klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; } static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { return MY_APPLICATION(g_object_new(my_application_get_type(), nullptr)); } ================================================ FILE: packages/url_launcher/url_launcher_linux/example/linux/my_application.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef FLUTTER_MY_APPLICATION_H_ #define FLUTTER_MY_APPLICATION_H_ #include G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, GtkApplication) /** * my_application_new: * * Creates a new Flutter-based application. * * Returns: a new #MyApplication. */ MyApplication* my_application_new(); #endif // FLUTTER_MY_APPLICATION_H_ ================================================ FILE: packages/url_launcher/url_launcher_linux/example/pubspec.yaml ================================================ name: url_launcher_example description: Demonstrates how to use the url_launcher plugin. publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter url_launcher_linux: # When depending on this package from a real application you should use: # url_launcher_linux: ^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: ../ url_launcher_platform_interface: ^2.0.0 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/url_launcher/url_launcher_linux/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart ================================================ // Copyright 2013 The Flutter 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: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_linux'); /// An implementation of [UrlLauncherPlatform] for Linux. class UrlLauncherLinux extends UrlLauncherPlatform { /// Registers this class as the default instance of [UrlLauncherPlatform]. static void registerWith() { UrlLauncherPlatform.instance = UrlLauncherLinux(); } @override final LinkDelegate? linkDelegate = null; @override Future canLaunch(String url) { return _channel.invokeMethod( 'canLaunch', {'url': url}, ).then((bool? value) => value ?? false); } @override Future launch( String url, { required bool useSafariVC, required bool useWebView, required bool enableJavaScript, required bool enableDomStorage, 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); } } ================================================ FILE: packages/url_launcher/url_launcher_linux/linux/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) set(PROJECT_NAME "url_launcher_linux") project(${PROJECT_NAME} LANGUAGES CXX) set(PLUGIN_NAME "${PROJECT_NAME}_plugin") list(APPEND PLUGIN_SOURCES "url_launcher_plugin.cc" ) add_library(${PLUGIN_NAME} SHARED ${PLUGIN_SOURCES} ) apply_standard_settings(${PLUGIN_NAME}) set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden) target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) target_include_directories(${PLUGIN_NAME} INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) # === Tests === if (${include_${PROJECT_NAME}_tests}) if(${CMAKE_VERSION} VERSION_LESS "3.11.0") message("Unit tests require CMake 3.11.0 or later") else() set(TEST_RUNNER "${PROJECT_NAME}_test") enable_testing() # TODO(stuartmorgan): Consider using a single shared, pre-checked-in googletest # instance rather than downloading for each plugin. This approach makes sense # for a template, but not for a monorepo with many plugins. include(FetchContent) FetchContent_Declare( googletest URL https://github.com/google/googletest/archive/release-1.11.0.zip ) # Prevent overriding the parent project's compiler/linker settings set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Disable install commands for gtest so it doesn't end up in the bundle. set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE) FetchContent_MakeAvailable(googletest) # The plugin's exported API is not very useful for unit testing, so build the # sources directly into the test binary rather than using the shared library. add_executable(${TEST_RUNNER} test/url_launcher_linux_test.cc ${PLUGIN_SOURCES} ) apply_standard_settings(${TEST_RUNNER}) target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") target_link_libraries(${TEST_RUNNER} PRIVATE flutter) target_link_libraries(${TEST_RUNNER} PRIVATE PkgConfig::GTK) target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock) include(GoogleTest) gtest_discover_tests(${TEST_RUNNER}) endif() # CMake version check endif() # include_${PROJECT_NAME}_tests ================================================ FILE: packages/url_launcher/url_launcher_linux/linux/include/url_launcher_linux/url_launcher_plugin.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_URL_LAUNCHER_URL_LAUNCHER_LINUX_LINUX_INCLUDE_URL_LAUNCHER_URL_LAUNCHER_PLUGIN_H_ #define PACKAGES_URL_LAUNCHER_URL_LAUNCHER_LINUX_LINUX_INCLUDE_URL_LAUNCHER_URL_LAUNCHER_PLUGIN_H_ // A plugin to launch URLs. #include G_BEGIN_DECLS #ifdef FLUTTER_PLUGIN_IMPL #define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) #else #define FLUTTER_PLUGIN_EXPORT #endif G_DECLARE_FINAL_TYPE(FlUrlLauncherPlugin, fl_url_launcher_plugin, FL, URL_LAUNCHER_PLUGIN, GObject) FLUTTER_PLUGIN_EXPORT FlUrlLauncherPlugin* fl_url_launcher_plugin_new( FlPluginRegistrar* registrar); FLUTTER_PLUGIN_EXPORT void url_launcher_plugin_register_with_registrar( FlPluginRegistrar* registrar); G_END_DECLS #endif // PACKAGES_URL_LAUNCHER_URL_LAUNCHER_LINUX_LINUX_INCLUDE_URL_LAUNCHER_URL_LAUNCHER_PLUGIN_H_ ================================================ FILE: packages/url_launcher/url_launcher_linux/linux/test/url_launcher_linux_test.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include #include "include/url_launcher_linux/url_launcher_plugin.h" #include "url_launcher_plugin_private.h" namespace url_launcher_plugin { namespace test { TEST(UrlLauncherPlugin, CanLaunchSuccess) { g_autoptr(FlValue) args = fl_value_new_map(); fl_value_set_string_take(args, "url", fl_value_new_string("https://flutter.dev")); g_autoptr(FlMethodResponse) response = can_launch(nullptr, args); ASSERT_NE(response, nullptr); ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); g_autoptr(FlValue) expected = fl_value_new_bool(true); EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( FL_METHOD_SUCCESS_RESPONSE(response)), expected)); } TEST(UrlLauncherPlugin, CanLaunchFailureUnhandled) { g_autoptr(FlValue) args = fl_value_new_map(); fl_value_set_string_take(args, "url", fl_value_new_string("madeup:scheme")); g_autoptr(FlMethodResponse) response = can_launch(nullptr, args); ASSERT_NE(response, nullptr); ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); g_autoptr(FlValue) expected = fl_value_new_bool(false); EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( FL_METHOD_SUCCESS_RESPONSE(response)), expected)); } TEST(UrlLauncherPlugin, CanLaunchFileSuccess) { g_autoptr(FlValue) args = fl_value_new_map(); fl_value_set_string_take(args, "url", fl_value_new_string("file:///")); g_autoptr(FlMethodResponse) response = can_launch(nullptr, args); ASSERT_NE(response, nullptr); ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); g_autoptr(FlValue) expected = fl_value_new_bool(true); EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( FL_METHOD_SUCCESS_RESPONSE(response)), expected)); } TEST(UrlLauncherPlugin, CanLaunchFailureInvalidFileExtension) { g_autoptr(FlValue) args = fl_value_new_map(); fl_value_set_string_take( args, "url", fl_value_new_string("file:///madeup.madeupextension")); g_autoptr(FlMethodResponse) response = can_launch(nullptr, args); ASSERT_NE(response, nullptr); ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); g_autoptr(FlValue) expected = fl_value_new_bool(false); EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( FL_METHOD_SUCCESS_RESPONSE(response)), expected)); } // For consistency with the established mobile implementations, // an invalid URL should return false, not an error. TEST(UrlLauncherPlugin, CanLaunchFailureInvalidUrl) { g_autoptr(FlValue) args = fl_value_new_map(); fl_value_set_string_take(args, "url", fl_value_new_string("")); g_autoptr(FlMethodResponse) response = can_launch(nullptr, args); ASSERT_NE(response, nullptr); ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); g_autoptr(FlValue) expected = fl_value_new_bool(false); EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( FL_METHOD_SUCCESS_RESPONSE(response)), expected)); } } // namespace test } // namespace url_launcher_plugin ================================================ FILE: packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "include/url_launcher_linux/url_launcher_plugin.h" #include #include #include #include "url_launcher_plugin_private.h" // See url_launcher_channel.dart for documentation. const char kChannelName[] = "plugins.flutter.io/url_launcher_linux"; const char kBadArgumentsError[] = "Bad Arguments"; const char kLaunchError[] = "Launch Error"; const char kCanLaunchMethod[] = "canLaunch"; const char kLaunchMethod[] = "launch"; const char kUrlKey[] = "url"; struct _FlUrlLauncherPlugin { GObject parent_instance; FlPluginRegistrar* registrar; // Connection to Flutter engine. FlMethodChannel* channel; }; G_DEFINE_TYPE(FlUrlLauncherPlugin, fl_url_launcher_plugin, g_object_get_type()) // Gets the URL from the arguments or generates an error. static gchar* get_url(FlValue* args, GError** error) { if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { g_set_error(error, 0, 0, "Argument map missing or malformed"); return nullptr; } FlValue* url_value = fl_value_lookup_string(args, kUrlKey); if (url_value == nullptr) { g_set_error(error, 0, 0, "Missing URL"); return nullptr; } return g_strdup(fl_value_get_string(url_value)); } // Checks if URI has launchable file resource. static gboolean can_launch_uri_with_file_resource(FlUrlLauncherPlugin* self, const gchar* url) { g_autoptr(GError) error = nullptr; g_autoptr(GFile) file = g_file_new_for_uri(url); g_autoptr(GAppInfo) app_info = g_file_query_default_handler(file, NULL, &error); return app_info != nullptr; } // Called to check if a URL can be launched. FlMethodResponse* can_launch(FlUrlLauncherPlugin* self, FlValue* args) { g_autoptr(GError) error = nullptr; g_autofree gchar* url = get_url(args, &error); if (url == nullptr) { return FL_METHOD_RESPONSE(fl_method_error_response_new( kBadArgumentsError, error->message, nullptr)); } gboolean is_launchable = FALSE; g_autofree gchar* scheme = g_uri_parse_scheme(url); if (scheme != nullptr) { g_autoptr(GAppInfo) app_info = g_app_info_get_default_for_uri_scheme(scheme); is_launchable = app_info != nullptr; if (!is_launchable) { is_launchable = can_launch_uri_with_file_resource(self, url); } } g_autoptr(FlValue) result = fl_value_new_bool(is_launchable); return FL_METHOD_RESPONSE(fl_method_success_response_new(result)); } // Called when a URL should launch. static FlMethodResponse* launch(FlUrlLauncherPlugin* self, FlValue* args) { g_autoptr(GError) error = nullptr; g_autofree gchar* url = get_url(args, &error); if (url == nullptr) { return FL_METHOD_RESPONSE(fl_method_error_response_new( kBadArgumentsError, error->message, nullptr)); } FlView* view = fl_plugin_registrar_get_view(self->registrar); gboolean launched; if (view != nullptr) { GtkWindow* window = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view))); launched = gtk_show_uri_on_window(window, url, GDK_CURRENT_TIME, &error); } else { launched = g_app_info_launch_default_for_uri(url, nullptr, &error); } if (!launched) { g_autofree gchar* message = g_strdup_printf("Failed to launch URL: %s", error->message); return FL_METHOD_RESPONSE( fl_method_error_response_new(kLaunchError, message, nullptr)); } g_autoptr(FlValue) result = fl_value_new_bool(TRUE); return FL_METHOD_RESPONSE(fl_method_success_response_new(result)); } // Called when a method call is received from Flutter. static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, gpointer user_data) { FlUrlLauncherPlugin* self = FL_URL_LAUNCHER_PLUGIN(user_data); const gchar* method = fl_method_call_get_name(method_call); FlValue* args = fl_method_call_get_args(method_call); g_autoptr(FlMethodResponse) response = nullptr; if (strcmp(method, kCanLaunchMethod) == 0) response = can_launch(self, args); else if (strcmp(method, kLaunchMethod) == 0) response = launch(self, args); else response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); g_autoptr(GError) error = nullptr; if (!fl_method_call_respond(method_call, response, &error)) g_warning("Failed to send method call response: %s", error->message); } static void fl_url_launcher_plugin_dispose(GObject* object) { FlUrlLauncherPlugin* self = FL_URL_LAUNCHER_PLUGIN(object); g_clear_object(&self->registrar); g_clear_object(&self->channel); G_OBJECT_CLASS(fl_url_launcher_plugin_parent_class)->dispose(object); } static void fl_url_launcher_plugin_class_init(FlUrlLauncherPluginClass* klass) { G_OBJECT_CLASS(klass)->dispose = fl_url_launcher_plugin_dispose; } FlUrlLauncherPlugin* fl_url_launcher_plugin_new(FlPluginRegistrar* registrar) { FlUrlLauncherPlugin* self = FL_URL_LAUNCHER_PLUGIN( g_object_new(fl_url_launcher_plugin_get_type(), nullptr)); self->registrar = FL_PLUGIN_REGISTRAR(g_object_ref(registrar)); g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); self->channel = fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar), kChannelName, FL_METHOD_CODEC(codec)); fl_method_channel_set_method_call_handler(self->channel, method_call_cb, g_object_ref(self), g_object_unref); return self; } static void fl_url_launcher_plugin_init(FlUrlLauncherPlugin* self) {} void url_launcher_plugin_register_with_registrar(FlPluginRegistrar* registrar) { FlUrlLauncherPlugin* plugin = fl_url_launcher_plugin_new(registrar); g_object_unref(plugin); } ================================================ FILE: packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin_private.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include "include/url_launcher_linux/url_launcher_plugin.h" // TODO(stuartmorgan): Remove this private header and change the below back to // a static function once https://github.com/flutter/flutter/issues/88724 // is fixed, and test through the public API instead. // Handles the canLaunch method call. FlMethodResponse* can_launch(FlUrlLauncherPlugin* self, FlValue* args); ================================================ FILE: packages/url_launcher/url_launcher_linux/pubspec.yaml ================================================ 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.2 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: url_launcher platforms: linux: pluginClass: UrlLauncherPlugin dartPluginClass: UrlLauncherLinux dependencies: flutter: sdk: flutter url_launcher_platform_interface: ^2.0.3 dev_dependencies: flutter_test: sdk: flutter test: ^1.16.3 ================================================ FILE: packages/url_launcher/url_launcher_linux/test/url_launcher_linux_test.dart ================================================ // Copyright 2013 The Flutter 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:url_launcher_linux/url_launcher_linux.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$UrlLauncherLinux', () { const MethodChannel channel = MethodChannel('plugins.flutter.io/url_launcher_linux'); final List log = []; _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, (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; }); tearDown(() { log.clear(); }); test('registers instance', () { UrlLauncherLinux.registerWith(); expect(UrlLauncherPlatform.instance, isA()); }); test('canLaunch', () async { final UrlLauncherLinux launcher = UrlLauncherLinux(); await launcher.canLaunch('http://example.com/'); expect( log, [ isMethodCall('canLaunch', arguments: { 'url': 'http://example.com/', }) ], ); }); test('canLaunch should return false if platform returns null', () async { final UrlLauncherLinux launcher = UrlLauncherLinux(); final bool canLaunch = await launcher.canLaunch('http://example.com/'); expect(canLaunch, false); }); test('launch', () async { final UrlLauncherLinux launcher = UrlLauncherLinux(); 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('launch with headers', () async { final UrlLauncherLinux launcher = UrlLauncherLinux(); 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'}, }) ], ); }); test('launch universal links only', () async { final UrlLauncherLinux launcher = UrlLauncherLinux(); 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': {}, }) ], ); }); test('launch should return false if platform returns null', () async { final UrlLauncherLinux launcher = UrlLauncherLinux(); final bool launched = await launcher.launch( 'http://example.com/', useSafariVC: true, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: const {}, ); expect(launched, false); }); }); } /// 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; ================================================ FILE: packages/url_launcher/url_launcher_macos/AUTHORS ================================================ # 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> ================================================ FILE: packages/url_launcher/url_launcher_macos/CHANGELOG.md ================================================ ## 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 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 3.0.0 * Changes the major version since, due to a typo in `default_package` in existing versions of `url_launcher`, requiring Dart registration in this package is in practice a breaking change. * Does not include any API changes; clients can allow both 2.x or 3.x. ## 2.0.4 * **\[Retracted\]** Switches to an in-package method channel implementation. ## 2.0.3 * Updates code for new analysis options. * Updates unit tests. ## 2.0.2 * Replaced reference to `shared_preferences` plugin with the `url_launcher` in the README. ## 2.0.1 * Add native unit tests. * Updated installation instructions in README. ## 2.0.0 * Migrate to null safety. * Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets. * Set `implementation` in pubspec.yaml ## 0.0.2+1 * Update Flutter SDK constraint. ## 0.0.2 * Update integration test examples to use `testWidgets` instead of `test`. # 0.0.1+9 * Update Dart SDK constraint in example. # 0.0.1+8 * Remove no-op android folder in the example app. # 0.0.1+7 * Remove Android folder from url_launcher_web and url_launcher_macos. # 0.0.1+6 * 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+5 * Fixed the launchUniversalLinkIos method. * Fix CocoaPods podspec lint warnings. # 0.0.1+4 * Make the pedantic dev_dependency explicit. # 0.0.1+3 * Update Gradle version. # 0.0.1+2 * Update README. # 0.0.1+1 * Add an android/ folder with no-op implementation to workaround https:// # 0.0.1 * Initial open source release. ================================================ FILE: packages/url_launcher/url_launcher_macos/LICENSE ================================================ 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. ================================================ FILE: packages/url_launcher/url_launcher_macos/README.md ================================================ # url\_launcher\_macos The macos implementation of [`url_launcher`][1]. ## Usage This package is [endorsed][2], which means you can simply use `url_launcher` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/url_launcher [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/url_launcher/url_launcher_macos/example/README.md ================================================ # 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. ================================================ FILE: packages/url_launcher/url_launcher_macos/example/integration_test/url_launcher_test.dart ================================================ // Copyright 2013 The Flutter 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:url_launcher_platform_interface/url_launcher_platform_interface.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('canLaunch', (WidgetTester _) async { final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; expect(await launcher.canLaunch('randomstring'), false); // Generally all devices should have some default browser. expect(await launcher.canLaunch('http://flutter.dev'), true); // Generally all devices should have some default SMS app. expect(await launcher.canLaunch('sms:5555555555'), true); }); } ================================================ FILE: packages/url_launcher/url_launcher_macos/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'package:flutter/material.dart'; import 'package:url_launcher_platform_interface/url_launcher_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: 'URL Launcher', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'URL Launcher'), ); } } 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 { Future? _launched; Future _launchInBrowser(String url) async { if (await UrlLauncherPlatform.instance.canLaunch(url)) { await UrlLauncherPlatform.instance.launch( url, useSafariVC: false, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: {}, ); } else { throw Exception('Could not launch $url'); } } Widget _launchStatus(BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { return const Text(''); } } @override Widget build(BuildContext context) { const String toLaunch = 'https://www.cylog.org/headers/'; return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: ListView( children: [ Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Padding( padding: EdgeInsets.all(16.0), child: Text(toLaunch), ), ElevatedButton( onPressed: () => setState(() { _launched = _launchInBrowser(toLaunch); }), child: const Text('Launch in browser'), ), const Padding(padding: EdgeInsets.all(16.0)), FutureBuilder(future: _launched, builder: _launchStatus), ], ), ], ), ); } } ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/Flutter/Flutter-Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/Flutter/Flutter-Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/Podfile ================================================ platform :osx, '10.11' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_macos_podfile_setup target 'Runner' do use_frameworks! use_modular_headers! flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) end end ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/Runner/AppDelegate.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS @NSApplicationMain class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } } ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "app_icon_64.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "app_icon_1024.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/Runner/Base.lproj/MainMenu.xib ================================================

================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/Runner/Configs/AppInfo.xcconfig ================================================ // Application-level settings for the Runner target. // // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the // future. If not, the values below would default to using the project name when this becomes a // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. PRODUCT_NAME = url_launcher_example_example // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.urlLauncherExample // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2019 The Flutter Authors. All rights reserved. ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/Runner/Configs/Debug.xcconfig ================================================ #include "../../Flutter/Flutter-Debug.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/Runner/Configs/Release.xcconfig ================================================ #include "../../Flutter/Flutter-Release.xcconfig" #include "Warnings.xcconfig" ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/Runner/Configs/Warnings.xcconfig ================================================ WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings GCC_WARN_UNDECLARED_SELECTOR = YES CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE CLANG_WARN__DUPLICATE_METHOD_MATCH = YES CLANG_WARN_PRAGMA_PACK = YES CLANG_WARN_STRICT_PROTOTYPES = YES CLANG_WARN_COMMA = YES GCC_WARN_STRICT_SELECTOR_MATCH = YES CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES GCC_WARN_SHADOW = YES CLANG_WARN_UNREACHABLE_CODE = YES ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/Runner/DebugProfile.entitlements ================================================ com.apple.security.app-sandbox com.apple.security.cs.allow-jit com.apple.security.network.server ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright $(PRODUCT_COPYRIGHT) NSMainNibFile MainMenu NSPrincipalClass NSApplication ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/Runner/MainFlutterWindow.swift ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import Cocoa import FlutterMacOS class MainFlutterWindow: NSWindow { override func awakeFromNib() { let flutterViewController = FlutterViewController.init() let windowFrame = self.frame self.contentViewController = flutterViewController self.setFrame(windowFrame, display: true) RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } } ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/Runner/Release.entitlements ================================================ com.apple.security.app-sandbox ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 51; objects = { /* Begin PBXAggregateTarget section */ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { isa = PBXAggregateTarget; buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; buildPhases = ( 33CC111E2044C6BF0003C045 /* ShellScript */, ); dependencies = ( ); name = "Flutter Assemble"; productName = FLX; }; /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 33EBD3B9267296CB0013E557 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33EBD3B8267296CB0013E557 /* RunnerTests.swift */; }; B0E8018BA137CF3E1D668F89 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A7581585AB49438450A8105 /* Pods_RunnerTests.framework */; }; DD4A1B9DEDBB72C87CD7AE27 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5067D74CB28D28AE3B3DD05B /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC111A2044C6BA0003C045; remoteInfo = FLX; }; 33EBD3BB267296CB0013E557 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; proxyType = 1; remoteGlobalIDString = 33CC10EC2044A3C60003C045; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 33CC110E2044A8840003C045 /* Bundle Framework */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Bundle Framework"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 220FFDB920A73FF04EA40119 /* 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 = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 33CC10ED2044A3C60003C045 /* url_launcher_example_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = url_launcher_example_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 33EBD3B6267296CB0013E557 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 33EBD3B8267296CB0013E557 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 33EBD3BA267296CB0013E557 /* 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 = ""; }; 5A7581585AB49438450A8105 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 5CFCAA4A883B5A0C4BD62DCF /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 899489AD6AA35AECA4E2BEA6 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; B36FDC1D769C9045B8821207 /* 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 = ""; }; FD671DB5E266C257DCC5AD6A /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( DD4A1B9DEDBB72C87CD7AE27 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 33EBD3B3267296CB0013E557 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( B0E8018BA137CF3E1D668F89 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( 33E5194F232828860026EE4D /* AppInfo.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, ); path = Configs; sourceTree = ""; }; 33CC10E42044A3C60003C045 = { isa = PBXGroup; children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, 33EBD3B7267296CB0013E557 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, 96C1F6D923BD5787E8EBE8FC /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( 33CC10ED2044A3C60003C045 /* url_launcher_example_example.app */, 33EBD3B6267296CB0013E557 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 33CC11242044D66E0003C045 /* Resources */ = { isa = PBXGroup; children = ( 33CC10F22044A3C60003C045 /* Assets.xcassets */, 33CC10F42044A3C60003C045 /* MainMenu.xib */, 33CC10F72044A3C60003C045 /* Info.plist */, ); name = Resources; path = ..; sourceTree = ""; }; 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, ); path = Flutter; sourceTree = ""; }; 33EBD3B7267296CB0013E557 /* RunnerTests */ = { isa = PBXGroup; children = ( 33EBD3B8267296CB0013E557 /* RunnerTests.swift */, 33EBD3BA267296CB0013E557 /* Info.plist */, ); path = RunnerTests; sourceTree = ""; }; 33FAB671232836740065AC1E /* Runner */ = { isa = PBXGroup; children = ( 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, 33CC11242044D66E0003C045 /* Resources */, 33BA886A226E78AF003329D5 /* Configs */, ); path = Runner; sourceTree = ""; }; 96C1F6D923BD5787E8EBE8FC /* Pods */ = { isa = PBXGroup; children = ( 899489AD6AA35AECA4E2BEA6 /* Pods-Runner.debug.xcconfig */, B36FDC1D769C9045B8821207 /* Pods-Runner.release.xcconfig */, 53F020549CA1E801ACA3428F /* Pods-Runner.profile.xcconfig */, 220FFDB920A73FF04EA40119 /* Pods-RunnerTests.debug.xcconfig */, 5CFCAA4A883B5A0C4BD62DCF /* Pods-RunnerTests.release.xcconfig */, FD671DB5E266C257DCC5AD6A /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( 5067D74CB28D28AE3B3DD05B /* Pods_Runner.framework */, 5A7581585AB49438450A8105 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( C318D59394D0E38099411848 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, 50C74DCD840D9B569BE3D48F /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* url_launcher_example_example.app */; productType = "com.apple.product-type.application"; }; 33EBD3B5267296CB0013E557 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = 33EBD3C0267296CB0013E557 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( 460A36EFD54BEB8122DDAC6D /* [CP] Check Pods Manifest.lock */, 33EBD3B2267296CB0013E557 /* Sources */, 33EBD3B3267296CB0013E557 /* Frameworks */, 33EBD3B4267296CB0013E557 /* Resources */, ); buildRules = ( ); dependencies = ( 33EBD3BC267296CB0013E557 /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = 33EBD3B6267296CB0013E557 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 33CC10E52044A3C60003C045 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1250; LastUpgradeCheck = 0930; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; }; }; }; 33CC111A2044C6BA0003C045 = { CreatedOnToolsVersion = 9.2; ProvisioningStyle = Manual; }; 33EBD3B5267296CB0013E557 = { CreatedOnToolsVersion = 12.5; TestTargetID = 33CC10EC2044A3C60003C045; }; }; }; buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 8.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 33CC10E42044A3C60003C045; productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, 33EBD3B5267296CB0013E557 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 33EBD3B4267296CB0013E557 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( Flutter/ephemeral/FlutterInputs.xcfilelist, ); inputPaths = ( Flutter/ephemeral/tripwire, ); outputFileListPaths = ( Flutter/ephemeral/FlutterOutputs.xcfilelist, ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh\ntouch Flutter/ephemeral/tripwire\n"; }; 460A36EFD54BEB8122DDAC6D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 50C74DCD840D9B569BE3D48F /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", "${BUILT_PRODUCTS_DIR}/url_launcher_macos/url_launcher_macos.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_macos.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; C318D59394D0E38099411848 /* [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", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 33EBD3B2267296CB0013E557 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 33EBD3B9267296CB0013E557 /* RunnerTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; }; 33EBD3BC267296CB0013E557 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC10EC2044A3C60003C045 /* Runner */; targetProxy = 33EBD3BB267296CB0013E557 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 33CC10F52044A3C60003C045 /* Base */, ); name = MainMenu.xib; path = Runner; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Profile; }; 338D0CEA231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter/ephemeral", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Profile; }; 338D0CEB231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Profile; }; 33CC10F92044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 33CC10FA2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; 33CC10FC2044A3C60003C045 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter/ephemeral", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; }; name = Debug; }; 33CC10FD2044A3C60003C045 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter/ephemeral", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Release; }; 33CC111C2044C6BA0003C045 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Manual; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 33CC111D2044C6BA0003C045 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 33EBD3BD267296CB0013E557 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 220FFDB920A73FF04EA40119 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COMBINE_HIDPI_IMAGES = YES; 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)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/url_launcher_example_example.app/Contents/MacOS/url_launcher_example_example"; }; name = Debug; }; 33EBD3BE267296CB0013E557 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5CFCAA4A883B5A0C4BD62DCF /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COMBINE_HIDPI_IMAGES = YES; 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)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/url_launcher_example_example.app/Contents/MacOS/url_launcher_example_example"; }; name = Release; }; 33EBD3BF267296CB0013E557 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = FD671DB5E266C257DCC5AD6A /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COMBINE_HIDPI_IMAGES = YES; 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)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/url_launcher_example_example.app/Contents/MacOS/url_launcher_example_example"; }; name = Profile; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10F92044A3C60003C045 /* Debug */, 33CC10FA2044A3C60003C045 /* Release */, 338D0CE9231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC10FC2044A3C60003C045 /* Debug */, 33CC10FD2044A3C60003C045 /* Release */, 338D0CEA231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { isa = XCConfigurationList; buildConfigurations = ( 33CC111C2044C6BA0003C045 /* Debug */, 33CC111D2044C6BA0003C045 /* Release */, 338D0CEB231458BD00FA5F75 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 33EBD3C0267296CB0013E557 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 33EBD3BD267296CB0013E557 /* Debug */, 33EBD3BE267296CB0013E557 /* Release */, 33EBD3BF267296CB0013E557 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/RunnerTests/Info.plist ================================================ 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 ================================================ FILE: packages/url_launcher/url_launcher_macos/example/macos/RunnerTests/RunnerTests.swift ================================================ // Copyright 2013 The Flutter 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 url_launcher_macos /// A stub to simulate the system Url handler. class StubWorkspace: SystemURLHandler { var isSuccessful = true func open(_ url: URL) -> Bool { return isSuccessful } func urlForApplication(toOpen: URL) -> URL? { return toOpen } } class RunnerTests: XCTestCase { func testCanLaunchSuccessReturnsTrue() throws { let expectation = XCTestExpectation(description: "Check if the URL can be launched") let plugin = UrlLauncherPlugin() let call = FlutterMethodCall( methodName: "canLaunch", arguments: ["url": "https://flutter.dev"]) plugin.handle( call, result: { (result: Any?) -> Void in XCTAssertEqual(result as? Bool, true) expectation.fulfill() }) wait(for: [expectation], timeout: 10.0) } func testCanLaunchNoAppIsAbleToOpenUrlReturnsFalse() throws { let expectation = XCTestExpectation(description: "Check if the URL can be launched") let plugin = UrlLauncherPlugin() let call = FlutterMethodCall( methodName: "canLaunch", arguments: ["url": "example://flutter.dev"]) plugin.handle( call, result: { (result: Any?) -> Void in XCTAssertEqual(result as? Bool, false) expectation.fulfill() }) wait(for: [expectation], timeout: 10.0) } func testCanLaunchInvalidUrlReturnsFalse() throws { let expectation = XCTestExpectation(description: "Check if the URL can be launched") let plugin = UrlLauncherPlugin() let call = FlutterMethodCall( methodName: "canLaunch", arguments: ["url": "brokenUrl"]) plugin.handle( call, result: { (result: Any?) -> Void in XCTAssertEqual(result as? Bool, false) expectation.fulfill() }) wait(for: [expectation], timeout: 10.0) } func testCanLaunchMissingArgumentReturnsFlutterError() throws { let expectation = XCTestExpectation(description: "Check if the URL can be launched") let plugin = UrlLauncherPlugin() let call = FlutterMethodCall( methodName: "canLaunch", arguments: []) plugin.handle( call, result: { (result: Any?) -> Void in XCTAssertTrue(result is FlutterError) expectation.fulfill() }) wait(for: [expectation], timeout: 10.0) } func testLaunchSuccessReturnsTrue() throws { let expectation = XCTestExpectation(description: "Try to open the URL") let workspace = StubWorkspace() let pluginWithStubWorkspace = UrlLauncherPlugin(workspace) let call = FlutterMethodCall( methodName: "launch", arguments: ["url": "https://flutter.dev"]) pluginWithStubWorkspace.handle( call, result: { (result: Any?) -> Void in XCTAssertEqual(result as? Bool, true) expectation.fulfill() }) wait(for: [expectation], timeout: 10.0) } func testLaunchNoAppIsAbleToOpenUrlReturnsFalse() throws { let expectation = XCTestExpectation(description: "Try to open the URL") let workspace = StubWorkspace() workspace.isSuccessful = false let pluginWithStubWorkspace = UrlLauncherPlugin(workspace) let call = FlutterMethodCall( methodName: "launch", arguments: ["url": "schemethatdoesnotexist://flutter.dev"]) pluginWithStubWorkspace.handle( call, result: { (result: Any?) -> Void in XCTAssertEqual(result as? Bool, false) expectation.fulfill() }) wait(for: [expectation], timeout: 10.0) } func testLaunchMissingArgumentReturnsFlutterError() throws { let expectation = XCTestExpectation(description: "Try to open the URL") let workspace = StubWorkspace() let pluginWithStubWorkspace = UrlLauncherPlugin(workspace) let call = FlutterMethodCall( methodName: "launch", arguments: []) pluginWithStubWorkspace.handle( call, result: { (result: Any?) -> Void in XCTAssertTrue(result is FlutterError) expectation.fulfill() }) wait(for: [expectation], timeout: 10.0) } } ================================================ FILE: packages/url_launcher/url_launcher_macos/example/pubspec.yaml ================================================ name: url_launcher_example description: Demonstrates how to use the url_launcher plugin. publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter url_launcher_macos: # When depending on this package from a real application you should use: # url_launcher_macos: ^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: ../ url_launcher_platform_interface: ^2.0.0 dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/url_launcher/url_launcher_macos/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/url_launcher/url_launcher_macos/lib/url_launcher_macos.dart ================================================ // Copyright 2013 The Flutter 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: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_macos'); /// An implementation of [UrlLauncherPlatform] for macOS. class UrlLauncherMacOS extends UrlLauncherPlatform { /// Registers this class as the default instance of [UrlLauncherPlatform]. static void registerWith() { UrlLauncherPlatform.instance = UrlLauncherMacOS(); } @override final LinkDelegate? linkDelegate = null; @override Future canLaunch(String url) { return _channel.invokeMethod( 'canLaunch', {'url': url}, ).then((bool? value) => value ?? false); } @override Future launch( String url, { required bool useSafariVC, required bool useWebView, required bool enableJavaScript, required bool enableDomStorage, 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); } } ================================================ FILE: packages/url_launcher/url_launcher_macos/macos/Classes/UrlLauncherPlugin.swift ================================================ // Copyright 2013 The Flutter 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 /// A handler that can launch other apps, check if any app is able to open the URL. public protocol SystemURLHandler { /// Opens the location at the specified URL. /// /// - Parameters: /// - url: A URL specifying the location to open. /// - Returns: true if the location was successfully opened; otherwise, false. func open(_ url: URL) -> Bool /// Returns the URL to the default app that would be opened. /// /// - Parameters: /// - toOpen: The URL of the file to open. /// - Returns: The URL of the default app that would open the specified url. /// Returns nil if no app is able to open the URL, or if the file URL does not exist. func urlForApplication(toOpen: URL) -> URL? } extension NSWorkspace: SystemURLHandler {} public class UrlLauncherPlugin: NSObject, FlutterPlugin { private var workspace: SystemURLHandler public init(_ workspace: SystemURLHandler = NSWorkspace.shared) { self.workspace = workspace } public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel( name: "plugins.flutter.io/url_launcher_macos", binaryMessenger: registrar.messenger) let instance = UrlLauncherPlugin() registrar.addMethodCallDelegate(instance, channel: channel) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { let urlString: String? = (call.arguments as? [String: Any])?["url"] as? String switch call.method { case "canLaunch": guard let unwrappedURLString = urlString, let url = URL.init(string: unwrappedURLString) else { result(invalidURLError(urlString)) return } result(workspace.urlForApplication(toOpen: url) != nil) case "launch": guard let unwrappedURLString = urlString, let url = URL.init(string: unwrappedURLString) else { result(invalidURLError(urlString)) return } result(workspace.open(url)) default: result(FlutterMethodNotImplemented) } } } /// Returns an error for the case where a URL string can't be parsed as a URL. private func invalidURLError(_ url: String?) -> FlutterError { return FlutterError( code: "argument_error", message: "Unable to parse URL", details: "Provided URL: \(String(describing: url))") } ================================================ FILE: packages/url_launcher/url_launcher_macos/macos/url_launcher_macos.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| s.name = 'url_launcher_macos' s.version = '0.0.1' s.summary = 'Flutter macos plugin for launching a URL.' s.description = <<-DESC A macOS implementation of the url_launcher plugin. DESC s.homepage = 'https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_macos' 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/url_launcher/url_launcher_macos' } s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' s.platform = :osx, '10.11' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' end ================================================ FILE: packages/url_launcher/url_launcher_macos/pubspec.yaml ================================================ 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.2 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: url_launcher platforms: macos: pluginClass: UrlLauncherPlugin fileName: url_launcher_macos.dart dartPluginClass: UrlLauncherMacOS dependencies: flutter: sdk: flutter url_launcher_platform_interface: ^2.0.3 dev_dependencies: flutter_test: sdk: flutter test: ^1.16.3 ================================================ FILE: packages/url_launcher/url_launcher_macos/test/url_launcher_macos_test.dart ================================================ // Copyright 2013 The Flutter 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:url_launcher_macos/url_launcher_macos.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$UrlLauncherMacOS', () { const MethodChannel channel = MethodChannel('plugins.flutter.io/url_launcher_macos'); final List log = []; _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, (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; }); tearDown(() { log.clear(); }); test('registers instance', () { UrlLauncherMacOS.registerWith(); expect(UrlLauncherPlatform.instance, isA()); }); test('canLaunch', () async { final UrlLauncherMacOS launcher = UrlLauncherMacOS(); await launcher.canLaunch('http://example.com/'); expect( log, [ isMethodCall('canLaunch', arguments: { 'url': 'http://example.com/', }) ], ); }); test('canLaunch should return false if platform returns null', () async { final UrlLauncherMacOS launcher = UrlLauncherMacOS(); final bool canLaunch = await launcher.canLaunch('http://example.com/'); expect(canLaunch, false); }); test('launch', () async { final UrlLauncherMacOS launcher = UrlLauncherMacOS(); 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('launch with headers', () async { final UrlLauncherMacOS launcher = UrlLauncherMacOS(); 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'}, }) ], ); }); test('launch universal links only', () async { final UrlLauncherMacOS launcher = UrlLauncherMacOS(); 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': {}, }) ], ); }); test('launch should return false if platform returns null', () async { final UrlLauncherMacOS launcher = UrlLauncherMacOS(); final bool launched = await launcher.launch( 'http://example.com/', useSafariVC: true, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: const {}, ); expect(launched, false); }); }); } /// 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; ================================================ FILE: packages/url_launcher/url_launcher_platform_interface/AUTHORS ================================================ # 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> ================================================ FILE: packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 2.1.1 * Updates imports for `prefer_relative_imports`. * Updates minimum Flutter version to 2.10. ## 2.1.0 * Adds a new `launchUrl` method corresponding to the new app-facing interface. ## 2.0.5 * Updates code for new analysis options. * Update to use the `verify` method introduced in platform_plugin_interface 2.1.0. ## 2.0.4 * Silenced warnings that may occur during build when using a very recent version of Flutter relating to null safety. ## 2.0.3 * Migrate `pushRouteNameToFramework` to use ChannelBuffers API. ## 2.0.2 * Update platform_plugin_interface version requirement. ## 2.0.1 * Fix SDK range. ## 2.0.0 * Migrate to null safety. ## 1.0.10 * Update Flutter SDK constraint. ## 1.0.9 * Laid the groundwork for introducing a Link widget. ## 1.0.8 * Added webOnlyWindowName parameter ## 1.0.7 * Update lower bound of dart dependency to 2.1.0. ## 1.0.6 * Make the pedantic dev_dependency explicit. ## 1.0.5 * Make the `PlatformInterface` `_token` non `const` (as `const` `Object`s are not unique). ## 1.0.4 * Use the common PlatformInterface code from plugin_platform_interface. * [TEST ONLY BREAKING CHANGE] remove UrlLauncherPlatform.isMock, we're not increasing the major version as doing so for platform interfaces has bad implications, given that this is only going to break test code, and that the plugin is young and shouldn't have third-party users we've decided to land this as a patch bump. ## 1.0.3 * Minor DartDoc changes and add a lint for missing DartDocs. ## 1.0.2 * Use package URI in test directory to import code from lib. ## 1.0.1 * Enforce that UrlLauncherPlatform isn't implemented with `implements`. ## 1.0.0 * Initial release. ================================================ FILE: packages/url_launcher/url_launcher_platform_interface/LICENSE ================================================ 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. ================================================ FILE: packages/url_launcher/url_launcher_platform_interface/README.md ================================================ # url_launcher_platform_interface A common platform interface for the [`url_launcher`][1] plugin. This interface allows platform-specific implementations of the `url_launcher` plugin, as well as the plugin itself, to ensure they are supporting the same interface. # Usage To implement a new platform-specific implementation of `url_launcher`, extend [`UrlLauncherPlatform`][2] with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `UrlLauncherPlatform` by calling `UrlLauncherPlatform.instance = MyPlatformUrlLauncher()`. # Note on breaking changes Strongly prefer non-breaking changes (such as adding a method to the interface) over breaking changes for this package. See https://flutter.dev/go/platform-interface-breaking-changes for a discussion on why a less-clean interface is preferable to a breaking change. [1]: ../url_launcher [2]: lib/url_launcher_platform_interface.dart ================================================ FILE: packages/url_launcher/url_launcher_platform_interface/lib/link.dart ================================================ // Copyright 2013 The Flutter 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 'dart:ui' as ui; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; /// Signature for a function provided by the [Link] widget that instructs it to /// follow the link. typedef FollowLink = Future Function(); /// Signature for a builder function passed to the [Link] widget to construct /// the widget tree under it. typedef LinkWidgetBuilder = Widget Function( BuildContext context, FollowLink? followLink, ); /// Signature for a delegate function to build the [Link] widget. typedef LinkDelegate = Widget Function(LinkInfo linkWidget); const MethodCodec _codec = JSONMethodCodec(); /// Defines where a Link URL should be open. /// /// This is a class instead of an enum to allow future customizability e.g. /// opening a link in a specific iframe. class LinkTarget { /// Const private constructor with a [debugLabel] to allow the creation of /// multiple distinct const instances. const LinkTarget._({required this.debugLabel}); /// Used to distinguish multiple const instances of [LinkTarget]. final String debugLabel; /// Use the default target for each platform. /// /// On Android, the default is [blank]. On the web, the default is [self]. /// /// iOS, on the other hand, defaults to [self] for web URLs, and [blank] for /// non-web URLs. static const LinkTarget defaultTarget = LinkTarget._(debugLabel: 'defaultTarget'); /// On the web, this opens the link in the same tab where the flutter app is /// running. /// /// On Android and iOS, this opens the link in a webview within the app. static const LinkTarget self = LinkTarget._(debugLabel: 'self'); /// On the web, this opens the link in a new tab or window (depending on the /// browser and user configuration). /// /// On Android and iOS, this opens the link in the browser or the relevant /// app. static const LinkTarget blank = LinkTarget._(debugLabel: 'blank'); } /// Encapsulates all the information necessary to build a Link widget. abstract class LinkInfo { /// Called at build time to construct the widget tree under the link. LinkWidgetBuilder get builder; /// The destination that this link leads to. Uri? get uri; /// The target indicating where to open the link. LinkTarget get target; /// Whether the link is disabled or not. bool get isDisabled; } typedef _SendMessage = Function(String, ByteData?, void Function(ByteData?)); /// Pushes the [routeName] into Flutter's navigation system via a platform /// message. /// /// The platform is notified using [SystemNavigator.routeInformationUpdated]. On /// older versions of Flutter, this means it will not work unless the /// application uses a [Router] (e.g. using [MaterialApp.router]). /// /// Returns the raw data returned by the framework. // TODO(ianh): Remove the first argument. Future pushRouteNameToFramework(Object? _, String routeName) { final Completer completer = Completer(); SystemNavigator.routeInformationUpdated(location: routeName); final _SendMessage sendMessage = _ambiguate(WidgetsBinding.instance) ?.platformDispatcher .onPlatformMessage ?? ui.channelBuffers.push; sendMessage( 'flutter/navigation', _codec.encodeMethodCall( MethodCall('pushRouteInformation', { 'location': routeName, 'state': null, }), ), completer.complete, ); return completer.future; } /// 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; ================================================ FILE: packages/url_launcher/url_launcher_platform_interface/lib/method_channel_url_launcher.dart ================================================ // Copyright 2013 The Flutter 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 'link.dart'; import 'url_launcher_platform_interface.dart'; const MethodChannel _channel = MethodChannel('plugins.flutter.io/url_launcher'); /// An implementation of [UrlLauncherPlatform] that uses method channels. class MethodChannelUrlLauncher extends UrlLauncherPlatform { @override final LinkDelegate? linkDelegate = null; @override Future canLaunch(String url) { return _channel.invokeMethod( 'canLaunch', {'url': url}, ).then((bool? value) => value ?? false); } @override Future closeWebView() { return _channel.invokeMethod('closeWebView'); } @override Future launch( String url, { required bool useSafariVC, required bool useWebView, required bool enableJavaScript, required bool enableDomStorage, required bool universalLinksOnly, required Map headers, String? webOnlyWindowName, }) { return _channel.invokeMethod( 'launch', { 'url': url, 'useSafariVC': useSafariVC, 'useWebView': useWebView, 'enableJavaScript': enableJavaScript, 'enableDomStorage': enableDomStorage, 'universalLinksOnly': universalLinksOnly, 'headers': headers, }, ).then((bool? value) => value ?? false); } } ================================================ FILE: packages/url_launcher/url_launcher_platform_interface/lib/src/types.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; /// The desired mode to launch a URL. /// /// Support for these modes varies by platform. Platforms that do not support /// the requested mode may substitute another mode. enum PreferredLaunchMode { /// Leaves the decision of how to launch the URL to the platform /// implementation. platformDefault, /// Loads the URL in an in-app web view (e.g., Safari View Controller). inAppWebView, /// Passes the URL to the OS to be handled by another application. externalApplication, /// Passes the URL to the OS to be handled by another non-browser application. externalNonBrowserApplication, } /// Additional configuration options for [PreferredLaunchMode.inAppWebView]. /// /// Not all options are supported on all platforms. This is a superset of /// available options exposed across all implementations. @immutable class InAppWebViewConfiguration { /// Creates a new WebViewConfiguration with the given settings. const InAppWebViewConfiguration({ this.enableJavaScript = true, this.enableDomStorage = true, this.headers = const {}, }); /// Whether or not JavaScript is enabled for the web content. final bool enableJavaScript; /// Whether or not DOM storage is enabled for the web content. final bool enableDomStorage; /// Additional headers to pass in the load request. final Map headers; } /// Options for [launchUrl]. @immutable class LaunchOptions { /// Creates a new parameter object with the given options. const LaunchOptions({ this.mode = PreferredLaunchMode.platformDefault, this.webViewConfiguration = const InAppWebViewConfiguration(), this.webOnlyWindowName, }); /// The requested launch mode. final PreferredLaunchMode mode; /// Configuration for the web view in [PreferredLaunchMode.inAppWebView] mode. final InAppWebViewConfiguration webViewConfiguration; /// A web-platform-specific option to set the link target. /// /// Default behaviour when unset should be to open the url in a new tab. final String? webOnlyWindowName; } ================================================ FILE: packages/url_launcher/url_launcher_platform_interface/lib/src/url_launcher_platform.dart ================================================ // Copyright 2013 The Flutter 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:plugin_platform_interface/plugin_platform_interface.dart'; import '../link.dart'; import '../method_channel_url_launcher.dart'; import '../url_launcher_platform_interface.dart'; /// The interface that implementations of url_launcher must implement. /// /// Platform implementations should extend this class rather than implement it as `url_launcher` /// does not consider newly added methods to be breaking changes. Extending this class /// (using `extends`) ensures that the subclass will get the default implementation, while /// platform implementations that `implements` this interface will be broken by newly added /// [UrlLauncherPlatform] methods. abstract class UrlLauncherPlatform extends PlatformInterface { /// Constructs a UrlLauncherPlatform. UrlLauncherPlatform() : super(token: _token); static final Object _token = Object(); static UrlLauncherPlatform _instance = MethodChannelUrlLauncher(); /// The default instance of [UrlLauncherPlatform] to use. /// /// Defaults to [MethodChannelUrlLauncher]. static UrlLauncherPlatform get instance => _instance; /// Platform-specific plugins should set this with their own platform-specific /// class that extends [UrlLauncherPlatform] when they register themselves. // TODO(amirh): Extract common platform interface logic. // https://github.com/flutter/flutter/issues/43368 static set instance(UrlLauncherPlatform instance) { PlatformInterface.verify(instance, _token); _instance = instance; } /// The delegate used by the Link widget to build itself. LinkDelegate? get linkDelegate; /// Returns `true` if this platform is able to launch [url]. Future canLaunch(String url) { throw UnimplementedError('canLaunch() has not been implemented.'); } /// Passes [url] to the underlying platform for handling. /// /// Returns `true` if the given [url] was successfully launched. /// /// For documentation on the other arguments, see the `launch` documentation /// in `package:url_launcher/url_launcher.dart`. Future launch( String url, { required bool useSafariVC, required bool useWebView, required bool enableJavaScript, required bool enableDomStorage, required bool universalLinksOnly, required Map headers, String? webOnlyWindowName, }) { throw UnimplementedError('launch() has not been implemented.'); } /// Passes [url] to the underlying platform for handling. /// /// Returns `true` if the given [url] was successfully launched. Future launchUrl(String url, LaunchOptions options) { final bool isWebURL = url.startsWith('http:') || url.startsWith('https:'); final bool useWebView = options.mode == PreferredLaunchMode.inAppWebView || (isWebURL && options.mode == PreferredLaunchMode.platformDefault); return launch( url, useSafariVC: useWebView, useWebView: useWebView, enableJavaScript: options.webViewConfiguration.enableJavaScript, enableDomStorage: options.webViewConfiguration.enableDomStorage, universalLinksOnly: options.mode == PreferredLaunchMode.externalNonBrowserApplication, headers: options.webViewConfiguration.headers, webOnlyWindowName: options.webOnlyWindowName, ); } /// Closes the WebView, if one was opened earlier by [launch]. Future closeWebView() { throw UnimplementedError('closeWebView() has not been implemented.'); } } ================================================ FILE: packages/url_launcher/url_launcher_platform_interface/lib/url_launcher_platform_interface.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'src/types.dart'; export 'src/url_launcher_platform.dart'; ================================================ FILE: packages/url_launcher/url_launcher_platform_interface/pubspec.yaml ================================================ name: url_launcher_platform_interface description: A common platform interface for the url_launcher plugin. repository: https://github.com/flutter/plugins/tree/main/packages/url_launcher/url_launcher_platform_interface issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+url_launcher%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.1.1 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.1.0 dev_dependencies: flutter_test: sdk: flutter mockito: ^5.0.0 ================================================ FILE: packages/url_launcher/url_launcher_platform_interface/test/link_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:collection'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:url_launcher_platform_interface/link.dart'; void main() { testWidgets('Link with Navigator', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( home: const Placeholder(key: Key('home')), routes: { '/a': (BuildContext context) => const Placeholder(key: Key('a')), }, )); expect(find.byKey(const Key('home')), findsOneWidget); expect(find.byKey(const Key('a')), findsNothing); await tester.runAsync(() => pushRouteNameToFramework(null, '/a')); // start animation await tester.pump(); // skip past animation (5s is arbitrary, just needs to be long enough) await tester.pump(const Duration(seconds: 5)); expect(find.byKey(const Key('a')), findsOneWidget); expect(find.byKey(const Key('home')), findsNothing); }); testWidgets('Link with Navigator', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp.router( routeInformationParser: _RouteInformationParser(), routerDelegate: _RouteDelegate(), )); expect(find.byKey(const Key('/')), findsOneWidget); expect(find.byKey(const Key('/a')), findsNothing); await tester.runAsync(() => pushRouteNameToFramework(null, '/a')); // start animation await tester.pump(); // skip past animation (5s is arbitrary, just needs to be long enough) await tester.pump(const Duration(seconds: 5)); expect(find.byKey(const Key('/a')), findsOneWidget); expect(find.byKey(const Key('/')), findsNothing); }); } class _RouteInformationParser extends RouteInformationParser { @override Future parseRouteInformation( RouteInformation routeInformation) { return SynchronousFuture(routeInformation); } @override RouteInformation? restoreRouteInformation(RouteInformation configuration) { return configuration; } } class _RouteDelegate extends RouterDelegate with ChangeNotifier { final Queue _history = Queue(); @override Future setNewRoutePath(RouteInformation configuration) { _history.add(configuration); return SynchronousFuture(null); } @override Future popRoute() { if (_history.isEmpty) { return SynchronousFuture(false); } _history.removeLast(); return SynchronousFuture(true); } @override Widget build(BuildContext context) { if (_history.isEmpty) { return const Placeholder(key: Key('empty')); } return Placeholder(key: Key('${_history.last.location}')); } } ================================================ FILE: packages/url_launcher/url_launcher_platform_interface/test/method_channel_url_launcher_test.dart ================================================ // Copyright 2013 The Flutter 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:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:url_launcher_platform_interface/link.dart'; import 'package:url_launcher_platform_interface/method_channel_url_launcher.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); // Store the initial instance before any tests change it. final UrlLauncherPlatform initialInstance = UrlLauncherPlatform.instance; group('$UrlLauncherPlatform', () { test('$MethodChannelUrlLauncher() is the default instance', () { expect(initialInstance, isInstanceOf()); }); test('Cannot be implemented with `implements`', () { expect(() { UrlLauncherPlatform.instance = ImplementsUrlLauncherPlatform(); // In versions of `package:plugin_platform_interface` prior to fixing // https://github.com/flutter/flutter/issues/109339, an attempt to // implement a platform interface using `implements` would sometimes // throw a `NoSuchMethodError` and other times throw an // `AssertionError`. After the issue is fixed, an `AssertionError` will // always be thrown. For the purpose of this test, we don't really care // what exception is thrown, so just allow any exception. }, throwsA(anything)); }); test('Can be mocked with `implements`', () { final UrlLauncherPlatformMock mock = UrlLauncherPlatformMock(); UrlLauncherPlatform.instance = mock; }); test('Can be extended', () { UrlLauncherPlatform.instance = ExtendsUrlLauncherPlatform(); }); }); group('$MethodChannelUrlLauncher', () { const MethodChannel channel = MethodChannel('plugins.flutter.io/url_launcher'); final List log = []; _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(channel, (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; }); final MethodChannelUrlLauncher launcher = MethodChannelUrlLauncher(); tearDown(() { log.clear(); }); test('canLaunch', () async { await launcher.canLaunch('http://example.com/'); expect( log, [ isMethodCall('canLaunch', arguments: { 'url': 'http://example.com/', }) ], ); }); test('canLaunch should return false if platform returns null', () async { final bool canLaunch = await launcher.canLaunch('http://example.com/'); expect(canLaunch, false); }); test('launch', () async { 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/', 'useSafariVC': true, 'useWebView': false, 'enableJavaScript': false, 'enableDomStorage': false, 'universalLinksOnly': false, 'headers': {}, }) ], ); }); test('launch with headers', () async { 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/', 'useSafariVC': true, 'useWebView': false, 'enableJavaScript': false, 'enableDomStorage': false, 'universalLinksOnly': false, 'headers': {'key': 'value'}, }) ], ); }); test('launch force SafariVC', () async { 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/', 'useSafariVC': true, 'useWebView': false, 'enableJavaScript': false, 'enableDomStorage': false, 'universalLinksOnly': false, 'headers': {}, }) ], ); }); test('launch universal links only', () async { 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/', 'useSafariVC': false, 'useWebView': false, 'enableJavaScript': false, 'enableDomStorage': false, 'universalLinksOnly': true, 'headers': {}, }) ], ); }); test('launch force WebView', () async { await launcher.launch( 'http://example.com/', useSafariVC: true, useWebView: true, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: const {}, ); expect( log, [ isMethodCall('launch', arguments: { 'url': 'http://example.com/', 'useSafariVC': true, 'useWebView': true, 'enableJavaScript': false, 'enableDomStorage': false, 'universalLinksOnly': false, 'headers': {}, }) ], ); }); test('launch force WebView enable javascript', () async { await launcher.launch( 'http://example.com/', useSafariVC: true, useWebView: true, enableJavaScript: true, enableDomStorage: false, universalLinksOnly: false, headers: const {}, ); expect( log, [ isMethodCall('launch', arguments: { 'url': 'http://example.com/', 'useSafariVC': true, 'useWebView': true, 'enableJavaScript': true, 'enableDomStorage': false, 'universalLinksOnly': false, 'headers': {}, }) ], ); }); test('launch force WebView enable DOM storage', () async { await launcher.launch( 'http://example.com/', useSafariVC: true, useWebView: true, enableJavaScript: false, enableDomStorage: true, universalLinksOnly: false, headers: const {}, ); expect( log, [ isMethodCall('launch', arguments: { 'url': 'http://example.com/', 'useSafariVC': true, 'useWebView': true, 'enableJavaScript': false, 'enableDomStorage': true, 'universalLinksOnly': false, 'headers': {}, }) ], ); }); test('launch force SafariVC to false', () async { await launcher.launch( 'http://example.com/', useSafariVC: false, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: const {}, ); expect( log, [ isMethodCall('launch', arguments: { 'url': 'http://example.com/', 'useSafariVC': false, 'useWebView': false, 'enableJavaScript': false, 'enableDomStorage': false, 'universalLinksOnly': false, 'headers': {}, }) ], ); }); test('launch should return false if platform returns null', () async { 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('closeWebView default behavior', () async { await launcher.closeWebView(); expect( log, [isMethodCall('closeWebView', arguments: null)], ); }); }); } class UrlLauncherPlatformMock extends Mock with MockPlatformInterfaceMixin implements UrlLauncherPlatform {} class ImplementsUrlLauncherPlatform extends Mock implements UrlLauncherPlatform {} class ExtendsUrlLauncherPlatform extends UrlLauncherPlatform { @override final LinkDelegate? linkDelegate = null; } /// 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; ================================================ FILE: packages/url_launcher/url_launcher_platform_interface/test/url_launcher_platform_test.dart ================================================ // Copyright 2013 The Flutter 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:url_launcher_platform_interface/link.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; class CapturingUrlLauncher extends UrlLauncherPlatform { String? url; bool? useSafariVC; bool? useWebView; bool? enableJavaScript; bool? enableDomStorage; bool? universalLinksOnly; Map headers = {}; String? webOnlyWindowName; @override final LinkDelegate? linkDelegate = null; @override Future launch( String url, { required bool useSafariVC, required bool useWebView, required bool enableJavaScript, required bool enableDomStorage, required bool universalLinksOnly, required Map headers, String? webOnlyWindowName, }) async { this.url = url; this.useSafariVC = useSafariVC; this.useWebView = useWebView; this.enableJavaScript = enableJavaScript; this.enableDomStorage = enableDomStorage; this.universalLinksOnly = universalLinksOnly; this.headers = headers; this.webOnlyWindowName = webOnlyWindowName; return true; } } void main() { test('launchUrl calls through to launch with default options for web URL', () async { final CapturingUrlLauncher launcher = CapturingUrlLauncher(); await launcher.launchUrl('https://flutter.dev', const LaunchOptions()); expect(launcher.url, 'https://flutter.dev'); expect(launcher.useSafariVC, true); expect(launcher.useWebView, true); expect(launcher.enableJavaScript, true); expect(launcher.enableDomStorage, true); expect(launcher.universalLinksOnly, false); expect(launcher.headers, isEmpty); expect(launcher.webOnlyWindowName, null); }); test('launchUrl calls through to launch with default options for non-web URL', () async { final CapturingUrlLauncher launcher = CapturingUrlLauncher(); await launcher.launchUrl('tel:123456789', const LaunchOptions()); expect(launcher.url, 'tel:123456789'); expect(launcher.useSafariVC, false); expect(launcher.useWebView, false); expect(launcher.enableJavaScript, true); expect(launcher.enableDomStorage, true); expect(launcher.universalLinksOnly, false); expect(launcher.headers, isEmpty); expect(launcher.webOnlyWindowName, null); }); test('launchUrl calls through to launch with universal links', () async { final CapturingUrlLauncher launcher = CapturingUrlLauncher(); await launcher.launchUrl( 'https://flutter.dev', const LaunchOptions( mode: PreferredLaunchMode.externalNonBrowserApplication)); expect(launcher.url, 'https://flutter.dev'); expect(launcher.useSafariVC, false); expect(launcher.useWebView, false); expect(launcher.enableJavaScript, true); expect(launcher.enableDomStorage, true); expect(launcher.universalLinksOnly, true); expect(launcher.headers, isEmpty); expect(launcher.webOnlyWindowName, null); }); test('launchUrl calls through to launch with all non-default options', () async { final CapturingUrlLauncher launcher = CapturingUrlLauncher(); await launcher.launchUrl( 'https://flutter.dev', const LaunchOptions( mode: PreferredLaunchMode.externalApplication, webViewConfiguration: InAppWebViewConfiguration( enableJavaScript: false, enableDomStorage: false, headers: {'foo': 'bar'}), webOnlyWindowName: 'a_name', )); expect(launcher.url, 'https://flutter.dev'); expect(launcher.useSafariVC, false); expect(launcher.useWebView, false); expect(launcher.enableJavaScript, false); expect(launcher.enableDomStorage, false); expect(launcher.universalLinksOnly, false); expect(launcher.headers['foo'], 'bar'); expect(launcher.webOnlyWindowName, 'a_name'); }); } ================================================ FILE: packages/url_launcher/url_launcher_web/AUTHORS ================================================ # 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> TheOneWithTheBraid ================================================ FILE: packages/url_launcher/url_launcher_web/CHANGELOG.md ================================================ ## 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 * Updates `url_launcher_platform_interface` constraint to the correct minimum version. ## 2.0.12 * Fixes call to `setState` after dispose on the `Link` widget. [Issue](https://github.com/flutter/flutter/issues/102741). * Removes unused `BuildContext` from the `LinkViewController`. ## 2.0.11 * Minor fixes for new analysis options. ## 2.0.10 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 2.0.9 - Fixes invalid routes when opening a `Link` in a new tab ## 2.0.8 * Updates the minimum Flutter version to 2.10, which is required by the change in 2.0.7. ## 2.0.7 * Marks the `Link` widget as invisible so it can be optimized by the engine. ## 2.0.6 * Removes dependency on `meta`. ## 2.0.5 * Updates code for new analysis options. ## 2.0.4 - Add `implements` to pubspec. ## 2.0.3 - Replaced reference to `shared_preferences` plugin with the `url_launcher` in the README. ## 2.0.2 - Updated installation instructions in README. ## 2.0.1 - Change sizing code of `Link` widget's `HtmlElementView` so it works well when slotted. ## 2.0.0 - Migrate to null safety. ## 0.1.5+3 - Fix Link misalignment [issue](https://github.com/flutter/flutter/issues/70053). ## 0.1.5+2 - Update Flutter SDK constraint. ## 0.1.5+1 - Substitute `undefined_prefixed_name: ignore` analyzer setting by a `dart:ui` shim with conditional exports. [Issue](https://github.com/flutter/flutter/issues/69309). ## 0.1.5 - Added the web implementation of the Link widget. ## 0.1.4+2 - Move `lib/third_party` to `lib/src/third_party`. ## 0.1.4+1 - Add a more correct attribution to `package:platform_detect` code. ## 0.1.4 - (Null safety) Remove dependency on `package:platform_detect` - Port unit tests to run with `flutter drive` ## 0.1.3+2 - Fix a typo in a test name and fix some style inconsistencies. ## 0.1.3+1 - Depend explicitly on the `platform_interface` package that adds the `webOnlyWindowName` parameter. ## 0.1.3 - Added webOnlyWindowName parameter to launch() ## 0.1.2+1 - Update docs ## 0.1.2 - Adds "tel" and "sms" support ## 0.1.1+6 - Open "mailto" urls with target set as "\_top" on Safari browsers. - Update lower bound of dart dependency to 2.2.0. ## 0.1.1+5 - Update lower bound of dart dependency to 2.1.0. ## 0.1.1+4 - 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.1.1+3 - Refactor tests to not rely on the underlying browser behavior. ## 0.1.1+2 - Open urls with target "\_top" on iOS PWAs. ## 0.1.1+1 - Make the pedantic dev_dependency explicit. ## 0.1.1 - Added support for mailto scheme ## 0.1.0+2 - Remove androidx references from the no-op android implemenation. ## 0.1.0+1 - Add an android/ folder with no-op implementation to workaround https://github.com/flutter/flutter/issues/46304. - Bump the minimal required Flutter version to 1.10.0. ## 0.1.0 - Update docs and pubspec. ## 0.0.2 - Switch to using `url_launcher_platform_interface`. ## 0.0.1 - Initial open-source release. ================================================ FILE: packages/url_launcher/url_launcher_web/LICENSE ================================================ url_launcher_web 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. -------------------------------------------------------------------------------- platform_detect Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2017 Workiva Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: packages/url_launcher/url_launcher_web/README.md ================================================ # url\_launcher\_web The web implementation of [`url_launcher`][1]. ## Usage This package is [endorsed][2], which means you can simply use `url_launcher` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/url_launcher [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/url_launcher/url_launcher_web/example/README.md ================================================ # 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. ## Testing This package uses `package:integration_test` to run its tests in a web browser. See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/wiki/Plugin-Tests#web-tests) in the Flutter wiki for instructions to setup and run the tests in this package. Check [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) for more info. ================================================ FILE: packages/url_launcher/url_launcher_web/example/build.yaml ================================================ targets: $default: sources: - integration_test/*.dart - lib/$lib$ - $package$ ================================================ FILE: packages/url_launcher/url_launcher_web/example/integration_test/link_widget_test.dart ================================================ // Copyright 2013 The Flutter 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:html' as html; import 'dart:js_util'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:integration_test/integration_test.dart'; import 'package:url_launcher_platform_interface/link.dart'; import 'package:url_launcher_web/src/link.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('Link Widget', () { testWidgets('creates anchor with correct attributes', (WidgetTester tester) async { final Uri uri = Uri.parse('http://foobar/example?q=1'); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: WebLinkDelegate(TestLinkInfo( uri: uri, target: LinkTarget.blank, builder: (BuildContext context, FollowLink? followLink) { return const SizedBox(width: 100, height: 100); }, )), )); // Platform view creation happens asynchronously. await tester.pumpAndSettle(); final html.Element anchor = _findSingleAnchor(); expect(anchor.getAttribute('href'), uri.toString()); expect(anchor.getAttribute('target'), '_blank'); final Uri uri2 = Uri.parse('http://foobar2/example?q=2'); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: WebLinkDelegate(TestLinkInfo( uri: uri2, target: LinkTarget.self, builder: (BuildContext context, FollowLink? followLink) { return const SizedBox(width: 100, height: 100); }, )), )); await tester.pumpAndSettle(); // Check that the same anchor has been updated. expect(anchor.getAttribute('href'), uri2.toString()); expect(anchor.getAttribute('target'), '_self'); final Uri uri3 = Uri.parse('/foobar'); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: WebLinkDelegate(TestLinkInfo( uri: uri3, target: LinkTarget.self, builder: (BuildContext context, FollowLink? followLink) { return const SizedBox(width: 100, height: 100); }, )), )); await tester.pumpAndSettle(); // Check that internal route properly prepares using the default // [UrlStrategy] expect(anchor.getAttribute('href'), urlStrategy?.prepareExternalUrl(uri3.toString())); expect(anchor.getAttribute('target'), '_self'); }); testWidgets('sizes itself correctly', (WidgetTester tester) async { final Key containerKey = GlobalKey(); final Uri uri = Uri.parse('http://foobar'); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: Center( child: ConstrainedBox( constraints: BoxConstraints.tight(const Size(100.0, 100.0)), child: WebLinkDelegate(TestLinkInfo( uri: uri, target: LinkTarget.blank, builder: (BuildContext context, FollowLink? followLink) { return Container( key: containerKey, child: const SizedBox(width: 50.0, height: 50.0), ); }, )), ), ), )); await tester.pumpAndSettle(); final Size containerSize = tester.getSize(find.byKey(containerKey)); // The Stack widget inserted by the `WebLinkDelegate` shouldn't loosen the // constraints before passing them to the inner container. So the inner // container should respect the tight constraints given by the ancestor // `ConstrainedBox` widget. expect(containerSize.width, 100.0); expect(containerSize.height, 100.0); }); // See: https://github.com/flutter/plugins/pull/3522#discussion_r574703724 testWidgets('uri can be null', (WidgetTester tester) async { await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: WebLinkDelegate(TestLinkInfo( uri: null, target: LinkTarget.defaultTarget, builder: (BuildContext context, FollowLink? followLink) { return const SizedBox(width: 100, height: 100); }, )), )); // Platform view creation happens asynchronously. await tester.pumpAndSettle(); final html.Element anchor = _findSingleAnchor(); expect(anchor.hasAttribute('href'), false); }); testWidgets('can be created and disposed', (WidgetTester tester) async { final Uri uri = Uri.parse('http://foobar'); const int itemCount = 500; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: MediaQuery( data: const MediaQueryData(), child: ListView.builder( itemCount: itemCount, itemBuilder: (_, int index) => WebLinkDelegate(TestLinkInfo( uri: uri, target: LinkTarget.defaultTarget, builder: (BuildContext context, FollowLink? followLink) => Text('#$index', textAlign: TextAlign.center), )), ), ), ), ); await tester.pumpAndSettle(); await tester.scrollUntilVisible( find.text('#${itemCount - 1}'), 800, maxScrolls: 1000, ); }); }); } html.Element _findSingleAnchor() { final List foundAnchors = []; for (final html.Element anchor in html.document.querySelectorAll('a')) { if (hasProperty(anchor, linkViewIdProperty)) { foundAnchors.add(anchor); } } // Search inside the shadow DOM as well. final html.ShadowRoot? shadowRoot = html.document.querySelector('flt-glass-pane')?.shadowRoot; if (shadowRoot != null) { for (final html.Element anchor in shadowRoot.querySelectorAll('a')) { if (hasProperty(anchor, linkViewIdProperty)) { foundAnchors.add(anchor); } } } return foundAnchors.single; } class TestLinkInfo extends LinkInfo { TestLinkInfo({ required this.uri, required this.target, required this.builder, }); @override final LinkWidgetBuilder builder; @override final Uri? uri; @override final LinkTarget target; @override bool get isDisabled => uri == null; } ================================================ FILE: packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.dart ================================================ // Copyright 2013 The Flutter 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:html' as html; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:url_launcher_web/url_launcher_web.dart'; import 'url_launcher_web_test.mocks.dart'; @GenerateMocks([html.Window, html.Navigator]) void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('UrlLauncherPlugin', () { late MockWindow mockWindow; late MockNavigator mockNavigator; late UrlLauncherPlugin plugin; setUp(() { mockWindow = MockWindow(); mockNavigator = MockNavigator(); when(mockWindow.navigator).thenReturn(mockNavigator); // Simulate that window.open does something. when(mockWindow.open(any, any)).thenReturn(MockWindow()); when(mockNavigator.vendor).thenReturn('Google LLC'); when(mockNavigator.appVersion).thenReturn('Mock version!'); plugin = UrlLauncherPlugin(debugWindow: mockWindow); }); group('canLaunch', () { testWidgets('"http" URLs -> true', (WidgetTester _) async { expect(plugin.canLaunch('http://google.com'), completion(isTrue)); }); testWidgets('"https" URLs -> true', (WidgetTester _) async { expect( plugin.canLaunch('https://go, (Widogle.com'), completion(isTrue)); }); testWidgets('"mailto" URLs -> true', (WidgetTester _) async { expect( plugin.canLaunch('mailto:name@mydomain.com'), completion(isTrue)); }); testWidgets('"tel" URLs -> true', (WidgetTester _) async { expect(plugin.canLaunch('tel:5551234567'), completion(isTrue)); }); testWidgets('"sms" URLs -> true', (WidgetTester _) async { expect(plugin.canLaunch('sms:+19725551212?body=hello%20there'), completion(isTrue)); }); }); group('launch', () { testWidgets('launching a URL returns true', (WidgetTester _) async { expect( plugin.launch( 'https://www.google.com', ), completion(isTrue)); }); testWidgets('launching a "mailto" returns true', (WidgetTester _) async { expect( plugin.launch( 'mailto:name@mydomain.com', ), completion(isTrue)); }); testWidgets('launching a "tel" returns true', (WidgetTester _) async { expect( plugin.launch( 'tel:5551234567', ), completion(isTrue)); }); testWidgets('launching a "sms" returns true', (WidgetTester _) async { expect( plugin.launch( 'sms:+19725551212?body=hello%20there', ), completion(isTrue)); }); }); group('openNewWindow', () { testWidgets('http urls should be launched in a new window', (WidgetTester _) async { plugin.openNewWindow('http://www.google.com'); verify(mockWindow.open('http://www.google.com', '')); }); testWidgets('https urls should be launched in a new window', (WidgetTester _) async { plugin.openNewWindow('https://www.google.com'); verify(mockWindow.open('https://www.google.com', '')); }); testWidgets('mailto urls should be launched on a new window', (WidgetTester _) async { plugin.openNewWindow('mailto:name@mydomain.com'); verify(mockWindow.open('mailto:name@mydomain.com', '')); }); testWidgets('tel urls should be launched on a new window', (WidgetTester _) async { plugin.openNewWindow('tel:5551234567'); verify(mockWindow.open('tel:5551234567', '')); }); testWidgets('sms urls should be launched on a new window', (WidgetTester _) async { plugin.openNewWindow('sms:+19725551212?body=hello%20there'); verify(mockWindow.open('sms:+19725551212?body=hello%20there', '')); }); testWidgets( 'setting webOnlyLinkTarget as _self opens the url in the same tab', (WidgetTester _) async { plugin.openNewWindow('https://www.google.com', webOnlyWindowName: '_self'); verify(mockWindow.open('https://www.google.com', '_self')); }); testWidgets( 'setting webOnlyLinkTarget as _blank opens the url in a new tab', (WidgetTester _) async { plugin.openNewWindow('https://www.google.com', webOnlyWindowName: '_blank'); verify(mockWindow.open('https://www.google.com', '_blank')); }); group('Safari', () { setUp(() { when(mockNavigator.vendor).thenReturn('Apple Computer, Inc.'); when(mockNavigator.appVersion).thenReturn( '5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15'); // Recreate the plugin, so it grabs the overrides from this group plugin = UrlLauncherPlugin(debugWindow: mockWindow); }); testWidgets('http urls should be launched in a new window', (WidgetTester _) async { plugin.openNewWindow('http://www.google.com'); verify(mockWindow.open('http://www.google.com', '')); }); testWidgets('https urls should be launched in a new window', (WidgetTester _) async { plugin.openNewWindow('https://www.google.com'); verify(mockWindow.open('https://www.google.com', '')); }); testWidgets('mailto urls should be launched on the same window', (WidgetTester _) async { plugin.openNewWindow('mailto:name@mydomain.com'); verify(mockWindow.open('mailto:name@mydomain.com', '_top')); }); testWidgets('tel urls should be launched on the same window', (WidgetTester _) async { plugin.openNewWindow('tel:5551234567'); verify(mockWindow.open('tel:5551234567', '_top')); }); testWidgets('sms urls should be launched on the same window', (WidgetTester _) async { plugin.openNewWindow('sms:+19725551212?body=hello%20there'); verify( mockWindow.open('sms:+19725551212?body=hello%20there', '_top')); }); testWidgets( 'mailto urls should use _blank if webOnlyWindowName is set as _blank', (WidgetTester _) async { plugin.openNewWindow('mailto:name@mydomain.com', webOnlyWindowName: '_blank'); verify(mockWindow.open('mailto:name@mydomain.com', '_blank')); }); }); }); }); } ================================================ FILE: packages/url_launcher/url_launcher_web/example/integration_test/url_launcher_web_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in regular_integration_tests/integration_test/url_launcher_web_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i3; import 'dart:html' as _i2; import 'dart:math' as _i4; import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class class _FakeDocument_0 extends _i1.SmartFake implements _i2.Document { _FakeDocument_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeLocation_1 extends _i1.SmartFake implements _i2.Location { _FakeLocation_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeConsole_2 extends _i1.SmartFake implements _i2.Console { _FakeConsole_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeHistory_3 extends _i1.SmartFake implements _i2.History { _FakeHistory_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeStorage_4 extends _i1.SmartFake implements _i2.Storage { _FakeStorage_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeNavigator_5 extends _i1.SmartFake implements _i2.Navigator { _FakeNavigator_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePerformance_6 extends _i1.SmartFake implements _i2.Performance { _FakePerformance_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeEvents_7 extends _i1.SmartFake implements _i2.Events { _FakeEvents_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWindowBase_8 extends _i1.SmartFake implements _i2.WindowBase { _FakeWindowBase_8( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeFileSystem_9 extends _i1.SmartFake implements _i2.FileSystem { _FakeFileSystem_9( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeStylePropertyMapReadonly_10 extends _i1.SmartFake implements _i2.StylePropertyMapReadonly { _FakeStylePropertyMapReadonly_10( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeMediaQueryList_11 extends _i1.SmartFake implements _i2.MediaQueryList { _FakeMediaQueryList_11( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeEntry_12 extends _i1.SmartFake implements _i2.Entry { _FakeEntry_12( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeGeolocation_13 extends _i1.SmartFake implements _i2.Geolocation { _FakeGeolocation_13( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeMediaStream_14 extends _i1.SmartFake implements _i2.MediaStream { _FakeMediaStream_14( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeRelatedApplication_15 extends _i1.SmartFake implements _i2.RelatedApplication { _FakeRelatedApplication_15( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [Window]. /// /// See the documentation for Mockito's code generation for more information. class MockWindow extends _i1.Mock implements _i2.Window { MockWindow() { _i1.throwOnMissingStub(this); } @override _i3.Future get animationFrame => (super.noSuchMethod( Invocation.getter(#animationFrame), returnValue: _i3.Future.value(0), ) as _i3.Future); @override _i2.Document get document => (super.noSuchMethod( Invocation.getter(#document), returnValue: _FakeDocument_0( this, Invocation.getter(#document), ), ) as _i2.Document); @override _i2.Location get location => (super.noSuchMethod( Invocation.getter(#location), returnValue: _FakeLocation_1( this, Invocation.getter(#location), ), ) as _i2.Location); @override set location(_i2.LocationBase? value) => super.noSuchMethod( Invocation.setter( #location, value, ), returnValueForMissingStub: null, ); @override _i2.Console get console => (super.noSuchMethod( Invocation.getter(#console), returnValue: _FakeConsole_2( this, Invocation.getter(#console), ), ) as _i2.Console); @override set defaultStatus(String? value) => super.noSuchMethod( Invocation.setter( #defaultStatus, value, ), returnValueForMissingStub: null, ); @override set defaultstatus(String? value) => super.noSuchMethod( Invocation.setter( #defaultstatus, value, ), returnValueForMissingStub: null, ); @override num get devicePixelRatio => (super.noSuchMethod( Invocation.getter(#devicePixelRatio), returnValue: 0, ) as num); @override _i2.History get history => (super.noSuchMethod( Invocation.getter(#history), returnValue: _FakeHistory_3( this, Invocation.getter(#history), ), ) as _i2.History); @override _i2.Storage get localStorage => (super.noSuchMethod( Invocation.getter(#localStorage), returnValue: _FakeStorage_4( this, Invocation.getter(#localStorage), ), ) as _i2.Storage); @override set name(String? value) => super.noSuchMethod( Invocation.setter( #name, value, ), returnValueForMissingStub: null, ); @override _i2.Navigator get navigator => (super.noSuchMethod( Invocation.getter(#navigator), returnValue: _FakeNavigator_5( this, Invocation.getter(#navigator), ), ) as _i2.Navigator); @override set opener(_i2.WindowBase? value) => super.noSuchMethod( Invocation.setter( #opener, value, ), returnValueForMissingStub: null, ); @override int get outerHeight => (super.noSuchMethod( Invocation.getter(#outerHeight), returnValue: 0, ) as int); @override int get outerWidth => (super.noSuchMethod( Invocation.getter(#outerWidth), returnValue: 0, ) as int); @override _i2.Performance get performance => (super.noSuchMethod( Invocation.getter(#performance), returnValue: _FakePerformance_6( this, Invocation.getter(#performance), ), ) as _i2.Performance); @override _i2.Storage get sessionStorage => (super.noSuchMethod( Invocation.getter(#sessionStorage), returnValue: _FakeStorage_4( this, Invocation.getter(#sessionStorage), ), ) as _i2.Storage); @override set status(String? value) => super.noSuchMethod( Invocation.setter( #status, value, ), returnValueForMissingStub: null, ); @override _i3.Stream<_i2.Event> get onContentLoaded => (super.noSuchMethod( Invocation.getter(#onContentLoaded), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onAbort => (super.noSuchMethod( Invocation.getter(#onAbort), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onBlur => (super.noSuchMethod( Invocation.getter(#onBlur), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onCanPlay => (super.noSuchMethod( Invocation.getter(#onCanPlay), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onCanPlayThrough => (super.noSuchMethod( Invocation.getter(#onCanPlayThrough), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onChange => (super.noSuchMethod( Invocation.getter(#onChange), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.MouseEvent> get onClick => (super.noSuchMethod( Invocation.getter(#onClick), returnValue: _i3.Stream<_i2.MouseEvent>.empty(), ) as _i3.Stream<_i2.MouseEvent>); @override _i3.Stream<_i2.MouseEvent> get onContextMenu => (super.noSuchMethod( Invocation.getter(#onContextMenu), returnValue: _i3.Stream<_i2.MouseEvent>.empty(), ) as _i3.Stream<_i2.MouseEvent>); @override _i3.Stream<_i2.Event> get onDoubleClick => (super.noSuchMethod( Invocation.getter(#onDoubleClick), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.DeviceMotionEvent> get onDeviceMotion => (super.noSuchMethod( Invocation.getter(#onDeviceMotion), returnValue: _i3.Stream<_i2.DeviceMotionEvent>.empty(), ) as _i3.Stream<_i2.DeviceMotionEvent>); @override _i3.Stream<_i2.DeviceOrientationEvent> get onDeviceOrientation => (super.noSuchMethod( Invocation.getter(#onDeviceOrientation), returnValue: _i3.Stream<_i2.DeviceOrientationEvent>.empty(), ) as _i3.Stream<_i2.DeviceOrientationEvent>); @override _i3.Stream<_i2.MouseEvent> get onDrag => (super.noSuchMethod( Invocation.getter(#onDrag), returnValue: _i3.Stream<_i2.MouseEvent>.empty(), ) as _i3.Stream<_i2.MouseEvent>); @override _i3.Stream<_i2.MouseEvent> get onDragEnd => (super.noSuchMethod( Invocation.getter(#onDragEnd), returnValue: _i3.Stream<_i2.MouseEvent>.empty(), ) as _i3.Stream<_i2.MouseEvent>); @override _i3.Stream<_i2.MouseEvent> get onDragEnter => (super.noSuchMethod( Invocation.getter(#onDragEnter), returnValue: _i3.Stream<_i2.MouseEvent>.empty(), ) as _i3.Stream<_i2.MouseEvent>); @override _i3.Stream<_i2.MouseEvent> get onDragLeave => (super.noSuchMethod( Invocation.getter(#onDragLeave), returnValue: _i3.Stream<_i2.MouseEvent>.empty(), ) as _i3.Stream<_i2.MouseEvent>); @override _i3.Stream<_i2.MouseEvent> get onDragOver => (super.noSuchMethod( Invocation.getter(#onDragOver), returnValue: _i3.Stream<_i2.MouseEvent>.empty(), ) as _i3.Stream<_i2.MouseEvent>); @override _i3.Stream<_i2.MouseEvent> get onDragStart => (super.noSuchMethod( Invocation.getter(#onDragStart), returnValue: _i3.Stream<_i2.MouseEvent>.empty(), ) as _i3.Stream<_i2.MouseEvent>); @override _i3.Stream<_i2.MouseEvent> get onDrop => (super.noSuchMethod( Invocation.getter(#onDrop), returnValue: _i3.Stream<_i2.MouseEvent>.empty(), ) as _i3.Stream<_i2.MouseEvent>); @override _i3.Stream<_i2.Event> get onDurationChange => (super.noSuchMethod( Invocation.getter(#onDurationChange), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onEmptied => (super.noSuchMethod( Invocation.getter(#onEmptied), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onEnded => (super.noSuchMethod( Invocation.getter(#onEnded), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onError => (super.noSuchMethod( Invocation.getter(#onError), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onFocus => (super.noSuchMethod( Invocation.getter(#onFocus), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onHashChange => (super.noSuchMethod( Invocation.getter(#onHashChange), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onInput => (super.noSuchMethod( Invocation.getter(#onInput), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onInvalid => (super.noSuchMethod( Invocation.getter(#onInvalid), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.KeyboardEvent> get onKeyDown => (super.noSuchMethod( Invocation.getter(#onKeyDown), returnValue: _i3.Stream<_i2.KeyboardEvent>.empty(), ) as _i3.Stream<_i2.KeyboardEvent>); @override _i3.Stream<_i2.KeyboardEvent> get onKeyPress => (super.noSuchMethod( Invocation.getter(#onKeyPress), returnValue: _i3.Stream<_i2.KeyboardEvent>.empty(), ) as _i3.Stream<_i2.KeyboardEvent>); @override _i3.Stream<_i2.KeyboardEvent> get onKeyUp => (super.noSuchMethod( Invocation.getter(#onKeyUp), returnValue: _i3.Stream<_i2.KeyboardEvent>.empty(), ) as _i3.Stream<_i2.KeyboardEvent>); @override _i3.Stream<_i2.Event> get onLoad => (super.noSuchMethod( Invocation.getter(#onLoad), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onLoadedData => (super.noSuchMethod( Invocation.getter(#onLoadedData), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onLoadedMetadata => (super.noSuchMethod( Invocation.getter(#onLoadedMetadata), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onLoadStart => (super.noSuchMethod( Invocation.getter(#onLoadStart), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.MessageEvent> get onMessage => (super.noSuchMethod( Invocation.getter(#onMessage), returnValue: _i3.Stream<_i2.MessageEvent>.empty(), ) as _i3.Stream<_i2.MessageEvent>); @override _i3.Stream<_i2.MouseEvent> get onMouseDown => (super.noSuchMethod( Invocation.getter(#onMouseDown), returnValue: _i3.Stream<_i2.MouseEvent>.empty(), ) as _i3.Stream<_i2.MouseEvent>); @override _i3.Stream<_i2.MouseEvent> get onMouseEnter => (super.noSuchMethod( Invocation.getter(#onMouseEnter), returnValue: _i3.Stream<_i2.MouseEvent>.empty(), ) as _i3.Stream<_i2.MouseEvent>); @override _i3.Stream<_i2.MouseEvent> get onMouseLeave => (super.noSuchMethod( Invocation.getter(#onMouseLeave), returnValue: _i3.Stream<_i2.MouseEvent>.empty(), ) as _i3.Stream<_i2.MouseEvent>); @override _i3.Stream<_i2.MouseEvent> get onMouseMove => (super.noSuchMethod( Invocation.getter(#onMouseMove), returnValue: _i3.Stream<_i2.MouseEvent>.empty(), ) as _i3.Stream<_i2.MouseEvent>); @override _i3.Stream<_i2.MouseEvent> get onMouseOut => (super.noSuchMethod( Invocation.getter(#onMouseOut), returnValue: _i3.Stream<_i2.MouseEvent>.empty(), ) as _i3.Stream<_i2.MouseEvent>); @override _i3.Stream<_i2.MouseEvent> get onMouseOver => (super.noSuchMethod( Invocation.getter(#onMouseOver), returnValue: _i3.Stream<_i2.MouseEvent>.empty(), ) as _i3.Stream<_i2.MouseEvent>); @override _i3.Stream<_i2.MouseEvent> get onMouseUp => (super.noSuchMethod( Invocation.getter(#onMouseUp), returnValue: _i3.Stream<_i2.MouseEvent>.empty(), ) as _i3.Stream<_i2.MouseEvent>); @override _i3.Stream<_i2.WheelEvent> get onMouseWheel => (super.noSuchMethod( Invocation.getter(#onMouseWheel), returnValue: _i3.Stream<_i2.WheelEvent>.empty(), ) as _i3.Stream<_i2.WheelEvent>); @override _i3.Stream<_i2.Event> get onOffline => (super.noSuchMethod( Invocation.getter(#onOffline), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onOnline => (super.noSuchMethod( Invocation.getter(#onOnline), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onPageHide => (super.noSuchMethod( Invocation.getter(#onPageHide), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onPageShow => (super.noSuchMethod( Invocation.getter(#onPageShow), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onPause => (super.noSuchMethod( Invocation.getter(#onPause), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onPlay => (super.noSuchMethod( Invocation.getter(#onPlay), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onPlaying => (super.noSuchMethod( Invocation.getter(#onPlaying), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.PopStateEvent> get onPopState => (super.noSuchMethod( Invocation.getter(#onPopState), returnValue: _i3.Stream<_i2.PopStateEvent>.empty(), ) as _i3.Stream<_i2.PopStateEvent>); @override _i3.Stream<_i2.Event> get onProgress => (super.noSuchMethod( Invocation.getter(#onProgress), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onRateChange => (super.noSuchMethod( Invocation.getter(#onRateChange), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onReset => (super.noSuchMethod( Invocation.getter(#onReset), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onResize => (super.noSuchMethod( Invocation.getter(#onResize), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onScroll => (super.noSuchMethod( Invocation.getter(#onScroll), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onSearch => (super.noSuchMethod( Invocation.getter(#onSearch), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onSeeked => (super.noSuchMethod( Invocation.getter(#onSeeked), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onSeeking => (super.noSuchMethod( Invocation.getter(#onSeeking), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onSelect => (super.noSuchMethod( Invocation.getter(#onSelect), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onStalled => (super.noSuchMethod( Invocation.getter(#onStalled), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.StorageEvent> get onStorage => (super.noSuchMethod( Invocation.getter(#onStorage), returnValue: _i3.Stream<_i2.StorageEvent>.empty(), ) as _i3.Stream<_i2.StorageEvent>); @override _i3.Stream<_i2.Event> get onSubmit => (super.noSuchMethod( Invocation.getter(#onSubmit), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onSuspend => (super.noSuchMethod( Invocation.getter(#onSuspend), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onTimeUpdate => (super.noSuchMethod( Invocation.getter(#onTimeUpdate), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.TouchEvent> get onTouchCancel => (super.noSuchMethod( Invocation.getter(#onTouchCancel), returnValue: _i3.Stream<_i2.TouchEvent>.empty(), ) as _i3.Stream<_i2.TouchEvent>); @override _i3.Stream<_i2.TouchEvent> get onTouchEnd => (super.noSuchMethod( Invocation.getter(#onTouchEnd), returnValue: _i3.Stream<_i2.TouchEvent>.empty(), ) as _i3.Stream<_i2.TouchEvent>); @override _i3.Stream<_i2.TouchEvent> get onTouchMove => (super.noSuchMethod( Invocation.getter(#onTouchMove), returnValue: _i3.Stream<_i2.TouchEvent>.empty(), ) as _i3.Stream<_i2.TouchEvent>); @override _i3.Stream<_i2.TouchEvent> get onTouchStart => (super.noSuchMethod( Invocation.getter(#onTouchStart), returnValue: _i3.Stream<_i2.TouchEvent>.empty(), ) as _i3.Stream<_i2.TouchEvent>); @override _i3.Stream<_i2.TransitionEvent> get onTransitionEnd => (super.noSuchMethod( Invocation.getter(#onTransitionEnd), returnValue: _i3.Stream<_i2.TransitionEvent>.empty(), ) as _i3.Stream<_i2.TransitionEvent>); @override _i3.Stream<_i2.Event> get onUnload => (super.noSuchMethod( Invocation.getter(#onUnload), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onVolumeChange => (super.noSuchMethod( Invocation.getter(#onVolumeChange), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.Event> get onWaiting => (super.noSuchMethod( Invocation.getter(#onWaiting), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.AnimationEvent> get onAnimationEnd => (super.noSuchMethod( Invocation.getter(#onAnimationEnd), returnValue: _i3.Stream<_i2.AnimationEvent>.empty(), ) as _i3.Stream<_i2.AnimationEvent>); @override _i3.Stream<_i2.AnimationEvent> get onAnimationIteration => (super.noSuchMethod( Invocation.getter(#onAnimationIteration), returnValue: _i3.Stream<_i2.AnimationEvent>.empty(), ) as _i3.Stream<_i2.AnimationEvent>); @override _i3.Stream<_i2.AnimationEvent> get onAnimationStart => (super.noSuchMethod( Invocation.getter(#onAnimationStart), returnValue: _i3.Stream<_i2.AnimationEvent>.empty(), ) as _i3.Stream<_i2.AnimationEvent>); @override _i3.Stream<_i2.Event> get onBeforeUnload => (super.noSuchMethod( Invocation.getter(#onBeforeUnload), returnValue: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.WheelEvent> get onWheel => (super.noSuchMethod( Invocation.getter(#onWheel), returnValue: _i3.Stream<_i2.WheelEvent>.empty(), ) as _i3.Stream<_i2.WheelEvent>); @override int get pageXOffset => (super.noSuchMethod( Invocation.getter(#pageXOffset), returnValue: 0, ) as int); @override int get pageYOffset => (super.noSuchMethod( Invocation.getter(#pageYOffset), returnValue: 0, ) as int); @override int get scrollX => (super.noSuchMethod( Invocation.getter(#scrollX), returnValue: 0, ) as int); @override int get scrollY => (super.noSuchMethod( Invocation.getter(#scrollY), returnValue: 0, ) as int); @override _i2.Events get on => (super.noSuchMethod( Invocation.getter(#on), returnValue: _FakeEvents_7( this, Invocation.getter(#on), ), ) as _i2.Events); @override _i2.WindowBase open( String? url, String? name, [ String? options, ]) => (super.noSuchMethod( Invocation.method( #open, [ url, name, options, ], ), returnValue: _FakeWindowBase_8( this, Invocation.method( #open, [ url, name, options, ], ), ), ) as _i2.WindowBase); @override int requestAnimationFrame(_i2.FrameRequestCallback? callback) => (super.noSuchMethod( Invocation.method( #requestAnimationFrame, [callback], ), returnValue: 0, ) as int); @override void cancelAnimationFrame(int? id) => super.noSuchMethod( Invocation.method( #cancelAnimationFrame, [id], ), returnValueForMissingStub: null, ); @override _i3.Future<_i2.FileSystem> requestFileSystem( int? size, { bool? persistent = false, }) => (super.noSuchMethod( Invocation.method( #requestFileSystem, [size], {#persistent: persistent}, ), returnValue: _i3.Future<_i2.FileSystem>.value(_FakeFileSystem_9( this, Invocation.method( #requestFileSystem, [size], {#persistent: persistent}, ), )), ) as _i3.Future<_i2.FileSystem>); @override void alert([String? message]) => super.noSuchMethod( Invocation.method( #alert, [message], ), returnValueForMissingStub: null, ); @override void cancelIdleCallback(int? handle) => super.noSuchMethod( Invocation.method( #cancelIdleCallback, [handle], ), returnValueForMissingStub: null, ); @override void close() => super.noSuchMethod( Invocation.method( #close, [], ), returnValueForMissingStub: null, ); @override bool confirm([String? message]) => (super.noSuchMethod( Invocation.method( #confirm, [message], ), returnValue: false, ) as bool); @override _i3.Future fetch( dynamic input, [ Map? init, ]) => (super.noSuchMethod( Invocation.method( #fetch, [ input, init, ], ), returnValue: _i3.Future.value(), ) as _i3.Future); @override bool find( String? string, bool? caseSensitive, bool? backwards, bool? wrap, bool? wholeWord, bool? searchInFrames, bool? showDialog, ) => (super.noSuchMethod( Invocation.method( #find, [ string, caseSensitive, backwards, wrap, wholeWord, searchInFrames, showDialog, ], ), returnValue: false, ) as bool); @override _i2.StylePropertyMapReadonly getComputedStyleMap( _i2.Element? element, String? pseudoElement, ) => (super.noSuchMethod( Invocation.method( #getComputedStyleMap, [ element, pseudoElement, ], ), returnValue: _FakeStylePropertyMapReadonly_10( this, Invocation.method( #getComputedStyleMap, [ element, pseudoElement, ], ), ), ) as _i2.StylePropertyMapReadonly); @override List<_i2.CssRule> getMatchedCssRules( _i2.Element? element, String? pseudoElement, ) => (super.noSuchMethod( Invocation.method( #getMatchedCssRules, [ element, pseudoElement, ], ), returnValue: <_i2.CssRule>[], ) as List<_i2.CssRule>); @override _i2.MediaQueryList matchMedia(String? query) => (super.noSuchMethod( Invocation.method( #matchMedia, [query], ), returnValue: _FakeMediaQueryList_11( this, Invocation.method( #matchMedia, [query], ), ), ) as _i2.MediaQueryList); @override void moveBy( int? x, int? y, ) => super.noSuchMethod( Invocation.method( #moveBy, [ x, y, ], ), returnValueForMissingStub: null, ); @override void postMessage( dynamic message, String? targetOrigin, [ List? transfer, ]) => super.noSuchMethod( Invocation.method( #postMessage, [ message, targetOrigin, transfer, ], ), returnValueForMissingStub: null, ); @override void print() => super.noSuchMethod( Invocation.method( #print, [], ), returnValueForMissingStub: null, ); @override int requestIdleCallback( _i2.IdleRequestCallback? callback, [ Map? options, ]) => (super.noSuchMethod( Invocation.method( #requestIdleCallback, [ callback, options, ], ), returnValue: 0, ) as int); @override void resizeBy( int? x, int? y, ) => super.noSuchMethod( Invocation.method( #resizeBy, [ x, y, ], ), returnValueForMissingStub: null, ); @override void resizeTo( int? x, int? y, ) => super.noSuchMethod( Invocation.method( #resizeTo, [ x, y, ], ), returnValueForMissingStub: null, ); @override void scroll([ dynamic options_OR_x, dynamic y, Map? scrollOptions, ]) => super.noSuchMethod( Invocation.method( #scroll, [ options_OR_x, y, scrollOptions, ], ), returnValueForMissingStub: null, ); @override void scrollBy([ dynamic options_OR_x, dynamic y, Map? scrollOptions, ]) => super.noSuchMethod( Invocation.method( #scrollBy, [ options_OR_x, y, scrollOptions, ], ), returnValueForMissingStub: null, ); @override void scrollTo([ dynamic options_OR_x, dynamic y, Map? scrollOptions, ]) => super.noSuchMethod( Invocation.method( #scrollTo, [ options_OR_x, y, scrollOptions, ], ), returnValueForMissingStub: null, ); @override void stop() => super.noSuchMethod( Invocation.method( #stop, [], ), returnValueForMissingStub: null, ); @override _i3.Future<_i2.Entry> resolveLocalFileSystemUrl(String? url) => (super.noSuchMethod( Invocation.method( #resolveLocalFileSystemUrl, [url], ), returnValue: _i3.Future<_i2.Entry>.value(_FakeEntry_12( this, Invocation.method( #resolveLocalFileSystemUrl, [url], ), )), ) as _i3.Future<_i2.Entry>); @override String atob(String? atob) => (super.noSuchMethod( Invocation.method( #atob, [atob], ), returnValue: '', ) as String); @override String btoa(String? btoa) => (super.noSuchMethod( Invocation.method( #btoa, [btoa], ), returnValue: '', ) as String); @override void moveTo(_i4.Point? p) => super.noSuchMethod( Invocation.method( #moveTo, [p], ), returnValueForMissingStub: null, ); @override void addEventListener( String? type, _i2.EventListener? listener, [ bool? useCapture, ]) => super.noSuchMethod( Invocation.method( #addEventListener, [ type, listener, useCapture, ], ), returnValueForMissingStub: null, ); @override void removeEventListener( String? type, _i2.EventListener? listener, [ bool? useCapture, ]) => super.noSuchMethod( Invocation.method( #removeEventListener, [ type, listener, useCapture, ], ), returnValueForMissingStub: null, ); @override bool dispatchEvent(_i2.Event? event) => (super.noSuchMethod( Invocation.method( #dispatchEvent, [event], ), returnValue: false, ) as bool); } /// A class which mocks [Navigator]. /// /// See the documentation for Mockito's code generation for more information. class MockNavigator extends _i1.Mock implements _i2.Navigator { MockNavigator() { _i1.throwOnMissingStub(this); } @override String get language => (super.noSuchMethod( Invocation.getter(#language), returnValue: '', ) as String); @override _i2.Geolocation get geolocation => (super.noSuchMethod( Invocation.getter(#geolocation), returnValue: _FakeGeolocation_13( this, Invocation.getter(#geolocation), ), ) as _i2.Geolocation); @override String get vendor => (super.noSuchMethod( Invocation.getter(#vendor), returnValue: '', ) as String); @override String get vendorSub => (super.noSuchMethod( Invocation.getter(#vendorSub), returnValue: '', ) as String); @override String get appCodeName => (super.noSuchMethod( Invocation.getter(#appCodeName), returnValue: '', ) as String); @override String get appName => (super.noSuchMethod( Invocation.getter(#appName), returnValue: '', ) as String); @override String get appVersion => (super.noSuchMethod( Invocation.getter(#appVersion), returnValue: '', ) as String); @override String get product => (super.noSuchMethod( Invocation.getter(#product), returnValue: '', ) as String); @override String get userAgent => (super.noSuchMethod( Invocation.getter(#userAgent), returnValue: '', ) as String); @override List<_i2.Gamepad?> getGamepads() => (super.noSuchMethod( Invocation.method( #getGamepads, [], ), returnValue: <_i2.Gamepad?>[], ) as List<_i2.Gamepad?>); @override _i3.Future<_i2.MediaStream> getUserMedia({ dynamic audio = false, dynamic video = false, }) => (super.noSuchMethod( Invocation.method( #getUserMedia, [], { #audio: audio, #video: video, }, ), returnValue: _i3.Future<_i2.MediaStream>.value(_FakeMediaStream_14( this, Invocation.method( #getUserMedia, [], { #audio: audio, #video: video, }, ), )), ) as _i3.Future<_i2.MediaStream>); @override void cancelKeyboardLock() => super.noSuchMethod( Invocation.method( #cancelKeyboardLock, [], ), returnValueForMissingStub: null, ); @override _i3.Future getBattery() => (super.noSuchMethod( Invocation.method( #getBattery, [], ), returnValue: _i3.Future.value(), ) as _i3.Future); @override _i3.Future<_i2.RelatedApplication> getInstalledRelatedApps() => (super.noSuchMethod( Invocation.method( #getInstalledRelatedApps, [], ), returnValue: _i3.Future<_i2.RelatedApplication>.value(_FakeRelatedApplication_15( this, Invocation.method( #getInstalledRelatedApps, [], ), )), ) as _i3.Future<_i2.RelatedApplication>); @override _i3.Future getVRDisplays() => (super.noSuchMethod( Invocation.method( #getVRDisplays, [], ), returnValue: _i3.Future.value(), ) as _i3.Future); @override void registerProtocolHandler( String? scheme, String? url, String? title, ) => super.noSuchMethod( Invocation.method( #registerProtocolHandler, [ scheme, url, title, ], ), returnValueForMissingStub: null, ); @override _i3.Future requestKeyboardLock([List? keyCodes]) => (super.noSuchMethod( Invocation.method( #requestKeyboardLock, [keyCodes], ), returnValue: _i3.Future.value(), ) as _i3.Future); @override _i3.Future requestMidiAccess([Map? options]) => (super.noSuchMethod( Invocation.method( #requestMidiAccess, [options], ), returnValue: _i3.Future.value(), ) as _i3.Future); @override _i3.Future requestMediaKeySystemAccess( String? keySystem, List>? supportedConfigurations, ) => (super.noSuchMethod( Invocation.method( #requestMediaKeySystemAccess, [ keySystem, supportedConfigurations, ], ), returnValue: _i3.Future.value(), ) as _i3.Future); @override bool sendBeacon( String? url, Object? data, ) => (super.noSuchMethod( Invocation.method( #sendBeacon, [ url, data, ], ), returnValue: false, ) as bool); @override _i3.Future share([Map? data]) => (super.noSuchMethod( Invocation.method( #share, [data], ), returnValue: _i3.Future.value(), ) as _i3.Future); } ================================================ FILE: packages/url_launcher/url_launcher_web/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } /// App for testing class MyApp extends StatefulWidget { /// Default Constructor const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override Widget build(BuildContext context) { return const Directionality( textDirection: TextDirection.ltr, child: Text('Testing... Look at the console output for results!'), ); } } ================================================ FILE: packages/url_launcher/url_launcher_web/example/pubspec.yaml ================================================ name: regular_integration_tests publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter dev_dependencies: build_runner: ^2.1.1 flutter_driver: sdk: flutter flutter_test: sdk: flutter flutter_web_plugins: sdk: flutter integration_test: sdk: flutter mockito: ^5.3.2 url_launcher_platform_interface: ^2.0.3 url_launcher_web: path: ../ ================================================ FILE: packages/url_launcher/url_launcher_web/example/run_test.sh ================================================ #!/usr/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. if pgrep -lf chromedriver > /dev/null; then echo "chromedriver is running." flutter pub get echo "(Re)generating mocks." flutter pub run build_runner build --delete-conflicting-outputs if [ $# -eq 0 ]; then echo "No target specified, running all tests..." find integration_test/ -iname *_test.dart | xargs -n1 -i -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target='{}' else echo "Running test target: $1..." set -x flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target=$1 fi else echo "chromedriver is not running." echo "Please, check the README.md for instructions on how to use run_test.sh" fi ================================================ FILE: packages/url_launcher/url_launcher_web/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/url_launcher/url_launcher_web/example/web/index.html ================================================ Browser Tests ================================================ FILE: packages/url_launcher/url_launcher_web/lib/src/link.dart ================================================ // Copyright 2013 The Flutter 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 'dart:html' as html; import 'dart:js_util'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart' show urlStrategy; import 'package:url_launcher_platform_interface/link.dart'; /// The unique identifier for the view type to be used for link platform views. const String linkViewType = '__url_launcher::link'; /// The name of the property used to set the viewId on the DOM element. const String linkViewIdProperty = '__url_launcher::link::viewId'; /// Signature for a function that takes a unique [id] and creates an HTML element. typedef HtmlViewFactory = html.Element Function(int viewId); /// Factory that returns the link DOM element for each unique view id. HtmlViewFactory get linkViewFactory => LinkViewController._viewFactory; /// The delegate for building the [Link] widget on the web. /// /// It uses a platform view to render an anchor element in the DOM. class WebLinkDelegate extends StatefulWidget { /// Creates a delegate for the given [link]. const WebLinkDelegate(this.link, {Key? key}) : super(key: key); /// Information about the link built by the app. final LinkInfo link; @override WebLinkDelegateState createState() => WebLinkDelegateState(); } /// The link delegate used on the web platform. /// /// For external URIs, it lets the browser do its thing. For app route names, it /// pushes the route name to the framework. class WebLinkDelegateState extends State { late LinkViewController _controller; @override void didUpdateWidget(WebLinkDelegate oldWidget) { super.didUpdateWidget(oldWidget); if (widget.link.uri != oldWidget.link.uri) { _controller.setUri(widget.link.uri); } if (widget.link.target != oldWidget.link.target) { _controller.setTarget(widget.link.target); } } Future _followLink() { LinkViewController.registerHitTest(_controller); return Future.value(); } @override Widget build(BuildContext context) { return Stack( fit: StackFit.passthrough, children: [ widget.link.builder( context, widget.link.isDisabled ? null : _followLink, ), Positioned.fill( child: PlatformViewLink( viewType: linkViewType, onCreatePlatformView: (PlatformViewCreationParams params) { _controller = LinkViewController.fromParams(params); return _controller ..setUri(widget.link.uri) ..setTarget(widget.link.target); }, surfaceFactory: (BuildContext context, PlatformViewController controller) { return PlatformViewSurface( controller: controller, gestureRecognizers: const < Factory>{}, hitTestBehavior: PlatformViewHitTestBehavior.transparent, ); }, ), ), ], ); } } /// Controls link views. class LinkViewController extends PlatformViewController { /// Creates a [LinkViewController] instance with the unique [viewId]. LinkViewController(this.viewId) { if (_instances.isEmpty) { // This is the first controller being created, attach the global click // listener. _clickSubscription = html.window.onClick.listen(_onGlobalClick); } _instances[viewId] = this; } /// Creates and initializes a [LinkViewController] instance with the given /// platform view [params]. factory LinkViewController.fromParams( PlatformViewCreationParams params, ) { final int viewId = params.id; final LinkViewController controller = LinkViewController(viewId); controller._initialize().then((_) { /// Because _initialize is async, it can happen that [LinkViewController.dispose] /// may get called before this `then` callback. /// Check that the `controller` that was created by this factory is not /// disposed before calling `onPlatformViewCreated`. if (_instances[viewId] == controller) { params.onPlatformViewCreated(viewId); } }); return controller; } static final Map _instances = {}; static html.Element _viewFactory(int viewId) { return _instances[viewId]!._element; } static int? _hitTestedViewId; static late StreamSubscription _clickSubscription; static void _onGlobalClick(html.MouseEvent event) { final int? viewId = getViewIdFromTarget(event); _instances[viewId]?._onDomClick(event); // After the DOM click event has been received, clean up the hit test state // so we can start fresh on the next click. unregisterHitTest(); } /// Call this method to indicate that a hit test has been registered for the /// given [controller]. /// /// The [onClick] callback is invoked when the anchor element receives a /// `click` from the browser. static void registerHitTest(LinkViewController controller) { _hitTestedViewId = controller.viewId; } /// Removes all information about previously registered hit tests. static void unregisterHitTest() { _hitTestedViewId = null; } @override final int viewId; late html.Element _element; bool get _isInitialized => _element != null; Future _initialize() async { _element = html.Element.tag('a'); setProperty(_element, linkViewIdProperty, viewId); _element.style ..opacity = '0' ..display = 'block' ..width = '100%' ..height = '100%' ..cursor = 'unset'; // This is recommended on MDN: // - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-target _element.setAttribute('rel', 'noreferrer noopener'); final Map args = { 'id': viewId, 'viewType': linkViewType, }; await SystemChannels.platform_views.invokeMethod('create', args); } void _onDomClick(html.MouseEvent event) { final bool isHitTested = _hitTestedViewId == viewId; if (!isHitTested) { // There was no hit test registered for this click. This means the click // landed on the anchor element but not on the underlying widget. In this // case, we prevent the browser from following the click. event.preventDefault(); return; } if (_uri != null && _uri!.hasScheme) { // External links will be handled by the browser, so we don't have to do // anything. return; } // A uri that doesn't have a scheme is an internal route name. In this // case, we push it via Flutter's navigation system instead of letting the // browser handle it. event.preventDefault(); final String routeName = _uri.toString(); pushRouteNameToFramework(null, routeName); } Uri? _uri; /// Set the [Uri] value for this link. /// /// When Uri is null, the `href` attribute of the link is removed. void setUri(Uri? uri) { assert(_isInitialized); _uri = uri; if (uri == null) { _element.removeAttribute('href'); } else { String href = uri.toString(); // in case an internal uri is given, the url mus be properly encoded // using the currently used [UrlStrategy] if (!uri.hasScheme) { href = urlStrategy?.prepareExternalUrl(href) ?? href; } _element.setAttribute('href', href); } } /// Set the [LinkTarget] value for this link. void setTarget(LinkTarget target) { assert(_isInitialized); _element.setAttribute('target', _getHtmlTarget(target)); } String _getHtmlTarget(LinkTarget target) { switch (target) { case LinkTarget.defaultTarget: case LinkTarget.self: return '_self'; case LinkTarget.blank: return '_blank'; } // 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 Future clearFocus() async { // Currently this does nothing on Flutter Web. // TODO(het): Implement this. See https://github.com/flutter/flutter/issues/39496 } @override Future dispatchPointerEvent(PointerEvent event) async { // We do not dispatch pointer events to HTML views because they may contain // cross-origin iframes, which only accept user-generated events. } @override Future dispose() async { if (_isInitialized) { assert(_instances[viewId] == this); _instances.remove(viewId); if (_instances.isEmpty) { await _clickSubscription.cancel(); } await SystemChannels.platform_views.invokeMethod('dispose', viewId); } } } /// Finds the view id of the DOM element targeted by the [event]. int? getViewIdFromTarget(html.Event event) { final html.Element? linkElement = getLinkElementFromTarget(event); if (linkElement != null) { // TODO(stuartmorgan): Remove this ignore (and change to getProperty) // once the templated version is available on stable. On master (2.8) this // is already not necessary. // ignore: return_of_invalid_type return getProperty(linkElement, linkViewIdProperty); } return null; } /// Finds the targeted DOM element by the [event]. /// /// It handles the case where the target element is inside a shadow DOM too. html.Element? getLinkElementFromTarget(html.Event event) { final html.EventTarget? target = event.target; if (target != null && target is html.Element) { if (isLinkElement(target)) { return target; } if (target.shadowRoot != null) { final html.Node? child = target.shadowRoot!.lastChild; if (child != null && child is html.Element && isLinkElement(child)) { return child; } } } return null; } /// Checks if the given [element] is a link that was created by /// [LinkViewController]. bool isLinkElement(html.Element? element) { return element != null && element.tagName == 'A' && hasProperty(element, linkViewIdProperty); } ================================================ FILE: packages/url_launcher/url_launcher_web/lib/src/shims/dart_ui.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// This file shims dart:ui in web-only scenarios, getting rid of the need to /// suppress analyzer warnings. // TODO(ditman): Remove this file once web-only dart:ui APIs // are exposed from a dedicated place, flutter/flutter#55000. export 'dart_ui_fake.dart' if (dart.library.html) 'dart_ui_real.dart'; ================================================ FILE: packages/url_launcher/url_launcher_web/lib/src/shims/dart_ui_fake.dart ================================================ // Copyright 2013 The Flutter 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:html' as html; // Fake interface for the logic that this package needs from (web-only) dart:ui. // This is conditionally exported so the analyzer sees these methods as available. // ignore_for_file: avoid_classes_with_only_static_members // ignore_for_file: camel_case_types /// Shim for web_ui engine.PlatformViewRegistry /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/ui.dart#L62 class platformViewRegistry { /// Shim for registerViewFactory /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/ui.dart#L72 static bool registerViewFactory( String viewTypeId, html.Element Function(int viewId) viewFactory, {bool isVisible = true}) { return false; } } /// Shim for web_ui engine.AssetManager. /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/src/engine/assets.dart#L12 class webOnlyAssetManager { /// Shim for getAssetUrl. /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/src/engine/assets.dart#L45 static String getAssetUrl(String asset) => ''; } /// Signature of callbacks that have no arguments and return no data. typedef VoidCallback = void Function(); ================================================ FILE: packages/url_launcher/url_launcher_web/lib/src/shims/dart_ui_real.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'dart:ui'; ================================================ FILE: packages/url_launcher/url_launcher_web/lib/src/third_party/platform_detect/AUTHORS ================================================ # 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. 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> ================================================ FILE: packages/url_launcher/url_launcher_web/lib/src/third_party/platform_detect/LICENSE ================================================ Copyright 2017 Workiva Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: packages/url_launcher/url_launcher_web/lib/src/third_party/platform_detect/README.md ================================================ The code in this directory is a stripped down, and modified version of `package:platform_detect`. You can find the original file in Workiva's repository, here: * https://github.com/Workiva/platform_detect/blob/77d160f1c3be4e20dc085a094209e8cab4aec135/lib/src/browser.dart ================================================ FILE: packages/url_launcher/url_launcher_web/lib/src/third_party/platform_detect/browser.dart ================================================ // Copyright 2017 Workiva Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ////////////////////////////////////////////////////////// // // This file is a stripped down, and slightly modified version of // package:platform_detect's. // // Original version here: https://github.com/Workiva/platform_detect // // ////////////////////////////////////////////////////////// import 'dart:html' as html show Navigator; /// Determines if the `navigator` is Safari. bool navigatorIsSafari(html.Navigator navigator) { // An web view running in an iOS app does not have a 'Version/X.X.X' string in the appVersion final String vendor = navigator.vendor; final String appVersion = navigator.appVersion; return vendor != null && vendor.contains('Apple') && appVersion != null && appVersion.contains('Version'); } ================================================ FILE: packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart ================================================ // Copyright 2013 The Flutter 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 'dart:html' as html; import 'package:flutter/foundation.dart' show visibleForTesting; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:url_launcher_platform_interface/link.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'src/link.dart'; import 'src/shims/dart_ui.dart' as ui; import 'src/third_party/platform_detect/browser.dart'; const Set _safariTargetTopSchemes = { 'mailto', 'tel', 'sms', }; String? _getUrlScheme(String url) => Uri.tryParse(url)?.scheme; bool _isSafariTargetTopScheme(String url) => _safariTargetTopSchemes.contains(_getUrlScheme(url)); /// The web implementation of [UrlLauncherPlatform]. /// /// This class implements the `package:url_launcher` functionality for the web. class UrlLauncherPlugin extends UrlLauncherPlatform { /// A constructor that allows tests to override the window object used by the plugin. UrlLauncherPlugin({@visibleForTesting html.Window? debugWindow}) : _window = debugWindow ?? html.window { _isSafari = navigatorIsSafari(_window.navigator); } final html.Window _window; bool _isSafari = false; // The set of schemes that can be handled by the plugin static final Set _supportedSchemes = { 'http', 'https', }.union(_safariTargetTopSchemes); /// Registers this class as the default instance of [UrlLauncherPlatform]. static void registerWith(Registrar registrar) { UrlLauncherPlatform.instance = UrlLauncherPlugin(); ui.platformViewRegistry .registerViewFactory(linkViewType, linkViewFactory, isVisible: false); } @override LinkDelegate get linkDelegate { return (LinkInfo linkInfo) => WebLinkDelegate(linkInfo); } /// Opens the given [url] in the specified [webOnlyWindowName]. /// /// Returns the newly created window. @visibleForTesting html.WindowBase openNewWindow(String url, {String? webOnlyWindowName}) { // We need to open mailto, tel and sms urls on the _top window context on safari browsers. // See https://github.com/flutter/flutter/issues/51461 for reference. final String target = webOnlyWindowName ?? ((_isSafari && _isSafariTargetTopScheme(url)) ? '_top' : ''); // ignore: unsafe_html return _window.open(url, target); } @override Future canLaunch(String url) { return Future.value(_supportedSchemes.contains(_getUrlScheme(url))); } @override Future launch( String url, { bool useSafariVC = false, bool useWebView = false, bool enableJavaScript = false, bool enableDomStorage = false, bool universalLinksOnly = false, Map headers = const {}, String? webOnlyWindowName, }) { return Future.value( openNewWindow(url, webOnlyWindowName: webOnlyWindowName) != null); } } ================================================ FILE: packages/url_launcher/url_launcher_web/pubspec.yaml ================================================ 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.14 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: url_launcher platforms: web: pluginClass: UrlLauncherPlugin fileName: url_launcher_web.dart dependencies: flutter: sdk: flutter flutter_web_plugins: sdk: flutter url_launcher_platform_interface: ^2.0.3 dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/url_launcher/url_launcher_web/test/README.md ================================================ ## test This package uses integration tests for testing. See `example/README.md` for more info. ================================================ FILE: packages/url_launcher/url_launcher_web/test/tests_exist_elsewhere_test.dart ================================================ // Copyright 2013 The Flutter 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: avoid_print import 'package:flutter_test/flutter_test.dart'; void main() { test('Tell the user where to find the real tests', () { print('---'); print('This package uses integration_test for its tests.'); print('See `example/README.md` for more info.'); print('---'); }); } ================================================ FILE: packages/url_launcher/url_launcher_windows/.gitignore ================================================ .packages .flutter-plugins pubspec.lock ================================================ FILE: packages/url_launcher/url_launcher_windows/.metadata ================================================ # 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: 6d1c244b79f3a2747281f718297ce248bd5ad099 channel: master project_type: plugin ================================================ FILE: packages/url_launcher/url_launcher_windows/AUTHORS ================================================ # 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> ================================================ FILE: packages/url_launcher/url_launcher_windows/CHANGELOG.md ================================================ ## 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 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 3.0.0 * Changes the major version since, due to a typo in `default_package` in existing versions of `url_launcher`, requiring Dart registration in this package is in practice a breaking change. * Does not include any API changes; clients can allow both 2.x or 3.x. ## 2.0.3 **\[Retracted\]** * Switches to an in-package method channel implementation. * Adds unit tests. * Updates code for new analysis options. ## 2.0.2 * Replaced reference to `shared_preferences` plugin with the `url_launcher` in the README. ## 2.0.1 * Updated installation instructions in README. ## 2.0.0 * Migrate to null-safety. * Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets. * Set `implementation` in pubspec.yaml ## 0.0.2+1 * Update Flutter SDK constraint. ## 0.0.2 * Update integration test examples to use `testWidgets` instead of `test`. ## 0.0.1+3 * Update Dart SDK constraint in example. ## 0.0.1+2 * Check in windows/ directory for example/ ## 0.0.1+1 * Update README to reflect endorsement. ## 0.0.1 * Initial Windows implementation of `url_launcher`. ================================================ FILE: packages/url_launcher/url_launcher_windows/LICENSE ================================================ 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. ================================================ FILE: packages/url_launcher/url_launcher_windows/README.md ================================================ # url\_launcher\_windows The Windows implementation of [`url_launcher`][1]. ## Usage This package is [endorsed][2], which means you can simply use `url_launcher` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/url_launcher [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/url_launcher/url_launcher_windows/example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies .packages .pub-cache/ .pub/ /build/ # Web related lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols # Obfuscation related app.*.map.json ================================================ FILE: packages/url_launcher/url_launcher_windows/example/.metadata ================================================ # 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: 6d1c244b79f3a2747281f718297ce248bd5ad099 channel: master project_type: app ================================================ FILE: packages/url_launcher/url_launcher_windows/example/README.md ================================================ # 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. ================================================ FILE: packages/url_launcher/url_launcher_windows/example/integration_test/url_launcher_test.dart ================================================ // Copyright 2013 The Flutter 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:url_launcher_platform_interface/url_launcher_platform_interface.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('canLaunch', (WidgetTester _) async { final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; expect(await launcher.canLaunch('randomstring'), false); // Generally all devices should have some default browser. expect(await launcher.canLaunch('http://flutter.dev'), true); }); } ================================================ FILE: packages/url_launcher/url_launcher_windows/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'package:flutter/material.dart'; import 'package:url_launcher_platform_interface/url_launcher_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: 'URL Launcher', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'URL Launcher'), ); } } 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 { Future? _launched; Future _launchInBrowser(String url) async { if (await UrlLauncherPlatform.instance.canLaunch(url)) { await UrlLauncherPlatform.instance.launch( url, useSafariVC: false, useWebView: false, enableJavaScript: false, enableDomStorage: false, universalLinksOnly: false, headers: {}, ); } else { throw Exception('Could not launch $url'); } } Widget _launchStatus(BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { return const Text(''); } } @override Widget build(BuildContext context) { const String toLaunch = 'https://www.cylog.org/headers/'; return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: ListView( children: [ Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Padding( padding: EdgeInsets.all(16.0), child: Text(toLaunch), ), ElevatedButton( onPressed: () => setState(() { _launched = _launchInBrowser(toLaunch); }), child: const Text('Launch in browser'), ), const Padding(padding: EdgeInsets.all(16.0)), FutureBuilder(future: _launched, builder: _launchStatus), ], ), ], ), ); } } ================================================ FILE: packages/url_launcher/url_launcher_windows/example/pubspec.yaml ================================================ name: url_launcher_example description: Demonstrates the Windows implementation of the url_launcher plugin. publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter url_launcher_platform_interface: ^2.0.0 url_launcher_windows: # When depending on this package from a real application you should use: # url_launcher_windows: ^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: ../ dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/url_launcher/url_launcher_windows/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/url_launcher/url_launcher_windows/example/windows/.gitignore ================================================ flutter/ephemeral/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: packages/url_launcher/url_launcher_windows/example/windows/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(example LANGUAGES CXX) set(BINARY_NAME "example") cmake_policy(SET CMP0063 NEW) set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") # Configure build options. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" CACHE STRING "" FORCE) else() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() endif() set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") # Use Unicode for all projects. add_definitions(-DUNICODE -D_UNICODE) # Compilation settings that should be applied to most targets. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") target_compile_options(${TARGET} PRIVATE /EHsc) target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") # Flutter library and tool build rules. add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build add_subdirectory("runner") # Enable the test target. set(include_url_launcher_windows_tests TRUE) # Provide an alias for the test target using the name expected by repo tooling. add_custom_target(unit_tests DEPENDS url_launcher_windows_test) # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake) # === Installation === # Support files are copied into place next to the executable, so that it can # run in place. This is done instead of making a separate bundle (as on Linux) # so that building and running from within Visual Studio will work. set(BUILD_BUNDLE_DIR "$") # Make the "install" step default, as it's required to run. set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) endif() set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") " COMPONENT Runtime) install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) # Install the AOT library on non-Debug builds only. install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" CONFIGURATIONS Profile;Release COMPONENT Runtime) ================================================ FILE: packages/url_launcher/url_launcher_windows/example/windows/flutter/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") # Configuration provided via flutter tool. include(${EPHEMERAL_DIR}/generated_config.cmake) # TODO: Move the rest of this into files in ephemeral. See # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") # Published to parent scope for install step. set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) list(APPEND FLUTTER_LIBRARY_HEADERS "flutter_export.h" "flutter_windows.h" "flutter_messenger.h" "flutter_plugin_registrar.h" ) list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") add_library(flutter INTERFACE) target_include_directories(flutter INTERFACE "${EPHEMERAL_DIR}" ) target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") add_dependencies(flutter flutter_assemble) # === Wrapper === list(APPEND CPP_WRAPPER_SOURCES_CORE "core_implementations.cc" "standard_codec.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_PLUGIN "plugin_registrar.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") list(APPEND CPP_WRAPPER_SOURCES_APP "flutter_engine.cc" "flutter_view_controller.cc" ) list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") # Wrapper sources needed for a plugin. add_library(flutter_wrapper_plugin STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ) apply_standard_settings(flutter_wrapper_plugin) set_target_properties(flutter_wrapper_plugin PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(flutter_wrapper_plugin PROPERTIES CXX_VISIBILITY_PRESET hidden) target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) target_include_directories(flutter_wrapper_plugin PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_plugin flutter_assemble) # Wrapper sources needed for the runner. add_library(flutter_wrapper_app STATIC ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_APP} ) apply_standard_settings(flutter_wrapper_app) target_link_libraries(flutter_wrapper_app PUBLIC flutter) target_include_directories(flutter_wrapper_app PUBLIC "${WRAPPER_ROOT}/include" ) add_dependencies(flutter_wrapper_app flutter_assemble) # === Flutter tool backend === # _phony_ is a non-existent file to force this command to run every time, # since currently there's no way to get a full input/output list from the # flutter tool. set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) add_custom_command( OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ${PHONY_OUTPUT} COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" windows-x64 $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS "${FLUTTER_LIBRARY}" ${FLUTTER_LIBRARY_HEADERS} ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} ${CPP_WRAPPER_SOURCES_APP} ) ================================================ FILE: packages/url_launcher/url_launcher_windows/example/windows/flutter/generated_plugins.cmake ================================================ # # Generated file, do not edit. # list(APPEND FLUTTER_PLUGIN_LIST url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST ) set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) endforeach(ffi_plugin) ================================================ FILE: packages/url_launcher/url_launcher_windows/example/windows/runner/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.15) project(runner LANGUAGES CXX) add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" "run_loop.cpp" "utils.cpp" "win32_window.cpp" "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" "Runner.rc" "runner.exe.manifest" ) apply_standard_settings(${BINARY_NAME}) target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") add_dependencies(${BINARY_NAME} flutter_assemble) ================================================ FILE: packages/url_launcher/url_launcher_windows/example/windows/runner/Runner.rc ================================================ // Microsoft Visual C++ generated resource script. // #pragma code_page(65001) #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "winres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (United States) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE BEGIN "resource.h\0" END 2 TEXTINCLUDE BEGIN "#include ""winres.h""\r\n" "\0" END 3 TEXTINCLUDE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APP_ICON ICON "resources\\app_icon.ico" ///////////////////////////////////////////////////////////////////////////// // // Version // #ifdef FLUTTER_BUILD_NUMBER #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER #else #define VERSION_AS_NUMBER 1,0,0 #endif #ifdef FLUTTER_BUILD_NAME #define VERSION_AS_STRING #FLUTTER_BUILD_NAME #else #define VERSION_AS_STRING "1.0.0" #endif VS_VERSION_INFO VERSIONINFO FILEVERSION VERSION_AS_NUMBER PRODUCTVERSION VERSION_AS_NUMBER FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG #else FILEFLAGS 0x0L #endif FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "com.example" "\0" VALUE "FileDescription", "A new Flutter project." "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "example" "\0" VALUE "LegalCopyright", "Copyright (C) 2020 com.example. All rights reserved." "\0" VALUE "OriginalFilename", "example.exe" "\0" VALUE "ProductName", "example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED ================================================ FILE: packages/url_launcher/url_launcher_windows/example/windows/runner/flutter_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "flutter_window.h" #include #include "flutter/generated_plugin_registrant.h" FlutterWindow::FlutterWindow(RunLoop* run_loop, const flutter::DartProject& project) : run_loop_(run_loop), project_(project) {} FlutterWindow::~FlutterWindow() {} bool FlutterWindow::OnCreate() { if (!Win32Window::OnCreate()) { return false; } RECT frame = GetClientArea(); // The size here must match the window dimensions to avoid unnecessary surface // creation / destruction in the startup path. flutter_controller_ = std::make_unique( frame.right - frame.left, frame.bottom - frame.top, project_); // Ensure that basic setup of the controller was successful. if (!flutter_controller_->engine() || !flutter_controller_->view()) { return false; } RegisterPlugins(flutter_controller_->engine()); run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); SetChildContent(flutter_controller_->view()->GetNativeWindow()); return true; } void FlutterWindow::OnDestroy() { if (flutter_controller_) { run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); flutter_controller_ = nullptr; } Win32Window::OnDestroy(); } LRESULT FlutterWindow::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { // Give Flutter, including plugins, an opporutunity to handle window messages. if (flutter_controller_) { std::optional result = flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, lparam); if (result) { return *result; } } switch (message) { case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); } ================================================ FILE: packages/url_launcher/url_launcher_windows/example/windows/runner/flutter_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_FLUTTER_WINDOW_H_ #define RUNNER_FLUTTER_WINDOW_H_ #include #include #include #include "run_loop.h" #include "win32_window.h" // A window that does nothing but host a Flutter view. class FlutterWindow : public Win32Window { public: // Creates a new FlutterWindow driven by the |run_loop|, hosting a // Flutter view running |project|. explicit FlutterWindow(RunLoop* run_loop, const flutter::DartProject& project); virtual ~FlutterWindow(); protected: // Win32Window: bool OnCreate() override; void OnDestroy() override; LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; private: // The run loop driving events for this window. RunLoop* run_loop_; // The project to run. flutter::DartProject project_; // The Flutter instance hosted by this window. std::unique_ptr flutter_controller_; }; #endif // RUNNER_FLUTTER_WINDOW_H_ ================================================ FILE: packages/url_launcher/url_launcher_windows/example/windows/runner/main.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include "flutter_window.h" #include "run_loop.h" #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, _In_ wchar_t* command_line, _In_ int show_command) { // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { CreateAndAttachConsole(); } // Initialize COM, so that it is available for use in the library and/or // plugins. ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); RunLoop run_loop; flutter::DartProject project(L"data"); FlutterWindow window(&run_loop, project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); if (!window.CreateAndShow(L"example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); run_loop.Run(); ::CoUninitialize(); return EXIT_SUCCESS; } ================================================ FILE: packages/url_launcher/url_launcher_windows/example/windows/runner/resource.h ================================================ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by Runner.rc // #define IDI_APP_ICON 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif ================================================ FILE: packages/url_launcher/url_launcher_windows/example/windows/runner/run_loop.cpp ================================================ // Copyright 2013 The Flutter 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 "run_loop.h" #include #include RunLoop::RunLoop() {} RunLoop::~RunLoop() {} void RunLoop::Run() { bool keep_running = true; TimePoint next_flutter_event_time = TimePoint::clock::now(); while (keep_running) { std::chrono::nanoseconds wait_duration = std::max(std::chrono::nanoseconds(0), next_flutter_event_time - TimePoint::clock::now()); ::MsgWaitForMultipleObjects( 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000), QS_ALLINPUT); bool processed_events = false; MSG message; // All pending Windows messages must be processed; MsgWaitForMultipleObjects // won't return again for items left in the queue after PeekMessage. while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) { processed_events = true; if (message.message == WM_QUIT) { keep_running = false; break; } ::TranslateMessage(&message); ::DispatchMessage(&message); // Allow Flutter to process messages each time a Windows message is // processed, to prevent starvation. next_flutter_event_time = std::min(next_flutter_event_time, ProcessFlutterMessages()); } // If the PeekMessage loop didn't run, process Flutter messages. if (!processed_events) { next_flutter_event_time = std::min(next_flutter_event_time, ProcessFlutterMessages()); } } } void RunLoop::RegisterFlutterInstance( flutter::FlutterEngine* flutter_instance) { flutter_instances_.insert(flutter_instance); } void RunLoop::UnregisterFlutterInstance( flutter::FlutterEngine* flutter_instance) { flutter_instances_.erase(flutter_instance); } RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { TimePoint next_event_time = TimePoint::max(); for (auto instance : flutter_instances_) { std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); if (wait_duration != std::chrono::nanoseconds::max()) { next_event_time = std::min(next_event_time, TimePoint::clock::now() + wait_duration); } } return next_event_time; } ================================================ FILE: packages/url_launcher/url_launcher_windows/example/windows/runner/run_loop.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_RUN_LOOP_H_ #define RUNNER_RUN_LOOP_H_ #include #include #include // A runloop that will service events for Flutter instances as well // as native messages. class RunLoop { public: RunLoop(); ~RunLoop(); // Prevent copying RunLoop(RunLoop const&) = delete; RunLoop& operator=(RunLoop const&) = delete; // Runs the run loop until the application quits. void Run(); // Registers the given Flutter instance for event servicing. void RegisterFlutterInstance(flutter::FlutterEngine* flutter_instance); // Unregisters the given Flutter instance from event servicing. void UnregisterFlutterInstance(flutter::FlutterEngine* flutter_instance); private: using TimePoint = std::chrono::steady_clock::time_point; // Processes all currently pending messages for registered Flutter instances. TimePoint ProcessFlutterMessages(); std::set flutter_instances_; }; #endif // RUNNER_RUN_LOOP_H_ ================================================ FILE: packages/url_launcher/url_launcher_windows/example/windows/runner/runner.exe.manifest ================================================ PerMonitorV2 ================================================ FILE: packages/url_launcher/url_launcher_windows/example/windows/runner/utils.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "utils.h" #include #include #include #include #include void CreateAndAttachConsole() { if (::AllocConsole()) { FILE* unused; if (freopen_s(&unused, "CONOUT$", "w", stdout)) { _dup2(_fileno(stdout), 1); } if (freopen_s(&unused, "CONOUT$", "w", stderr)) { _dup2(_fileno(stdout), 2); } std::ios::sync_with_stdio(); FlutterDesktopResyncOutputStreams(); } } ================================================ FILE: packages/url_launcher/url_launcher_windows/example/windows/runner/utils.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_UTILS_H_ #define RUNNER_UTILS_H_ // Creates a console for the process, and redirects stdout and stderr to // it for both the runner and the Flutter library. void CreateAndAttachConsole(); #endif // RUNNER_UTILS_H_ ================================================ FILE: packages/url_launcher/url_launcher_windows/example/windows/runner/win32_window.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "win32_window.h" #include #include "resource.h" namespace { constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; // The number of Win32Window objects that currently exist. static int g_active_window_count = 0; using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); // Scale helper to convert logical scaler values to physical using passed in // scale factor int Scale(int source, double scale_factor) { return static_cast(source * scale_factor); } // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. // This API is only needed for PerMonitor V1 awareness mode. void EnableFullDpiSupportIfAvailable(HWND hwnd) { HMODULE user32_module = LoadLibraryA("User32.dll"); if (!user32_module) { return; } auto enable_non_client_dpi_scaling = reinterpret_cast( GetProcAddress(user32_module, "EnableNonClientDpiScaling")); if (enable_non_client_dpi_scaling != nullptr) { enable_non_client_dpi_scaling(hwnd); FreeLibrary(user32_module); } } } // namespace // Manages the Win32Window's window class registration. class WindowClassRegistrar { public: ~WindowClassRegistrar() = default; // Returns the singleton registar instance. static WindowClassRegistrar* GetInstance() { if (!instance_) { instance_ = new WindowClassRegistrar(); } return instance_; } // Returns the name of the window class, registering the class if it hasn't // previously been registered. const wchar_t* GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); private: WindowClassRegistrar() = default; static WindowClassRegistrar* instance_; bool class_registered_ = false; }; WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); window_class.hIcon = LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; RegisterClass(&window_class); class_registered_ = true; } return kWindowClassName; } void WindowClassRegistrar::UnregisterWindowClass() { UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } Win32Window::Win32Window() { ++g_active_window_count; } Win32Window::~Win32Window() { --g_active_window_count; Destroy(); } bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, const Size& size) { Destroy(); const wchar_t* window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), static_cast(origin.y)}; HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; HWND window = CreateWindow( window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } return OnCreate(); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { if (message == WM_NCCREATE) { auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; } else if (Win32Window* that = GetThisFromHandle(window)) { return that->MessageHandler(window, message, wparam, lparam); } return DefWindowProc(window, message, wparam, lparam); } LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { case WM_DESTROY: window_handle_ = nullptr; Destroy(); if (quit_on_close_) { PostQuitMessage(0); } return 0; case WM_DPICHANGED: { auto newRectSize = reinterpret_cast(lparam); LONG newWidth = newRectSize->right - newRectSize->left; LONG newHeight = newRectSize->bottom - newRectSize->top; SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); return 0; } case WM_SIZE: RECT rect = GetClientArea(); if (child_content_ != nullptr) { // Size and position the child window. MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); } return 0; case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_); } return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } void Win32Window::Destroy() { OnDestroy(); if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } if (g_active_window_count == 0) { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } void Win32Window::SetChildContent(HWND content) { child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); MoveWindow(content, frame.left, frame.top, frame.right - frame.left, frame.bottom - frame.top, true); SetFocus(child_content_); } RECT Win32Window::GetClientArea() { RECT frame; GetClientRect(window_handle_, &frame); return frame; } HWND Win32Window::GetHandle() { return window_handle_; } void Win32Window::SetQuitOnClose(bool quit_on_close) { quit_on_close_ = quit_on_close; } bool Win32Window::OnCreate() { // No-op; provided for subclasses. return true; } void Win32Window::OnDestroy() { // No-op; provided for subclasses. } ================================================ FILE: packages/url_launcher/url_launcher_windows/example/windows/runner/win32_window.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef RUNNER_WIN32_WINDOW_H_ #define RUNNER_WIN32_WINDOW_H_ #include #include #include #include // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling class Win32Window { public: struct Point { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; struct Size { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) : width(width), height(height) {} }; Win32Window(); virtual ~Win32Window(); // Creates and shows a win32 window with |title| and position and size using // |origin| and |size|. New windows are created on the default monitor. Window // sizes are specified to the OS in physical pixels, hence to ensure a // consistent size to will treat the width height passed in to this function // as logical pixels and scale to appropriate for the default monitor. Returns // true if the window was created successfully. bool CreateAndShow(const std::wstring& title, const Point& origin, const Size& size); // Release OS resources associated with window. void Destroy(); // Inserts |content| into the window tree. void SetChildContent(HWND content); // Returns the backing Window handle to enable clients to set icon and other // window properties. Returns nullptr if the window has been destroyed. HWND GetHandle(); // If true, closing this window will quit the application. void SetQuitOnClose(bool quit_on_close); // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. virtual LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Called when CreateAndShow is called, allowing subclass window-related // setup. Subclasses should return false if setup fails. virtual bool OnCreate(); // Called when Destroy is called. virtual void OnDestroy(); private: friend class WindowClassRegistrar; // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically // responsponds to changes in DPI. All other messages are handled by // MessageHandler. static LRESULT CALLBACK WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| static Win32Window* GetThisFromHandle(HWND const window) noexcept; bool quit_on_close_ = false; // window handle for top level window. HWND window_handle_ = nullptr; // window handle for hosted content. HWND child_content_ = nullptr; }; #endif // RUNNER_WIN32_WINDOW_H_ ================================================ FILE: packages/url_launcher/url_launcher_windows/lib/src/messages.g.dart ================================================ // Copyright 2013 The Flutter 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; } } } ================================================ FILE: packages/url_launcher/url_launcher_windows/lib/url_launcher_windows.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:url_launcher_platform_interface/link.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; 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(); } @override final LinkDelegate? linkDelegate = null; @override Future canLaunch(String url) { return _hostApi.canLaunchUrl(url); } @override Future launch( String url, { required bool useSafariVC, required bool useWebView, required bool enableJavaScript, required bool enableDomStorage, required bool universalLinksOnly, required Map headers, String? webOnlyWindowName, }) async { await _hostApi.launchUrl(url); // Failure is handled via a PlatformException from `launchUrl`. return true; } } ================================================ FILE: packages/url_launcher/url_launcher_windows/pigeons/copyright.txt ================================================ Copyright 2013 The Flutter Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. ================================================ FILE: packages/url_launcher/url_launcher_windows/pigeons/messages.dart ================================================ // Copyright 2013 The Flutter 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); } ================================================ FILE: packages/url_launcher/url_launcher_windows/pubspec.yaml ================================================ 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.3 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: url_launcher platforms: windows: pluginClass: UrlLauncherWindows dartPluginClass: UrlLauncherWindows dependencies: flutter: sdk: flutter url_launcher_platform_interface: ^2.0.3 dev_dependencies: flutter_test: sdk: flutter pigeon: ^5.0.1 test: ^1.16.3 ================================================ FILE: packages/url_launcher/url_launcher_windows/test/url_launcher_windows_test.dart ================================================ // Copyright 2013 The Flutter 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: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() { late _FakeUrlLauncherApi api; late UrlLauncherWindows plugin; setUp(() { api = _FakeUrlLauncherApi(); plugin = UrlLauncherWindows(api: api); }); test('registers instance', () { UrlLauncherWindows.registerWith(); expect(UrlLauncherPlatform.instance, isA()); }); group('canLaunch', () { test('handles true', () async { api.canLaunch = true; final bool result = await plugin.canLaunch('http://example.com/'); expect(result, isTrue); expect(api.argument, 'http://example.com/'); }); test('handles false', () async { api.canLaunch = false; 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; expect( 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('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'); } } } ================================================ FILE: packages/url_launcher/url_launcher_windows/windows/.gitignore ================================================ flutter/ # Visual Studio user-specific files. *.suo *.user *.userosscache *.sln.docstates # Visual Studio build-related files. x64/ x86/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ ================================================ FILE: packages/url_launcher/url_launcher_windows/windows/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) set(PROJECT_NAME "url_launcher_windows") 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" "url_launcher_plugin.h" ) add_library(${PLUGIN_NAME} SHARED "include/url_launcher_windows/url_launcher_windows.h" "url_launcher_windows.cpp" ${PLUGIN_SOURCES} ) apply_standard_settings(${PLUGIN_NAME}) set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden) target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) target_include_directories(${PLUGIN_NAME} INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) # List of absolute paths to libraries that should be bundled with the plugin set(file_chooser_bundled_libraries "" PARENT_SCOPE ) # === Tests === if (${include_${PROJECT_NAME}_tests}) set(TEST_RUNNER "${PROJECT_NAME}_test") enable_testing() # TODO(stuartmorgan): Consider using a single shared, pre-checked-in googletest # instance rather than downloading for each plugin. This approach makes sense # for a template, but not for a monorepo with many plugins. include(FetchContent) FetchContent_Declare( googletest URL https://github.com/google/googletest/archive/release-1.11.0.zip ) # Prevent overriding the parent project's compiler/linker settings set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Disable install commands for gtest so it doesn't end up in the bundle. set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE) FetchContent_MakeAvailable(googletest) # The plugin's C API is not very useful for unit testing, so build the sources # directly into the test binary rather than using the DLL. add_executable(${TEST_RUNNER} test/url_launcher_windows_test.cpp ${PLUGIN_SOURCES} ) apply_standard_settings(${TEST_RUNNER}) target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") target_link_libraries(${TEST_RUNNER} PRIVATE flutter_wrapper_plugin) target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock) # flutter_wrapper_plugin has link dependencies on the Flutter DLL. add_custom_command(TARGET ${TEST_RUNNER} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${FLUTTER_LIBRARY}" $ ) include(GoogleTest) gtest_discover_tests(${TEST_RUNNER}) endif() ================================================ FILE: packages/url_launcher/url_launcher_windows/windows/include/url_launcher_windows/url_launcher_windows.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef PACKAGES_URL_LAUNCHER_URL_LAUNCHER_WINDOWS_WINDOWS_INCLUDE_URL_LAUNCHER_WINDOWS_URL_LAUNCHER_PLUGIN_H_ #define PACKAGES_URL_LAUNCHER_URL_LAUNCHER_WINDOWS_WINDOWS_INCLUDE_URL_LAUNCHER_WINDOWS_URL_LAUNCHER_PLUGIN_H_ #include #ifdef FLUTTER_PLUGIN_IMPL #define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) #else #define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) #endif #if defined(__cplusplus) extern "C" { #endif FLUTTER_PLUGIN_EXPORT void UrlLauncherWindowsRegisterWithRegistrar( FlutterDesktopPluginRegistrarRef registrar); #if defined(__cplusplus) } // extern "C" #endif #endif // PACKAGES_URL_LAUNCHER_URL_LAUNCHER_WINDOWS_WINDOWS_INCLUDE_URL_LAUNCHER_WINDOWS_URL_LAUNCHER_PLUGIN_H_ ================================================ FILE: packages/url_launcher/url_launcher_windows/windows/messages.g.cpp ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/url_launcher/url_launcher_windows/windows/messages.g.h ================================================ // Copyright 2013 The Flutter 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_ ================================================ FILE: packages/url_launcher/url_launcher_windows/windows/system_apis.cpp ================================================ // Copyright 2013 The Flutter 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 "system_apis.h" #include namespace url_launcher_windows { SystemApis::SystemApis() {} SystemApis::~SystemApis() {} SystemApisImpl::SystemApisImpl() {} SystemApisImpl::~SystemApisImpl() {} LSTATUS SystemApisImpl::RegCloseKey(HKEY key) { return ::RegCloseKey(key); } LSTATUS SystemApisImpl::RegOpenKeyExW(HKEY key, LPCWSTR sub_key, DWORD options, REGSAM desired, PHKEY result) { return ::RegOpenKeyExW(key, sub_key, options, desired, result); } LSTATUS SystemApisImpl::RegQueryValueExW(HKEY key, LPCWSTR value_name, LPDWORD type, LPBYTE data, LPDWORD data_size) { return ::RegQueryValueExW(key, value_name, nullptr, type, data, data_size); } HINSTANCE SystemApisImpl::ShellExecuteW(HWND hwnd, LPCWSTR operation, LPCWSTR file, LPCWSTR parameters, LPCWSTR directory, int show_flags) { return ::ShellExecuteW(hwnd, operation, file, parameters, directory, show_flags); } } // namespace url_launcher_windows ================================================ FILE: packages/url_launcher/url_launcher_windows/windows/system_apis.h ================================================ // Copyright 2013 The Flutter 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 namespace url_launcher_windows { // An interface wrapping system APIs used by the plugin, for mocking. class SystemApis { public: SystemApis(); virtual ~SystemApis(); // Disallow copy and move. SystemApis(const SystemApis&) = delete; SystemApis& operator=(const SystemApis&) = delete; // Wrapper for RegCloseKey. virtual LSTATUS RegCloseKey(HKEY key) = 0; // Wrapper for RegQueryValueEx. virtual LSTATUS RegQueryValueExW(HKEY key, LPCWSTR value_name, LPDWORD type, LPBYTE data, LPDWORD data_size) = 0; // Wrapper for RegOpenKeyEx. virtual LSTATUS RegOpenKeyExW(HKEY key, LPCWSTR sub_key, DWORD options, REGSAM desired, PHKEY result) = 0; // Wrapper for ShellExecute. virtual HINSTANCE ShellExecuteW(HWND hwnd, LPCWSTR operation, LPCWSTR file, LPCWSTR parameters, LPCWSTR directory, int show_flags) = 0; }; // Implementation of SystemApis using the Win32 APIs. class SystemApisImpl : public SystemApis { public: SystemApisImpl(); virtual ~SystemApisImpl(); // Disallow copy and move. SystemApisImpl(const SystemApisImpl&) = delete; SystemApisImpl& operator=(const SystemApisImpl&) = delete; // SystemApis Implementation: virtual LSTATUS RegCloseKey(HKEY key); virtual LSTATUS RegOpenKeyExW(HKEY key, LPCWSTR sub_key, DWORD options, REGSAM desired, PHKEY result); virtual LSTATUS RegQueryValueExW(HKEY key, LPCWSTR value_name, LPDWORD type, LPBYTE data, LPDWORD data_size); virtual HINSTANCE ShellExecuteW(HWND hwnd, LPCWSTR operation, LPCWSTR file, LPCWSTR parameters, LPCWSTR directory, int show_flags); }; } // namespace url_launcher_windows ================================================ FILE: packages/url_launcher/url_launcher_windows/windows/test/url_launcher_windows_test.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include #include #include #include #include #include "messages.g.h" #include "url_launcher_plugin.h" namespace url_launcher_windows { namespace test { namespace { using flutter::EncodableMap; using flutter::EncodableValue; using ::testing::DoAll; using ::testing::Pointee; using ::testing::Return; using ::testing::SetArgPointee; class MockSystemApis : public SystemApis { public: MOCK_METHOD(LSTATUS, RegCloseKey, (HKEY key), (override)); MOCK_METHOD(LSTATUS, RegQueryValueExW, (HKEY key, LPCWSTR value_name, LPDWORD type, LPBYTE data, LPDWORD data_size), (override)); MOCK_METHOD(LSTATUS, RegOpenKeyExW, (HKEY key, LPCWSTR sub_key, DWORD options, REGSAM desired, PHKEY result), (override)); MOCK_METHOD(HINSTANCE, ShellExecuteW, (HWND hwnd, LPCWSTR operation, LPCWSTR file, LPCWSTR parameters, LPCWSTR directory, int show_flags), (override)); }; } // namespace TEST(UrlLauncherPlugin, CanLaunchSuccessTrue) { std::unique_ptr system = std::make_unique(); // Return success values from the registery commands. HKEY fake_key = reinterpret_cast(1); EXPECT_CALL(*system, RegOpenKeyExW) .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)); UrlLauncherPlugin plugin(std::move(system)); 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(); // 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. HKEY fake_key = reinterpret_cast(1); EXPECT_CALL(*system, RegOpenKeyExW) .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)); UrlLauncherPlugin plugin(std::move(system)); 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(); // Return failure for opening. EXPECT_CALL(*system, RegOpenKeyExW).WillOnce(Return(ERROR_BAD_PATHNAME)); UrlLauncherPlugin plugin(std::move(system)); 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(); // Return a success value (>32) from launching. EXPECT_CALL(*system, ShellExecuteW) .WillOnce(Return(reinterpret_cast(33))); UrlLauncherPlugin plugin(std::move(system)); std::optional error = plugin.LaunchUrl("https://some.url.com"); EXPECT_FALSE(error.has_value()); } TEST(UrlLauncherPlugin, LaunchReportsFailure) { std::unique_ptr system = std::make_unique(); // Return a faile value (<=32) from launching. EXPECT_CALL(*system, ShellExecuteW) .WillOnce(Return(reinterpret_cast(32))); UrlLauncherPlugin plugin(std::move(system)); std::optional error = plugin.LaunchUrl("https://some.url.com"); EXPECT_TRUE(error.has_value()); } } // namespace test } // namespace url_launcher_windows ================================================ FILE: packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.cpp ================================================ // Copyright 2013 The Flutter 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 "url_launcher_plugin.h" #include #include #include #include #include #include #include #include #include "messages.g.h" namespace url_launcher_windows { namespace { using flutter::EncodableMap; using flutter::EncodableValue; // Converts the given UTF-8 string to UTF-16. std::wstring Utf16FromUtf8(const std::string& utf8_string) { if (utf8_string.empty()) { return std::wstring(); } int target_length = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), static_cast(utf8_string.length()), nullptr, 0); if (target_length == 0) { return std::wstring(); } std::wstring utf16_string; utf16_string.resize(target_length); int converted_length = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string.data(), static_cast(utf8_string.length()), utf16_string.data(), target_length); if (converted_length == 0) { return std::wstring(); } return utf16_string; } // Returns the URL argument from |method_call| if it is present, otherwise // returns an empty string. std::string GetUrlArgument(const flutter::MethodCall<>& method_call) { std::string url; const auto* arguments = std::get_if(method_call.arguments()); if (arguments) { auto url_it = arguments->find(EncodableValue("url")); if (url_it != arguments->end()) { url = std::get(url_it->second); } } return url; } } // namespace // static void UrlLauncherPlugin::RegisterWithRegistrar( flutter::PluginRegistrar* registrar) { std::unique_ptr plugin = std::make_unique(); UrlLauncherApi::SetUp(registrar->messenger(), plugin.get()); registrar->AddPlugin(std::move(plugin)); } UrlLauncherPlugin::UrlLauncherPlugin() : system_apis_(std::make_unique()) {} UrlLauncherPlugin::UrlLauncherPlugin(std::unique_ptr system_apis) : system_apis_(std::move(system_apis)) {} UrlLauncherPlugin::~UrlLauncherPlugin() = default; ErrorOr UrlLauncherPlugin::CanLaunchUrl(const std::string& url) { size_t separator_location = url.find(":"); if (separator_location == std::string::npos) { return false; } std::wstring scheme = Utf16FromUtf8(url.substr(0, separator_location)); HKEY key = nullptr; if (system_apis_->RegOpenKeyExW(HKEY_CLASSES_ROOT, scheme.c_str(), 0, KEY_QUERY_VALUE, &key) != ERROR_SUCCESS) { return false; } bool has_handler = system_apis_->RegQueryValueExW(key, L"URL Protocol", nullptr, nullptr, nullptr) == ERROR_SUCCESS; system_apis_->RegCloseKey(key); return has_handler; } std::optional UrlLauncherPlugin::LaunchUrl( const std::string& url) { std::wstring url_wide = Utf16FromUtf8(url); int status = static_cast(reinterpret_cast( system_apis_->ShellExecuteW(nullptr, TEXT("open"), url_wide.c_str(), nullptr, nullptr, SW_SHOWNORMAL))); // Per ::ShellExecuteW documentation, anything >32 indicates success. if (status <= 32) { std::ostringstream error_message; error_message << "Failed to open " << url << ": ShellExecute error code " << status; return FlutterError("open_error", error_message.str()); } return std::nullopt; } } // namespace url_launcher_windows ================================================ FILE: packages/url_launcher/url_launcher_windows/windows/url_launcher_plugin.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include #include #include #include "messages.g.h" #include "system_apis.h" namespace url_launcher_windows { class UrlLauncherPlugin : public flutter::Plugin, public UrlLauncherApi { public: static void RegisterWithRegistrar(flutter::PluginRegistrar* registrar); UrlLauncherPlugin(); // Creates a plugin instance with the given SystemApi instance. // // Exists for unit testing with mock implementations. UrlLauncherPlugin(std::unique_ptr system_apis); virtual ~UrlLauncherPlugin(); // Disallow copy and move. UrlLauncherPlugin(const UrlLauncherPlugin&) = delete; UrlLauncherPlugin& operator=(const UrlLauncherPlugin&) = delete; // UrlLauncherApi: ErrorOr CanLaunchUrl(const std::string& url) override; std::optional LaunchUrl(const std::string& url) override; private: std::unique_ptr system_apis_; }; } // namespace url_launcher_windows ================================================ FILE: packages/url_launcher/url_launcher_windows/windows/url_launcher_windows.cpp ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "include/url_launcher_windows/url_launcher_windows.h" #include #include "url_launcher_plugin.h" void UrlLauncherWindowsRegisterWithRegistrar( FlutterDesktopPluginRegistrarRef registrar) { url_launcher_windows::UrlLauncherPlugin::RegisterWithRegistrar( flutter::PluginRegistrarManager::GetInstance() ->GetRegistrar(registrar)); } ================================================ FILE: packages/video_player/video_player/AUTHORS ================================================ # 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> Koen Van Looveren ================================================ FILE: packages/video_player/video_player/CHANGELOG.md ================================================ ## 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. ## 2.4.9 * Fixes file URI construction. ## 2.4.8 * Updates code for new analysis options. * Updates code for `no_leading_underscores_for_local_identifiers` lint. ## 2.4.7 * Updates README via code excerpts. * Fixes violations of new analysis option use_named_constants. ## 2.4.6 * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 2.4.5 * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/104231). * Fixes an exception when a disposed VideoPlayerController is disposed again. ## 2.4.4 * Updates references to the obsolete master branch. ## 2.4.3 * Fixes Android to correctly display videos recorded in landscapeRight ([#60327](https://github.com/flutter/flutter/issues/60327)). * Fixes order-dependent unit tests. ## 2.4.2 * Minor fixes for new analysis options. ## 2.4.1 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 2.4.0 * Updates minimum Flutter version to 2.10. * Adds OS version support information to README. * Adds `setClosedCaptionFile` method to `VideoPlayerController`. ## 2.3.0 * Adds `allowBackgroundPlayback` to `VideoPlayerOptions`. ## 2.2.19 * Internal code cleanup for stricter analysis options. ## 2.2.18 * Moves Android and iOS implementations to federated packages. * Update audio URL in iOS tests. ## 2.2.17 * Avoid blocking the main thread loading video count on iOS. ## 2.2.16 * Introduces `setCaptionOffset` to offset the caption display based on a Duration. ## 2.2.15 * Updates README discussion of permissions. ## 2.2.14 * Removes KVO observer on AVPlayerItem on iOS. ## 2.2.13 * Fixes persisting of hasError even after successful initialize. ## 2.2.12 * iOS: Validate size only when assets contain video tracks. ## 2.2.11 * Removes dependency on `meta`. ## 2.2.10 * iOS: Updates texture on `seekTo`. ## 2.2.9 * Adds compatibility with `video_player_platform_interface` 5.0, which does not include non-dev test dependencies. ## 2.2.8 * Changes the way the `VideoPlayerPlatform` instance is cached in the controller, so that it's no longer impossible to change after the first use. * Updates unit tests to be self-contained. * Fixes integration tests. * Updates Android compileSdkVersion to 31. * Fixes a flaky integration test. * Integration tests now use WebM on web, to allow running with Chromium. ## 2.2.7 * Fixes a regression where dragging a [VideoProgressIndicator] while playing would restart playback from the start of the video. ## 2.2.6 * Initialize player when size and duration become available on iOS ## 2.2.5 * Support to closed caption WebVTT format added. ## 2.2.4 * Update minimum Flutter SDK to 2.5 and iOS deployment target to 9.0. ## 2.2.3 * Fixed empty caption text still showing the caption widget. ## 2.2.2 * Fix a disposed `VideoPlayerController` throwing an exception when being replaced in the `VideoPlayer`. ## 2.2.1 * Specify Java 8 for Android build. ## 2.2.0 * Add `contentUri` based VideoPlayerController. ## 2.1.15 * Ensured seekTo isn't called before video player is initialized. Fixes [#89259](https://github.com/flutter/flutter/issues/89259). * Updated Android lint settings. ## 2.1.14 * Removed dependency on the `flutter_test` package. ## 2.1.13 * Removed obsolete warning about not working in iOS simulators from README. ## 2.1.12 * Update the video url in the readme code sample ## 2.1.11 * Remove references to the Android V1 embedding. ## 2.1.10 * Ensure video pauses correctly when it finishes. ## 2.1.9 * Silenced warnings that may occur during build when using a very recent version of Flutter relating to null safety. ## 2.1.8 * Refactor `FLTCMTimeToMillis` to support indefinite streams. Fixes [#48670](https://github.com/flutter/flutter/issues/48670). ## 2.1.7 * Update exoplayer to 2.14.1, removing dependency on Bintray. ## 2.1.6 * Remove obsolete pre-1.0 warning from README. * Add iOS unit and UI integration test targets. ## 2.1.5 * Update example code in README to fix broken url. ## 2.1.4 * Add an exoplayer URL to the maven repositories to address a possible build regression in 2.1.2. ## 2.1.3 * Fix pointer value to boolean conversion analyzer warnings. ## 2.1.2 * Migrate maven repository from jcenter to mavenCentral. ## 2.1.1 * Update example code in README to reflect API changes. ## 2.1.0 * Add `httpHeaders` option to `VideoPlayerController.network` ## 2.0.2 * Fix `VideoPlayerValue` size and aspect ratio documentation ## 2.0.1 * Remove the deprecated API "exoPlayer.setAudioAttributes". ## 2.0.0 * Migrate to null safety. * Fix an issue where `isBuffering` was not updating on Android. * Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) * Fix `VideoPlayerValue toString()` test. * Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets. * Migrate from deprecated `defaultBinaryMessenger`. * Fix an issue where a crash can occur after a closing a video player view on iOS. * Setting the `mixWithOthers` `VideoPlayerOptions` in web now is silently ignored instead of throwing an exception. ## 1.0.2 * Update Flutter SDK constraint. ## 1.0.1 * Android: Dispose video players when app is closed. ## 1.0.0 * Announce 1.0.0. ## 0.11.1+5 * Update Dart SDK constraint in example. * Remove `test` dependency. * Convert disabled driver test to integration_test. ## 0.11.1+4 * Add `toString()` to `Caption`. * Fix a bug on Android when loading videos from assets would crash. ## 0.11.1+3 * Android: Upgrade ExoPlayer to 2.12.1. ## 0.11.1+2 * Update android compileSdkVersion to 29. ## 0.11.1+1 * Fixed uncanceled timers when calling `play` on the controller multiple times before `pause`, which caused value listeners to be called indefinitely (after `pause`) and more often than needed. ## 0.11.1 * Enable TLSv1.1 & TLSv1.2 for API 19 and below. ## 0.11.0 * Added option to set the video playback speed on the video controller. * **Minor breaking change**: fixed `VideoPlayerValue.toString` to insert a comma after `isBuffering`. ## 0.10.12+5 * Depend on `video_player_platform_interface` version that contains the new `TestHostVideoPlayerApi` in order for tests to pass using the latest dependency. ## 0.10.12+4 * Keep handling deprecated Android v1 classes for backward compatibility. ## 0.10.12+3 * Avoiding uses or overrides a deprecated API in `VideoPlayerPlugin` class. ## 0.10.12+2 * Fix `setMixWithOthers` test. ## 0.10.12+1 * Depend on the version of `video_player_platform_interface` that contains the new `VideoPlayerOptions` class. ## 0.10.12 * Introduce VideoPlayerOptions to set the audio mix mode. ## 0.10.11+2 * Fix aspectRatio calculation when size.width or size.height are zero. ## 0.10.11+1 * Post-v2 Android embedding cleanups. ## 0.10.11 * iOS: Fixed crash when detaching from a dying engine. * Android: Fixed exception when detaching from any engine. ## 0.10.10 * Migrated to [pigeon](https://pub.dev/packages/pigeon). ## 0.10.9+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.10.9+1 * Readme updated to include web support and details on how to use for web ## 0.10.9 * Remove Android dependencies fallback. * Require Flutter SDK 1.12.13+hotfix.5 or greater. * Fix CocoaPods podspec lint warnings. ## 0.10.8+2 * Replace deprecated `getFlutterEngine` call on Android. ## 0.10.8+1 * Make the pedantic dev_dependency explicit. ## 0.10.8 * Added support for cleaning up the plugin if used for add-to-app (Flutter v1.15.3 is required for that feature). ## 0.10.7 * `VideoPlayerController` support for reading closed caption files. * `VideoPlayerValue` has a `caption` field for reading the current closed caption at any given time. ## 0.10.6 * `ClosedCaptionFile` and `SubRipCaptionFile` classes added to read [SubRip](https://en.wikipedia.org/wiki/SubRip) files into dart objects. ## 0.10.5+3 * Add integration instructions for the `web` platform. ## 0.10.5+2 * Make sure the plugin is correctly initialized ## 0.10.5+1 * Fixes issue where `initialize()` `Future` stalls when failing to load source data and does not throw an error. ## 0.10.5 * Support `web` by default. * Require Flutter SDK 1.12.13+hotfix.4 or greater. ## 0.10.4+2 * Remove the deprecated `author:` field form pubspec.yaml * Migrate the plugin to the pubspec platforms manifest. * Require Flutter SDK 1.10.0 or greater. ## 0.10.4+1 * Fix pedantic lints. This fixes some potential race conditions in cases where futures within some video_player methods weren't being awaited correctly. ## 0.10.4 * Port plugin code to use the federated Platform Interface, instead of a MethodChannel directly. ## 0.10.3+3 * Add DartDocs and unit tests. ## 0.10.3+2 * Update the homepage to point to the new plugin location ## 0.10.3+1 * Dispose `FLTVideoPlayer` in `onTextureUnregistered` callback on iOS. * Add a temporary fix to dispose the `FLTVideoPlayer` with a delay to avoid race condition. * Updated the example app to include a new page that pop back after video is done playing. ## 0.10.3 * Add support for the v2 Android embedding. This shouldn't impact existing functionality. ## 0.10.2+6 * Remove AndroidX warnings. ## 0.10.2+5 * Update unit test for compatibility with Flutter stable branch. ## 0.10.2+4 * Define clang module for iOS. ## 0.10.2+3 * Fix bug where formatHint was not being pass down to network sources. ## 0.10.2+2 * Update and migrate iOS example project. ## 0.10.2+1 * Use DefaultHttpDataSourceFactory only when network schemas and use DefaultHttpDataSourceFactory by default. ## 0.10.2 * **Android Only** Adds optional VideoFormat used to signal what format the plugin should try. ## 0.10.1+7 * Fix tests by ignoring deprecated member use. ## 0.10.1+6 * [iOS] Fixed a memory leak with notification observing. ## 0.10.1+5 * Fix race condition while disposing the VideoController. ## 0.10.1+4 * Fixed syntax error in README.md. ## 0.10.1+3 * Add missing template type parameter to `invokeMethod` calls. * Bump minimum Flutter version to 1.5.0. * Replace invokeMethod with invokeMapMethod wherever necessary. ## 0.10.1+2 * Example: Fixed tab display and added scroll view ## 0.10.1+1 * iOS: Avoid deprecated `seekToTime` API ## 0.10.1 * iOS: Consider a player only `initialized` once duration is determined. ## 0.10.0+8 * iOS: Fix an issue where the player sends initialization message incorrectly. * Fix a few other IDE warnings. ## 0.10.0+7 * Android: Fix issue where buffering status in percentage instead of milliseconds * Android: Update buffering status everytime we notify for position change ## 0.10.0+6 * Android: Fix missing call to `event.put("event", "completed");` which makes it possible to detect when the video is over. ## 0.10.0+5 * Fixed iOS build warnings about implicit retains. ## 0.10.0+4 * Android: Upgrade ExoPlayer to 2.9.6. ## 0.10.0+3 * Fix divide by zero bug on iOS. ## 0.10.0+2 * Added supported format documentation in README. ## 0.10.0+1 * Log a more detailed warning at build time about the previous AndroidX migration. ## 0.10.0 * **Breaking change**. Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to [also migrate](https://developer.android.com/jetpack/androidx/migrate) if they're using the original support library. ## 0.9.0 * Fixed the aspect ratio and orientation of videos. Videos are now properly displayed when recorded in portrait mode both in iOS and Android. ## 0.8.0 * Android: Upgrade ExoPlayer to 2.9.1 * Android: Use current gradle dependencies * Android 9 compatibility fixes for Demo App ## 0.7.2 * Updated to use factories on exoplayer `MediaSource`s for Android instead of the now-deprecated constructors. ## 0.7.1 * Fixed null exception on Android when the video has a width or height of 0. ## 0.7.0 * Add a unit test for controller and texture changes. This is a breaking change since the interface had to be cleaned up to facilitate faking. ## 0.6.6 * Fix the condition where the player doesn't update when attached controller is changed. ## 0.6.5 * Eliminate race conditions around initialization: now initialization events are queued and guaranteed to be delivered to the Dart side. VideoPlayer widget is rebuilt upon completion of initialization. ## 0.6.4 * Android: add support for hls, dash and ss video formats. ## 0.6.3 * iOS: Allow audio playback in silent mode. ## 0.6.2 * `VideoPlayerController.seekTo()` is now frame accurate on both platforms. ## 0.6.1 * iOS: add missing observer removals to prevent crashes on deallocation. ## 0.6.0 * Android: use ExoPlayer instead of MediaPlayer for better video format support. ## 0.5.5 * **Breaking change** `VideoPlayerController.initialize()` now only completes after the controller is initialized. * Updated example in README.md. ## 0.5.4 * Updated Gradle tooling to match Android Studio 3.1.2. ## 0.5.3 * Added video buffering status. ## 0.5.2 * Fixed a bug on iOS that could lead to missing initialization. * Added support for HLS video on iOS. ## 0.5.1 * Fixed bug on video loop feature for iOS. ## 0.5.0 * Added the constructor `VideoPlayerController.file`. * **Breaking change**. Changed `VideoPlayerController.isNetwork` to an enum `VideoPlayerController.dataSourceType`. ## 0.4.1 * Updated Flutter SDK constraint to reflect the changes in v0.4.0. ## 0.4.0 * **Breaking change**. Removed the `VideoPlayerController` constructor * Added two new factory constructors `VideoPlayerController.asset` and `VideoPlayerController.network` to respectively play a video from the Flutter assets and from a network uri. ## 0.3.0 * **Breaking change**. Set SDK constraints to match the Flutter beta release. ## 0.2.1 * Fixed some signatures to account for strong mode runtime errors. * Fixed spelling mistake in toString output. ## 0.2.0 * **Breaking change**. Renamed `VideoPlayerController.isErroneous` to `VideoPlayerController.hasError`. * Updated documentation of when fields are available on `VideoPlayerController`. * Updated links in README.md. ## 0.1.1 * Simplified and upgraded Android project template to Android SDK 27. * Moved Android package to io.flutter.plugins. * Fixed warnings from the Dart 2.0 analyzer. ## 0.1.0 * **Breaking change**. Upgraded to Gradle 4.1 and Android Studio Gradle plugin 3.0.1. Older Flutter projects need to upgrade their Gradle setup as well in order to use this version of the plugin. Instructions can be found [here](https://github.com/flutter/flutter/wiki/Updating-Flutter-projects-to-Gradle-4.1-and-Android-Studio-Gradle-plugin-3.0.1). ## 0.0.7 * Added access to the video size. * Made the VideoProgressIndicator render using a LinearProgressIndicator. ## 0.0.6 * Fixed a bug related to hot restart on Android. ## 0.0.5 * Added VideoPlayerValue.toString(). * Added FLT prefix to iOS types. ## 0.0.4 * The player will now pause on app pause, and resume on app resume. * Implemented scrubbing on the progress bar. ## 0.0.3 * Made creating a VideoPlayerController a synchronous operation. Must be followed by a call to initialize(). * Added VideoPlayerController.setVolume(). * Moved the package to flutter/plugins github repo. ## 0.0.2 * Fix meta dependency version. ## 0.0.1 * Initial release ================================================ FILE: packages/video_player/video_player/LICENSE ================================================ 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. ================================================ FILE: packages/video_player/video_player/README.md ================================================ # Video Player plugin for Flutter [![pub package](https://img.shields.io/pub/v/video_player.svg)](https://pub.dev/packages/video_player) A Flutter plugin for iOS, Android and Web for playing back video on a Widget surface. | | Android | iOS | Web | |-------------|---------|------|-------| | **Support** | SDK 16+ | 9.0+ | Any\* | ![The example app running in iOS](https://github.com/flutter/plugins/blob/main/packages/video_player/video_player/doc/demo_ipod.gif?raw=true) ## Installation First, add `video_player` as a [dependency in your pubspec.yaml file](https://flutter.dev/using-packages/). ### iOS If you need to access videos using `http` (rather than `https`) URLs, you will need to add the appropriate `NSAppTransportSecurity` permissions to your app's _Info.plist_ file, located in `/ios/Runner/Info.plist`. See [Apple's documentation](https://developer.apple.com/documentation/bundleresources/information_property_list/nsapptransportsecurity) to determine the right combination of entries for your use case and supported iOS versions. ### Android If you are using network-based videos, ensure that the following permission is present in your Android Manifest file, located in `/android/app/src/main/AndroidManifest.xml`: ```xml ``` ### Web > The Web platform does **not** suppport `dart:io`, so avoid using the `VideoPlayerController.file` constructor for the plugin. Using the constructor attempts to create a `VideoPlayerController.file` that will throw an `UnimplementedError`. \* Different web browsers may have different video-playback capabilities (supported formats, autoplay...). Check [package:video_player_web](https://pub.dev/packages/video_player_web) for more web-specific information. The `VideoPlayerOptions.mixWithOthers` option can't be implemented in web, at least at the moment. If you use this option in web it will be silently ignored. ## Supported Formats - On iOS, the backing player is [AVPlayer](https://developer.apple.com/documentation/avfoundation/avplayer). The supported formats vary depending on the version of iOS, [AVURLAsset](https://developer.apple.com/documentation/avfoundation/avurlasset) class has [audiovisualTypes](https://developer.apple.com/documentation/avfoundation/avurlasset/1386800-audiovisualtypes?language=objc) that you can query for supported av formats. - On Android, the backing player is [ExoPlayer](https://google.github.io/ExoPlayer/), please refer [here](https://google.github.io/ExoPlayer/supported-formats.html) for list of supported formats. - On Web, available formats depend on your users' browsers (vendor and version). Check [package:video_player_web](https://pub.dev/packages/video_player_web) for more specific information. ## Example ```dart import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; void main() => runApp(const VideoApp()); /// Stateful widget to fetch and then display video content. class VideoApp extends StatefulWidget { const VideoApp({Key? key}) : super(key: key); @override _VideoAppState createState() => _VideoAppState(); } class _VideoAppState extends State { late VideoPlayerController _controller; @override void initState() { super.initState(); _controller = VideoPlayerController.network( 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4') ..initialize().then((_) { // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed. setState(() {}); }); } @override Widget build(BuildContext context) { return MaterialApp( title: 'Video Demo', home: Scaffold( body: Center( child: _controller.value.isInitialized ? AspectRatio( aspectRatio: _controller.value.aspectRatio, child: VideoPlayer(_controller), ) : Container(), ), floatingActionButton: FloatingActionButton( onPressed: () { setState(() { _controller.value.isPlaying ? _controller.pause() : _controller.play(); }); }, child: Icon( _controller.value.isPlaying ? Icons.pause : Icons.play_arrow, ), ), ), ); } @override void dispose() { super.dispose(); _controller.dispose(); } } ``` ## Usage The following section contains usage information that goes beyond what is included in the documentation in order to give a more elaborate overview of the API. This is not complete as of now. You can contribute to this section by [opening a pull request](https://github.com/flutter/plugins/pulls). ### Playback speed You can set the playback speed on your `_controller` (instance of `VideoPlayerController`) by calling `_controller.setPlaybackSpeed`. `setPlaybackSpeed` takes a `double` speed value indicating the rate of playback for your video. For example, when given a value of `2.0`, your video will play at 2x the regular playback speed and so on. To learn about playback speed limitations, see the [`setPlaybackSpeed` method documentation](https://pub.dev/documentation/video_player/latest/video_player/VideoPlayerController/setPlaybackSpeed.html). Furthermore, see the example app for an example playback speed implementation. ================================================ FILE: packages/video_player/video_player/example/.gitignore ================================================ lib/generated_plugin_registrant.dart ================================================ FILE: packages/video_player/video_player/example/README.md ================================================ # video_player_example Demonstrates how to use the video_player plugin. ================================================ FILE: packages/video_player/video_player/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 lintOptions { disable 'InvalidPackage' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { applicationId "io.flutter.plugins.videoplayerexample" minSdkVersion 21 targetSdkVersion 29 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.8.1' testImplementation 'org.mockito:mockito-core:5.0.0' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } ================================================ FILE: packages/video_player/video_player/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/video_player/video_player/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/video_player/video_player/example/android/app/src/androidTest/java/io/flutter/plugins/videoplayerexample/FlutterActivityTest.java ================================================ // Copyright 2013 The Flutter 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.videoplayerexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class FlutterActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/video_player/video_player/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/video_player/video_player/example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: packages/video_player/video_player/example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: packages/video_player/video_player/example/android/app/src/main/res/xml/network_security_config.xml ================================================ www.sample-videos.com 184.72.239.149 ================================================ FILE: packages/video_player/video_player/example/android/app/src/test/java/io/flutter/plugins/videoplayerexample/FlutterActivityTest.java ================================================ // Copyright 2013 The Flutter 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.videoplayerexample; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngineCache; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.plugins.videoplayer.VideoPlayerPlugin; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class FlutterActivityTest { @Test public void disposeAllPlayers() { VideoPlayerPlugin videoPlayerPlugin = spy(new VideoPlayerPlugin()); FlutterLoader flutterLoader = mock(FlutterLoader.class); FlutterJNI flutterJNI = mock(FlutterJNI.class); ArgumentCaptor pluginBindingCaptor = ArgumentCaptor.forClass(FlutterPlugin.FlutterPluginBinding.class); when(flutterJNI.isAttached()).thenReturn(true); FlutterEngine engine = spy(new FlutterEngine(RuntimeEnvironment.application, flutterLoader, flutterJNI)); FlutterEngineCache.getInstance().put("my_flutter_engine", engine); engine.getPlugins().add(videoPlayerPlugin); verify(videoPlayerPlugin, times(1)).onAttachedToEngine(pluginBindingCaptor.capture()); engine.destroy(); verify(videoPlayerPlugin, times(1)).onDetachedFromEngine(pluginBindingCaptor.capture()); verify(videoPlayerPlugin, times(1)).initialize(); } } ================================================ FILE: packages/video_player/video_player/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/video_player/video_player/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/video_player/video_player/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true android.enableR8=true ================================================ FILE: packages/video_player/video_player/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withInputStream { stream -> plugins.load(stream) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/video_player/video_player/example/assets/bumble_bee_captions.srt ================================================ 1 00:00:00,200 --> 00:00:01,750 [ Birds chirping ] 2 00:00:02,300 --> 00:00:05,000 [ Buzzing ] ================================================ FILE: packages/video_player/video_player/example/assets/bumble_bee_captions.vtt ================================================ WEBVTT 00:00:00.200 --> 00:00:01.750 [ Birds chirping ] 00:00:02.300 --> 00:00:05.000 [ Buzzing ] ================================================ FILE: packages/video_player/video_player/example/build.excerpt.yaml ================================================ targets: $default: sources: include: - lib/** - android/app/src/main/** # Some default includes that aren't really used here but will prevent # false-negative warnings: - $package$ - lib/$lib$ exclude: - '**/.*/**' - '**/build/**' - 'android/app/src/main/res/**' builders: code_excerpter|code_excerpter: enabled: true generate_for: - '**/*.dart' - android/**/*.xml ================================================ FILE: packages/video_player/video_player/example/integration_test/controller_swap_test.dart ================================================ // Copyright 2013 The Flutter 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:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:video_player/video_player.dart'; const Duration _playDuration = Duration(seconds: 1); void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets( 'can substitute one controller by another without crashing', (WidgetTester tester) async { // Use WebM for web to allow CI to use Chromium. const String videoAssetKey = kIsWeb ? 'assets/Butterfly-209.webm' : 'assets/Butterfly-209.mp4'; final VideoPlayerController controller = VideoPlayerController.asset( videoAssetKey, ); final VideoPlayerController another = VideoPlayerController.asset( videoAssetKey, ); await controller.initialize(); await another.initialize(); await controller.setVolume(0); await another.setVolume(0); final Completer started = Completer(); final Completer ended = Completer(); another.addListener(() { if (another.value.isBuffering && !started.isCompleted) { started.complete(); } if (started.isCompleted && !another.value.isBuffering && !ended.isCompleted) { ended.complete(); } }); // Inject a widget with `controller`... await tester.pumpWidget(renderVideoWidget(controller)); await controller.play(); await tester.pumpAndSettle(_playDuration); await controller.pause(); // Disposing controller causes the Widget to crash in the next line // (Issue https://github.com/flutter/flutter/issues/90046) await controller.dispose(); // Now replace it with `another` controller... await tester.pumpWidget(renderVideoWidget(another)); await another.play(); await another.seekTo(const Duration(seconds: 5)); await tester.pumpAndSettle(_playDuration); await another.pause(); // Expect that `another` played. expect(another.value.position, (Duration position) => position > Duration.zero); await expectLater(started.future, completes); await expectLater(ended.future, completes); }, skip: !(kIsWeb || defaultTargetPlatform == TargetPlatform.android), ); } Widget renderVideoWidget(VideoPlayerController controller) { return Material( child: Directionality( textDirection: TextDirection.ltr, child: Center( child: AspectRatio( key: const Key('same'), aspectRatio: controller.value.aspectRatio, child: VideoPlayer(controller), ), ), ), ); } ================================================ FILE: packages/video_player/video_player/example/integration_test/video_player_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:path_provider/path_provider.dart'; import 'package:video_player/video_player.dart'; const Duration _playDuration = Duration(seconds: 1); // Use WebM for web to allow CI to use Chromium. const String _videoAssetKey = kIsWeb ? 'assets/Butterfly-209.webm' : 'assets/Butterfly-209.mp4'; // Returns the URL to load an asset from this example app as a network source. // // TODO(stuartmorgan): Convert this to a local `HttpServer` that vends the // assets directly, https://github.com/flutter/flutter/issues/95420 String getUrlForAssetAsNetworkSource(String assetKey) { return 'https://github.com/flutter/plugins/blob/' // This hash can be rolled forward to pick up newly-added assets. 'cb381ced070d356799dddf24aca38ce0579d3d7b' '/packages/video_player/video_player/example/' '$assetKey' '?raw=true'; } void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); late VideoPlayerController controller; tearDown(() async => controller.dispose()); group('asset videos', () { setUp(() { controller = VideoPlayerController.asset(_videoAssetKey); }); testWidgets('can be initialized', (WidgetTester tester) async { await controller.initialize(); expect(controller.value.isInitialized, true); expect(controller.value.position, Duration.zero); expect(controller.value.isPlaying, false); // The WebM version has a slightly different duration than the MP4. expect(controller.value.duration, const Duration(seconds: 7, milliseconds: kIsWeb ? 544 : 540)); }); testWidgets( 'live stream duration != 0', (WidgetTester tester) async { final VideoPlayerController networkController = VideoPlayerController.network( 'https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8', ); await networkController.initialize(); expect(networkController.value.isInitialized, true); // Live streams should have either a positive duration or C.TIME_UNSET if the duration is unknown // See https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/Player.html#getDuration-- expect(networkController.value.duration, (Duration duration) => duration != Duration.zero); }, skip: kIsWeb, ); testWidgets( 'can be played', (WidgetTester tester) async { await controller.initialize(); // Mute to allow playing without DOM interaction on Web. // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes await controller.setVolume(0); await controller.play(); await tester.pumpAndSettle(_playDuration); expect(controller.value.isPlaying, true); expect(controller.value.position, (Duration position) => position > Duration.zero); }, ); testWidgets( 'can seek', (WidgetTester tester) async { await controller.initialize(); await controller.seekTo(const Duration(seconds: 3)); expect(controller.value.position, const Duration(seconds: 3)); }, ); testWidgets( 'can be paused', (WidgetTester tester) async { await controller.initialize(); // Mute to allow playing without DOM interaction on Web. // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes await controller.setVolume(0); // Play for a second, then pause, and then wait a second. await controller.play(); await tester.pumpAndSettle(_playDuration); await controller.pause(); final Duration pausedPosition = controller.value.position; await tester.pumpAndSettle(_playDuration); // Verify that we stopped playing after the pause. expect(controller.value.isPlaying, false); expect(controller.value.position, pausedPosition); }, ); testWidgets( 'stay paused when seeking after video completed', (WidgetTester tester) async { await controller.initialize(); // Mute to allow playing without DOM interaction on Web. // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes await controller.setVolume(0); final Duration tenMillisBeforeEnd = controller.value.duration - const Duration(milliseconds: 10); await controller.seekTo(tenMillisBeforeEnd); await controller.play(); await tester.pumpAndSettle(_playDuration); expect(controller.value.isPlaying, false); expect(controller.value.position, controller.value.duration); await controller.seekTo(tenMillisBeforeEnd); await tester.pumpAndSettle(_playDuration); expect(controller.value.isPlaying, false); expect(controller.value.position, tenMillisBeforeEnd); }, ); testWidgets( 'do not exceed duration on play after video completed', (WidgetTester tester) async { await controller.initialize(); // Mute to allow playing without DOM interaction on Web. // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes await controller.setVolume(0); await controller.seekTo( controller.value.duration - const Duration(milliseconds: 10)); await controller.play(); await tester.pumpAndSettle(_playDuration); expect(controller.value.isPlaying, false); expect(controller.value.position, controller.value.duration); await controller.play(); await tester.pumpAndSettle(_playDuration); expect(controller.value.position, lessThanOrEqualTo(controller.value.duration)); }, ); testWidgets('test video player view with local asset', (WidgetTester tester) async { Future started() async { await controller.initialize(); await controller.play(); return true; } await tester.pumpWidget(Material( child: Directionality( textDirection: TextDirection.ltr, child: Center( child: FutureBuilder( future: started(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.data ?? false) { return AspectRatio( aspectRatio: controller.value.aspectRatio, child: VideoPlayer(controller), ); } else { return const Text('waiting for video to load'); } }, ), ), ), )); await tester.pumpAndSettle(); expect(controller.value.isPlaying, true); }, skip: kIsWeb || // Web does not support local assets. // Extremely flaky on iOS: https://github.com/flutter/flutter/issues/86915 defaultTargetPlatform == TargetPlatform.iOS); }); group('file-based videos', () { setUp(() async { // Load the data from the asset. final String tempDir = (await getTemporaryDirectory()).path; final ByteData bytes = await rootBundle.load(_videoAssetKey); // Write it to a file to use as a source. final String filename = _videoAssetKey.split('/').last; final File file = File('$tempDir/$filename'); await file.writeAsBytes(bytes.buffer.asInt8List()); controller = VideoPlayerController.file(file); }); testWidgets('test video player using static file() method as constructor', (WidgetTester tester) async { await controller.initialize(); await controller.play(); expect(controller.value.isPlaying, true); await controller.pause(); expect(controller.value.isPlaying, false); }, skip: kIsWeb); }); group('network videos', () { setUp(() { controller = VideoPlayerController.network( getUrlForAssetAsNetworkSource(_videoAssetKey)); }); testWidgets( 'reports buffering status', (WidgetTester tester) async { await controller.initialize(); // Mute to allow playing without DOM interaction on Web. // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes await controller.setVolume(0); final Completer started = Completer(); final Completer ended = Completer(); controller.addListener(() { if (!started.isCompleted && controller.value.isBuffering) { started.complete(); } if (started.isCompleted && !controller.value.isBuffering && !ended.isCompleted) { ended.complete(); } }); await controller.play(); await controller.seekTo(const Duration(seconds: 5)); await tester.pumpAndSettle(_playDuration); await controller.pause(); expect(controller.value.isPlaying, false); expect(controller.value.position, (Duration position) => position > Duration.zero); await expectLater(started.future, completes); await expectLater(ended.future, completes); }, skip: !(kIsWeb || defaultTargetPlatform == TargetPlatform.android), ); }); // Audio playback is tested to prevent accidental regression, // but could be removed in the future. group('asset audios', () { setUp(() { controller = VideoPlayerController.asset('assets/Audio.mp3'); }); testWidgets('can be initialized', (WidgetTester tester) async { await controller.initialize(); expect(controller.value.isInitialized, true); expect(controller.value.position, Duration.zero); expect(controller.value.isPlaying, false); // Due to the duration calculation accuracy between platforms, // the milliseconds on Web will be a slightly different from natives. // The audio was made with 44100 Hz, 192 Kbps CBR, and 32 bits. expect( controller.value.duration, const Duration(seconds: 5, milliseconds: kIsWeb ? 42 : 41), ); }); testWidgets('can be played', (WidgetTester tester) async { await controller.initialize(); // Mute to allow playing without DOM interaction on Web. // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes await controller.setVolume(0); await controller.play(); await tester.pumpAndSettle(_playDuration); expect(controller.value.isPlaying, true); expect( controller.value.position, (Duration position) => position > Duration.zero, ); }); testWidgets('can seek', (WidgetTester tester) async { await controller.initialize(); await controller.seekTo(const Duration(seconds: 3)); expect(controller.value.position, const Duration(seconds: 3)); }); testWidgets('can be paused', (WidgetTester tester) async { await controller.initialize(); // Mute to allow playing without DOM interaction on Web. // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes await controller.setVolume(0); // Play for a second, then pause, and then wait a second. await controller.play(); await tester.pumpAndSettle(_playDuration); await controller.pause(); final Duration pausedPosition = controller.value.position; await tester.pumpAndSettle(_playDuration); // Verify that we stopped playing after the pause. expect(controller.value.isPlaying, false); expect(controller.value.position, pausedPosition); }); }); } ================================================ FILE: packages/video_player/video_player/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 UIRequiredDeviceCapabilities arm64 MinimumOSVersion 9.0 ================================================ FILE: packages/video_player/video_player/example/ios/Flutter/Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/video_player/video_player/example/ios/Flutter/Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/video_player/video_player/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/video_player/video_player/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/video_player/video_player/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end ================================================ FILE: packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/video_player/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "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" } } ================================================ FILE: packages/video_player/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: packages/video_player/video_player/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/video_player/video_player/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/video_player/video_player/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName video_player_example CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS NSAppTransportSecurity NSAllowsArbitraryLoads UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: packages/video_player/video_player/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/video_player/video_player/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; B0F5C77B94E32FB72444AE9F /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 20721C28387E1F78689EC502 /* libPods-Runner.a */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 20721C28387E1F78689EC502 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 2A2EA522BDC492279A91AB75 /* 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 = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 6CDC4DA5940705A6E7671616 /* 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 = ""; }; 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 = ""; }; 7BD232FD3BD3343A5F52AF50 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; B15EC39F4617FE1082B18834 /* 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 = ""; }; C18C242FF01156F58C0DAF1C /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( B0F5C77B94E32FB72444AE9F /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 05E898481BC29A7FA83AA441 /* Pods */ = { isa = PBXGroup; children = ( C18C242FF01156F58C0DAF1C /* Pods-Runner.debug.xcconfig */, B15EC39F4617FE1082B18834 /* Pods-Runner.release.xcconfig */, 6CDC4DA5940705A6E7671616 /* Pods-RunnerTests.debug.xcconfig */, 2A2EA522BDC492279A91AB75 /* Pods-RunnerTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; 23104BB9DCF267F65AD246F9 /* Frameworks */ = { isa = PBXGroup; children = ( 20721C28387E1F78689EC502 /* libPods-Runner.a */, 7BD232FD3BD3343A5F52AF50 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 05E898481BC29A7FA83AA441 /* Pods */, 23104BB9DCF267F65AD246F9 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( F31A669BD45D5A7C940BF077 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; 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"; }; F31A669BD45D5A7C940BF077 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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 = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.videoPlayerExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.videoPlayerExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/video_player/video_player/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/video_player/video_player/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/video_player/video_player/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/video_player/video_player/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/video_player/video_player/example/lib/basic.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This file is used to extract code samples for the README.md file. // Run update-excerpts if you modify this file. // ignore_for_file: library_private_types_in_public_api, public_member_api_docs // #docregion basic-example import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; void main() => runApp(const VideoApp()); /// Stateful widget to fetch and then display video content. class VideoApp extends StatefulWidget { const VideoApp({Key? key}) : super(key: key); @override _VideoAppState createState() => _VideoAppState(); } class _VideoAppState extends State { late VideoPlayerController _controller; @override void initState() { super.initState(); _controller = VideoPlayerController.network( 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4') ..initialize().then((_) { // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed. setState(() {}); }); } @override Widget build(BuildContext context) { return MaterialApp( title: 'Video Demo', home: Scaffold( body: Center( child: _controller.value.isInitialized ? AspectRatio( aspectRatio: _controller.value.aspectRatio, child: VideoPlayer(_controller), ) : Container(), ), floatingActionButton: FloatingActionButton( onPressed: () { setState(() { _controller.value.isPlaying ? _controller.pause() : _controller.play(); }); }, child: Icon( _controller.value.isPlaying ? Icons.pause : Icons.play_arrow, ), ), ), ); } @override void dispose() { super.dispose(); _controller.dispose(); } } // #enddocregion basic-example ================================================ FILE: packages/video_player/video_player/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 /// An example of using the plugin, controlling lifecycle and playback of the /// video. import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; void main() { runApp( MaterialApp( home: _App(), ), ); } class _App extends StatelessWidget { @override Widget build(BuildContext context) { return DefaultTabController( length: 3, child: Scaffold( key: const ValueKey('home_page'), appBar: AppBar( title: const Text('Video player example'), actions: [ IconButton( key: const ValueKey('push_tab'), icon: const Icon(Icons.navigation), onPressed: () { Navigator.push<_PlayerVideoAndPopPage>( context, MaterialPageRoute<_PlayerVideoAndPopPage>( builder: (BuildContext context) => _PlayerVideoAndPopPage(), ), ); }, ) ], bottom: const TabBar( isScrollable: true, tabs: [ Tab( icon: Icon(Icons.cloud), text: 'Remote', ), Tab(icon: Icon(Icons.insert_drive_file), text: 'Asset'), Tab(icon: Icon(Icons.list), text: 'List example'), ], ), ), body: TabBarView( children: [ _BumbleBeeRemoteVideo(), _ButterFlyAssetVideo(), _ButterFlyAssetVideoInList(), ], ), ), ); } } class _ButterFlyAssetVideoInList extends StatelessWidget { @override Widget build(BuildContext context) { return ListView( children: [ const _ExampleCard(title: 'Item a'), const _ExampleCard(title: 'Item b'), const _ExampleCard(title: 'Item c'), const _ExampleCard(title: 'Item d'), const _ExampleCard(title: 'Item e'), const _ExampleCard(title: 'Item f'), const _ExampleCard(title: 'Item g'), Card( child: Column(children: [ Column( children: [ const ListTile( leading: Icon(Icons.cake), title: Text('Video video'), ), Stack( alignment: FractionalOffset.bottomRight + const FractionalOffset(-0.1, -0.1), children: [ _ButterFlyAssetVideo(), Image.asset('assets/flutter-mark-square-64.png'), ]), ], ), ])), const _ExampleCard(title: 'Item h'), const _ExampleCard(title: 'Item i'), const _ExampleCard(title: 'Item j'), const _ExampleCard(title: 'Item k'), const _ExampleCard(title: 'Item l'), ], ); } } /// A filler card to show the video in a list of scrolling contents. class _ExampleCard extends StatelessWidget { const _ExampleCard({Key? key, required this.title}) : super(key: key); final String title; @override Widget build(BuildContext context) { return Card( child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Icons.airline_seat_flat_angled), title: Text(title), ), ButtonBar( children: [ TextButton( child: const Text('BUY TICKETS'), onPressed: () { /* ... */ }, ), TextButton( child: const Text('SELL TICKETS'), onPressed: () { /* ... */ }, ), ], ), ], ), ); } } class _ButterFlyAssetVideo extends StatefulWidget { @override _ButterFlyAssetVideoState createState() => _ButterFlyAssetVideoState(); } class _ButterFlyAssetVideoState extends State<_ButterFlyAssetVideo> { late VideoPlayerController _controller; @override void initState() { super.initState(); _controller = VideoPlayerController.asset('assets/Butterfly-209.mp4'); _controller.addListener(() { setState(() {}); }); _controller.setLooping(true); _controller.initialize().then((_) => setState(() {})); _controller.play(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SingleChildScrollView( child: Column( children: [ Container( padding: const EdgeInsets.only(top: 20.0), ), const Text('With assets mp4'), Container( padding: const EdgeInsets.all(20), child: AspectRatio( aspectRatio: _controller.value.aspectRatio, child: Stack( alignment: Alignment.bottomCenter, children: [ VideoPlayer(_controller), _ControlsOverlay(controller: _controller), VideoProgressIndicator(_controller, allowScrubbing: true), ], ), ), ), ], ), ); } } class _BumbleBeeRemoteVideo extends StatefulWidget { @override _BumbleBeeRemoteVideoState createState() => _BumbleBeeRemoteVideoState(); } class _BumbleBeeRemoteVideoState extends State<_BumbleBeeRemoteVideo> { late VideoPlayerController _controller; Future _loadCaptions() async { final String fileContents = await DefaultAssetBundle.of(context) .loadString('assets/bumble_bee_captions.vtt'); return WebVTTCaptionFile( fileContents); // For vtt files, use WebVTTCaptionFile } @override void initState() { super.initState(); _controller = VideoPlayerController.network( 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', closedCaptionFile: _loadCaptions(), videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true), ); _controller.addListener(() { setState(() {}); }); _controller.setLooping(true); _controller.initialize(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SingleChildScrollView( child: Column( children: [ Container(padding: const EdgeInsets.only(top: 20.0)), const Text('With remote mp4'), Container( padding: const EdgeInsets.all(20), child: AspectRatio( aspectRatio: _controller.value.aspectRatio, child: Stack( alignment: Alignment.bottomCenter, children: [ VideoPlayer(_controller), ClosedCaption(text: _controller.value.caption.text), _ControlsOverlay(controller: _controller), VideoProgressIndicator(_controller, allowScrubbing: true), ], ), ), ), ], ), ); } } class _ControlsOverlay extends StatelessWidget { const _ControlsOverlay({Key? key, required this.controller}) : super(key: key); static const List _exampleCaptionOffsets = [ Duration(seconds: -10), Duration(seconds: -3), Duration(seconds: -1, milliseconds: -500), Duration(milliseconds: -250), Duration.zero, Duration(milliseconds: 250), Duration(seconds: 1, milliseconds: 500), Duration(seconds: 3), Duration(seconds: 10), ]; static const List _examplePlaybackRates = [ 0.25, 0.5, 1.0, 1.5, 2.0, 3.0, 5.0, 10.0, ]; final VideoPlayerController controller; @override Widget build(BuildContext context) { return Stack( children: [ AnimatedSwitcher( duration: const Duration(milliseconds: 50), reverseDuration: const Duration(milliseconds: 200), child: controller.value.isPlaying ? const SizedBox.shrink() : Container( color: Colors.black26, child: const Center( child: Icon( Icons.play_arrow, color: Colors.white, size: 100.0, semanticLabel: 'Play', ), ), ), ), GestureDetector( onTap: () { controller.value.isPlaying ? controller.pause() : controller.play(); }, ), Align( alignment: Alignment.topLeft, child: PopupMenuButton( initialValue: controller.value.captionOffset, tooltip: 'Caption Offset', onSelected: (Duration delay) { controller.setCaptionOffset(delay); }, itemBuilder: (BuildContext context) { return >[ for (final Duration offsetDuration in _exampleCaptionOffsets) PopupMenuItem( value: offsetDuration, child: Text('${offsetDuration.inMilliseconds}ms'), ) ]; }, child: Padding( padding: const EdgeInsets.symmetric( // Using less vertical padding as the text is also longer // horizontally, so it feels like it would need more spacing // horizontally (matching the aspect ratio of the video). vertical: 12, horizontal: 16, ), child: Text('${controller.value.captionOffset.inMilliseconds}ms'), ), ), ), Align( alignment: Alignment.topRight, child: PopupMenuButton( initialValue: controller.value.playbackSpeed, tooltip: 'Playback speed', onSelected: (double speed) { controller.setPlaybackSpeed(speed); }, itemBuilder: (BuildContext context) { return >[ for (final double speed in _examplePlaybackRates) PopupMenuItem( value: speed, child: Text('${speed}x'), ) ]; }, child: Padding( padding: const EdgeInsets.symmetric( // Using less vertical padding as the text is also longer // horizontally, so it feels like it would need more spacing // horizontally (matching the aspect ratio of the video). vertical: 12, horizontal: 16, ), child: Text('${controller.value.playbackSpeed}x'), ), ), ), ], ); } } class _PlayerVideoAndPopPage extends StatefulWidget { @override _PlayerVideoAndPopPageState createState() => _PlayerVideoAndPopPageState(); } class _PlayerVideoAndPopPageState extends State<_PlayerVideoAndPopPage> { late VideoPlayerController _videoPlayerController; bool startedPlaying = false; @override void initState() { super.initState(); _videoPlayerController = VideoPlayerController.asset('assets/Butterfly-209.mp4'); _videoPlayerController.addListener(() { if (startedPlaying && !_videoPlayerController.value.isPlaying) { Navigator.pop(context); } }); } @override void dispose() { _videoPlayerController.dispose(); super.dispose(); } Future started() async { await _videoPlayerController.initialize(); await _videoPlayerController.play(); startedPlaying = true; return true; } @override Widget build(BuildContext context) { return Material( child: Center( child: FutureBuilder( future: started(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.data ?? false) { return AspectRatio( aspectRatio: _videoPlayerController.value.aspectRatio, child: VideoPlayer(_videoPlayerController), ); } else { return const Text('waiting for video to load'); } }, ), ), ); } } ================================================ FILE: packages/video_player/video_player/example/pubspec.yaml ================================================ name: video_player_example description: Demonstrates how to use the video_player plugin. publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter video_player: # When depending on this package from a real application you should use: # video_player: ^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: ../ dev_dependencies: build_runner: ^2.1.10 flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter path_provider: ^2.0.6 test: any flutter: uses-material-design: true assets: - assets/flutter-mark-square-64.png - assets/Butterfly-209.mp4 - assets/Butterfly-209.webm - assets/bumble_bee_captions.srt - assets/bumble_bee_captions.vtt - assets/Audio.mp3 ================================================ FILE: packages/video_player/video_player/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/video_player/video_player/example/test_driver/video_player.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_driver/driver_extension.dart'; import 'package:video_player_example/main.dart' as app; void main() { enableFlutterDriverExtension(); app.main(); } ================================================ FILE: packages/video_player/video_player/example/test_driver/video_player_test.dart ================================================ // Copyright 2013 The Flutter 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_driver/flutter_driver.dart'; import 'package:test/test.dart'; Future main() async { final FlutterDriver driver = await FlutterDriver.connect(); tearDownAll(() async { await driver.close(); }); // TODO(cyanglaz): Use TabBar tabs to navigate between pages after https://github.com/flutter/flutter/issues/16991 is fixed. // TODO(cyanglaz): Un-skip the test after https://github.com/flutter/flutter/issues/43012 is fixed test('Push a page contains video and pop back, do not crash.', () async { final SerializableFinder pushTab = find.byValueKey('push_tab'); await driver.waitFor(pushTab); await driver.tap(pushTab); await driver.waitForAbsent(pushTab); await driver.waitFor(find.byValueKey('home_page')); await driver.waitUntilNoTransientCallbacks(); final Health health = await driver.checkHealth(); expect(health.status, HealthStatus.ok); }, skip: 'Cirrus CI currently hangs while playing videos'); } ================================================ FILE: packages/video_player/video_player/example/web/index.html ================================================ video_player web example ================================================ FILE: packages/video_player/video_player/lib/src/closed_caption_file.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart' show objectRuntimeType; import 'sub_rip.dart'; import 'web_vtt.dart'; export 'sub_rip.dart' show SubRipCaptionFile; export 'web_vtt.dart' show WebVTTCaptionFile; /// A structured representation of a parsed closed caption file. /// /// A closed caption file includes a list of captions, each with a start and end /// time for when the given closed caption should be displayed. /// /// The [captions] are a list of all captions in a file, in the order that they /// appeared in the file. /// /// See: /// * [SubRipCaptionFile]. /// * [WebVTTCaptionFile]. abstract class ClosedCaptionFile { /// The full list of captions from a given file. /// /// The [captions] will be in the order that they appear in the given file. List get captions; } /// A representation of a single caption. /// /// A typical closed captioning file will include several [Caption]s, each /// linked to a start and end time. class Caption { /// Creates a new [Caption] object. /// /// This is not recommended for direct use unless you are writing a parser for /// a new closed captioning file type. const Caption({ required this.number, required this.start, required this.end, required this.text, }); /// The number that this caption was assigned. final int number; /// When in the given video should this [Caption] begin displaying. final Duration start; /// When in the given video should this [Caption] be dismissed. final Duration end; /// The actual text that should appear on screen to be read between [start] /// and [end]. final String text; /// A no caption object. This is a caption with [start] and [end] durations of zero, /// and an empty [text] string. static const Caption none = Caption( number: 0, start: Duration.zero, end: Duration.zero, text: '', ); @override String toString() { return '${objectRuntimeType(this, 'Caption')}(' 'number: $number, ' 'start: $start, ' 'end: $end, ' 'text: $text)'; } } ================================================ FILE: packages/video_player/video_player/lib/src/sub_rip.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:convert'; import 'closed_caption_file.dart'; /// Represents a [ClosedCaptionFile], parsed from the SubRip file format. /// See: https://en.wikipedia.org/wiki/SubRip class SubRipCaptionFile extends ClosedCaptionFile { /// Parses a string into a [ClosedCaptionFile], assuming [fileContents] is in /// the SubRip file format. /// * See: https://en.wikipedia.org/wiki/SubRip SubRipCaptionFile(this.fileContents) : _captions = _parseCaptionsFromSubRipString(fileContents); /// The entire body of the SubRip file. // TODO(cyanglaz): Remove this public member as it doesn't seem need to exist. // https://github.com/flutter/flutter/issues/90471 final String fileContents; @override List get captions => _captions; final List _captions; } List _parseCaptionsFromSubRipString(String file) { final List captions = []; for (final List captionLines in _readSubRipFile(file)) { if (captionLines.length < 3) { break; } final int captionNumber = int.parse(captionLines[0]); final _CaptionRange captionRange = _CaptionRange.fromSubRipString(captionLines[1]); final String text = captionLines.sublist(2).join('\n'); final Caption newCaption = Caption( number: captionNumber, start: captionRange.start, end: captionRange.end, text: text, ); if (newCaption.start != newCaption.end) { captions.add(newCaption); } } return captions; } class _CaptionRange { _CaptionRange(this.start, this.end); final Duration start; final Duration end; // Assumes format from an SubRip file. // For example: // 00:01:54,724 --> 00:01:56,760 static _CaptionRange fromSubRipString(String line) { final RegExp format = RegExp(_subRipTimeStamp + _subRipArrow + _subRipTimeStamp); if (!format.hasMatch(line)) { return _CaptionRange(Duration.zero, Duration.zero); } final List times = line.split(_subRipArrow); final Duration start = _parseSubRipTimestamp(times[0]); final Duration end = _parseSubRipTimestamp(times[1]); return _CaptionRange(start, end); } } // Parses a time stamp in an SubRip file into a Duration. // For example: // // _parseSubRipTimestamp('00:01:59,084') // returns // Duration(hours: 0, minutes: 1, seconds: 59, milliseconds: 084) Duration _parseSubRipTimestamp(String timestampString) { if (!RegExp(_subRipTimeStamp).hasMatch(timestampString)) { return Duration.zero; } final List commaSections = timestampString.split(','); final List hoursMinutesSeconds = commaSections[0].split(':'); final int hours = int.parse(hoursMinutesSeconds[0]); final int minutes = int.parse(hoursMinutesSeconds[1]); final int seconds = int.parse(hoursMinutesSeconds[2]); final int milliseconds = int.parse(commaSections[1]); return Duration( hours: hours, minutes: minutes, seconds: seconds, milliseconds: milliseconds, ); } // Reads on SubRip file and splits it into Lists of strings where each list is one // caption. List> _readSubRipFile(String file) { final List lines = LineSplitter.split(file).toList(); final List> captionStrings = >[]; List currentCaption = []; int lineIndex = 0; for (final String line in lines) { final bool isLineBlank = line.trim().isEmpty; if (!isLineBlank) { currentCaption.add(line); } if (isLineBlank || lineIndex == lines.length - 1) { captionStrings.add(currentCaption); currentCaption = []; } lineIndex += 1; } return captionStrings; } const String _subRipTimeStamp = r'\d\d:\d\d:\d\d,\d\d\d'; const String _subRipArrow = r' --> '; ================================================ FILE: packages/video_player/video_player/lib/src/web_vtt.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:convert'; import 'package:html/dom.dart'; import 'package:html/parser.dart' as html_parser; import 'closed_caption_file.dart'; /// Represents a [ClosedCaptionFile], parsed from the WebVTT file format. /// See: https://en.wikipedia.org/wiki/WebVTT class WebVTTCaptionFile extends ClosedCaptionFile { /// Parses a string into a [ClosedCaptionFile], assuming [fileContents] is in /// the WebVTT file format. /// * See: https://en.wikipedia.org/wiki/WebVTT WebVTTCaptionFile(String fileContents) : _captions = _parseCaptionsFromWebVTTString(fileContents); @override List get captions => _captions; final List _captions; } List _parseCaptionsFromWebVTTString(String file) { final List captions = []; // Ignore metadata final Set metadata = {'HEADER', 'NOTE', 'REGION', 'WEBVTT'}; int captionNumber = 1; for (final List captionLines in _readWebVTTFile(file)) { // CaptionLines represent a complete caption. // E.g // [ // [00:00.000 --> 01:24.000 align:center] // ['Introduction'] // ] // If caption has just header or time, but no text, `captionLines.length` will be 1. if (captionLines.length < 2) { continue; } // If caption has header equal metadata, ignore. final String metadaType = captionLines[0].split(' ')[0]; if (metadata.contains(metadaType)) { continue; } // Caption has header final bool hasHeader = captionLines.length > 2; if (hasHeader) { final int? tryParseCaptionNumber = int.tryParse(captionLines[0]); if (tryParseCaptionNumber != null) { captionNumber = tryParseCaptionNumber; } } final _CaptionRange? captionRange = _CaptionRange.fromWebVTTString( hasHeader ? captionLines[1] : captionLines[0], ); if (captionRange == null) { continue; } final String text = captionLines.sublist(hasHeader ? 2 : 1).join('\n'); // TODO(cyanglaz): Handle special syntax in VTT captions. // https://github.com/flutter/flutter/issues/90007. final String textWithoutFormat = _extractTextFromHtml(text); final Caption newCaption = Caption( number: captionNumber, start: captionRange.start, end: captionRange.end, text: textWithoutFormat, ); captions.add(newCaption); captionNumber++; } return captions; } class _CaptionRange { _CaptionRange(this.start, this.end); final Duration start; final Duration end; // Assumes format from an VTT file. // For example: // 00:09.000 --> 00:11.000 static _CaptionRange? fromWebVTTString(String line) { final RegExp format = RegExp(_webVTTTimeStamp + _webVTTArrow + _webVTTTimeStamp); if (!format.hasMatch(line)) { return null; } final List times = line.split(_webVTTArrow); final Duration? start = _parseWebVTTTimestamp(times[0]); final Duration? end = _parseWebVTTTimestamp(times[1]); if (start == null || end == null) { return null; } return _CaptionRange(start, end); } } String _extractTextFromHtml(String htmlString) { final Document document = html_parser.parse(htmlString); final Element? body = document.body; if (body == null) { return ''; } final Element? bodyElement = html_parser.parse(body.text).documentElement; return bodyElement?.text ?? ''; } // Parses a time stamp in an VTT file into a Duration. // // Returns `null` if `timestampString` is in an invalid format. // // For example: // // _parseWebVTTTimestamp('00:01:08.430') // returns // Duration(hours: 0, minutes: 1, seconds: 8, milliseconds: 430) Duration? _parseWebVTTTimestamp(String timestampString) { if (!RegExp(_webVTTTimeStamp).hasMatch(timestampString)) { return null; } final List dotSections = timestampString.split('.'); final List timeComponents = dotSections[0].split(':'); // Validating and parsing the `timestampString`, invalid format will result this method // to return `null`. See https://www.w3.org/TR/webvtt1/#webvtt-timestamp for valid // WebVTT timestamp format. if (timeComponents.length > 3 || timeComponents.length < 2) { return null; } int hours = 0; if (timeComponents.length == 3) { final String hourString = timeComponents.removeAt(0); if (hourString.length < 2) { return null; } hours = int.parse(hourString); } final int minutes = int.parse(timeComponents.removeAt(0)); if (minutes < 0 || minutes > 59) { return null; } final int seconds = int.parse(timeComponents.removeAt(0)); if (seconds < 0 || seconds > 59) { return null; } final List milisecondsStyles = dotSections[1].split(' '); // TODO(cyanglaz): Handle caption styles. // https://github.com/flutter/flutter/issues/90009. // ```dart // if (milisecondsStyles.length > 1) { // List styles = milisecondsStyles.sublist(1); // } // ``` // For a better readable code style, style parsing should happen before // calling this method. See: https://github.com/flutter/plugins/pull/2878/files#r713381134. final int milliseconds = int.parse(milisecondsStyles[0]); return Duration( hours: hours, minutes: minutes, seconds: seconds, milliseconds: milliseconds, ); } // Reads on VTT file and splits it into Lists of strings where each list is one // caption. List> _readWebVTTFile(String file) { final List lines = LineSplitter.split(file).toList(); final List> captionStrings = >[]; List currentCaption = []; int lineIndex = 0; for (final String line in lines) { final bool isLineBlank = line.trim().isEmpty; if (!isLineBlank) { currentCaption.add(line); } if (isLineBlank || lineIndex == lines.length - 1) { captionStrings.add(currentCaption); currentCaption = []; } lineIndex += 1; } return captionStrings; } const String _webVTTTimeStamp = r'(\d+):(\d{2})(:\d{2})?\.(\d{3})'; const String _webVTTArrow = r' --> '; ================================================ FILE: packages/video_player/video_player/lib/video_player.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; import 'dart:math' as math; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; import 'src/closed_caption_file.dart'; export 'package:video_player_platform_interface/video_player_platform_interface.dart' show DurationRange, DataSourceType, VideoFormat, VideoPlayerOptions; export 'src/closed_caption_file.dart'; VideoPlayerPlatform? _lastVideoPlayerPlatform; VideoPlayerPlatform get _videoPlayerPlatform { final VideoPlayerPlatform currentInstance = VideoPlayerPlatform.instance; if (_lastVideoPlayerPlatform != currentInstance) { // This will clear all open videos on the platform when a full restart is // performed. currentInstance.init(); _lastVideoPlayerPlatform = currentInstance; } return currentInstance; } /// The duration, current position, buffering state, error state and settings /// of a [VideoPlayerController]. class VideoPlayerValue { /// Constructs a video with the given values. Only [duration] is required. The /// rest will initialize with default values when unset. VideoPlayerValue({ required this.duration, this.size = Size.zero, this.position = Duration.zero, this.caption = Caption.none, this.captionOffset = Duration.zero, this.buffered = const [], this.isInitialized = false, this.isPlaying = false, this.isLooping = false, this.isBuffering = false, this.volume = 1.0, this.playbackSpeed = 1.0, this.rotationCorrection = 0, this.errorDescription, }); /// Returns an instance for a video that hasn't been loaded. VideoPlayerValue.uninitialized() : this(duration: Duration.zero, isInitialized: false); /// Returns an instance with the given [errorDescription]. VideoPlayerValue.erroneous(String errorDescription) : this( duration: Duration.zero, isInitialized: false, errorDescription: errorDescription); /// This constant is just to indicate that parameter is not passed to [copyWith] /// workaround for this issue https://github.com/dart-lang/language/issues/2009 static const String _defaultErrorDescription = 'defaultErrorDescription'; /// The total duration of the video. /// /// The duration is [Duration.zero] if the video hasn't been initialized. final Duration duration; /// The current playback position. final Duration position; /// The [Caption] that should be displayed based on the current [position]. /// /// This field will never be null. If there is no caption for the current /// [position], this will be a [Caption.none] object. final Caption caption; /// The [Duration] that should be used to offset the current [position] to get the correct [Caption]. /// /// Defaults to Duration.zero. final Duration captionOffset; /// The currently buffered ranges. final List buffered; /// True if the video is playing. False if it's paused. final bool isPlaying; /// True if the video is looping. final bool isLooping; /// True if the video is currently buffering. final bool isBuffering; /// The current volume of the playback. final double volume; /// The current speed of the playback. final double playbackSpeed; /// A description of the error if present. /// /// If [hasError] is false this is `null`. final String? errorDescription; /// The [size] of the currently loaded video. final Size size; /// Degrees to rotate the video (clockwise) so it is displayed correctly. final int rotationCorrection; /// Indicates whether or not the video has been loaded and is ready to play. final bool isInitialized; /// Indicates whether or not the video is in an error state. If this is true /// [errorDescription] should have information about the problem. bool get hasError => errorDescription != null; /// Returns [size.width] / [size.height]. /// /// Will return `1.0` if: /// * [isInitialized] is `false` /// * [size.width], or [size.height] is equal to `0.0` /// * aspect ratio would be less than or equal to `0.0` double get aspectRatio { if (!isInitialized || size.width == 0 || size.height == 0) { return 1.0; } final double aspectRatio = size.width / size.height; if (aspectRatio <= 0) { return 1.0; } return aspectRatio; } /// Returns a new instance that has the same values as this current instance, /// except for any overrides passed in as arguments to [copyWith]. VideoPlayerValue copyWith({ Duration? duration, Size? size, Duration? position, Caption? caption, Duration? captionOffset, List? buffered, bool? isInitialized, bool? isPlaying, bool? isLooping, bool? isBuffering, double? volume, double? playbackSpeed, int? rotationCorrection, String? errorDescription = _defaultErrorDescription, }) { return VideoPlayerValue( duration: duration ?? this.duration, size: size ?? this.size, position: position ?? this.position, caption: caption ?? this.caption, captionOffset: captionOffset ?? this.captionOffset, buffered: buffered ?? this.buffered, isInitialized: isInitialized ?? this.isInitialized, isPlaying: isPlaying ?? this.isPlaying, isLooping: isLooping ?? this.isLooping, isBuffering: isBuffering ?? this.isBuffering, volume: volume ?? this.volume, playbackSpeed: playbackSpeed ?? this.playbackSpeed, rotationCorrection: rotationCorrection ?? this.rotationCorrection, errorDescription: errorDescription != _defaultErrorDescription ? errorDescription : this.errorDescription, ); } @override String toString() { return '${objectRuntimeType(this, 'VideoPlayerValue')}(' 'duration: $duration, ' 'size: $size, ' 'position: $position, ' 'caption: $caption, ' 'captionOffset: $captionOffset, ' 'buffered: [${buffered.join(', ')}], ' 'isInitialized: $isInitialized, ' 'isPlaying: $isPlaying, ' 'isLooping: $isLooping, ' 'isBuffering: $isBuffering, ' 'volume: $volume, ' 'playbackSpeed: $playbackSpeed, ' 'errorDescription: $errorDescription)'; } } /// Controls a platform video player, and provides updates when the state is /// changing. /// /// Instances must be initialized with initialize. /// /// The video is displayed in a Flutter app by creating a [VideoPlayer] widget. /// /// To reclaim the resources used by the player call [dispose]. /// /// After [dispose] all further calls are ignored. class VideoPlayerController extends ValueNotifier { /// Constructs a [VideoPlayerController] playing a video from an asset. /// /// The name of the asset is given by the [dataSource] argument and must not be /// null. The [package] argument must be non-null when the asset comes from a /// package and null otherwise. VideoPlayerController.asset(this.dataSource, {this.package, Future? closedCaptionFile, this.videoPlayerOptions}) : _closedCaptionFileFuture = closedCaptionFile, dataSourceType = DataSourceType.asset, formatHint = null, httpHeaders = const {}, super(VideoPlayerValue(duration: Duration.zero)); /// Constructs a [VideoPlayerController] playing a video from obtained from /// the network. /// /// The URI for the video is given by the [dataSource] argument and must not be /// null. /// **Android only**: The [formatHint] option allows the caller to override /// the video format detection code. /// [httpHeaders] option allows to specify HTTP headers /// for the request to the [dataSource]. VideoPlayerController.network( this.dataSource, { this.formatHint, Future? closedCaptionFile, this.videoPlayerOptions, this.httpHeaders = const {}, }) : _closedCaptionFileFuture = closedCaptionFile, dataSourceType = DataSourceType.network, package = null, super(VideoPlayerValue(duration: Duration.zero)); /// Constructs a [VideoPlayerController] playing a video from a file. /// /// This will load the file from a file:// URI constructed from [file]'s path. VideoPlayerController.file(File file, {Future? closedCaptionFile, this.videoPlayerOptions}) : _closedCaptionFileFuture = closedCaptionFile, dataSource = Uri.file(file.absolute.path).toString(), dataSourceType = DataSourceType.file, package = null, formatHint = null, httpHeaders = const {}, super(VideoPlayerValue(duration: Duration.zero)); /// Constructs a [VideoPlayerController] playing a video from a contentUri. /// /// This will load the video from the input content-URI. /// This is supported on Android only. VideoPlayerController.contentUri(Uri contentUri, {Future? closedCaptionFile, this.videoPlayerOptions}) : assert(defaultTargetPlatform == TargetPlatform.android, 'VideoPlayerController.contentUri is only supported on Android.'), _closedCaptionFileFuture = closedCaptionFile, dataSource = contentUri.toString(), dataSourceType = DataSourceType.contentUri, package = null, formatHint = null, httpHeaders = const {}, super(VideoPlayerValue(duration: Duration.zero)); /// The URI to the video file. This will be in different formats depending on /// the [DataSourceType] of the original video. final String dataSource; /// HTTP headers used for the request to the [dataSource]. /// Only for [VideoPlayerController.network]. /// Always empty for other video types. final Map httpHeaders; /// **Android only**. Will override the platform's generic file format /// detection with whatever is set here. final VideoFormat? formatHint; /// Describes the type of data source this [VideoPlayerController] /// is constructed with. final DataSourceType dataSourceType; /// Provide additional configuration options (optional). Like setting the audio mode to mix final VideoPlayerOptions? videoPlayerOptions; /// Only set for [asset] videos. The package that the asset was loaded from. final String? package; Future? _closedCaptionFileFuture; ClosedCaptionFile? _closedCaptionFile; Timer? _timer; bool _isDisposed = false; Completer? _creatingCompleter; StreamSubscription? _eventSubscription; _VideoAppLifeCycleObserver? _lifeCycleObserver; /// The id of a texture that hasn't been initialized. @visibleForTesting static const int kUninitializedTextureId = -1; int _textureId = kUninitializedTextureId; /// This is just exposed for testing. It shouldn't be used by anyone depending /// on the plugin. @visibleForTesting int get textureId => _textureId; /// Attempts to open the given [dataSource] and load metadata about the video. Future initialize() async { final bool allowBackgroundPlayback = videoPlayerOptions?.allowBackgroundPlayback ?? false; if (!allowBackgroundPlayback) { _lifeCycleObserver = _VideoAppLifeCycleObserver(this); } _lifeCycleObserver?.initialize(); _creatingCompleter = Completer(); late DataSource dataSourceDescription; switch (dataSourceType) { case DataSourceType.asset: dataSourceDescription = DataSource( sourceType: DataSourceType.asset, asset: dataSource, package: package, ); break; case DataSourceType.network: dataSourceDescription = DataSource( sourceType: DataSourceType.network, uri: dataSource, formatHint: formatHint, httpHeaders: httpHeaders, ); break; case DataSourceType.file: dataSourceDescription = DataSource( sourceType: DataSourceType.file, uri: dataSource, ); break; case DataSourceType.contentUri: dataSourceDescription = DataSource( sourceType: DataSourceType.contentUri, uri: dataSource, ); break; } if (videoPlayerOptions?.mixWithOthers != null) { await _videoPlayerPlatform .setMixWithOthers(videoPlayerOptions!.mixWithOthers); } _textureId = (await _videoPlayerPlatform.create(dataSourceDescription)) ?? kUninitializedTextureId; _creatingCompleter!.complete(null); final Completer initializingCompleter = Completer(); void eventListener(VideoEvent event) { if (_isDisposed) { return; } switch (event.eventType) { case VideoEventType.initialized: value = value.copyWith( duration: event.duration, size: event.size, rotationCorrection: event.rotationCorrection, isInitialized: event.duration != null, errorDescription: null, ); initializingCompleter.complete(null); _applyLooping(); _applyVolume(); _applyPlayPause(); break; case VideoEventType.completed: // In this case we need to stop _timer, set isPlaying=false, and // position=value.duration. Instead of setting the values directly, // we use pause() and seekTo() to ensure the platform stops playing // and seeks to the last frame of the video. pause().then((void pauseResult) => seekTo(value.duration)); break; case VideoEventType.bufferingUpdate: value = value.copyWith(buffered: event.buffered); break; case VideoEventType.bufferingStart: value = value.copyWith(isBuffering: true); break; case VideoEventType.bufferingEnd: value = value.copyWith(isBuffering: false); break; case VideoEventType.unknown: break; } } if (_closedCaptionFileFuture != null) { await _updateClosedCaptionWithFuture(_closedCaptionFileFuture); } void errorListener(Object obj) { final PlatformException e = obj as PlatformException; value = VideoPlayerValue.erroneous(e.message!); _timer?.cancel(); if (!initializingCompleter.isCompleted) { initializingCompleter.completeError(obj); } } _eventSubscription = _videoPlayerPlatform .videoEventsFor(_textureId) .listen(eventListener, onError: errorListener); return initializingCompleter.future; } @override Future dispose() async { if (_isDisposed) { return; } if (_creatingCompleter != null) { await _creatingCompleter!.future; if (!_isDisposed) { _isDisposed = true; _timer?.cancel(); await _eventSubscription?.cancel(); await _videoPlayerPlatform.dispose(_textureId); } _lifeCycleObserver?.dispose(); } _isDisposed = true; super.dispose(); } /// Starts playing the video. /// /// If the video is at the end, this method starts playing from the beginning. /// /// This method returns a future that completes as soon as the "play" command /// has been sent to the platform, not when playback itself is totally /// finished. Future play() async { if (value.position == value.duration) { await seekTo(Duration.zero); } value = value.copyWith(isPlaying: true); await _applyPlayPause(); } /// Sets whether or not the video should loop after playing once. See also /// [VideoPlayerValue.isLooping]. Future setLooping(bool looping) async { value = value.copyWith(isLooping: looping); await _applyLooping(); } /// Pauses the video. Future pause() async { value = value.copyWith(isPlaying: false); await _applyPlayPause(); } Future _applyLooping() async { if (_isDisposedOrNotInitialized) { return; } await _videoPlayerPlatform.setLooping(_textureId, value.isLooping); } Future _applyPlayPause() async { if (_isDisposedOrNotInitialized) { return; } if (value.isPlaying) { await _videoPlayerPlatform.play(_textureId); // Cancel previous timer. _timer?.cancel(); _timer = Timer.periodic( const Duration(milliseconds: 500), (Timer timer) async { if (_isDisposed) { return; } final Duration? newPosition = await position; if (newPosition == null) { return; } _updatePosition(newPosition); }, ); // This ensures that the correct playback speed is always applied when // playing back. This is necessary because we do not set playback speed // when paused. await _applyPlaybackSpeed(); } else { _timer?.cancel(); await _videoPlayerPlatform.pause(_textureId); } } Future _applyVolume() async { if (_isDisposedOrNotInitialized) { return; } await _videoPlayerPlatform.setVolume(_textureId, value.volume); } Future _applyPlaybackSpeed() async { if (_isDisposedOrNotInitialized) { return; } // Setting the playback speed on iOS will trigger the video to play. We // prevent this from happening by not applying the playback speed until // the video is manually played from Flutter. if (!value.isPlaying) { return; } await _videoPlayerPlatform.setPlaybackSpeed( _textureId, value.playbackSpeed, ); } /// The position in the current video. Future get position async { if (_isDisposed) { return null; } return _videoPlayerPlatform.getPosition(_textureId); } /// Sets the video's current timestamp to be at [moment]. The next /// time the video is played it will resume from the given [moment]. /// /// If [moment] is outside of the video's full range it will be automatically /// and silently clamped. Future seekTo(Duration position) async { if (_isDisposedOrNotInitialized) { return; } if (position > value.duration) { position = value.duration; } else if (position < Duration.zero) { position = Duration.zero; } await _videoPlayerPlatform.seekTo(_textureId, position); _updatePosition(position); } /// Sets the audio volume of [this]. /// /// [volume] indicates a value between 0.0 (silent) and 1.0 (full volume) on a /// linear scale. Future setVolume(double volume) async { value = value.copyWith(volume: volume.clamp(0.0, 1.0)); await _applyVolume(); } /// Sets the playback speed of [this]. /// /// [speed] indicates a speed value with different platforms accepting /// different ranges for speed values. The [speed] must be greater than 0. /// /// The values will be handled as follows: /// * On web, the audio will be muted at some speed when the browser /// determines that the sound would not be useful anymore. For example, /// "Gecko mutes the sound outside the range `0.25` to `5.0`" (see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/playbackRate). /// * On Android, some very extreme speeds will not be played back accurately. /// Instead, your video will still be played back, but the speed will be /// clamped by ExoPlayer (but the values are allowed by the player, like on /// web). /// * On iOS, you can sometimes not go above `2.0` playback speed on a video. /// An error will be thrown for if the option is unsupported. It is also /// possible that your specific video cannot be slowed down, in which case /// the plugin also reports errors. Future setPlaybackSpeed(double speed) async { if (speed < 0) { throw ArgumentError.value( speed, 'Negative playback speeds are generally unsupported.', ); } else if (speed == 0) { throw ArgumentError.value( speed, 'Zero playback speed is generally unsupported. Consider using [pause].', ); } value = value.copyWith(playbackSpeed: speed); await _applyPlaybackSpeed(); } /// Sets the caption offset. /// /// The [offset] will be used when getting the correct caption for a specific position. /// The [offset] can be positive or negative. /// /// The values will be handled as follows: /// * 0: This is the default behaviour. No offset will be applied. /// * >0: The caption will have a negative offset. So you will get caption text from the past. /// * <0: The caption will have a positive offset. So you will get caption text from the future. void setCaptionOffset(Duration offset) { value = value.copyWith( captionOffset: offset, caption: _getCaptionAt(value.position), ); } /// The closed caption based on the current [position] in the video. /// /// If there are no closed captions at the current [position], this will /// return an empty [Caption]. /// /// If no [closedCaptionFile] was specified, this will always return an empty /// [Caption]. Caption _getCaptionAt(Duration position) { if (_closedCaptionFile == null) { return Caption.none; } final Duration delayedPosition = position + value.captionOffset; // TODO(johnsonmh): This would be more efficient as a binary search. for (final Caption caption in _closedCaptionFile!.captions) { if (caption.start <= delayedPosition && caption.end >= delayedPosition) { return caption; } } return Caption.none; } /// Returns the file containing closed captions for the video, if any. Future? get closedCaptionFile { return _closedCaptionFileFuture; } /// Sets a closed caption file. /// /// If [closedCaptionFile] is null, closed captions will be removed. Future setClosedCaptionFile( Future? closedCaptionFile, ) async { await _updateClosedCaptionWithFuture(closedCaptionFile); _closedCaptionFileFuture = closedCaptionFile; } Future _updateClosedCaptionWithFuture( Future? closedCaptionFile, ) async { _closedCaptionFile = await closedCaptionFile; value = value.copyWith(caption: _getCaptionAt(value.position)); } void _updatePosition(Duration position) { value = value.copyWith( position: position, caption: _getCaptionAt(position), ); } @override void removeListener(VoidCallback listener) { // Prevent VideoPlayer from causing an exception to be thrown when attempting to // remove its own listener after the controller has already been disposed. if (!_isDisposed) { super.removeListener(listener); } } bool get _isDisposedOrNotInitialized => _isDisposed || !value.isInitialized; } class _VideoAppLifeCycleObserver extends Object with WidgetsBindingObserver { _VideoAppLifeCycleObserver(this._controller); bool _wasPlayingBeforePause = false; final VideoPlayerController _controller; void initialize() { _ambiguate(WidgetsBinding.instance)!.addObserver(this); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.paused) { _wasPlayingBeforePause = _controller.value.isPlaying; _controller.pause(); } else if (state == AppLifecycleState.resumed) { if (_wasPlayingBeforePause) { _controller.play(); } } } void dispose() { _ambiguate(WidgetsBinding.instance)!.removeObserver(this); } } /// Widget that displays the video controlled by [controller]. class VideoPlayer extends StatefulWidget { /// Uses the given [controller] for all video rendered in this widget. const VideoPlayer(this.controller, {Key? key}) : super(key: key); /// The [VideoPlayerController] responsible for the video being rendered in /// this widget. final VideoPlayerController controller; @override State createState() => _VideoPlayerState(); } class _VideoPlayerState extends State { _VideoPlayerState() { _listener = () { final int newTextureId = widget.controller.textureId; if (newTextureId != _textureId) { setState(() { _textureId = newTextureId; }); } }; } late VoidCallback _listener; late int _textureId; @override void initState() { super.initState(); _textureId = widget.controller.textureId; // Need to listen for initialization events since the actual texture ID // becomes available after asynchronous initialization finishes. widget.controller.addListener(_listener); } @override void didUpdateWidget(VideoPlayer oldWidget) { super.didUpdateWidget(oldWidget); oldWidget.controller.removeListener(_listener); _textureId = widget.controller.textureId; widget.controller.addListener(_listener); } @override void deactivate() { super.deactivate(); widget.controller.removeListener(_listener); } @override Widget build(BuildContext context) { return _textureId == VideoPlayerController.kUninitializedTextureId ? Container() : _VideoPlayerWithRotation( rotation: widget.controller.value.rotationCorrection, child: _videoPlayerPlatform.buildView(_textureId), ); } } class _VideoPlayerWithRotation extends StatelessWidget { const _VideoPlayerWithRotation( {Key? key, required this.rotation, required this.child}) : super(key: key); final int rotation; final Widget child; @override Widget build(BuildContext context) => rotation == 0 ? child : Transform.rotate( angle: rotation * math.pi / 180, child: child, ); } /// Used to configure the [VideoProgressIndicator] widget's colors for how it /// describes the video's status. /// /// The widget uses default colors that are customizable through this class. class VideoProgressColors { /// Any property can be set to any color. They each have defaults. /// /// [playedColor] defaults to red at 70% opacity. This fills up a portion of /// the [VideoProgressIndicator] to represent how much of the video has played /// so far. /// /// [bufferedColor] defaults to blue at 20% opacity. This fills up a portion /// of [VideoProgressIndicator] to represent how much of the video has /// buffered so far. /// /// [backgroundColor] defaults to gray at 50% opacity. This is the background /// color behind both [playedColor] and [bufferedColor] to denote the total /// size of the video compared to either of those values. const VideoProgressColors({ this.playedColor = const Color.fromRGBO(255, 0, 0, 0.7), this.bufferedColor = const Color.fromRGBO(50, 50, 200, 0.2), this.backgroundColor = const Color.fromRGBO(200, 200, 200, 0.5), }); /// [playedColor] defaults to red at 70% opacity. This fills up a portion of /// the [VideoProgressIndicator] to represent how much of the video has played /// so far. final Color playedColor; /// [bufferedColor] defaults to blue at 20% opacity. This fills up a portion /// of [VideoProgressIndicator] to represent how much of the video has /// buffered so far. final Color bufferedColor; /// [backgroundColor] defaults to gray at 50% opacity. This is the background /// color behind both [playedColor] and [bufferedColor] to denote the total /// size of the video compared to either of those values. final Color backgroundColor; } /// 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 State createState() => _VideoScrubberState(); } class _VideoScrubberState extends State { bool _controllerWasPlaying = false; VideoPlayerController get controller => widget.controller; @override Widget build(BuildContext context) { void seekToRelativePosition(Offset globalPosition) { final RenderBox box = context.findRenderObject()! as RenderBox; final Offset tapPos = box.globalToLocal(globalPosition); final double relative = tapPos.dx / box.size.width; final Duration position = controller.value.duration * relative; controller.seekTo(position); } return GestureDetector( behavior: HitTestBehavior.opaque, child: widget.child, onHorizontalDragStart: (DragStartDetails details) { if (!controller.value.isInitialized) { return; } _controllerWasPlaying = controller.value.isPlaying; if (_controllerWasPlaying) { controller.pause(); } }, onHorizontalDragUpdate: (DragUpdateDetails details) { if (!controller.value.isInitialized) { return; } seekToRelativePosition(details.globalPosition); }, onHorizontalDragEnd: (DragEndDetails details) { if (_controllerWasPlaying && controller.value.position != controller.value.duration) { controller.play(); } }, onTapDown: (TapDownDetails details) { if (!controller.value.isInitialized) { return; } seekToRelativePosition(details.globalPosition); }, ); } } /// Displays the play/buffering status of the video controlled by [controller]. /// /// If [allowScrubbing] is true, this widget will detect taps and drags and /// seek the video accordingly. /// /// [padding] allows to specify some extra padding around the progress indicator /// that will also detect the gestures. class VideoProgressIndicator extends StatefulWidget { /// Construct an instance that displays the play/buffering status of the video /// controlled by [controller]. /// /// Defaults will be used for everything except [controller] if they're not /// provided. [allowScrubbing] defaults to false, and [padding] will default /// to `top: 5.0`. const VideoProgressIndicator( this.controller, { Key? key, this.colors = const VideoProgressColors(), required this.allowScrubbing, this.padding = const EdgeInsets.only(top: 5.0), }) : super(key: key); /// The [VideoPlayerController] that actually associates a video with this /// widget. final VideoPlayerController controller; /// The default colors used throughout the indicator. /// /// See [VideoProgressColors] for default values. final VideoProgressColors colors; /// When true, the widget will detect touch input and try to seek the video /// accordingly. The widget ignores such input when false. /// /// Defaults to false. final bool allowScrubbing; /// This allows for visual padding around the progress indicator that can /// still detect gestures via [allowScrubbing]. /// /// Defaults to `top: 5.0`. final EdgeInsets padding; @override State createState() => _VideoProgressIndicatorState(); } class _VideoProgressIndicatorState extends State { _VideoProgressIndicatorState() { listener = () { if (!mounted) { return; } setState(() {}); }; } late VoidCallback listener; VideoPlayerController get controller => widget.controller; VideoProgressColors get colors => widget.colors; @override void initState() { super.initState(); controller.addListener(listener); } @override void deactivate() { controller.removeListener(listener); super.deactivate(); } @override Widget build(BuildContext context) { Widget progressIndicator; if (controller.value.isInitialized) { final int duration = controller.value.duration.inMilliseconds; final int position = controller.value.position.inMilliseconds; int maxBuffering = 0; for (final DurationRange range in controller.value.buffered) { final int end = range.end.inMilliseconds; if (end > maxBuffering) { maxBuffering = end; } } progressIndicator = Stack( fit: StackFit.passthrough, children: [ LinearProgressIndicator( value: maxBuffering / duration, valueColor: AlwaysStoppedAnimation(colors.bufferedColor), backgroundColor: colors.backgroundColor, ), LinearProgressIndicator( value: position / duration, valueColor: AlwaysStoppedAnimation(colors.playedColor), backgroundColor: Colors.transparent, ), ], ); } else { progressIndicator = LinearProgressIndicator( valueColor: AlwaysStoppedAnimation(colors.playedColor), backgroundColor: colors.backgroundColor, ); } final Widget paddedProgressIndicator = Padding( padding: widget.padding, child: progressIndicator, ); if (widget.allowScrubbing) { return VideoScrubber( controller: controller, child: paddedProgressIndicator, ); } else { return paddedProgressIndicator; } } } /// Widget for displaying closed captions on top of a video. /// /// If [text] is null, this widget will not display anything. /// /// If [textStyle] is supplied, it will be used to style the text in the closed /// caption. /// /// Note: in order to have closed captions, you need to specify a /// [VideoPlayerController.closedCaptionFile]. /// /// Usage: /// /// ```dart /// Stack(children: [ /// VideoPlayer(_controller), /// ClosedCaption(text: _controller.value.caption.text), /// ]), /// ``` class ClosedCaption extends StatelessWidget { /// Creates a a new closed caption, designed to be used with /// [VideoPlayerValue.caption]. /// /// If [text] is null or empty, nothing will be displayed. const ClosedCaption({Key? key, this.text, this.textStyle}) : super(key: key); /// The text that will be shown in the closed caption, or null if no caption /// should be shown. /// If the text is empty the caption will not be shown. final String? text; /// Specifies how the text in the closed caption should look. /// /// If null, defaults to [DefaultTextStyle.of(context).style] with size 36 /// font colored white. final TextStyle? textStyle; @override Widget build(BuildContext context) { final String? text = this.text; if (text == null || text.isEmpty) { return const SizedBox.shrink(); } final TextStyle effectiveTextStyle = textStyle ?? DefaultTextStyle.of(context).style.copyWith( fontSize: 36.0, color: Colors.white, ); return Align( alignment: Alignment.bottomCenter, child: Padding( padding: const EdgeInsets.only(bottom: 24.0), child: DecoratedBox( decoration: BoxDecoration( color: const Color(0xB8000000), borderRadius: BorderRadius.circular(2.0), ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 2.0), child: Text(text, style: effectiveTextStyle), ), ), ), ); } } /// 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; ================================================ FILE: packages/video_player/video_player/pubspec.yaml ================================================ name: video_player 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.5.1 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: platforms: android: default_package: video_player_android ios: default_package: video_player_avfoundation web: default_package: video_player_web dependencies: flutter: sdk: flutter html: ^0.15.0 video_player_android: ^2.3.5 video_player_avfoundation: ^2.2.17 video_player_platform_interface: ">=5.1.1 <7.0.0" video_player_web: ^2.0.0 dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/video_player/video_player/test/closed_caption_file_test.dart ================================================ // Copyright 2013 The Flutter 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:video_player/src/closed_caption_file.dart'; void main() { group('ClosedCaptionFile', () { test('toString()', () { const Caption caption = Caption( number: 1, start: Duration(seconds: 1), end: Duration(seconds: 2), text: 'caption', ); expect( caption.toString(), 'Caption(' 'number: 1, ' 'start: 0:00:01.000000, ' 'end: 0:00:02.000000, ' 'text: caption)'); }); }); } ================================================ FILE: packages/video_player/video_player/test/sub_rip_file_test.dart ================================================ // Copyright 2013 The Flutter 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:video_player/src/closed_caption_file.dart'; import 'package:video_player/video_player.dart'; void main() { test('Parses SubRip file', () { final SubRipCaptionFile parsedFile = SubRipCaptionFile(_validSubRip); expect(parsedFile.captions.length, 4); final Caption firstCaption = parsedFile.captions.first; expect(firstCaption.number, 1); expect(firstCaption.start, const Duration(seconds: 6)); expect(firstCaption.end, const Duration(seconds: 12, milliseconds: 74)); expect(firstCaption.text, 'This is a test file'); final Caption secondCaption = parsedFile.captions[1]; expect(secondCaption.number, 2); expect( secondCaption.start, const Duration(minutes: 1, seconds: 54, milliseconds: 724), ); expect( secondCaption.end, const Duration(minutes: 1, seconds: 56, milliseconds: 760), ); expect(secondCaption.text, '- Hello.\n- Yes?'); final Caption thirdCaption = parsedFile.captions[2]; expect(thirdCaption.number, 3); expect( thirdCaption.start, const Duration(minutes: 1, seconds: 56, milliseconds: 884), ); expect( thirdCaption.end, const Duration(minutes: 1, seconds: 58, milliseconds: 954), ); expect( thirdCaption.text, 'These are more test lines\nYes, these are more test lines.', ); final Caption fourthCaption = parsedFile.captions[3]; expect(fourthCaption.number, 4); expect( fourthCaption.start, const Duration(hours: 1, minutes: 1, seconds: 59, milliseconds: 84), ); expect( fourthCaption.end, const Duration(hours: 1, minutes: 2, seconds: 1, milliseconds: 552), ); expect( fourthCaption.text, "- [ Machinery Beeping ]\n- I'm not sure what that was,", ); }); test('Parses SubRip file with malformed input', () { final ClosedCaptionFile parsedFile = SubRipCaptionFile(_malformedSubRip); expect(parsedFile.captions.length, 1); final Caption firstCaption = parsedFile.captions.single; expect(firstCaption.number, 2); expect(firstCaption.start, const Duration(seconds: 15)); expect(firstCaption.end, const Duration(seconds: 17, milliseconds: 74)); expect(firstCaption.text, 'This one is valid'); }); } const String _validSubRip = ''' 1 00:00:06,000 --> 00:00:12,074 This is a test file 2 00:01:54,724 --> 00:01:56,760 - Hello. - Yes? 3 00:01:56,884 --> 00:01:58,954 These are more test lines Yes, these are more test lines. 4 01:01:59,084 --> 01:02:01,552 - [ Machinery Beeping ] - I'm not sure what that was, '''; const String _malformedSubRip = ''' 1 00:00:06,000--> 00:00:12,074 This one should be ignored because the arrow needs a space. 2 00:00:15,000 --> 00:00:17,074 This one is valid 3 00:01:54,724 --> 00:01:6,760 This one should be ignored because the ned time is missing a digit. '''; ================================================ FILE: packages/video_player/video_player/test/video_player_initialization_test.dart ================================================ // Copyright 2013 The Flutter 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:video_player/video_player.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; import 'video_player_test.dart' show FakeVideoPlayerPlatform; void main() { // This test needs to run first and therefore needs to be the only test // in this file. test('plugin initialized', () async { TestWidgetsFlutterBinding.ensureInitialized(); final FakeVideoPlayerPlatform fakeVideoPlayerPlatform = FakeVideoPlayerPlatform(); VideoPlayerPlatform.instance = fakeVideoPlayerPlatform; final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); expect(fakeVideoPlayerPlatform.calls.first, 'init'); }); } ================================================ FILE: packages/video_player/video_player/test/video_player_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; import 'dart:math' as math; import 'dart:typed_data'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:video_player/video_player.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; class FakeController extends ValueNotifier implements VideoPlayerController { FakeController() : super(VideoPlayerValue(duration: Duration.zero)); FakeController.value(VideoPlayerValue value) : super(value); @override Future dispose() async { super.dispose(); } @override int textureId = VideoPlayerController.kUninitializedTextureId; @override String get dataSource => ''; @override Map get httpHeaders => {}; @override DataSourceType get dataSourceType => DataSourceType.file; @override String get package => ''; @override Future get position async => value.position; @override Future seekTo(Duration moment) async {} @override Future setVolume(double volume) async {} @override Future setPlaybackSpeed(double speed) async {} @override Future initialize() async {} @override Future pause() async {} @override Future play() async {} @override Future setLooping(bool looping) async {} @override VideoFormat? get formatHint => null; @override Future get closedCaptionFile => _loadClosedCaption(); @override VideoPlayerOptions? get videoPlayerOptions => null; @override void setCaptionOffset(Duration delay) {} @override Future setClosedCaptionFile( Future? closedCaptionFile, ) async {} } Future _loadClosedCaption() async => _FakeClosedCaptionFile(); class _FakeClosedCaptionFile extends ClosedCaptionFile { @override List get captions { return [ const Caption( text: 'one', number: 0, start: Duration(milliseconds: 100), end: Duration(milliseconds: 200), ), const Caption( text: 'two', number: 1, start: Duration(milliseconds: 300), end: Duration(milliseconds: 400), ), ]; } } void main() { late FakeVideoPlayerPlatform fakeVideoPlayerPlatform; setUp(() { fakeVideoPlayerPlatform = FakeVideoPlayerPlatform(); VideoPlayerPlatform.instance = fakeVideoPlayerPlatform; }); void verifyPlayStateRespondsToLifecycle( VideoPlayerController controller, { required bool shouldPlayInBackground, }) { expect(controller.value.isPlaying, true); _ambiguate(WidgetsBinding.instance)! .handleAppLifecycleStateChanged(AppLifecycleState.paused); expect(controller.value.isPlaying, shouldPlayInBackground); _ambiguate(WidgetsBinding.instance)! .handleAppLifecycleStateChanged(AppLifecycleState.resumed); expect(controller.value.isPlaying, true); } testWidgets('update texture', (WidgetTester tester) async { final FakeController controller = FakeController(); await tester.pumpWidget(VideoPlayer(controller)); expect(find.byType(Texture), findsNothing); controller.textureId = 123; controller.value = controller.value.copyWith( duration: const Duration(milliseconds: 100), isInitialized: true, ); await tester.pump(); expect(find.byType(Texture), findsOneWidget); }); testWidgets('update controller', (WidgetTester tester) async { final FakeController controller1 = FakeController(); controller1.textureId = 101; await tester.pumpWidget(VideoPlayer(controller1)); expect( find.byWidgetPredicate( (Widget widget) => widget is Texture && widget.textureId == 101, ), findsOneWidget); final FakeController controller2 = FakeController(); controller2.textureId = 102; await tester.pumpWidget(VideoPlayer(controller2)); expect( find.byWidgetPredicate( (Widget widget) => widget is Texture && widget.textureId == 102, ), findsOneWidget); }); testWidgets('non-zero rotationCorrection value is used', (WidgetTester tester) async { final FakeController controller = FakeController.value( VideoPlayerValue(duration: Duration.zero, rotationCorrection: 180)); controller.textureId = 1; await tester.pumpWidget(VideoPlayer(controller)); final Transform actualRotationCorrection = find.byType(Transform).evaluate().single.widget as Transform; final Float64List actualRotationCorrectionStorage = actualRotationCorrection.transform.storage; final Float64List expectedMatrixStorage = Matrix4.rotationZ(math.pi).storage; expect(actualRotationCorrectionStorage.length, equals(expectedMatrixStorage.length)); for (int i = 0; i < actualRotationCorrectionStorage.length; i++) { expect(actualRotationCorrectionStorage[i], moreOrLessEquals(expectedMatrixStorage[i])); } }); testWidgets('no transform when rotationCorrection is zero', (WidgetTester tester) async { final FakeController controller = FakeController.value(VideoPlayerValue(duration: Duration.zero)); controller.textureId = 1; await tester.pumpWidget(VideoPlayer(controller)); expect(find.byType(Transform), findsNothing); }); group('ClosedCaption widget', () { testWidgets('uses a default text style', (WidgetTester tester) async { const String text = 'foo'; await tester .pumpWidget(const MaterialApp(home: ClosedCaption(text: text))); final Text textWidget = tester.widget(find.text(text)); expect(textWidget.style!.fontSize, 36.0); expect(textWidget.style!.color, Colors.white); }); testWidgets('uses given text and style', (WidgetTester tester) async { const String text = 'foo'; const TextStyle textStyle = TextStyle(fontSize: 14.725); await tester.pumpWidget(const MaterialApp( home: ClosedCaption( text: text, textStyle: textStyle, ), )); expect(find.text(text), findsOneWidget); final Text textWidget = tester.widget(find.text(text)); expect(textWidget.style!.fontSize, textStyle.fontSize); }); testWidgets('handles null text', (WidgetTester tester) async { await tester.pumpWidget(const MaterialApp(home: ClosedCaption())); expect(find.byType(Text), findsNothing); }); testWidgets('handles empty text', (WidgetTester tester) async { await tester.pumpWidget(const MaterialApp(home: ClosedCaption(text: ''))); expect(find.byType(Text), findsNothing); }); testWidgets('Passes text contrast ratio guidelines', (WidgetTester tester) async { const String text = 'foo'; await tester.pumpWidget(const MaterialApp( home: Scaffold( backgroundColor: Colors.white, body: ClosedCaption(text: text), ), )); expect(find.text(text), findsOneWidget); await expectLater(tester, meetsGuideline(textContrastGuideline)); }, skip: isBrowser); }); group('VideoPlayerController', () { group('initialize', () { test('started app lifecycle observing', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); await controller.play(); verifyPlayStateRespondsToLifecycle(controller, shouldPlayInBackground: false); }); test('asset', () async { final VideoPlayerController controller = VideoPlayerController.asset( 'a.avi', ); await controller.initialize(); expect(fakeVideoPlayerPlatform.dataSources[0].asset, 'a.avi'); expect(fakeVideoPlayerPlatform.dataSources[0].package, null); }); test('network', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); expect( fakeVideoPlayerPlatform.dataSources[0].uri, 'https://127.0.0.1', ); expect( fakeVideoPlayerPlatform.dataSources[0].formatHint, null, ); expect( fakeVideoPlayerPlatform.dataSources[0].httpHeaders, {}, ); }); test('network with hint', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', formatHint: VideoFormat.dash, ); await controller.initialize(); expect( fakeVideoPlayerPlatform.dataSources[0].uri, 'https://127.0.0.1', ); expect( fakeVideoPlayerPlatform.dataSources[0].formatHint, VideoFormat.dash, ); expect( fakeVideoPlayerPlatform.dataSources[0].httpHeaders, {}, ); }); test('network with some headers', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', httpHeaders: {'Authorization': 'Bearer token'}, ); await controller.initialize(); expect( fakeVideoPlayerPlatform.dataSources[0].uri, 'https://127.0.0.1', ); expect( fakeVideoPlayerPlatform.dataSources[0].formatHint, null, ); expect( fakeVideoPlayerPlatform.dataSources[0].httpHeaders, {'Authorization': 'Bearer token'}, ); }); test('init errors', () async { final VideoPlayerController controller = VideoPlayerController.network( 'http://testing.com/invalid_url', ); late Object error; fakeVideoPlayerPlatform.forceInitError = true; await controller.initialize().catchError((Object e) => error = e); final PlatformException platformEx = error as PlatformException; expect(platformEx.code, equals('VideoError')); }); test('file', () async { final VideoPlayerController controller = VideoPlayerController.file(File('a.avi')); await controller.initialize(); final String uri = fakeVideoPlayerPlatform.dataSources[0].uri!; expect(uri.startsWith('file:///'), true, reason: 'Actual string: $uri'); expect(uri.endsWith('/a.avi'), true, reason: 'Actual string: $uri'); }, skip: kIsWeb /* Web does not support file assets. */); test('file with special characters', () async { final VideoPlayerController controller = VideoPlayerController.file(File('A #1 Hit?.avi')); await controller.initialize(); final String uri = fakeVideoPlayerPlatform.dataSources[0].uri!; expect(uri.startsWith('file:///'), true, reason: 'Actual string: $uri'); expect(uri.endsWith('/A%20%231%20Hit%3F.avi'), true, reason: 'Actual string: $uri'); }, skip: kIsWeb /* Web does not support file assets. */); test('successful initialize on controller with error clears error', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); fakeVideoPlayerPlatform.forceInitError = true; await controller.initialize().catchError((dynamic e) {}); expect(controller.value.hasError, equals(true)); fakeVideoPlayerPlatform.forceInitError = false; await controller.initialize(); expect(controller.value.hasError, equals(false)); }); }); test('contentUri', () async { final VideoPlayerController controller = VideoPlayerController.contentUri(Uri.parse('content://video')); await controller.initialize(); expect(fakeVideoPlayerPlatform.dataSources[0].uri, 'content://video'); }); test('dispose', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); expect( controller.textureId, VideoPlayerController.kUninitializedTextureId); expect(await controller.position, Duration.zero); await controller.initialize(); await controller.dispose(); expect(controller.textureId, 0); expect(await controller.position, isNull); }); test('calling dispose() on disposed controller does not throw', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); await controller.dispose(); expect(() async => controller.dispose(), returnsNormally); }); test('play', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); expect(controller.value.isPlaying, isFalse); await controller.play(); expect(controller.value.isPlaying, isTrue); // The two last calls will be "play" and then "setPlaybackSpeed". The // reason for this is that "play" calls "setPlaybackSpeed" internally. expect( fakeVideoPlayerPlatform .calls[fakeVideoPlayerPlatform.calls.length - 2], 'play'); expect(fakeVideoPlayerPlatform.calls.last, 'setPlaybackSpeed'); }); test('play before initialized does not call platform', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); expect(controller.value.isInitialized, isFalse); await controller.play(); expect(fakeVideoPlayerPlatform.calls, isEmpty); }); test('play restarts from beginning if video is at end', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); const Duration nonzeroDuration = Duration(milliseconds: 100); controller.value = controller.value.copyWith(duration: nonzeroDuration); await controller.seekTo(nonzeroDuration); expect(controller.value.isPlaying, isFalse); expect(controller.value.position, nonzeroDuration); await controller.play(); expect(controller.value.isPlaying, isTrue); expect(controller.value.position, Duration.zero); }); test('setLooping', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); expect(controller.value.isLooping, isFalse); await controller.setLooping(true); expect(controller.value.isLooping, isTrue); }); test('pause', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); await controller.play(); expect(controller.value.isPlaying, isTrue); await controller.pause(); expect(controller.value.isPlaying, isFalse); expect(fakeVideoPlayerPlatform.calls.last, 'pause'); }); group('seekTo', () { test('works', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); expect(await controller.position, Duration.zero); await controller.seekTo(const Duration(milliseconds: 500)); expect(await controller.position, const Duration(milliseconds: 500)); }); test('before initialized does not call platform', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); expect(controller.value.isInitialized, isFalse); await controller.seekTo(const Duration(milliseconds: 500)); expect(fakeVideoPlayerPlatform.calls, isEmpty); }); test('clamps values that are too high or low', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); expect(await controller.position, Duration.zero); await controller.seekTo(const Duration(seconds: 100)); expect(await controller.position, const Duration(seconds: 1)); await controller.seekTo(const Duration(seconds: -100)); expect(await controller.position, Duration.zero); }); }); group('setVolume', () { test('works', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); expect(controller.value.volume, 1.0); const double volume = 0.5; await controller.setVolume(volume); expect(controller.value.volume, volume); }); test('clamps values that are too high or low', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); expect(controller.value.volume, 1.0); await controller.setVolume(-1); expect(controller.value.volume, 0.0); await controller.setVolume(11); expect(controller.value.volume, 1.0); }); }); group('setPlaybackSpeed', () { test('works', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); expect(controller.value.playbackSpeed, 1.0); const double speed = 1.5; await controller.setPlaybackSpeed(speed); expect(controller.value.playbackSpeed, speed); }); test('rejects negative values', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); expect(controller.value.playbackSpeed, 1.0); expect(() => controller.setPlaybackSpeed(-1), throwsArgumentError); }); }); group('scrubbing', () { testWidgets('restarts on release if already playing', (WidgetTester tester) async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); final VideoProgressIndicator progressWidget = VideoProgressIndicator(controller, allowScrubbing: true); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: progressWidget, )); await controller.play(); expect(controller.value.isPlaying, isTrue); final Rect progressRect = tester.getRect(find.byWidget(progressWidget)); await tester.dragFrom(progressRect.center, const Offset(1.0, 0.0)); await tester.pumpAndSettle(); expect(controller.value.position, lessThan(controller.value.duration)); expect(controller.value.isPlaying, isTrue); await controller.pause(); }); testWidgets('does not restart when dragging to end', (WidgetTester tester) async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); final VideoProgressIndicator progressWidget = VideoProgressIndicator(controller, allowScrubbing: true); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: progressWidget, )); await controller.play(); expect(controller.value.isPlaying, isTrue); final Rect progressRect = tester.getRect(find.byWidget(progressWidget)); await tester.dragFrom(progressRect.center, progressRect.centerRight); await tester.pumpAndSettle(); expect(controller.value.position, controller.value.duration); expect(controller.value.isPlaying, isFalse); }); }); group('caption', () { test('works when seeking', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', closedCaptionFile: _loadClosedCaption(), ); await controller.initialize(); expect(controller.value.position, Duration.zero); expect(controller.value.caption.text, ''); await controller.seekTo(const Duration(milliseconds: 100)); expect(controller.value.caption.text, 'one'); await controller.seekTo(const Duration(milliseconds: 250)); expect(controller.value.caption.text, ''); await controller.seekTo(const Duration(milliseconds: 300)); expect(controller.value.caption.text, 'two'); await controller.seekTo(const Duration(milliseconds: 301)); expect(controller.value.caption.text, 'two'); await controller.seekTo(const Duration(milliseconds: 500)); expect(controller.value.caption.text, ''); await controller.seekTo(const Duration(milliseconds: 300)); expect(controller.value.caption.text, 'two'); await controller.seekTo(const Duration(milliseconds: 301)); expect(controller.value.caption.text, 'two'); }); test('works when seeking with captionOffset positive', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', closedCaptionFile: _loadClosedCaption(), ); await controller.initialize(); controller.setCaptionOffset(const Duration(milliseconds: 100)); expect(controller.value.position, Duration.zero); expect(controller.value.caption.text, ''); await controller.seekTo(const Duration(milliseconds: 100)); expect(controller.value.caption.text, 'one'); await controller.seekTo(const Duration(milliseconds: 101)); expect(controller.value.caption.text, ''); await controller.seekTo(const Duration(milliseconds: 250)); expect(controller.value.caption.text, 'two'); await controller.seekTo(const Duration(milliseconds: 300)); expect(controller.value.caption.text, 'two'); await controller.seekTo(const Duration(milliseconds: 301)); expect(controller.value.caption.text, ''); await controller.seekTo(const Duration(milliseconds: 500)); expect(controller.value.caption.text, ''); await controller.seekTo(const Duration(milliseconds: 300)); expect(controller.value.caption.text, 'two'); await controller.seekTo(const Duration(milliseconds: 301)); expect(controller.value.caption.text, ''); }); test('works when seeking with captionOffset negative', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', closedCaptionFile: _loadClosedCaption(), ); await controller.initialize(); controller.setCaptionOffset(const Duration(milliseconds: -100)); expect(controller.value.position, Duration.zero); expect(controller.value.caption.text, ''); await controller.seekTo(const Duration(milliseconds: 100)); expect(controller.value.caption.text, ''); await controller.seekTo(const Duration(milliseconds: 200)); expect(controller.value.caption.text, 'one'); await controller.seekTo(const Duration(milliseconds: 250)); expect(controller.value.caption.text, 'one'); await controller.seekTo(const Duration(milliseconds: 300)); expect(controller.value.caption.text, 'one'); await controller.seekTo(const Duration(milliseconds: 301)); expect(controller.value.caption.text, ''); await controller.seekTo(const Duration(milliseconds: 400)); expect(controller.value.caption.text, 'two'); await controller.seekTo(const Duration(milliseconds: 500)); expect(controller.value.caption.text, 'two'); await controller.seekTo(const Duration(milliseconds: 600)); expect(controller.value.caption.text, ''); await controller.seekTo(const Duration(milliseconds: 300)); expect(controller.value.caption.text, 'one'); }); test('setClosedCaptionFile loads caption file', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); expect(controller.closedCaptionFile, null); await controller.setClosedCaptionFile(_loadClosedCaption()); expect( (await controller.closedCaptionFile)!.captions, (await _loadClosedCaption()).captions, ); }); test('setClosedCaptionFile removes/changes caption file', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', closedCaptionFile: _loadClosedCaption(), ); await controller.initialize(); expect( (await controller.closedCaptionFile)!.captions, (await _loadClosedCaption()).captions, ); await controller.setClosedCaptionFile(null); expect(controller.closedCaptionFile, null); }); }); group('Platform callbacks', () { testWidgets('playing completed', (WidgetTester tester) async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); const Duration nonzeroDuration = Duration(milliseconds: 100); controller.value = controller.value.copyWith(duration: nonzeroDuration); expect(controller.value.isPlaying, isFalse); await controller.play(); expect(controller.value.isPlaying, isTrue); final StreamController fakeVideoEventStream = fakeVideoPlayerPlatform.streams[controller.textureId]!; fakeVideoEventStream .add(VideoEvent(eventType: VideoEventType.completed)); await tester.pumpAndSettle(); expect(controller.value.isPlaying, isFalse); expect(controller.value.position, nonzeroDuration); }); testWidgets('buffering status', (WidgetTester tester) async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); expect(controller.value.isBuffering, false); expect(controller.value.buffered, isEmpty); final StreamController fakeVideoEventStream = fakeVideoPlayerPlatform.streams[controller.textureId]!; fakeVideoEventStream .add(VideoEvent(eventType: VideoEventType.bufferingStart)); await tester.pumpAndSettle(); expect(controller.value.isBuffering, isTrue); const Duration bufferStart = Duration.zero; const Duration bufferEnd = Duration(milliseconds: 500); fakeVideoEventStream.add(VideoEvent( eventType: VideoEventType.bufferingUpdate, buffered: [ DurationRange(bufferStart, bufferEnd), ])); await tester.pumpAndSettle(); expect(controller.value.isBuffering, isTrue); expect(controller.value.buffered.length, 1); expect(controller.value.buffered[0].toString(), DurationRange(bufferStart, bufferEnd).toString()); fakeVideoEventStream .add(VideoEvent(eventType: VideoEventType.bufferingEnd)); await tester.pumpAndSettle(); expect(controller.value.isBuffering, isFalse); }); }); }); group('DurationRange', () { test('uses given values', () { const Duration start = Duration(seconds: 2); const Duration end = Duration(seconds: 8); final DurationRange range = DurationRange(start, end); expect(range.start, start); expect(range.end, end); expect(range.toString(), contains('start: $start, end: $end')); }); test('calculates fractions', () { const Duration start = Duration(seconds: 2); const Duration end = Duration(seconds: 8); const Duration total = Duration(seconds: 10); final DurationRange range = DurationRange(start, end); expect(range.startFraction(total), .2); expect(range.endFraction(total), .8); }); }); group('VideoPlayerValue', () { test('uninitialized()', () { final VideoPlayerValue uninitialized = VideoPlayerValue.uninitialized(); expect(uninitialized.duration, equals(Duration.zero)); expect(uninitialized.position, equals(Duration.zero)); expect(uninitialized.caption, equals(Caption.none)); expect(uninitialized.captionOffset, equals(Duration.zero)); expect(uninitialized.buffered, isEmpty); expect(uninitialized.isPlaying, isFalse); expect(uninitialized.isLooping, isFalse); expect(uninitialized.isBuffering, isFalse); expect(uninitialized.volume, 1.0); expect(uninitialized.playbackSpeed, 1.0); expect(uninitialized.errorDescription, isNull); expect(uninitialized.size, equals(Size.zero)); expect(uninitialized.isInitialized, isFalse); expect(uninitialized.hasError, isFalse); expect(uninitialized.aspectRatio, 1.0); }); test('erroneous()', () { const String errorMessage = 'foo'; final VideoPlayerValue error = VideoPlayerValue.erroneous(errorMessage); expect(error.duration, equals(Duration.zero)); expect(error.position, equals(Duration.zero)); expect(error.caption, equals(Caption.none)); expect(error.captionOffset, equals(Duration.zero)); expect(error.buffered, isEmpty); expect(error.isPlaying, isFalse); expect(error.isLooping, isFalse); expect(error.isBuffering, isFalse); expect(error.volume, 1.0); expect(error.playbackSpeed, 1.0); expect(error.errorDescription, errorMessage); expect(error.size, equals(Size.zero)); expect(error.isInitialized, isFalse); expect(error.hasError, isTrue); expect(error.aspectRatio, 1.0); }); test('toString()', () { const Duration duration = Duration(seconds: 5); const Size size = Size(400, 300); const Duration position = Duration(seconds: 1); const Caption caption = Caption( text: 'foo', number: 0, start: Duration.zero, end: Duration.zero); const Duration captionOffset = Duration(milliseconds: 250); final List buffered = [ DurationRange(Duration.zero, const Duration(seconds: 4)) ]; const bool isInitialized = true; const bool isPlaying = true; const bool isLooping = true; const bool isBuffering = true; const double volume = 0.5; const double playbackSpeed = 1.5; final VideoPlayerValue value = VideoPlayerValue( duration: duration, size: size, position: position, caption: caption, captionOffset: captionOffset, buffered: buffered, isInitialized: isInitialized, isPlaying: isPlaying, isLooping: isLooping, isBuffering: isBuffering, volume: volume, playbackSpeed: playbackSpeed, ); expect( value.toString(), 'VideoPlayerValue(duration: 0:00:05.000000, ' 'size: Size(400.0, 300.0), ' 'position: 0:00:01.000000, ' 'caption: Caption(number: 0, start: 0:00:00.000000, end: 0:00:00.000000, text: foo), ' 'captionOffset: 0:00:00.250000, ' 'buffered: [DurationRange(start: 0:00:00.000000, end: 0:00:04.000000)], ' 'isInitialized: true, ' 'isPlaying: true, ' 'isLooping: true, ' 'isBuffering: true, ' 'volume: 0.5, ' 'playbackSpeed: 1.5, ' 'errorDescription: null)'); }); group('copyWith()', () { test('exact copy', () { final VideoPlayerValue original = VideoPlayerValue.uninitialized(); final VideoPlayerValue exactCopy = original.copyWith(); expect(exactCopy.toString(), original.toString()); }); test('errorDescription is not persisted when copy with null', () { final VideoPlayerValue original = VideoPlayerValue.erroneous('error'); final VideoPlayerValue copy = original.copyWith(errorDescription: null); expect(copy.errorDescription, null); }); test('errorDescription is changed when copy with another error', () { final VideoPlayerValue original = VideoPlayerValue.erroneous('error'); final VideoPlayerValue copy = original.copyWith(errorDescription: 'new error'); expect(copy.errorDescription, 'new error'); }); test('errorDescription is changed when copy with error', () { final VideoPlayerValue original = VideoPlayerValue.uninitialized(); final VideoPlayerValue copy = original.copyWith(errorDescription: 'new error'); expect(copy.errorDescription, 'new error'); }); }); group('aspectRatio', () { test('640x480 -> 4:3', () { final VideoPlayerValue value = VideoPlayerValue( isInitialized: true, size: const Size(640, 480), duration: const Duration(seconds: 1), ); expect(value.aspectRatio, 4 / 3); }); test('no size -> 1.0', () { final VideoPlayerValue value = VideoPlayerValue( isInitialized: true, duration: const Duration(seconds: 1), ); expect(value.aspectRatio, 1.0); }); test('height = 0 -> 1.0', () { final VideoPlayerValue value = VideoPlayerValue( isInitialized: true, size: const Size(640, 0), duration: const Duration(seconds: 1), ); expect(value.aspectRatio, 1.0); }); test('width = 0 -> 1.0', () { final VideoPlayerValue value = VideoPlayerValue( isInitialized: true, size: const Size(0, 480), duration: const Duration(seconds: 1), ); expect(value.aspectRatio, 1.0); }); test('negative aspect ratio -> 1.0', () { final VideoPlayerValue value = VideoPlayerValue( isInitialized: true, size: const Size(640, -480), duration: const Duration(seconds: 1), ); expect(value.aspectRatio, 1.0); }); }); }); group('VideoPlayerOptions', () { test('setMixWithOthers', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true)); await controller.initialize(); expect(controller.videoPlayerOptions!.mixWithOthers, true); }); test('true allowBackgroundPlayback continues playback', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', videoPlayerOptions: VideoPlayerOptions( allowBackgroundPlayback: true, ), ); await controller.initialize(); await controller.play(); verifyPlayStateRespondsToLifecycle( controller, shouldPlayInBackground: true, ); }); test('false allowBackgroundPlayback pauses playback', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', videoPlayerOptions: VideoPlayerOptions(), ); await controller.initialize(); await controller.play(); verifyPlayStateRespondsToLifecycle( controller, shouldPlayInBackground: false, ); }); }); test('VideoProgressColors', () { const Color playedColor = Color.fromRGBO(0, 0, 255, 0.75); const Color bufferedColor = Color.fromRGBO(0, 255, 0, 0.5); const Color backgroundColor = Color.fromRGBO(255, 255, 0, 0.25); const VideoProgressColors colors = VideoProgressColors( playedColor: playedColor, bufferedColor: bufferedColor, backgroundColor: backgroundColor); expect(colors.playedColor, playedColor); expect(colors.bufferedColor, bufferedColor); expect(colors.backgroundColor, backgroundColor); }); } class FakeVideoPlayerPlatform extends VideoPlayerPlatform { Completer initialized = Completer(); List calls = []; List dataSources = []; final Map> streams = >{}; bool forceInitError = false; int nextTextureId = 0; final Map _positions = {}; @override Future create(DataSource dataSource) async { calls.add('create'); final StreamController stream = StreamController(); streams[nextTextureId] = stream; if (forceInitError) { stream.addError(PlatformException( code: 'VideoError', message: 'Video player had error XYZ')); } else { stream.add(VideoEvent( eventType: VideoEventType.initialized, size: const Size(100, 100), duration: const Duration(seconds: 1))); } dataSources.add(dataSource); return nextTextureId++; } @override Future dispose(int textureId) async { calls.add('dispose'); } @override Future init() async { calls.add('init'); initialized.complete(true); } @override Stream videoEventsFor(int textureId) { return streams[textureId]!.stream; } @override Future pause(int textureId) async { calls.add('pause'); } @override Future play(int textureId) async { calls.add('play'); } @override Future getPosition(int textureId) async { calls.add('position'); return _positions[textureId] ?? Duration.zero; } @override Future seekTo(int textureId, Duration position) async { calls.add('seekTo'); _positions[textureId] = position; } @override Future setLooping(int textureId, bool looping) async { calls.add('setLooping'); } @override Future setVolume(int textureId, double volume) async { calls.add('setVolume'); } @override Future setPlaybackSpeed(int textureId, double speed) async { calls.add('setPlaybackSpeed'); } @override Future setMixWithOthers(bool mixWithOthers) async { calls.add('setMixWithOthers'); } @override Widget buildView(int textureId) { return Texture(textureId: textureId); } } /// 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; ================================================ FILE: packages/video_player/video_player/test/web_vtt_test.dart ================================================ // Copyright 2013 The Flutter 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:video_player/src/closed_caption_file.dart'; import 'package:video_player/video_player.dart'; void main() { group('Parse VTT file', () { WebVTTCaptionFile parsedFile; test('with Metadata', () { parsedFile = WebVTTCaptionFile(_valid_vtt_with_metadata); expect(parsedFile.captions.length, 1); expect(parsedFile.captions[0].start, const Duration(seconds: 1)); expect(parsedFile.captions[0].end, const Duration(seconds: 2, milliseconds: 500)); expect(parsedFile.captions[0].text, 'We are in New York City'); }); test('with Multiline', () { parsedFile = WebVTTCaptionFile(_valid_vtt_with_multiline); expect(parsedFile.captions.length, 1); expect(parsedFile.captions[0].start, const Duration(seconds: 2, milliseconds: 800)); expect(parsedFile.captions[0].end, const Duration(seconds: 3, milliseconds: 283)); expect(parsedFile.captions[0].text, '— It will perforate your stomach.\n— You could die.'); }); test('with styles tags', () { parsedFile = WebVTTCaptionFile(_valid_vtt_with_styles); expect(parsedFile.captions.length, 3); expect(parsedFile.captions[0].start, const Duration(seconds: 5, milliseconds: 200)); expect(parsedFile.captions[0].end, const Duration(seconds: 6)); expect(parsedFile.captions[0].text, "You know I'm so excited my glasses are falling off here."); }); test('with subtitling features', () { parsedFile = WebVTTCaptionFile(_valid_vtt_with_subtitling_features); expect(parsedFile.captions.length, 3); expect(parsedFile.captions[0].number, 1); expect(parsedFile.captions.last.start, const Duration(seconds: 4)); expect(parsedFile.captions.last.end, const Duration(seconds: 5)); expect(parsedFile.captions.last.text, 'Transcrit par Célestes™'); }); test('with [hours]:[minutes]:[seconds].[milliseconds].', () { parsedFile = WebVTTCaptionFile(_valid_vtt_with_hours); expect(parsedFile.captions.length, 1); expect(parsedFile.captions[0].number, 1); expect(parsedFile.captions.last.start, const Duration(seconds: 1)); expect(parsedFile.captions.last.end, const Duration(seconds: 2)); expect(parsedFile.captions.last.text, 'This is a test.'); }); test('with [minutes]:[seconds].[milliseconds].', () { parsedFile = WebVTTCaptionFile(_valid_vtt_without_hours); expect(parsedFile.captions.length, 1); expect(parsedFile.captions[0].number, 1); expect(parsedFile.captions.last.start, const Duration(seconds: 3)); expect(parsedFile.captions.last.end, const Duration(seconds: 4)); expect(parsedFile.captions.last.text, 'This is a test.'); }); test('with invalid seconds format returns empty captions.', () { parsedFile = WebVTTCaptionFile(_invalid_seconds); expect(parsedFile.captions, isEmpty); }); test('with invalid minutes format returns empty captions.', () { parsedFile = WebVTTCaptionFile(_invalid_minutes); expect(parsedFile.captions, isEmpty); }); test('with invalid hours format returns empty captions.', () { parsedFile = WebVTTCaptionFile(_invalid_hours); expect(parsedFile.captions, isEmpty); }); test('with invalid component length returns empty captions.', () { parsedFile = WebVTTCaptionFile(_time_component_too_long); expect(parsedFile.captions, isEmpty); parsedFile = WebVTTCaptionFile(_time_component_too_short); expect(parsedFile.captions, isEmpty); }); }); test('Parses VTT file with malformed input.', () { final ClosedCaptionFile parsedFile = WebVTTCaptionFile(_malformedVTT); expect(parsedFile.captions.length, 1); final Caption firstCaption = parsedFile.captions.single; expect(firstCaption.number, 1); expect(firstCaption.start, const Duration(seconds: 13)); expect(firstCaption.end, const Duration(seconds: 16)); expect(firstCaption.text, 'Valid'); }); } /// See https://www.w3.org/TR/webvtt1/#introduction-comments const String _valid_vtt_with_metadata = ''' WEBVTT Kind: captions; Language: en REGION id:bill width:40% lines:3 regionanchor:100%,100% viewportanchor:90%,90% scroll:up NOTE This file was written by Jill. I hope you enjoy reading it. Some things to bear in mind: - I was lip-reading, so the cues may not be 100% accurate - I didn’t pay too close attention to when the cues should start or end. 1 00:01.000 --> 00:02.500 We are in New York City '''; /// See https://www.w3.org/TR/webvtt1/#introduction-multiple-lines const String _valid_vtt_with_multiline = ''' WEBVTT 2 00:02.800 --> 00:03.283 — It will perforate your stomach. — You could die. '''; /// See https://www.w3.org/TR/webvtt1/#styling const String _valid_vtt_with_styles = ''' WEBVTT 00:05.200 --> 00:06.000 align:start size:50% You know I'm so excited my glasses are falling off here. 00:00:06.050 --> 00:00:06.150 I have a different time! 00:06.200 --> 00:06.900 This is yellow text on a blue background '''; //See https://www.w3.org/TR/webvtt1/#introduction-other-features const String _valid_vtt_with_subtitling_features = ''' WEBVTT test 00:00.000 --> 00:02.000 This is a test. Slide 1 00:00:00.000 --> 00:00:10.700 Title Slide crédit de transcription 00:04.000 --> 00:05.000 Transcrit par Célestes™ '''; /// With format [hours]:[minutes]:[seconds].[milliseconds] const String _valid_vtt_with_hours = ''' WEBVTT test 00:00:01.000 --> 00:00:02.000 This is a test. '''; /// Invalid seconds format. const String _invalid_seconds = ''' WEBVTT 60:00:000.000 --> 60:02:000.000 This is a test. '''; /// Invalid minutes format. const String _invalid_minutes = ''' WEBVTT 60:60:00.000 --> 60:70:00.000 This is a test. '''; /// Invalid hours format. const String _invalid_hours = ''' WEBVTT 5:00:00.000 --> 5:02:00.000 This is a test. '''; /// Invalid seconds format. const String _time_component_too_long = ''' WEBVTT 60:00:00:00.000 --> 60:02:00:00.000 This is a test. '''; /// Invalid seconds format. const String _time_component_too_short = ''' WEBVTT 60:00.000 --> 60:02.000 This is a test. '''; /// With format [minutes]:[seconds].[milliseconds] const String _valid_vtt_without_hours = ''' WEBVTT 00:03.000 --> 00:04.000 This is a test. '''; const String _malformedVTT = ''' WEBVTT Kind: captions; Language: en 00:09.000--> 00:11.430 This one should be ignored because the arrow needs a space. 00:13.000 --> 00:16.000 Valid 00:16.000 --> 00:8.000 This one should be ignored because the time is missing a digit. '''; ================================================ FILE: packages/video_player/video_player_android/AUTHORS ================================================ # 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> ================================================ FILE: packages/video_player/video_player_android/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 2.3.10 * Adds compatibilty with version 6.0 of the platform interface. * Fixes file URI construction in example. * Updates code for new analysis options. * Updates code for `no_leading_underscores_for_local_identifiers` lint. * Updates minimum Flutter version to 2.10. * Fixes violations of new analysis option use_named_constants. * Removes an unnecessary override in example code. ## 2.3.9 * Updates ExoPlayer to 2.18.1. * Fixes avoid_redundant_argument_values lint warnings and minor typos. ## 2.3.8 * Updates ExoPlayer to 2.18.0. ## 2.3.7 * Bumps gradle version to 7.2.1. * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/106316). ## 2.3.6 * Updates references to the obsolete master branch. ## 2.3.5 * Sets rotationCorrection for videos recorded in landscapeRight (https://github.com/flutter/flutter/issues/60327). ## 2.3.4 * Updates ExoPlayer to 2.17.1. ## 2.3.3 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 2.3.2 * Updates ExoPlayer to 2.17.0. ## 2.3.1 * Renames internal method channels to avoid potential confusion with the default implementation's method channel. * Updates Pigeon to 2.0.1. ## 2.3.0 * Updates Pigeon to ^1.0.16. ## 2.2.17 * Splits from `video_player` as a federated implementation. ================================================ FILE: packages/video_player/video_player_android/CONTRIBUTING.md ================================================ ## Updating pigeon-generated files If you update files in the pigeons/ directory, run the following command in this directory: ```bash flutter pub upgrade flutter pub run pigeon --input pigeons/messages.dart # git commit your changes so that your working environment is clean (cd ../../../; ./script/tool_runner.sh format --clang-format=clang-format-7) ``` If you update pigeon itself and want to test the changes here, temporarily update the pubspec.yaml by adding the following to the `dependency_overrides` section, assuming you have checked out the `flutter/packages` repo in a sibling directory to the `plugins` repo: ```yaml pigeon: path: ../../../../packages/packages/pigeon/ ``` Then, run the commands above. When you run `pub get` it should warn you that you're using an override. If you do this, you will need to publish pigeon before you can land the updates to this package, since the CI tests run the analysis using latest published version of pigeon, not your version or the version on `main`. In either case, the configuration will be obtained automatically from the `pigeons/messages.dart` file (see `ConfigurePigeon` at the top of that file). ================================================ FILE: packages/video_player/video_player_android/LICENSE ================================================ 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. ================================================ FILE: packages/video_player/video_player_android/README.md ================================================ # video\_player\_android The Android implementation of [`video_player`][1]. ## Usage This package is [endorsed][2], which means you can simply use `video_player` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/video_player [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/video_player/video_player_android/android/build.gradle ================================================ group 'io.flutter.plugins.videoplayer' version '1.0-SNAPSHOT' def args = ["-Xlint:deprecation","-Xlint:unchecked","-Werror"] buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.2.1' } } rootProject.allprojects { repositories { google() mavenCentral() } } project.getTasks().withType(JavaCompile){ options.compilerArgs.addAll(args) } apply plugin: 'com.android.library' android { compileSdkVersion 31 defaultConfig { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } dependencies { implementation 'com.google.android.exoplayer:exoplayer-core:2.18.1' implementation 'com.google.android.exoplayer:exoplayer-hls:2.18.1' implementation 'com.google.android.exoplayer:exoplayer-dash:2.18.1' 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:5.0.0' testImplementation 'org.robolectric:robolectric:4.8.1' } testOptions { unitTests.includeAndroidResources = true unitTests.returnDefaultValues = true unitTests.all { testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" outputs.upToDateWhen {false} showStandardStreams = true } } } } ================================================ FILE: packages/video_player/video_player_android/android/settings.gradle ================================================ rootProject.name = 'video_player_android' ================================================ FILE: packages/video_player/video_player_android/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/CustomSSLSocketFactory.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.videoplayer; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; public class CustomSSLSocketFactory extends SSLSocketFactory { private SSLSocketFactory sslSocketFactory; public CustomSSLSocketFactory() throws KeyManagementException, NoSuchAlgorithmException { SSLContext context = SSLContext.getInstance("TLS"); context.init(null, null, null); sslSocketFactory = context.getSocketFactory(); } @Override public String[] getDefaultCipherSuites() { return sslSocketFactory.getDefaultCipherSuites(); } @Override public String[] getSupportedCipherSuites() { return sslSocketFactory.getSupportedCipherSuites(); } @Override public Socket createSocket() throws IOException { return enableProtocols(sslSocketFactory.createSocket()); } @Override public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { return enableProtocols(sslSocketFactory.createSocket(s, host, port, autoClose)); } @Override public Socket createSocket(String host, int port) throws IOException { return enableProtocols(sslSocketFactory.createSocket(host, port)); } @Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { return enableProtocols(sslSocketFactory.createSocket(host, port, localHost, localPort)); } @Override public Socket createSocket(InetAddress host, int port) throws IOException { return enableProtocols(sslSocketFactory.createSocket(host, port)); } @Override public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { return enableProtocols(sslSocketFactory.createSocket(address, port, localAddress, localPort)); } private Socket enableProtocols(Socket socket) { if (socket instanceof SSLSocket) { ((SSLSocket) socket).setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"}); } return socket; } } ================================================ FILE: packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java ================================================ // Copyright 2013 The Flutter 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 (v2.0.1), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.videoplayer; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.plugin.common.BasicMessageChannel; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MessageCodec; import io.flutter.plugin.common.StandardMessageCodec; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; /** Generated class from Pigeon. */ @SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"}) public class Messages { /** Generated class from Pigeon that represents data sent in messages. */ public static class TextureMessage { private @NonNull Long textureId; public @NonNull Long getTextureId() { return textureId; } public void setTextureId(@NonNull Long setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"textureId\" is null."); } this.textureId = setterArg; } /** Constructor is private to enforce null safety; use Builder. */ private TextureMessage() {} public static class Builder { private @Nullable Long textureId; public @NonNull Builder setTextureId(@NonNull Long setterArg) { this.textureId = setterArg; return this; } public @NonNull TextureMessage build() { TextureMessage pigeonReturn = new TextureMessage(); pigeonReturn.setTextureId(textureId); return pigeonReturn; } } @NonNull Map toMap() { Map toMapResult = new HashMap<>(); toMapResult.put("textureId", textureId); return toMapResult; } static @NonNull TextureMessage fromMap(@NonNull Map map) { TextureMessage pigeonResult = new TextureMessage(); Object textureId = map.get("textureId"); pigeonResult.setTextureId( (textureId == null) ? null : ((textureId instanceof Integer) ? (Integer) textureId : (Long) textureId)); return pigeonResult; } } /** Generated class from Pigeon that represents data sent in messages. */ public static class LoopingMessage { private @NonNull Long textureId; public @NonNull Long getTextureId() { return textureId; } public void setTextureId(@NonNull Long setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"textureId\" is null."); } this.textureId = setterArg; } private @NonNull Boolean isLooping; public @NonNull Boolean getIsLooping() { return isLooping; } public void setIsLooping(@NonNull Boolean setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"isLooping\" is null."); } this.isLooping = setterArg; } /** Constructor is private to enforce null safety; use Builder. */ private LoopingMessage() {} public static class Builder { private @Nullable Long textureId; public @NonNull Builder setTextureId(@NonNull Long setterArg) { this.textureId = setterArg; return this; } private @Nullable Boolean isLooping; public @NonNull Builder setIsLooping(@NonNull Boolean setterArg) { this.isLooping = setterArg; return this; } public @NonNull LoopingMessage build() { LoopingMessage pigeonReturn = new LoopingMessage(); pigeonReturn.setTextureId(textureId); pigeonReturn.setIsLooping(isLooping); return pigeonReturn; } } @NonNull Map toMap() { Map toMapResult = new HashMap<>(); toMapResult.put("textureId", textureId); toMapResult.put("isLooping", isLooping); return toMapResult; } static @NonNull LoopingMessage fromMap(@NonNull Map map) { LoopingMessage pigeonResult = new LoopingMessage(); Object textureId = map.get("textureId"); pigeonResult.setTextureId( (textureId == null) ? null : ((textureId instanceof Integer) ? (Integer) textureId : (Long) textureId)); Object isLooping = map.get("isLooping"); pigeonResult.setIsLooping((Boolean) isLooping); return pigeonResult; } } /** Generated class from Pigeon that represents data sent in messages. */ public static class VolumeMessage { private @NonNull Long textureId; public @NonNull Long getTextureId() { return textureId; } public void setTextureId(@NonNull Long setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"textureId\" is null."); } this.textureId = setterArg; } private @NonNull Double volume; public @NonNull Double getVolume() { return volume; } public void setVolume(@NonNull Double setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"volume\" is null."); } this.volume = setterArg; } /** Constructor is private to enforce null safety; use Builder. */ private VolumeMessage() {} public static class Builder { private @Nullable Long textureId; public @NonNull Builder setTextureId(@NonNull Long setterArg) { this.textureId = setterArg; return this; } private @Nullable Double volume; public @NonNull Builder setVolume(@NonNull Double setterArg) { this.volume = setterArg; return this; } public @NonNull VolumeMessage build() { VolumeMessage pigeonReturn = new VolumeMessage(); pigeonReturn.setTextureId(textureId); pigeonReturn.setVolume(volume); return pigeonReturn; } } @NonNull Map toMap() { Map toMapResult = new HashMap<>(); toMapResult.put("textureId", textureId); toMapResult.put("volume", volume); return toMapResult; } static @NonNull VolumeMessage fromMap(@NonNull Map map) { VolumeMessage pigeonResult = new VolumeMessage(); Object textureId = map.get("textureId"); pigeonResult.setTextureId( (textureId == null) ? null : ((textureId instanceof Integer) ? (Integer) textureId : (Long) textureId)); Object volume = map.get("volume"); pigeonResult.setVolume((Double) volume); return pigeonResult; } } /** Generated class from Pigeon that represents data sent in messages. */ public static class PlaybackSpeedMessage { private @NonNull Long textureId; public @NonNull Long getTextureId() { return textureId; } public void setTextureId(@NonNull Long setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"textureId\" is null."); } this.textureId = setterArg; } private @NonNull Double speed; public @NonNull Double getSpeed() { return speed; } public void setSpeed(@NonNull Double setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"speed\" is null."); } this.speed = setterArg; } /** Constructor is private to enforce null safety; use Builder. */ private PlaybackSpeedMessage() {} public static class Builder { private @Nullable Long textureId; public @NonNull Builder setTextureId(@NonNull Long setterArg) { this.textureId = setterArg; return this; } private @Nullable Double speed; public @NonNull Builder setSpeed(@NonNull Double setterArg) { this.speed = setterArg; return this; } public @NonNull PlaybackSpeedMessage build() { PlaybackSpeedMessage pigeonReturn = new PlaybackSpeedMessage(); pigeonReturn.setTextureId(textureId); pigeonReturn.setSpeed(speed); return pigeonReturn; } } @NonNull Map toMap() { Map toMapResult = new HashMap<>(); toMapResult.put("textureId", textureId); toMapResult.put("speed", speed); return toMapResult; } static @NonNull PlaybackSpeedMessage fromMap(@NonNull Map map) { PlaybackSpeedMessage pigeonResult = new PlaybackSpeedMessage(); Object textureId = map.get("textureId"); pigeonResult.setTextureId( (textureId == null) ? null : ((textureId instanceof Integer) ? (Integer) textureId : (Long) textureId)); Object speed = map.get("speed"); pigeonResult.setSpeed((Double) speed); return pigeonResult; } } /** Generated class from Pigeon that represents data sent in messages. */ public static class PositionMessage { private @NonNull Long textureId; public @NonNull Long getTextureId() { return textureId; } public void setTextureId(@NonNull Long setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"textureId\" is null."); } this.textureId = setterArg; } private @NonNull Long position; public @NonNull Long getPosition() { return position; } public void setPosition(@NonNull Long setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"position\" is null."); } this.position = setterArg; } /** Constructor is private to enforce null safety; use Builder. */ private PositionMessage() {} public static class Builder { private @Nullable Long textureId; public @NonNull Builder setTextureId(@NonNull Long setterArg) { this.textureId = setterArg; return this; } private @Nullable Long position; public @NonNull Builder setPosition(@NonNull Long setterArg) { this.position = setterArg; return this; } public @NonNull PositionMessage build() { PositionMessage pigeonReturn = new PositionMessage(); pigeonReturn.setTextureId(textureId); pigeonReturn.setPosition(position); return pigeonReturn; } } @NonNull Map toMap() { Map toMapResult = new HashMap<>(); toMapResult.put("textureId", textureId); toMapResult.put("position", position); return toMapResult; } static @NonNull PositionMessage fromMap(@NonNull Map map) { PositionMessage pigeonResult = new PositionMessage(); Object textureId = map.get("textureId"); pigeonResult.setTextureId( (textureId == null) ? null : ((textureId instanceof Integer) ? (Integer) textureId : (Long) textureId)); Object position = map.get("position"); pigeonResult.setPosition( (position == null) ? null : ((position instanceof Integer) ? (Integer) position : (Long) position)); return pigeonResult; } } /** Generated class from Pigeon that represents data sent in messages. */ public static class CreateMessage { private @Nullable String asset; public @Nullable String getAsset() { return asset; } public void setAsset(@Nullable String setterArg) { this.asset = setterArg; } private @Nullable String uri; public @Nullable String getUri() { return uri; } public void setUri(@Nullable String setterArg) { this.uri = setterArg; } private @Nullable String packageName; public @Nullable String getPackageName() { return packageName; } public void setPackageName(@Nullable String setterArg) { this.packageName = setterArg; } private @Nullable String formatHint; public @Nullable String getFormatHint() { return formatHint; } public void setFormatHint(@Nullable String setterArg) { this.formatHint = setterArg; } private @NonNull Map httpHeaders; public @NonNull Map getHttpHeaders() { return httpHeaders; } public void setHttpHeaders(@NonNull Map setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"httpHeaders\" is null."); } this.httpHeaders = setterArg; } /** Constructor is private to enforce null safety; use Builder. */ private CreateMessage() {} public static class Builder { private @Nullable String asset; public @NonNull Builder setAsset(@Nullable String setterArg) { this.asset = setterArg; return this; } private @Nullable String uri; public @NonNull Builder setUri(@Nullable String setterArg) { this.uri = setterArg; return this; } private @Nullable String packageName; public @NonNull Builder setPackageName(@Nullable String setterArg) { this.packageName = setterArg; return this; } private @Nullable String formatHint; public @NonNull Builder setFormatHint(@Nullable String setterArg) { this.formatHint = setterArg; return this; } private @Nullable Map httpHeaders; public @NonNull Builder setHttpHeaders(@NonNull Map setterArg) { this.httpHeaders = setterArg; return this; } public @NonNull CreateMessage build() { CreateMessage pigeonReturn = new CreateMessage(); pigeonReturn.setAsset(asset); pigeonReturn.setUri(uri); pigeonReturn.setPackageName(packageName); pigeonReturn.setFormatHint(formatHint); pigeonReturn.setHttpHeaders(httpHeaders); return pigeonReturn; } } @NonNull Map toMap() { Map toMapResult = new HashMap<>(); toMapResult.put("asset", asset); toMapResult.put("uri", uri); toMapResult.put("packageName", packageName); toMapResult.put("formatHint", formatHint); toMapResult.put("httpHeaders", httpHeaders); return toMapResult; } static @NonNull CreateMessage fromMap(@NonNull Map map) { CreateMessage pigeonResult = new CreateMessage(); Object asset = map.get("asset"); pigeonResult.setAsset((String) asset); Object uri = map.get("uri"); pigeonResult.setUri((String) uri); Object packageName = map.get("packageName"); pigeonResult.setPackageName((String) packageName); Object formatHint = map.get("formatHint"); pigeonResult.setFormatHint((String) formatHint); Object httpHeaders = map.get("httpHeaders"); pigeonResult.setHttpHeaders((Map) httpHeaders); return pigeonResult; } } /** Generated class from Pigeon that represents data sent in messages. */ public static class MixWithOthersMessage { private @NonNull Boolean mixWithOthers; public @NonNull Boolean getMixWithOthers() { return mixWithOthers; } public void setMixWithOthers(@NonNull Boolean setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"mixWithOthers\" is null."); } this.mixWithOthers = setterArg; } /** Constructor is private to enforce null safety; use Builder. */ private MixWithOthersMessage() {} public static class Builder { private @Nullable Boolean mixWithOthers; public @NonNull Builder setMixWithOthers(@NonNull Boolean setterArg) { this.mixWithOthers = setterArg; return this; } public @NonNull MixWithOthersMessage build() { MixWithOthersMessage pigeonReturn = new MixWithOthersMessage(); pigeonReturn.setMixWithOthers(mixWithOthers); return pigeonReturn; } } @NonNull Map toMap() { Map toMapResult = new HashMap<>(); toMapResult.put("mixWithOthers", mixWithOthers); return toMapResult; } static @NonNull MixWithOthersMessage fromMap(@NonNull Map map) { MixWithOthersMessage pigeonResult = new MixWithOthersMessage(); Object mixWithOthers = map.get("mixWithOthers"); pigeonResult.setMixWithOthers((Boolean) mixWithOthers); return pigeonResult; } } private static class AndroidVideoPlayerApiCodec extends StandardMessageCodec { public static final AndroidVideoPlayerApiCodec INSTANCE = new AndroidVideoPlayerApiCodec(); private AndroidVideoPlayerApiCodec() {} @Override protected Object readValueOfType(byte type, ByteBuffer buffer) { switch (type) { case (byte) 128: return CreateMessage.fromMap((Map) readValue(buffer)); case (byte) 129: return LoopingMessage.fromMap((Map) readValue(buffer)); case (byte) 130: return MixWithOthersMessage.fromMap((Map) readValue(buffer)); case (byte) 131: return PlaybackSpeedMessage.fromMap((Map) readValue(buffer)); case (byte) 132: return PositionMessage.fromMap((Map) readValue(buffer)); case (byte) 133: return TextureMessage.fromMap((Map) readValue(buffer)); case (byte) 134: return VolumeMessage.fromMap((Map) readValue(buffer)); default: return super.readValueOfType(type, buffer); } } @Override protected void writeValue(ByteArrayOutputStream stream, Object value) { if (value instanceof CreateMessage) { stream.write(128); writeValue(stream, ((CreateMessage) value).toMap()); } else if (value instanceof LoopingMessage) { stream.write(129); writeValue(stream, ((LoopingMessage) value).toMap()); } else if (value instanceof MixWithOthersMessage) { stream.write(130); writeValue(stream, ((MixWithOthersMessage) value).toMap()); } else if (value instanceof PlaybackSpeedMessage) { stream.write(131); writeValue(stream, ((PlaybackSpeedMessage) value).toMap()); } else if (value instanceof PositionMessage) { stream.write(132); writeValue(stream, ((PositionMessage) value).toMap()); } else if (value instanceof TextureMessage) { stream.write(133); writeValue(stream, ((TextureMessage) value).toMap()); } else if (value instanceof VolumeMessage) { stream.write(134); writeValue(stream, ((VolumeMessage) value).toMap()); } else { super.writeValue(stream, value); } } } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface AndroidVideoPlayerApi { void initialize(); @NonNull TextureMessage create(@NonNull CreateMessage msg); void dispose(@NonNull TextureMessage msg); void setLooping(@NonNull LoopingMessage msg); void setVolume(@NonNull VolumeMessage msg); void setPlaybackSpeed(@NonNull PlaybackSpeedMessage msg); void play(@NonNull TextureMessage msg); @NonNull PositionMessage position(@NonNull TextureMessage msg); void seekTo(@NonNull PositionMessage msg); void pause(@NonNull TextureMessage msg); void setMixWithOthers(@NonNull MixWithOthersMessage msg); /** The codec used by AndroidVideoPlayerApi. */ static MessageCodec getCodec() { return AndroidVideoPlayerApiCodec.INSTANCE; } /** * Sets up an instance of `AndroidVideoPlayerApi` to handle messages through the * `binaryMessenger`. */ static void setup(BinaryMessenger binaryMessenger, AndroidVideoPlayerApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.AndroidVideoPlayerApi.initialize", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { api.initialize(); 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.AndroidVideoPlayerApi.create", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; CreateMessage msgArg = (CreateMessage) args.get(0); if (msgArg == null) { throw new NullPointerException("msgArg unexpectedly null."); } TextureMessage output = api.create(msgArg); 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.AndroidVideoPlayerApi.dispose", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; TextureMessage msgArg = (TextureMessage) args.get(0); if (msgArg == null) { throw new NullPointerException("msgArg unexpectedly null."); } api.dispose(msgArg); 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.AndroidVideoPlayerApi.setLooping", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; LoopingMessage msgArg = (LoopingMessage) args.get(0); if (msgArg == null) { throw new NullPointerException("msgArg unexpectedly null."); } api.setLooping(msgArg); 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.AndroidVideoPlayerApi.setVolume", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; VolumeMessage msgArg = (VolumeMessage) args.get(0); if (msgArg == null) { throw new NullPointerException("msgArg unexpectedly null."); } api.setVolume(msgArg); 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.AndroidVideoPlayerApi.setPlaybackSpeed", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; PlaybackSpeedMessage msgArg = (PlaybackSpeedMessage) args.get(0); if (msgArg == null) { throw new NullPointerException("msgArg unexpectedly null."); } api.setPlaybackSpeed(msgArg); 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.AndroidVideoPlayerApi.play", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; TextureMessage msgArg = (TextureMessage) args.get(0); if (msgArg == null) { throw new NullPointerException("msgArg unexpectedly null."); } api.play(msgArg); 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.AndroidVideoPlayerApi.position", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; TextureMessage msgArg = (TextureMessage) args.get(0); if (msgArg == null) { throw new NullPointerException("msgArg unexpectedly null."); } PositionMessage output = api.position(msgArg); 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.AndroidVideoPlayerApi.seekTo", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; PositionMessage msgArg = (PositionMessage) args.get(0); if (msgArg == null) { throw new NullPointerException("msgArg unexpectedly null."); } api.seekTo(msgArg); 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.AndroidVideoPlayerApi.pause", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; TextureMessage msgArg = (TextureMessage) args.get(0); if (msgArg == null) { throw new NullPointerException("msgArg unexpectedly null."); } api.pause(msgArg); 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.AndroidVideoPlayerApi.setMixWithOthers", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { Map wrapped = new HashMap<>(); try { ArrayList args = (ArrayList) message; MixWithOthersMessage msgArg = (MixWithOthersMessage) args.get(0); if (msgArg == null) { throw new NullPointerException("msgArg unexpectedly null."); } api.setMixWithOthers(msgArg); wrapped.put("result", null); } catch (Error | RuntimeException exception) { wrapped.put("error", wrapError(exception)); } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } } } private static Map wrapError(Throwable exception) { Map errorMap = new HashMap<>(); errorMap.put("message", exception.toString()); errorMap.put("code", exception.getClass().getSimpleName()); errorMap.put( "details", "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); return errorMap; } } ================================================ FILE: packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/QueuingEventSink.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.videoplayer; import io.flutter.plugin.common.EventChannel; import java.util.ArrayList; /** * And implementation of {@link EventChannel.EventSink} which can wrap an underlying sink. * *

It delivers messages immediately when downstream is available, but it queues messages before * the delegate event sink is set with setDelegate. * *

This class is not thread-safe. All calls must be done on the same thread or synchronized * externally. */ final class QueuingEventSink implements EventChannel.EventSink { private EventChannel.EventSink delegate; private ArrayList eventQueue = new ArrayList<>(); private boolean done = false; public void setDelegate(EventChannel.EventSink delegate) { this.delegate = delegate; maybeFlush(); } @Override public void endOfStream() { enqueue(new EndOfStreamEvent()); maybeFlush(); done = true; } @Override public void error(String code, String message, Object details) { enqueue(new ErrorEvent(code, message, details)); maybeFlush(); } @Override public void success(Object event) { enqueue(event); maybeFlush(); } private void enqueue(Object event) { if (done) { return; } eventQueue.add(event); } private void maybeFlush() { if (delegate == null) { return; } for (Object event : eventQueue) { if (event instanceof EndOfStreamEvent) { delegate.endOfStream(); } else if (event instanceof ErrorEvent) { ErrorEvent errorEvent = (ErrorEvent) event; delegate.error(errorEvent.code, errorEvent.message, errorEvent.details); } else { delegate.success(event); } } eventQueue.clear(); } private static class EndOfStreamEvent {} private static class ErrorEvent { String code; String message; Object details; ErrorEvent(String code, String message, Object details) { this.code = code; this.message = message; this.details = details; } } } ================================================ FILE: packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.videoplayer; import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; import android.content.Context; import android.net.Uri; import android.view.Surface; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.PlaybackException; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player.Listener; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.source.dash.DashMediaSource; import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource; import com.google.android.exoplayer2.source.hls.HlsMediaSource; import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource; import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultDataSource; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; import com.google.android.exoplayer2.util.Util; import io.flutter.plugin.common.EventChannel; import io.flutter.view.TextureRegistry; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; final class VideoPlayer { private static final String FORMAT_SS = "ss"; private static final String FORMAT_DASH = "dash"; private static final String FORMAT_HLS = "hls"; private static final String FORMAT_OTHER = "other"; private ExoPlayer exoPlayer; private Surface surface; private final TextureRegistry.SurfaceTextureEntry textureEntry; private QueuingEventSink eventSink; private final EventChannel eventChannel; @VisibleForTesting boolean isInitialized = false; private final VideoPlayerOptions options; VideoPlayer( Context context, EventChannel eventChannel, TextureRegistry.SurfaceTextureEntry textureEntry, String dataSource, String formatHint, @NonNull Map httpHeaders, VideoPlayerOptions options) { this.eventChannel = eventChannel; this.textureEntry = textureEntry; this.options = options; ExoPlayer exoPlayer = new ExoPlayer.Builder(context).build(); Uri uri = Uri.parse(dataSource); DataSource.Factory dataSourceFactory; if (isHTTP(uri)) { DefaultHttpDataSource.Factory httpDataSourceFactory = new DefaultHttpDataSource.Factory() .setUserAgent("ExoPlayer") .setAllowCrossProtocolRedirects(true); if (httpHeaders != null && !httpHeaders.isEmpty()) { httpDataSourceFactory.setDefaultRequestProperties(httpHeaders); } dataSourceFactory = httpDataSourceFactory; } else { dataSourceFactory = new DefaultDataSource.Factory(context); } MediaSource mediaSource = buildMediaSource(uri, dataSourceFactory, formatHint, context); exoPlayer.setMediaSource(mediaSource); exoPlayer.prepare(); setUpVideoPlayer(exoPlayer, new QueuingEventSink()); } // Constructor used to directly test members of this class. @VisibleForTesting VideoPlayer( ExoPlayer exoPlayer, EventChannel eventChannel, TextureRegistry.SurfaceTextureEntry textureEntry, VideoPlayerOptions options, QueuingEventSink eventSink) { this.eventChannel = eventChannel; this.textureEntry = textureEntry; this.options = options; setUpVideoPlayer(exoPlayer, eventSink); } private static boolean isHTTP(Uri uri) { if (uri == null || uri.getScheme() == null) { return false; } String scheme = uri.getScheme(); return scheme.equals("http") || scheme.equals("https"); } private MediaSource buildMediaSource( Uri uri, DataSource.Factory mediaDataSourceFactory, String formatHint, Context context) { int type; if (formatHint == null) { type = Util.inferContentType(uri); } else { switch (formatHint) { case FORMAT_SS: type = C.CONTENT_TYPE_SS; break; case FORMAT_DASH: type = C.CONTENT_TYPE_DASH; break; case FORMAT_HLS: type = C.CONTENT_TYPE_HLS; break; case FORMAT_OTHER: type = C.CONTENT_TYPE_OTHER; break; default: type = -1; break; } } switch (type) { case C.CONTENT_TYPE_SS: return new SsMediaSource.Factory( new DefaultSsChunkSource.Factory(mediaDataSourceFactory), new DefaultDataSource.Factory(context, mediaDataSourceFactory)) .createMediaSource(MediaItem.fromUri(uri)); case C.CONTENT_TYPE_DASH: return new DashMediaSource.Factory( new DefaultDashChunkSource.Factory(mediaDataSourceFactory), new DefaultDataSource.Factory(context, mediaDataSourceFactory)) .createMediaSource(MediaItem.fromUri(uri)); case C.CONTENT_TYPE_HLS: return new HlsMediaSource.Factory(mediaDataSourceFactory) .createMediaSource(MediaItem.fromUri(uri)); case C.CONTENT_TYPE_OTHER: return new ProgressiveMediaSource.Factory(mediaDataSourceFactory) .createMediaSource(MediaItem.fromUri(uri)); default: { throw new IllegalStateException("Unsupported type: " + type); } } } private void setUpVideoPlayer(ExoPlayer exoPlayer, QueuingEventSink eventSink) { this.exoPlayer = exoPlayer; this.eventSink = eventSink; eventChannel.setStreamHandler( new EventChannel.StreamHandler() { @Override public void onListen(Object o, EventChannel.EventSink sink) { eventSink.setDelegate(sink); } @Override public void onCancel(Object o) { eventSink.setDelegate(null); } }); surface = new Surface(textureEntry.surfaceTexture()); exoPlayer.setVideoSurface(surface); setAudioAttributes(exoPlayer, options.mixWithOthers); exoPlayer.addListener( new Listener() { private boolean isBuffering = false; public void setBuffering(boolean buffering) { if (isBuffering != buffering) { isBuffering = buffering; Map event = new HashMap<>(); event.put("event", isBuffering ? "bufferingStart" : "bufferingEnd"); eventSink.success(event); } } @Override public void onPlaybackStateChanged(final int playbackState) { if (playbackState == Player.STATE_BUFFERING) { setBuffering(true); sendBufferingUpdate(); } else if (playbackState == Player.STATE_READY) { if (!isInitialized) { isInitialized = true; sendInitialized(); } } else if (playbackState == Player.STATE_ENDED) { Map event = new HashMap<>(); event.put("event", "completed"); eventSink.success(event); } if (playbackState != Player.STATE_BUFFERING) { setBuffering(false); } } @Override public void onPlayerError(final PlaybackException error) { setBuffering(false); if (eventSink != null) { eventSink.error("VideoError", "Video player had error " + error, null); } } }); } void sendBufferingUpdate() { Map event = new HashMap<>(); event.put("event", "bufferingUpdate"); List range = Arrays.asList(0, exoPlayer.getBufferedPosition()); // iOS supports a list of buffered ranges, so here is a list with a single range. event.put("values", Collections.singletonList(range)); eventSink.success(event); } private static void setAudioAttributes(ExoPlayer exoPlayer, boolean isMixMode) { exoPlayer.setAudioAttributes( new AudioAttributes.Builder().setContentType(C.AUDIO_CONTENT_TYPE_MOVIE).build(), !isMixMode); } void play() { exoPlayer.setPlayWhenReady(true); } void pause() { exoPlayer.setPlayWhenReady(false); } void setLooping(boolean value) { exoPlayer.setRepeatMode(value ? REPEAT_MODE_ALL : REPEAT_MODE_OFF); } void setVolume(double value) { float bracketedValue = (float) Math.max(0.0, Math.min(1.0, value)); exoPlayer.setVolume(bracketedValue); } void setPlaybackSpeed(double value) { // We do not need to consider pitch and skipSilence for now as we do not handle them and // therefore never diverge from the default values. final PlaybackParameters playbackParameters = new PlaybackParameters(((float) value)); exoPlayer.setPlaybackParameters(playbackParameters); } void seekTo(int location) { exoPlayer.seekTo(location); } long getPosition() { return exoPlayer.getCurrentPosition(); } @SuppressWarnings("SuspiciousNameCombination") @VisibleForTesting void sendInitialized() { if (isInitialized) { Map event = new HashMap<>(); event.put("event", "initialized"); event.put("duration", exoPlayer.getDuration()); if (exoPlayer.getVideoFormat() != null) { Format videoFormat = exoPlayer.getVideoFormat(); int width = videoFormat.width; int height = videoFormat.height; int rotationDegrees = videoFormat.rotationDegrees; // Switch the width/height if video was taken in portrait mode if (rotationDegrees == 90 || rotationDegrees == 270) { width = exoPlayer.getVideoFormat().height; height = exoPlayer.getVideoFormat().width; } event.put("width", width); event.put("height", height); // Rotating the video with ExoPlayer does not seem to be possible with a Surface, // so inform the Flutter code that the widget needs to be rotated to prevent // upside-down playback for videos with rotationDegrees of 180 (other orientations work // correctly without correction). if (rotationDegrees == 180) { event.put("rotationCorrection", rotationDegrees); } } eventSink.success(event); } } void dispose() { if (isInitialized) { exoPlayer.stop(); } textureEntry.release(); eventChannel.setStreamHandler(null); if (surface != null) { surface.release(); } if (exoPlayer != null) { exoPlayer.release(); } } } ================================================ FILE: packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerOptions.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.videoplayer; class VideoPlayerOptions { public boolean mixWithOthers; } ================================================ FILE: packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.videoplayer; import android.content.Context; import android.os.Build; import android.util.LongSparseArray; import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.EventChannel; import io.flutter.plugins.videoplayer.Messages.AndroidVideoPlayerApi; import io.flutter.plugins.videoplayer.Messages.CreateMessage; import io.flutter.plugins.videoplayer.Messages.LoopingMessage; import io.flutter.plugins.videoplayer.Messages.MixWithOthersMessage; import io.flutter.plugins.videoplayer.Messages.PlaybackSpeedMessage; import io.flutter.plugins.videoplayer.Messages.PositionMessage; import io.flutter.plugins.videoplayer.Messages.TextureMessage; import io.flutter.plugins.videoplayer.Messages.VolumeMessage; import io.flutter.view.TextureRegistry; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.util.Map; import javax.net.ssl.HttpsURLConnection; /** Android platform implementation of the VideoPlayerPlugin. */ public class VideoPlayerPlugin implements FlutterPlugin, AndroidVideoPlayerApi { private static final String TAG = "VideoPlayerPlugin"; private final LongSparseArray videoPlayers = new LongSparseArray<>(); private FlutterState flutterState; private VideoPlayerOptions options = new VideoPlayerOptions(); /** Register this with the v2 embedding for the plugin to respond to lifecycle callbacks. */ public VideoPlayerPlugin() {} @SuppressWarnings("deprecation") private VideoPlayerPlugin(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { this.flutterState = new FlutterState( registrar.context(), registrar.messenger(), registrar::lookupKeyForAsset, registrar::lookupKeyForAsset, registrar.textures()); flutterState.startListening(this, registrar.messenger()); } /** Registers this with the stable v1 embedding. Will not respond to lifecycle events. */ @SuppressWarnings("deprecation") public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { final VideoPlayerPlugin plugin = new VideoPlayerPlugin(registrar); registrar.addViewDestroyListener( view -> { plugin.onDestroy(); return false; // We are not interested in assuming ownership of the NativeView. }); } @Override public void onAttachedToEngine(FlutterPluginBinding binding) { if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { try { HttpsURLConnection.setDefaultSSLSocketFactory(new CustomSSLSocketFactory()); } catch (KeyManagementException | NoSuchAlgorithmException e) { Log.w( TAG, "Failed to enable TLSv1.1 and TLSv1.2 Protocols for API level 19 and below.\n" + "For more information about Socket Security, please consult the following link:\n" + "https://developer.android.com/reference/javax/net/ssl/SSLSocket", e); } } final FlutterInjector injector = FlutterInjector.instance(); this.flutterState = new FlutterState( binding.getApplicationContext(), binding.getBinaryMessenger(), injector.flutterLoader()::getLookupKeyForAsset, injector.flutterLoader()::getLookupKeyForAsset, binding.getTextureRegistry()); flutterState.startListening(this, binding.getBinaryMessenger()); } @Override public void onDetachedFromEngine(FlutterPluginBinding binding) { if (flutterState == null) { Log.wtf(TAG, "Detached from the engine before registering to it."); } flutterState.stopListening(binding.getBinaryMessenger()); flutterState = null; initialize(); } private void disposeAllPlayers() { for (int i = 0; i < videoPlayers.size(); i++) { videoPlayers.valueAt(i).dispose(); } videoPlayers.clear(); } private void onDestroy() { // The whole FlutterView is being destroyed. Here we release resources acquired for all // instances // of VideoPlayer. Once https://github.com/flutter/flutter/issues/19358 is resolved this may // be replaced with just asserting that videoPlayers.isEmpty(). // https://github.com/flutter/flutter/issues/20989 tracks this. disposeAllPlayers(); } public void initialize() { disposeAllPlayers(); } public TextureMessage create(CreateMessage arg) { TextureRegistry.SurfaceTextureEntry handle = flutterState.textureRegistry.createSurfaceTexture(); EventChannel eventChannel = new EventChannel( flutterState.binaryMessenger, "flutter.io/videoPlayer/videoEvents" + handle.id()); VideoPlayer player; if (arg.getAsset() != null) { String assetLookupKey; if (arg.getPackageName() != null) { assetLookupKey = flutterState.keyForAssetAndPackageName.get(arg.getAsset(), arg.getPackageName()); } else { assetLookupKey = flutterState.keyForAsset.get(arg.getAsset()); } player = new VideoPlayer( flutterState.applicationContext, eventChannel, handle, "asset:///" + assetLookupKey, null, null, options); } else { @SuppressWarnings("unchecked") Map httpHeaders = arg.getHttpHeaders(); player = new VideoPlayer( flutterState.applicationContext, eventChannel, handle, arg.getUri(), arg.getFormatHint(), httpHeaders, options); } videoPlayers.put(handle.id(), player); TextureMessage result = new TextureMessage.Builder().setTextureId(handle.id()).build(); return result; } public void dispose(TextureMessage arg) { VideoPlayer player = videoPlayers.get(arg.getTextureId()); player.dispose(); videoPlayers.remove(arg.getTextureId()); } public void setLooping(LoopingMessage arg) { VideoPlayer player = videoPlayers.get(arg.getTextureId()); player.setLooping(arg.getIsLooping()); } public void setVolume(VolumeMessage arg) { VideoPlayer player = videoPlayers.get(arg.getTextureId()); player.setVolume(arg.getVolume()); } public void setPlaybackSpeed(PlaybackSpeedMessage arg) { VideoPlayer player = videoPlayers.get(arg.getTextureId()); player.setPlaybackSpeed(arg.getSpeed()); } public void play(TextureMessage arg) { VideoPlayer player = videoPlayers.get(arg.getTextureId()); player.play(); } public PositionMessage position(TextureMessage arg) { VideoPlayer player = videoPlayers.get(arg.getTextureId()); PositionMessage result = new PositionMessage.Builder() .setPosition(player.getPosition()) .setTextureId(arg.getTextureId()) .build(); player.sendBufferingUpdate(); return result; } public void seekTo(PositionMessage arg) { VideoPlayer player = videoPlayers.get(arg.getTextureId()); player.seekTo(arg.getPosition().intValue()); } public void pause(TextureMessage arg) { VideoPlayer player = videoPlayers.get(arg.getTextureId()); player.pause(); } @Override public void setMixWithOthers(MixWithOthersMessage arg) { options.mixWithOthers = arg.getMixWithOthers(); } private interface KeyForAssetFn { String get(String asset); } private interface KeyForAssetAndPackageName { String get(String asset, String packageName); } private static final class FlutterState { private final Context applicationContext; private final BinaryMessenger binaryMessenger; private final KeyForAssetFn keyForAsset; private final KeyForAssetAndPackageName keyForAssetAndPackageName; private final TextureRegistry textureRegistry; FlutterState( Context applicationContext, BinaryMessenger messenger, KeyForAssetFn keyForAsset, KeyForAssetAndPackageName keyForAssetAndPackageName, TextureRegistry textureRegistry) { this.applicationContext = applicationContext; this.binaryMessenger = messenger; this.keyForAsset = keyForAsset; this.keyForAssetAndPackageName = keyForAssetAndPackageName; this.textureRegistry = textureRegistry; } void startListening(VideoPlayerPlugin methodCallHandler, BinaryMessenger messenger) { AndroidVideoPlayerApi.setup(messenger, methodCallHandler); } void stopListening(BinaryMessenger messenger) { AndroidVideoPlayerApi.setup(messenger, null); } } } ================================================ FILE: packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerPluginTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.videoplayer; import org.junit.Test; public class VideoPlayerPluginTest { // This is only a placeholder test and doesn't actually initialize the plugin. @Test public void initPluginDoesNotThrow() { final VideoPlayerPlugin plugin = new VideoPlayerPlugin(); } } ================================================ FILE: packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.videoplayer; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Format; import io.flutter.plugin.common.EventChannel; import io.flutter.view.TextureRegistry; import java.util.HashMap; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class VideoPlayerTest { private ExoPlayer fakeExoPlayer; private EventChannel fakeEventChannel; private TextureRegistry.SurfaceTextureEntry fakeSurfaceTextureEntry; private VideoPlayerOptions fakeVideoPlayerOptions; private QueuingEventSink fakeEventSink; @Captor private ArgumentCaptor> eventCaptor; @Before public void before() { MockitoAnnotations.openMocks(this); fakeExoPlayer = mock(ExoPlayer.class); fakeEventChannel = mock(EventChannel.class); fakeSurfaceTextureEntry = mock(TextureRegistry.SurfaceTextureEntry.class); fakeVideoPlayerOptions = mock(VideoPlayerOptions.class); fakeEventSink = mock(QueuingEventSink.class); } @Test public void sendInitializedSendsExpectedEvent_90RotationDegrees() { VideoPlayer videoPlayer = new VideoPlayer( fakeExoPlayer, fakeEventChannel, fakeSurfaceTextureEntry, fakeVideoPlayerOptions, fakeEventSink); Format testFormat = new Format.Builder().setWidth(100).setHeight(200).setRotationDegrees(90).build(); when(fakeExoPlayer.getVideoFormat()).thenReturn(testFormat); when(fakeExoPlayer.getDuration()).thenReturn(10L); videoPlayer.isInitialized = true; videoPlayer.sendInitialized(); verify(fakeEventSink).success(eventCaptor.capture()); HashMap event = eventCaptor.getValue(); assertEquals(event.get("event"), "initialized"); assertEquals(event.get("duration"), 10L); assertEquals(event.get("width"), 200); assertEquals(event.get("height"), 100); assertEquals(event.get("rotationCorrection"), null); } @Test public void sendInitializedSendsExpectedEvent_270RotationDegrees() { VideoPlayer videoPlayer = new VideoPlayer( fakeExoPlayer, fakeEventChannel, fakeSurfaceTextureEntry, fakeVideoPlayerOptions, fakeEventSink); Format testFormat = new Format.Builder().setWidth(100).setHeight(200).setRotationDegrees(270).build(); when(fakeExoPlayer.getVideoFormat()).thenReturn(testFormat); when(fakeExoPlayer.getDuration()).thenReturn(10L); videoPlayer.isInitialized = true; videoPlayer.sendInitialized(); verify(fakeEventSink).success(eventCaptor.capture()); HashMap event = eventCaptor.getValue(); assertEquals(event.get("event"), "initialized"); assertEquals(event.get("duration"), 10L); assertEquals(event.get("width"), 200); assertEquals(event.get("height"), 100); assertEquals(event.get("rotationCorrection"), null); } @Test public void sendInitializedSendsExpectedEvent_0RotationDegrees() { VideoPlayer videoPlayer = new VideoPlayer( fakeExoPlayer, fakeEventChannel, fakeSurfaceTextureEntry, fakeVideoPlayerOptions, fakeEventSink); Format testFormat = new Format.Builder().setWidth(100).setHeight(200).setRotationDegrees(0).build(); when(fakeExoPlayer.getVideoFormat()).thenReturn(testFormat); when(fakeExoPlayer.getDuration()).thenReturn(10L); videoPlayer.isInitialized = true; videoPlayer.sendInitialized(); verify(fakeEventSink).success(eventCaptor.capture()); HashMap event = eventCaptor.getValue(); assertEquals(event.get("event"), "initialized"); assertEquals(event.get("duration"), 10L); assertEquals(event.get("width"), 100); assertEquals(event.get("height"), 200); assertEquals(event.get("rotationCorrection"), null); } @Test public void sendInitializedSendsExpectedEvent_180RotationDegrees() { VideoPlayer videoPlayer = new VideoPlayer( fakeExoPlayer, fakeEventChannel, fakeSurfaceTextureEntry, fakeVideoPlayerOptions, fakeEventSink); Format testFormat = new Format.Builder().setWidth(100).setHeight(200).setRotationDegrees(180).build(); when(fakeExoPlayer.getVideoFormat()).thenReturn(testFormat); when(fakeExoPlayer.getDuration()).thenReturn(10L); videoPlayer.isInitialized = true; videoPlayer.sendInitialized(); verify(fakeEventSink).success(eventCaptor.capture()); HashMap event = eventCaptor.getValue(); assertEquals(event.get("event"), "initialized"); assertEquals(event.get("duration"), 10L); assertEquals(event.get("width"), 100); assertEquals(event.get("height"), 200); assertEquals(event.get("rotationCorrection"), 180); } } ================================================ FILE: packages/video_player/video_player_android/example/.gitignore ================================================ lib/generated_plugin_registrant.dart ================================================ FILE: packages/video_player/video_player_android/example/README.md ================================================ # 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. ================================================ FILE: packages/video_player/video_player_android/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 lintOptions { disable 'InvalidPackage' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { applicationId "io.flutter.plugins.videoplayerexample" minSdkVersion 21 targetSdkVersion 29 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { testImplementation 'junit:junit:4.13' testImplementation 'org.robolectric:robolectric:4.8.2' testImplementation 'org.mockito:mockito-core:5.0.0' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } ================================================ FILE: packages/video_player/video_player_android/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/video_player/video_player_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/video_player/video_player_android/example/android/app/src/androidTest/java/io/flutter/plugins/videoplayerexample/FlutterActivityTest.java ================================================ // Copyright 2013 The Flutter 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.videoplayerexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class FlutterActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/video_player/video_player_android/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/video_player/video_player_android/example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: packages/video_player/video_player_android/example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: packages/video_player/video_player_android/example/android/app/src/main/res/xml/network_security_config.xml ================================================ www.sample-videos.com 184.72.239.149 ================================================ FILE: packages/video_player/video_player_android/example/android/app/src/test/java/io/flutter/plugins/videoplayerexample/FlutterActivityTest.java ================================================ // Copyright 2013 The Flutter 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.videoplayerexample; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngineCache; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.plugins.videoplayer.VideoPlayerPlugin; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class FlutterActivityTest { @Test public void disposeAllPlayers() { VideoPlayerPlugin videoPlayerPlugin = spy(new VideoPlayerPlugin()); FlutterLoader flutterLoader = mock(FlutterLoader.class); FlutterJNI flutterJNI = mock(FlutterJNI.class); ArgumentCaptor pluginBindingCaptor = ArgumentCaptor.forClass(FlutterPlugin.FlutterPluginBinding.class); when(flutterJNI.isAttached()).thenReturn(true); FlutterEngine engine = spy(new FlutterEngine(RuntimeEnvironment.application, flutterLoader, flutterJNI)); FlutterEngineCache.getInstance().put("my_flutter_engine", engine); engine.getPlugins().add(videoPlayerPlugin); verify(videoPlayerPlugin, times(1)).onAttachedToEngine(pluginBindingCaptor.capture()); engine.destroy(); verify(videoPlayerPlugin, times(1)).onDetachedFromEngine(pluginBindingCaptor.capture()); verify(videoPlayerPlugin, times(1)).initialize(); } } ================================================ FILE: packages/video_player/video_player_android/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/video_player/video_player_android/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/video_player/video_player_android/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true android.enableR8=true ================================================ FILE: packages/video_player/video_player_android/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withInputStream { stream -> plugins.load(stream) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/video_player/video_player_android/example/integration_test/video_player_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; import 'dart:typed_data'; import 'package:flutter/services.dart' show rootBundle; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:path_provider/path_provider.dart'; import 'package:video_player_android/video_player_android.dart'; // TODO(stuartmorgan): Remove the use of MiniController in tests, as that is // testing test code; tests should instead be written directly against the // platform interface. (These tests were copied from the app-facing package // during federation and minimally modified, which is why they currently use the // controller.) import 'package:video_player_example/mini_controller.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; const Duration _playDuration = Duration(seconds: 1); const String _videoAssetKey = 'assets/Butterfly-209.mp4'; // Returns the URL to load an asset from this example app as a network source. // // TODO(stuartmorgan): Convert this to a local `HttpServer` that vends the // assets directly, https://github.com/flutter/flutter/issues/95420 String getUrlForAssetAsNetworkSource(String assetKey) { return 'https://github.com/flutter/plugins/blob/' // This hash can be rolled forward to pick up newly-added assets. 'cb381ced070d356799dddf24aca38ce0579d3d7b' '/packages/video_player/video_player/example/' '$assetKey' '?raw=true'; } void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); late MiniController controller; tearDown(() async => controller.dispose()); group('asset videos', () { setUp(() { controller = MiniController.asset(_videoAssetKey); }); testWidgets('registers expected implementation', (WidgetTester tester) async { AndroidVideoPlayer.registerWith(); expect(VideoPlayerPlatform.instance, isA()); }); testWidgets('can be initialized', (WidgetTester tester) async { await controller.initialize(); expect(controller.value.isInitialized, true); expect(await controller.position, Duration.zero); expect(controller.value.duration, const Duration(seconds: 7, milliseconds: 540)); }); testWidgets('can be played', (WidgetTester tester) async { await controller.initialize(); await controller.play(); await tester.pumpAndSettle(_playDuration); expect(await controller.position, greaterThan(Duration.zero)); }); testWidgets('can seek', (WidgetTester tester) async { await controller.initialize(); await controller.seekTo(const Duration(seconds: 3)); expect(await controller.position, const Duration(seconds: 3)); }); testWidgets('can be paused', (WidgetTester tester) async { await controller.initialize(); // Play for a second, then pause, and then wait a second. await controller.play(); await tester.pumpAndSettle(_playDuration); await controller.pause(); await tester.pumpAndSettle(_playDuration); final Duration pausedPosition = (await controller.position)!; await tester.pumpAndSettle(_playDuration); // Verify that we stopped playing after the pause. expect(await controller.position, pausedPosition); }); }); group('file-based videos', () { setUp(() async { // Load the data from the asset. final String tempDir = (await getTemporaryDirectory()).path; final ByteData bytes = await rootBundle.load(_videoAssetKey); // Write it to a file to use as a source. final String filename = _videoAssetKey.split('/').last; final File file = File('$tempDir/$filename'); await file.writeAsBytes(bytes.buffer.asInt8List()); controller = MiniController.file(file); }); testWidgets('test video player using static file() method as constructor', (WidgetTester tester) async { await controller.initialize(); await controller.play(); await tester.pumpAndSettle(_playDuration); expect(await controller.position, greaterThan(Duration.zero)); }); }); group('network videos', () { setUp(() { final String videoUrl = getUrlForAssetAsNetworkSource(_videoAssetKey); controller = MiniController.network(videoUrl); }); testWidgets('reports buffering status', (WidgetTester tester) async { await controller.initialize(); final Completer started = Completer(); final Completer ended = Completer(); controller.addListener(() { if (!started.isCompleted && controller.value.isBuffering) { started.complete(); } if (started.isCompleted && !controller.value.isBuffering && !ended.isCompleted) { ended.complete(); } }); await controller.play(); await controller.seekTo(const Duration(seconds: 5)); await tester.pumpAndSettle(_playDuration); await controller.pause(); expect(await controller.position, greaterThan(Duration.zero)); await expectLater(started.future, completes); await expectLater(ended.future, completes); }); testWidgets('live stream duration != 0', (WidgetTester tester) async { final MiniController livestreamController = MiniController.network( 'https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8', ); await livestreamController.initialize(); expect(livestreamController.value.isInitialized, true); // Live streams should have either a positive duration or C.TIME_UNSET if the duration is unknown // See https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/Player.html#getDuration-- expect(livestreamController.value.duration, (Duration duration) => duration != Duration.zero); }); }); } ================================================ FILE: packages/video_player/video_player_android/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'mini_controller.dart'; void main() { runApp( MaterialApp( home: _App(), ), ); } class _App extends StatelessWidget { @override Widget build(BuildContext context) { return DefaultTabController( length: 2, child: Scaffold( key: const ValueKey('home_page'), appBar: AppBar( title: const Text('Video player example'), bottom: const TabBar( isScrollable: true, tabs: [ Tab( icon: Icon(Icons.cloud), text: 'Remote', ), Tab(icon: Icon(Icons.insert_drive_file), text: 'Asset'), ], ), ), body: TabBarView( children: [ _BumbleBeeRemoteVideo(), _ButterFlyAssetVideo(), ], ), ), ); } } class _ButterFlyAssetVideo extends StatefulWidget { @override _ButterFlyAssetVideoState createState() => _ButterFlyAssetVideoState(); } class _ButterFlyAssetVideoState extends State<_ButterFlyAssetVideo> { late MiniController _controller; @override void initState() { super.initState(); _controller = MiniController.asset('assets/Butterfly-209.mp4'); _controller.addListener(() { setState(() {}); }); _controller.initialize().then((_) => setState(() {})); _controller.play(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SingleChildScrollView( child: Column( children: [ Container( padding: const EdgeInsets.only(top: 20.0), ), const Text('With assets mp4'), Container( padding: const EdgeInsets.all(20), child: AspectRatio( aspectRatio: _controller.value.aspectRatio, child: Stack( alignment: Alignment.bottomCenter, children: [ VideoPlayer(_controller), _ControlsOverlay(controller: _controller), VideoProgressIndicator(_controller), ], ), ), ), ], ), ); } } class _BumbleBeeRemoteVideo extends StatefulWidget { @override _BumbleBeeRemoteVideoState createState() => _BumbleBeeRemoteVideoState(); } class _BumbleBeeRemoteVideoState extends State<_BumbleBeeRemoteVideo> { late MiniController _controller; @override void initState() { super.initState(); _controller = MiniController.network( 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', ); _controller.addListener(() { setState(() {}); }); _controller.initialize(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SingleChildScrollView( child: Column( children: [ Container(padding: const EdgeInsets.only(top: 20.0)), const Text('With remote mp4'), Container( padding: const EdgeInsets.all(20), child: AspectRatio( aspectRatio: _controller.value.aspectRatio, child: Stack( alignment: Alignment.bottomCenter, children: [ VideoPlayer(_controller), _ControlsOverlay(controller: _controller), VideoProgressIndicator(_controller), ], ), ), ), ], ), ); } } class _ControlsOverlay extends StatelessWidget { const _ControlsOverlay({Key? key, required this.controller}) : super(key: key); static const List _examplePlaybackRates = [ 0.25, 0.5, 1.0, 1.5, 2.0, 3.0, 5.0, 10.0, ]; final MiniController controller; @override Widget build(BuildContext context) { return Stack( children: [ AnimatedSwitcher( duration: const Duration(milliseconds: 50), reverseDuration: const Duration(milliseconds: 200), child: controller.value.isPlaying ? const SizedBox.shrink() : Container( color: Colors.black26, child: const Center( child: Icon( Icons.play_arrow, color: Colors.white, size: 100.0, semanticLabel: 'Play', ), ), ), ), GestureDetector( onTap: () { controller.value.isPlaying ? controller.pause() : controller.play(); }, ), Align( alignment: Alignment.topRight, child: PopupMenuButton( initialValue: controller.value.playbackSpeed, tooltip: 'Playback speed', onSelected: (double speed) { controller.setPlaybackSpeed(speed); }, itemBuilder: (BuildContext context) { return >[ for (final double speed in _examplePlaybackRates) PopupMenuItem( value: speed, child: Text('${speed}x'), ) ]; }, child: Padding( padding: const EdgeInsets.symmetric( // Using less vertical padding as the text is also longer // horizontally, so it feels like it would need more spacing // horizontally (matching the aspect ratio of the video). vertical: 12, horizontal: 16, ), child: Text('${controller.value.playbackSpeed}x'), ), ), ), ], ); } } ================================================ FILE: packages/video_player/video_player_android/example/lib/mini_controller.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(stuartmorgan): Consider extracting this to a shared local (path-based) // package for use in all implementation packages. import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; VideoPlayerPlatform? _cachedPlatform; VideoPlayerPlatform get _platform { if (_cachedPlatform == null) { _cachedPlatform = VideoPlayerPlatform.instance; _cachedPlatform!.init(); } return _cachedPlatform!; } /// The duration, current position, buffering state, error state and settings /// of a [MiniController]. class VideoPlayerValue { /// Constructs a video with the given values. Only [duration] is required. The /// rest will initialize with default values when unset. VideoPlayerValue({ required this.duration, this.size = Size.zero, this.position = Duration.zero, this.buffered = const [], this.isInitialized = false, this.isPlaying = false, this.isBuffering = false, this.playbackSpeed = 1.0, this.errorDescription, }); /// Returns an instance for a video that hasn't been loaded. VideoPlayerValue.uninitialized() : this(duration: Duration.zero, isInitialized: false); /// Returns an instance with the given [errorDescription]. VideoPlayerValue.erroneous(String errorDescription) : this( duration: Duration.zero, isInitialized: false, errorDescription: errorDescription); /// The total duration of the video. /// /// The duration is [Duration.zero] if the video hasn't been initialized. final Duration duration; /// The current playback position. final Duration position; /// The currently buffered ranges. final List buffered; /// True if the video is playing. False if it's paused. final bool isPlaying; /// True if the video is currently buffering. final bool isBuffering; /// The current speed of the playback. final double playbackSpeed; /// A description of the error if present. /// /// If [hasError] is false this is `null`. final String? errorDescription; /// The [size] of the currently loaded video. final Size size; /// Indicates whether or not the video has been loaded and is ready to play. final bool isInitialized; /// Indicates whether or not the video is in an error state. If this is true /// [errorDescription] should have information about the problem. bool get hasError => errorDescription != null; /// Returns [size.width] / [size.height]. /// /// Will return `1.0` if: /// * [isInitialized] is `false` /// * [size.width], or [size.height] is equal to `0.0` /// * aspect ratio would be less than or equal to `0.0` double get aspectRatio { if (!isInitialized || size.width == 0 || size.height == 0) { return 1.0; } final double aspectRatio = size.width / size.height; if (aspectRatio <= 0) { return 1.0; } return aspectRatio; } /// Returns a new instance that has the same values as this current instance, /// except for any overrides passed in as arguments to [copyWidth]. VideoPlayerValue copyWith({ Duration? duration, Size? size, Duration? position, List? buffered, bool? isInitialized, bool? isPlaying, bool? isBuffering, double? playbackSpeed, String? errorDescription, }) { return VideoPlayerValue( duration: duration ?? this.duration, size: size ?? this.size, position: position ?? this.position, buffered: buffered ?? this.buffered, isInitialized: isInitialized ?? this.isInitialized, isPlaying: isPlaying ?? this.isPlaying, isBuffering: isBuffering ?? this.isBuffering, playbackSpeed: playbackSpeed ?? this.playbackSpeed, errorDescription: errorDescription ?? this.errorDescription, ); } } /// A very minimal version of `VideoPlayerController` for running the example /// without relying on `video_player`. class MiniController extends ValueNotifier { /// Constructs a [MiniController] playing a video from an asset. /// /// The name of the asset is given by the [dataSource] argument and must not be /// null. The [package] argument must be non-null when the asset comes from a /// package and null otherwise. MiniController.asset(this.dataSource, {this.package}) : dataSourceType = DataSourceType.asset, super(VideoPlayerValue(duration: Duration.zero)); /// Constructs a [MiniController] playing a video from obtained from /// the network. MiniController.network(this.dataSource) : dataSourceType = DataSourceType.network, package = null, super(VideoPlayerValue(duration: Duration.zero)); /// Constructs a [MiniController] playing a video from obtained from a file. MiniController.file(File file) : dataSource = Uri.file(file.absolute.path).toString(), dataSourceType = DataSourceType.file, package = null, super(VideoPlayerValue(duration: Duration.zero)); /// The URI to the video file. This will be in different formats depending on /// the [DataSourceType] of the original video. final String dataSource; /// Describes the type of data source this [MiniController] /// is constructed with. final DataSourceType dataSourceType; /// Only set for [asset] videos. The package that the asset was loaded from. final String? package; Timer? _timer; Completer? _creatingCompleter; StreamSubscription? _eventSubscription; /// The id of a texture that hasn't been initialized. @visibleForTesting static const int kUninitializedTextureId = -1; int _textureId = kUninitializedTextureId; /// This is just exposed for testing. It shouldn't be used by anyone depending /// on the plugin. @visibleForTesting int get textureId => _textureId; /// Attempts to open the given [dataSource] and load metadata about the video. Future initialize() async { _creatingCompleter = Completer(); late DataSource dataSourceDescription; switch (dataSourceType) { case DataSourceType.asset: dataSourceDescription = DataSource( sourceType: DataSourceType.asset, asset: dataSource, package: package, ); break; case DataSourceType.network: dataSourceDescription = DataSource( sourceType: DataSourceType.network, uri: dataSource, ); break; case DataSourceType.file: dataSourceDescription = DataSource( sourceType: DataSourceType.file, uri: dataSource, ); break; case DataSourceType.contentUri: dataSourceDescription = DataSource( sourceType: DataSourceType.contentUri, uri: dataSource, ); break; } _textureId = (await _platform.create(dataSourceDescription)) ?? kUninitializedTextureId; _creatingCompleter!.complete(null); final Completer initializingCompleter = Completer(); void eventListener(VideoEvent event) { switch (event.eventType) { case VideoEventType.initialized: value = value.copyWith( duration: event.duration, size: event.size, isInitialized: event.duration != null, ); initializingCompleter.complete(null); _platform.setVolume(_textureId, 1.0); _platform.setLooping(_textureId, true); _applyPlayPause(); break; case VideoEventType.completed: pause().then((void pauseResult) => seekTo(value.duration)); break; case VideoEventType.bufferingUpdate: value = value.copyWith(buffered: event.buffered); break; case VideoEventType.bufferingStart: value = value.copyWith(isBuffering: true); break; case VideoEventType.bufferingEnd: value = value.copyWith(isBuffering: false); break; case VideoEventType.unknown: break; } } void errorListener(Object obj) { final PlatformException e = obj as PlatformException; value = VideoPlayerValue.erroneous(e.message!); _timer?.cancel(); if (!initializingCompleter.isCompleted) { initializingCompleter.completeError(obj); } } _eventSubscription = _platform .videoEventsFor(_textureId) .listen(eventListener, onError: errorListener); return initializingCompleter.future; } @override Future dispose() async { if (_creatingCompleter != null) { await _creatingCompleter!.future; _timer?.cancel(); await _eventSubscription?.cancel(); await _platform.dispose(_textureId); } super.dispose(); } /// Starts playing the video. Future play() async { value = value.copyWith(isPlaying: true); await _applyPlayPause(); } /// Pauses the video. Future pause() async { value = value.copyWith(isPlaying: false); await _applyPlayPause(); } Future _applyPlayPause() async { _timer?.cancel(); if (value.isPlaying) { await _platform.play(_textureId); _timer = Timer.periodic( const Duration(milliseconds: 500), (Timer timer) async { final Duration? newPosition = await position; if (newPosition == null) { return; } _updatePosition(newPosition); }, ); await _applyPlaybackSpeed(); } else { await _platform.pause(_textureId); } } Future _applyPlaybackSpeed() async { if (value.isPlaying) { await _platform.setPlaybackSpeed( _textureId, value.playbackSpeed, ); } } /// The position in the current video. Future get position async { return _platform.getPosition(_textureId); } /// Sets the video's current timestamp to be at [position]. Future seekTo(Duration position) async { if (position > value.duration) { position = value.duration; } else if (position < Duration.zero) { position = Duration.zero; } await _platform.seekTo(_textureId, position); _updatePosition(position); } /// Sets the playback speed. Future setPlaybackSpeed(double speed) async { value = value.copyWith(playbackSpeed: speed); await _applyPlaybackSpeed(); } void _updatePosition(Duration position) { value = value.copyWith(position: position); } } /// Widget that displays the video controlled by [controller]. class VideoPlayer extends StatefulWidget { /// Uses the given [controller] for all video rendered in this widget. const VideoPlayer(this.controller, {Key? key}) : super(key: key); /// The [MiniController] responsible for the video being rendered in /// this widget. final MiniController controller; @override State createState() => _VideoPlayerState(); } class _VideoPlayerState extends State { _VideoPlayerState() { _listener = () { final int newTextureId = widget.controller.textureId; if (newTextureId != _textureId) { setState(() { _textureId = newTextureId; }); } }; } late VoidCallback _listener; late int _textureId; @override void initState() { super.initState(); _textureId = widget.controller.textureId; // Need to listen for initialization events since the actual texture ID // becomes available after asynchronous initialization finishes. widget.controller.addListener(_listener); } @override void didUpdateWidget(VideoPlayer oldWidget) { super.didUpdateWidget(oldWidget); oldWidget.controller.removeListener(_listener); _textureId = widget.controller.textureId; widget.controller.addListener(_listener); } @override void deactivate() { super.deactivate(); widget.controller.removeListener(_listener); } @override Widget build(BuildContext context) { return _textureId == MiniController.kUninitializedTextureId ? Container() : _platform.buildView(_textureId); } } class _VideoScrubber extends StatefulWidget { const _VideoScrubber({ required this.child, required this.controller, }); final Widget child; final MiniController controller; @override _VideoScrubberState createState() => _VideoScrubberState(); } class _VideoScrubberState extends State<_VideoScrubber> { MiniController get controller => widget.controller; @override Widget build(BuildContext context) { void seekToRelativePosition(Offset globalPosition) { final RenderBox box = context.findRenderObject()! as RenderBox; final Offset tapPos = box.globalToLocal(globalPosition); final double relative = tapPos.dx / box.size.width; final Duration position = controller.value.duration * relative; controller.seekTo(position); } return GestureDetector( behavior: HitTestBehavior.opaque, child: widget.child, onTapDown: (TapDownDetails details) { if (controller.value.isInitialized) { seekToRelativePosition(details.globalPosition); } }, ); } } /// Displays the play/buffering status of the video controlled by [controller]. class VideoProgressIndicator extends StatefulWidget { /// Construct an instance that displays the play/buffering status of the video /// controlled by [controller]. const VideoProgressIndicator(this.controller, {Key? key}) : super(key: key); /// The [MiniController] that actually associates a video with this /// widget. final MiniController controller; @override State createState() => _VideoProgressIndicatorState(); } class _VideoProgressIndicatorState extends State { _VideoProgressIndicatorState() { listener = () { if (mounted) { setState(() {}); } }; } late VoidCallback listener; MiniController get controller => widget.controller; @override void initState() { super.initState(); controller.addListener(listener); } @override void deactivate() { controller.removeListener(listener); super.deactivate(); } @override Widget build(BuildContext context) { const Color playedColor = Color.fromRGBO(255, 0, 0, 0.7); const Color bufferedColor = Color.fromRGBO(50, 50, 200, 0.2); const Color backgroundColor = Color.fromRGBO(200, 200, 200, 0.5); Widget progressIndicator; if (controller.value.isInitialized) { final int duration = controller.value.duration.inMilliseconds; final int position = controller.value.position.inMilliseconds; int maxBuffering = 0; for (final DurationRange range in controller.value.buffered) { final int end = range.end.inMilliseconds; if (end > maxBuffering) { maxBuffering = end; } } progressIndicator = Stack( fit: StackFit.passthrough, children: [ LinearProgressIndicator( value: maxBuffering / duration, valueColor: const AlwaysStoppedAnimation(bufferedColor), backgroundColor: backgroundColor, ), LinearProgressIndicator( value: position / duration, valueColor: const AlwaysStoppedAnimation(playedColor), backgroundColor: Colors.transparent, ), ], ); } else { progressIndicator = const LinearProgressIndicator( valueColor: AlwaysStoppedAnimation(playedColor), backgroundColor: backgroundColor, ); } return _VideoScrubber( controller: controller, child: Padding( padding: const EdgeInsets.only(top: 5.0), child: progressIndicator, ), ); } } ================================================ FILE: packages/video_player/video_player_android/example/pubspec.yaml ================================================ name: video_player_example description: Demonstrates how to use the video_player plugin. publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter video_player_android: # When depending on this package from a real application you should use: # video_player_android: ^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: ../ video_player_platform_interface: ">=5.1.1 <7.0.0" dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter path_provider: ^2.0.6 test: any flutter: uses-material-design: true assets: - assets/flutter-mark-square-64.png - assets/Butterfly-209.mp4 ================================================ FILE: packages/video_player/video_player_android/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/video_player/video_player_android/example/test_driver/video_player.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_driver/driver_extension.dart'; import 'package:video_player_example/main.dart' as app; void main() { enableFlutterDriverExtension(); app.main(); } ================================================ FILE: packages/video_player/video_player_android/lib/src/android_video_player.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; import 'messages.g.dart'; /// An Android implementation of [VideoPlayerPlatform] that uses the /// Pigeon-generated [VideoPlayerApi]. class AndroidVideoPlayer extends VideoPlayerPlatform { final AndroidVideoPlayerApi _api = AndroidVideoPlayerApi(); /// Registers this class as the default instance of [PathProviderPlatform]. static void registerWith() { VideoPlayerPlatform.instance = AndroidVideoPlayer(); } @override Future init() { return _api.initialize(); } @override Future dispose(int textureId) { return _api.dispose(TextureMessage(textureId: textureId)); } @override Future create(DataSource dataSource) async { String? asset; String? packageName; String? uri; String? formatHint; Map httpHeaders = {}; switch (dataSource.sourceType) { case DataSourceType.asset: asset = dataSource.asset; packageName = dataSource.package; break; case DataSourceType.network: uri = dataSource.uri; formatHint = _videoFormatStringMap[dataSource.formatHint]; httpHeaders = dataSource.httpHeaders; break; case DataSourceType.file: uri = dataSource.uri; break; case DataSourceType.contentUri: uri = dataSource.uri; break; } final CreateMessage message = CreateMessage( asset: asset, packageName: packageName, uri: uri, httpHeaders: httpHeaders, formatHint: formatHint, ); final TextureMessage response = await _api.create(message); return response.textureId; } @override Future setLooping(int textureId, bool looping) { return _api.setLooping(LoopingMessage( textureId: textureId, isLooping: looping, )); } @override Future play(int textureId) { return _api.play(TextureMessage(textureId: textureId)); } @override Future pause(int textureId) { return _api.pause(TextureMessage(textureId: textureId)); } @override Future setVolume(int textureId, double volume) { return _api.setVolume(VolumeMessage( textureId: textureId, volume: volume, )); } @override Future setPlaybackSpeed(int textureId, double speed) { assert(speed > 0); return _api.setPlaybackSpeed(PlaybackSpeedMessage( textureId: textureId, speed: speed, )); } @override Future seekTo(int textureId, Duration position) { return _api.seekTo(PositionMessage( textureId: textureId, position: position.inMilliseconds, )); } @override Future getPosition(int textureId) async { final PositionMessage response = await _api.position(TextureMessage(textureId: textureId)); return Duration(milliseconds: response.position); } @override Stream videoEventsFor(int textureId) { return _eventChannelFor(textureId) .receiveBroadcastStream() .map((dynamic event) { final Map map = event as Map; switch (map['event']) { case 'initialized': return VideoEvent( eventType: VideoEventType.initialized, duration: Duration(milliseconds: map['duration'] as int), size: Size((map['width'] as num?)?.toDouble() ?? 0.0, (map['height'] as num?)?.toDouble() ?? 0.0), rotationCorrection: map['rotationCorrection'] as int? ?? 0, ); case 'completed': return VideoEvent( eventType: VideoEventType.completed, ); case 'bufferingUpdate': final List values = map['values'] as List; return VideoEvent( buffered: values.map(_toDurationRange).toList(), eventType: VideoEventType.bufferingUpdate, ); case 'bufferingStart': return VideoEvent(eventType: VideoEventType.bufferingStart); case 'bufferingEnd': return VideoEvent(eventType: VideoEventType.bufferingEnd); default: return VideoEvent(eventType: VideoEventType.unknown); } }); } @override Widget buildView(int textureId) { return Texture(textureId: textureId); } @override Future setMixWithOthers(bool mixWithOthers) { return _api .setMixWithOthers(MixWithOthersMessage(mixWithOthers: mixWithOthers)); } EventChannel _eventChannelFor(int textureId) { return EventChannel('flutter.io/videoPlayer/videoEvents$textureId'); } static const Map _videoFormatStringMap = { VideoFormat.ss: 'ss', VideoFormat.hls: 'hls', VideoFormat.dash: 'dash', VideoFormat.other: 'other', }; DurationRange _toDurationRange(dynamic value) { final List pair = value as List; return DurationRange( Duration(milliseconds: pair[0] as int), Duration(milliseconds: pair[1] as int), ); } } ================================================ FILE: packages/video_player/video_player_android/lib/src/messages.g.dart ================================================ // Copyright 2013 The Flutter 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 (v2.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 // @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 TextureMessage { TextureMessage({ required this.textureId, }); int textureId; Object encode() { final Map pigeonMap = {}; pigeonMap['textureId'] = textureId; return pigeonMap; } static TextureMessage decode(Object message) { final Map pigeonMap = message as Map; return TextureMessage( textureId: pigeonMap['textureId']! as int, ); } } class LoopingMessage { LoopingMessage({ required this.textureId, required this.isLooping, }); int textureId; bool isLooping; Object encode() { final Map pigeonMap = {}; pigeonMap['textureId'] = textureId; pigeonMap['isLooping'] = isLooping; return pigeonMap; } static LoopingMessage decode(Object message) { final Map pigeonMap = message as Map; return LoopingMessage( textureId: pigeonMap['textureId']! as int, isLooping: pigeonMap['isLooping']! as bool, ); } } class VolumeMessage { VolumeMessage({ required this.textureId, required this.volume, }); int textureId; double volume; Object encode() { final Map pigeonMap = {}; pigeonMap['textureId'] = textureId; pigeonMap['volume'] = volume; return pigeonMap; } static VolumeMessage decode(Object message) { final Map pigeonMap = message as Map; return VolumeMessage( textureId: pigeonMap['textureId']! as int, volume: pigeonMap['volume']! as double, ); } } class PlaybackSpeedMessage { PlaybackSpeedMessage({ required this.textureId, required this.speed, }); int textureId; double speed; Object encode() { final Map pigeonMap = {}; pigeonMap['textureId'] = textureId; pigeonMap['speed'] = speed; return pigeonMap; } static PlaybackSpeedMessage decode(Object message) { final Map pigeonMap = message as Map; return PlaybackSpeedMessage( textureId: pigeonMap['textureId']! as int, speed: pigeonMap['speed']! as double, ); } } class PositionMessage { PositionMessage({ required this.textureId, required this.position, }); int textureId; int position; Object encode() { final Map pigeonMap = {}; pigeonMap['textureId'] = textureId; pigeonMap['position'] = position; return pigeonMap; } static PositionMessage decode(Object message) { final Map pigeonMap = message as Map; return PositionMessage( textureId: pigeonMap['textureId']! as int, position: pigeonMap['position']! as int, ); } } class CreateMessage { CreateMessage({ this.asset, this.uri, this.packageName, this.formatHint, required this.httpHeaders, }); String? asset; String? uri; String? packageName; String? formatHint; Map httpHeaders; Object encode() { final Map pigeonMap = {}; pigeonMap['asset'] = asset; pigeonMap['uri'] = uri; pigeonMap['packageName'] = packageName; pigeonMap['formatHint'] = formatHint; pigeonMap['httpHeaders'] = httpHeaders; return pigeonMap; } static CreateMessage decode(Object message) { final Map pigeonMap = message as Map; return CreateMessage( asset: pigeonMap['asset'] as String?, uri: pigeonMap['uri'] as String?, packageName: pigeonMap['packageName'] as String?, formatHint: pigeonMap['formatHint'] as String?, httpHeaders: (pigeonMap['httpHeaders'] as Map?)! .cast(), ); } } class MixWithOthersMessage { MixWithOthersMessage({ required this.mixWithOthers, }); bool mixWithOthers; Object encode() { final Map pigeonMap = {}; pigeonMap['mixWithOthers'] = mixWithOthers; return pigeonMap; } static MixWithOthersMessage decode(Object message) { final Map pigeonMap = message as Map; return MixWithOthersMessage( mixWithOthers: pigeonMap['mixWithOthers']! as bool, ); } } class _AndroidVideoPlayerApiCodec extends StandardMessageCodec { const _AndroidVideoPlayerApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is CreateMessage) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is LoopingMessage) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else if (value is MixWithOthersMessage) { buffer.putUint8(130); writeValue(buffer, value.encode()); } else if (value is PlaybackSpeedMessage) { buffer.putUint8(131); writeValue(buffer, value.encode()); } else if (value is PositionMessage) { buffer.putUint8(132); writeValue(buffer, value.encode()); } else if (value is TextureMessage) { buffer.putUint8(133); writeValue(buffer, value.encode()); } else if (value is VolumeMessage) { buffer.putUint8(134); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return CreateMessage.decode(readValue(buffer)!); case 129: return LoopingMessage.decode(readValue(buffer)!); case 130: return MixWithOthersMessage.decode(readValue(buffer)!); case 131: return PlaybackSpeedMessage.decode(readValue(buffer)!); case 132: return PositionMessage.decode(readValue(buffer)!); case 133: return TextureMessage.decode(readValue(buffer)!); case 134: return VolumeMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } class AndroidVideoPlayerApi { /// Constructor for [AndroidVideoPlayerApi]. 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. AndroidVideoPlayerApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = _AndroidVideoPlayerApiCodec(); Future initialize() async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.initialize', 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; } } Future create(CreateMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.create', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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 TextureMessage?)!; } } Future dispose(TextureMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.dispose', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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 setLooping(LoopingMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.setLooping', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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 setVolume(VolumeMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.setVolume', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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 setPlaybackSpeed(PlaybackSpeedMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.setPlaybackSpeed', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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 play(TextureMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.play', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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 position(TextureMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.position', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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 PositionMessage?)!; } } Future seekTo(PositionMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.seekTo', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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 pause(TextureMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.pause', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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 setMixWithOthers(MixWithOthersMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.setMixWithOthers', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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; } } } ================================================ FILE: packages/video_player/video_player_android/lib/video_player_android.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'src/android_video_player.dart'; ================================================ FILE: packages/video_player/video_player_android/pigeons/copyright.txt ================================================ Copyright 2013 The Flutter Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. ================================================ FILE: packages/video_player/video_player_android/pigeons/messages.dart ================================================ // Copyright 2013 The Flutter 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', dartTestOut: 'test/test_api.g.dart', javaOut: 'android/src/main/java/io/flutter/plugins/videoplayer/Messages.java', javaOptions: JavaOptions( package: 'io.flutter.plugins.videoplayer', ), copyrightHeader: 'pigeons/copyright.txt', )) class TextureMessage { TextureMessage(this.textureId); int textureId; } class LoopingMessage { LoopingMessage(this.textureId, this.isLooping); int textureId; bool isLooping; } class VolumeMessage { VolumeMessage(this.textureId, this.volume); int textureId; double volume; } class PlaybackSpeedMessage { PlaybackSpeedMessage(this.textureId, this.speed); int textureId; double speed; } class PositionMessage { PositionMessage(this.textureId, this.position); int textureId; int position; } class CreateMessage { CreateMessage({required this.httpHeaders}); String? asset; String? uri; String? packageName; String? formatHint; Map httpHeaders; } class MixWithOthersMessage { MixWithOthersMessage(this.mixWithOthers); bool mixWithOthers; } @HostApi(dartHostTestHandler: 'TestHostVideoPlayerApi') abstract class AndroidVideoPlayerApi { void initialize(); TextureMessage create(CreateMessage msg); void dispose(TextureMessage msg); void setLooping(LoopingMessage msg); void setVolume(VolumeMessage msg); void setPlaybackSpeed(PlaybackSpeedMessage msg); void play(TextureMessage msg); PositionMessage position(TextureMessage msg); void seekTo(PositionMessage msg); void pause(TextureMessage msg); void setMixWithOthers(MixWithOthersMessage msg); } ================================================ FILE: packages/video_player/video_player_android/pubspec.yaml ================================================ name: video_player_android description: Android implementation of the video_player plugin. repository: https://github.com/flutter/plugins/tree/main/packages/video_player/video_player_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 version: 2.3.10 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: video_player platforms: android: dartPluginClass: AndroidVideoPlayer package: io.flutter.plugins.videoplayer pluginClass: VideoPlayerPlugin dependencies: flutter: sdk: flutter video_player_platform_interface: ">=5.1.1 <7.0.0" dev_dependencies: flutter_test: sdk: flutter pigeon: ^2.0.1 ================================================ FILE: packages/video_player/video_player_android/test/android_video_player_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#106316) // ignore: unnecessary_import import 'dart:ui'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; 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.g.dart'; class _ApiLogger implements TestHostVideoPlayerApi { final List log = []; TextureMessage? textureMessage; CreateMessage? createMessage; PositionMessage? positionMessage; LoopingMessage? loopingMessage; VolumeMessage? volumeMessage; PlaybackSpeedMessage? playbackSpeedMessage; MixWithOthersMessage? mixWithOthersMessage; @override TextureMessage create(CreateMessage arg) { log.add('create'); createMessage = arg; return TextureMessage(textureId: 3); } @override void dispose(TextureMessage arg) { log.add('dispose'); textureMessage = arg; } @override void initialize() { log.add('init'); } @override void pause(TextureMessage arg) { log.add('pause'); textureMessage = arg; } @override void play(TextureMessage arg) { log.add('play'); textureMessage = arg; } @override void setMixWithOthers(MixWithOthersMessage arg) { log.add('setMixWithOthers'); mixWithOthersMessage = arg; } @override PositionMessage position(TextureMessage arg) { log.add('position'); textureMessage = arg; return PositionMessage(textureId: arg.textureId, position: 234); } @override void seekTo(PositionMessage arg) { log.add('seekTo'); positionMessage = arg; } @override void setLooping(LoopingMessage arg) { log.add('setLooping'); loopingMessage = arg; } @override void setVolume(VolumeMessage arg) { log.add('setVolume'); volumeMessage = arg; } @override void setPlaybackSpeed(PlaybackSpeedMessage arg) { log.add('setPlaybackSpeed'); playbackSpeedMessage = arg; } } void main() { TestWidgetsFlutterBinding.ensureInitialized(); test('registration', () async { AndroidVideoPlayer.registerWith(); expect(VideoPlayerPlatform.instance, isA()); }); group('$AndroidVideoPlayer', () { final AndroidVideoPlayer player = AndroidVideoPlayer(); late _ApiLogger log; setUp(() { log = _ApiLogger(); TestHostVideoPlayerApi.setup(log); }); test('init', () async { await player.init(); expect( log.log.last, 'init', ); }); test('dispose', () async { await player.dispose(1); expect(log.log.last, 'dispose'); expect(log.textureMessage?.textureId, 1); }); test('create with asset', () async { final int? textureId = await player.create(DataSource( sourceType: DataSourceType.asset, asset: 'someAsset', package: 'somePackage', )); expect(log.log.last, 'create'); expect(log.createMessage?.asset, 'someAsset'); expect(log.createMessage?.packageName, 'somePackage'); expect(textureId, 3); }); test('create with network', () async { final int? textureId = await player.create(DataSource( sourceType: DataSourceType.network, uri: 'someUri', formatHint: VideoFormat.dash, )); expect(log.log.last, 'create'); expect(log.createMessage?.asset, null); expect(log.createMessage?.uri, 'someUri'); expect(log.createMessage?.packageName, null); expect(log.createMessage?.formatHint, 'dash'); expect(log.createMessage?.httpHeaders, {}); expect(textureId, 3); }); test('create with network (some headers)', () async { final int? textureId = await player.create(DataSource( sourceType: DataSourceType.network, uri: 'someUri', httpHeaders: {'Authorization': 'Bearer token'}, )); expect(log.log.last, 'create'); expect(log.createMessage?.asset, null); expect(log.createMessage?.uri, 'someUri'); expect(log.createMessage?.packageName, null); expect(log.createMessage?.formatHint, null); expect(log.createMessage?.httpHeaders, {'Authorization': 'Bearer token'}); expect(textureId, 3); }); test('create with file', () async { final int? textureId = await player.create(DataSource( sourceType: DataSourceType.file, uri: 'someUri', )); expect(log.log.last, 'create'); expect(log.createMessage?.uri, 'someUri'); expect(textureId, 3); }); test('setLooping', () async { await player.setLooping(1, true); expect(log.log.last, 'setLooping'); expect(log.loopingMessage?.textureId, 1); expect(log.loopingMessage?.isLooping, true); }); test('play', () async { await player.play(1); expect(log.log.last, 'play'); expect(log.textureMessage?.textureId, 1); }); test('pause', () async { await player.pause(1); expect(log.log.last, 'pause'); expect(log.textureMessage?.textureId, 1); }); test('setMixWithOthers', () async { await player.setMixWithOthers(true); expect(log.log.last, 'setMixWithOthers'); expect(log.mixWithOthersMessage?.mixWithOthers, true); await player.setMixWithOthers(false); expect(log.log.last, 'setMixWithOthers'); expect(log.mixWithOthersMessage?.mixWithOthers, false); }); test('setVolume', () async { await player.setVolume(1, 0.7); expect(log.log.last, 'setVolume'); expect(log.volumeMessage?.textureId, 1); expect(log.volumeMessage?.volume, 0.7); }); test('setPlaybackSpeed', () async { await player.setPlaybackSpeed(1, 1.5); expect(log.log.last, 'setPlaybackSpeed'); expect(log.playbackSpeedMessage?.textureId, 1); expect(log.playbackSpeedMessage?.speed, 1.5); }); test('seekTo', () async { await player.seekTo(1, const Duration(milliseconds: 12345)); expect(log.log.last, 'seekTo'); expect(log.positionMessage?.textureId, 1); expect(log.positionMessage?.position, 12345); }); test('getPosition', () async { final Duration position = await player.getPosition(1); expect(log.log.last, 'position'); expect(log.textureMessage?.textureId, 1); expect(position, const Duration(milliseconds: 234)); }); test('videoEventsFor', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMessageHandler( 'flutter.io/videoPlayer/videoEvents123', (ByteData? message) async { final MethodCall methodCall = const StandardMethodCodec().decodeMethodCall(message); if (methodCall.method == 'listen') { await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() .encodeSuccessEnvelope({ 'event': 'initialized', 'duration': 98765, 'width': 1920, 'height': 1080, }), (ByteData? data) {}); await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() .encodeSuccessEnvelope({ 'event': 'initialized', 'duration': 98765, 'width': 1920, 'height': 1080, 'rotationCorrection': 180, }), (ByteData? data) {}); await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() .encodeSuccessEnvelope({ 'event': 'completed', }), (ByteData? data) {}); await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() .encodeSuccessEnvelope({ 'event': 'bufferingUpdate', 'values': >[ [0, 1234], [1235, 4000], ], }), (ByteData? data) {}); await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() .encodeSuccessEnvelope({ 'event': 'bufferingStart', }), (ByteData? data) {}); await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() .encodeSuccessEnvelope({ 'event': 'bufferingEnd', }), (ByteData? data) {}); return const StandardMethodCodec().encodeSuccessEnvelope(null); } else if (methodCall.method == 'cancel') { return const StandardMethodCodec().encodeSuccessEnvelope(null); } else { fail('Expected listen or cancel'); } }, ); expect( player.videoEventsFor(123), emitsInOrder([ VideoEvent( eventType: VideoEventType.initialized, duration: const Duration(milliseconds: 98765), size: const Size(1920, 1080), rotationCorrection: 0, ), VideoEvent( eventType: VideoEventType.initialized, duration: const Duration(milliseconds: 98765), size: const Size(1920, 1080), rotationCorrection: 180, ), VideoEvent(eventType: VideoEventType.completed), VideoEvent( eventType: VideoEventType.bufferingUpdate, buffered: [ DurationRange( Duration.zero, const Duration(milliseconds: 1234), ), DurationRange( const Duration(milliseconds: 1235), const Duration(milliseconds: 4000), ), ]), VideoEvent(eventType: VideoEventType.bufferingStart), VideoEvent(eventType: VideoEventType.bufferingEnd), ])); }); }); } /// 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; ================================================ FILE: packages/video_player/video_player_android/test/test_api.g.dart ================================================ // Copyright 2013 The Flutter 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 (v2.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 // ignore_for_file: avoid_relative_lib_imports // @dart = 2.12 import 'dart:async'; import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#106316) // ignore: unnecessary_import import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; // TODO(gaaclarke): This had to be hand tweaked from a relative path. import 'package:video_player_android/src/messages.g.dart'; class _TestHostVideoPlayerApiCodec extends StandardMessageCodec { const _TestHostVideoPlayerApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is CreateMessage) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is LoopingMessage) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else if (value is MixWithOthersMessage) { buffer.putUint8(130); writeValue(buffer, value.encode()); } else if (value is PlaybackSpeedMessage) { buffer.putUint8(131); writeValue(buffer, value.encode()); } else if (value is PositionMessage) { buffer.putUint8(132); writeValue(buffer, value.encode()); } else if (value is TextureMessage) { buffer.putUint8(133); writeValue(buffer, value.encode()); } else if (value is VolumeMessage) { buffer.putUint8(134); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return CreateMessage.decode(readValue(buffer)!); case 129: return LoopingMessage.decode(readValue(buffer)!); case 130: return MixWithOthersMessage.decode(readValue(buffer)!); case 131: return PlaybackSpeedMessage.decode(readValue(buffer)!); case 132: return PositionMessage.decode(readValue(buffer)!); case 133: return TextureMessage.decode(readValue(buffer)!); case 134: return VolumeMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } abstract class TestHostVideoPlayerApi { static const MessageCodec codec = _TestHostVideoPlayerApiCodec(); void initialize(); TextureMessage create(CreateMessage msg); void dispose(TextureMessage msg); void setLooping(LoopingMessage msg); void setVolume(VolumeMessage msg); void setPlaybackSpeed(PlaybackSpeedMessage msg); void play(TextureMessage msg); PositionMessage position(TextureMessage msg); void seekTo(PositionMessage msg); void pause(TextureMessage msg); void setMixWithOthers(MixWithOthersMessage msg); static void setup(TestHostVideoPlayerApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.initialize', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { // ignore message api.initialize(); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.create was null.'); final List args = (message as List?)!; final CreateMessage? arg_msg = (args[0] as CreateMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.create was null, expected non-null CreateMessage.'); final TextureMessage output = api.create(arg_msg!); return {'result': output}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.dispose', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.dispose was null.'); final List args = (message as List?)!; final TextureMessage? arg_msg = (args[0] as TextureMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.dispose was null, expected non-null TextureMessage.'); api.dispose(arg_msg!); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.setLooping', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.setLooping was null.'); final List args = (message as List?)!; final LoopingMessage? arg_msg = (args[0] as LoopingMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.setLooping was null, expected non-null LoopingMessage.'); api.setLooping(arg_msg!); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.setVolume', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.setVolume was null.'); final List args = (message as List?)!; final VolumeMessage? arg_msg = (args[0] as VolumeMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.setVolume was null, expected non-null VolumeMessage.'); api.setVolume(arg_msg!); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.setPlaybackSpeed', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.setPlaybackSpeed was null.'); final List args = (message as List?)!; final PlaybackSpeedMessage? arg_msg = (args[0] as PlaybackSpeedMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.setPlaybackSpeed was null, expected non-null PlaybackSpeedMessage.'); api.setPlaybackSpeed(arg_msg!); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.play', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.play was null.'); final List args = (message as List?)!; final TextureMessage? arg_msg = (args[0] as TextureMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.play was null, expected non-null TextureMessage.'); api.play(arg_msg!); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.position', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.position was null.'); final List args = (message as List?)!; final TextureMessage? arg_msg = (args[0] as TextureMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.position was null, expected non-null TextureMessage.'); final PositionMessage output = api.position(arg_msg!); return {'result': output}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.seekTo', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.seekTo was null.'); final List args = (message as List?)!; final PositionMessage? arg_msg = (args[0] as PositionMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.seekTo was null, expected non-null PositionMessage.'); api.seekTo(arg_msg!); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.pause', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.pause was null.'); final List args = (message as List?)!; final TextureMessage? arg_msg = (args[0] as TextureMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.pause was null, expected non-null TextureMessage.'); api.pause(arg_msg!); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AndroidVideoPlayerApi.setMixWithOthers', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.setMixWithOthers was null.'); final List args = (message as List?)!; final MixWithOthersMessage? arg_msg = (args[0] as MixWithOthersMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AndroidVideoPlayerApi.setMixWithOthers was null, expected non-null MixWithOthersMessage.'); api.setMixWithOthers(arg_msg!); return {}; }); } } } } ================================================ FILE: packages/video_player/video_player_avfoundation/AUTHORS ================================================ # 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> ================================================ FILE: packages/video_player/video_player_avfoundation/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 2.3.8 * Adds compatibilty with version 6.0 of the platform interface. * Fixes file URI construction in example. * Updates code for new analysis options. * Adds an integration test for a bug where the aspect ratios of some HLS videos are incorrectly inverted. * Removes an unnecessary override in example code. ## 2.3.7 * Fixes a bug where the aspect ratio of some HLS videos are incorrectly inverted. * Updates code for `no_leading_underscores_for_local_identifiers` lint. ## 2.3.6 * Fixes a bug in iOS 16 where videos from protected live streams are not shown. * Updates minimum Flutter version to 2.10. * Fixes violations of new analysis option use_named_constants. * Fixes avoid_redundant_argument_values lint warnings and minor typos. * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/106316). ## 2.3.5 * Updates references to the obsolete master branch. ## 2.3.4 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 2.3.3 * Fix XCUITest based on the new voice over announcement for tooltips. See: https://github.com/flutter/flutter/pull/87684 ## 2.3.2 * Applies the standardized transform for videos with different orientations. ## 2.3.1 * Renames internal method channels to avoid potential confusion with the default implementation's method channel. * Updates Pigeon to 2.0.1. ## 2.3.0 * Updates Pigeon to ^1.0.16. ## 2.2.18 * Wait to initialize m3u8 videos until size is set, fixing aspect ratio. * Adjusts test timeouts for network-dependent native tests to avoid flake. ## 2.2.17 * Splits from `video_player` as a federated implementation. ================================================ FILE: packages/video_player/video_player_avfoundation/CONTRIBUTING.md ================================================ ## Updating pigeon-generated files If you update files in the pigeons/ directory, run the following command in this directory: ```bash flutter pub upgrade flutter pub run pigeon --input pigeons/messages.dart # git commit your changes so that your working environment is clean (cd ../../../; ./script/tool_runner.sh format --clang-format=clang-format-7) ``` If you update pigeon itself and want to test the changes here, temporarily update the pubspec.yaml by adding the following to the `dependency_overrides` section, assuming you have checked out the `flutter/packages` repo in a sibling directory to the `plugins` repo: ```yaml pigeon: path: ../../../../packages/packages/pigeon/ ``` Then, run the commands above. When you run `pub get` it should warn you that you're using an override. If you do this, you will need to publish pigeon before you can land the updates to this package, since the CI tests run the analysis using latest published version of pigeon, not your version or the version on `main`. In either case, the configuration will be obtained automatically from the `pigeons/messages.dart` file (see `ConfigurePigeon` at the top of that file). ================================================ FILE: packages/video_player/video_player_avfoundation/LICENSE ================================================ 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. ================================================ FILE: packages/video_player/video_player_avfoundation/README.md ================================================ # video\_player\_avfoundation The iOS implementation of [`video_player`][1]. ## Usage This package is [endorsed][2], which means you can simply use `video_player` normally. This package will be automatically included in your app when you do. [1]: https://pub.dev/packages/video_player [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin ================================================ FILE: packages/video_player/video_player_avfoundation/example/.gitignore ================================================ lib/generated_plugin_registrant.dart ================================================ FILE: packages/video_player/video_player_avfoundation/example/README.md ================================================ # 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. ================================================ FILE: packages/video_player/video_player_avfoundation/example/integration_test/video_player_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; import 'dart:typed_data'; import 'package:flutter/services.dart' show rootBundle; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:path_provider/path_provider.dart'; import 'package:video_player_avfoundation/video_player_avfoundation.dart'; // TODO(stuartmorgan): Remove the use of MiniController in tests, as that is // testing test code; tests should instead be written directly against the // platform interface. (These tests were copied from the app-facing package // during federation and minimally modified, which is why they currently use the // controller.) import 'package:video_player_example/mini_controller.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; const Duration _playDuration = Duration(seconds: 1); const String _videoAssetKey = 'assets/Butterfly-209.mp4'; // Returns the URL to load an asset from this example app as a network source. // // TODO(stuartmorgan): Convert this to a local `HttpServer` that vends the // assets directly, https://github.com/flutter/flutter/issues/95420 String getUrlForAssetAsNetworkSource(String assetKey) { return 'https://github.com/flutter/plugins/blob/' // This hash can be rolled forward to pick up newly-added assets. 'cb381ced070d356799dddf24aca38ce0579d3d7b' '/packages/video_player/video_player/example/' '$assetKey' '?raw=true'; } void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); late MiniController controller; tearDown(() async => controller.dispose()); group('asset videos', () { setUp(() { controller = MiniController.asset(_videoAssetKey); }); testWidgets('registers expected implementation', (WidgetTester tester) async { AVFoundationVideoPlayer.registerWith(); expect(VideoPlayerPlatform.instance, isA()); }); testWidgets('can be initialized', (WidgetTester tester) async { await controller.initialize(); expect(controller.value.isInitialized, true); expect(await controller.position, Duration.zero); expect(controller.value.duration, const Duration(seconds: 7, milliseconds: 540)); }); testWidgets('can be played', (WidgetTester tester) async { await controller.initialize(); await controller.play(); await tester.pumpAndSettle(_playDuration); expect(await controller.position, greaterThan(Duration.zero)); }); testWidgets('can seek', (WidgetTester tester) async { await controller.initialize(); await controller.seekTo(const Duration(seconds: 3)); // TODO(stuartmorgan): Switch to _controller.position once seekTo is // fixed on the native side to wait for completion, so this is testing // the native code rather than the MiniController position cache. expect(controller.value.position, const Duration(seconds: 3)); }); testWidgets('can be paused', (WidgetTester tester) async { await controller.initialize(); // Play for a second, then pause, and then wait a second. await controller.play(); await tester.pumpAndSettle(_playDuration); await controller.pause(); final Duration pausedPosition = (await controller.position)!; await tester.pumpAndSettle(_playDuration); // Verify that we stopped playing after the pause. // TODO(stuartmorgan): Investigate why this has a slight discrepency, and // fix it if possible. Is AVPlayer's pause method internally async? const Duration allowableDelta = Duration(milliseconds: 10); expect( await controller.position, lessThan(pausedPosition + allowableDelta)); }); }); group('file-based videos', () { setUp(() async { // Load the data from the asset. final String tempDir = (await getTemporaryDirectory()).path; final ByteData bytes = await rootBundle.load(_videoAssetKey); // Write it to a file to use as a source. final String filename = _videoAssetKey.split('/').last; final File file = File('$tempDir/$filename'); await file.writeAsBytes(bytes.buffer.asInt8List()); controller = MiniController.file(file); }); testWidgets('test video player using static file() method as constructor', (WidgetTester tester) async { await controller.initialize(); await controller.play(); await tester.pumpAndSettle(_playDuration); expect(await controller.position, greaterThan(Duration.zero)); }); }); group('network videos', () { setUp(() { final String videoUrl = getUrlForAssetAsNetworkSource(_videoAssetKey); controller = MiniController.network(videoUrl); }); testWidgets('reports buffering status', (WidgetTester tester) async { await controller.initialize(); final Completer started = Completer(); final Completer ended = Completer(); controller.addListener(() { if (!started.isCompleted && controller.value.isBuffering) { started.complete(); } if (started.isCompleted && !controller.value.isBuffering && !ended.isCompleted) { ended.complete(); } }); await controller.play(); await controller.seekTo(const Duration(seconds: 5)); await tester.pumpAndSettle(_playDuration); await controller.pause(); // TODO(stuartmorgan): Switch to _controller.position once seekTo is // fixed on the native side to wait for completion, so this is testing // the native code rather than the MiniController position cache. expect(controller.value.position, greaterThan(Duration.zero)); await expectLater(started.future, completes); await expectLater(ended.future, completes); }, // TODO(stuartmorgan): Skipped on iOS without explanation in main // package. Needs investigation. skip: true); testWidgets('live stream duration != 0', (WidgetTester tester) async { final MiniController livestreamController = MiniController.network( 'https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8', ); await livestreamController.initialize(); expect(livestreamController.value.isInitialized, true); // Live streams should have either a positive duration or C.TIME_UNSET if the duration is unknown // See https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/Player.html#getDuration-- expect(livestreamController.value.duration, (Duration duration) => duration != Duration.zero); }); testWidgets('rotated m3u8 has correct aspect ratio', (WidgetTester tester) async { // Some m3u8 files contain rotation data that may incorrectly invert the aspect ratio. // More info [here](https://github.com/flutter/flutter/issues/109116). final MiniController livestreamController = MiniController.network( 'https://flutter.github.io/assets-for-api-docs/assets/videos/hls/rotated_nail_manifest.m3u8', ); await livestreamController.initialize(); expect(livestreamController.value.isInitialized, true); expect(livestreamController.value.size.width, lessThan(livestreamController.value.size.height)); }); }); } ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 UIRequiredDeviceCapabilities arm64 MinimumOSVersion 9.0 ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/Flutter/Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/Flutter/Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths pod 'OCMock', '3.5' end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "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" } } ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName video_player_example CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS NSAppTransportSecurity NSAllowsArbitraryLoads UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities arm64 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; B0F5C77B94E32FB72444AE9F /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 20721C28387E1F78689EC502 /* libPods-Runner.a */; }; D182ECB59C06DBC7E2D5D913 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7BD232FD3BD3343A5F52AF50 /* libPods-RunnerTests.a */; }; F7151F2F26603EBD0028CB91 /* VideoPlayerUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F2E26603EBD0028CB91 /* VideoPlayerUITests.m */; }; F7151F3D26603ECA0028CB91 /* VideoPlayerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F3C26603ECA0028CB91 /* VideoPlayerTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ F7151F3126603EBD0028CB91 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; F7151F3F26603ECA0028CB91 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 20721C28387E1F78689EC502 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 2A2EA522BDC492279A91AB75 /* 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 = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 6CDC4DA5940705A6E7671616 /* 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 = ""; }; 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 = ""; }; 7BD232FD3BD3343A5F52AF50 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; B15EC39F4617FE1082B18834 /* 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 = ""; }; C18C242FF01156F58C0DAF1C /* 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 = ""; }; F7151F2C26603EBD0028CB91 /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F7151F2E26603EBD0028CB91 /* VideoPlayerUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VideoPlayerUITests.m; sourceTree = ""; }; F7151F3026603EBD0028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F7151F3A26603ECA0028CB91 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F7151F3C26603ECA0028CB91 /* VideoPlayerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VideoPlayerTests.m; sourceTree = ""; }; F7151F3E26603ECA0028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( B0F5C77B94E32FB72444AE9F /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F2926603EBD0028CB91 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; F7151F3726603ECA0028CB91 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( D182ECB59C06DBC7E2D5D913 /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 05E898481BC29A7FA83AA441 /* Pods */ = { isa = PBXGroup; children = ( C18C242FF01156F58C0DAF1C /* Pods-Runner.debug.xcconfig */, B15EC39F4617FE1082B18834 /* Pods-Runner.release.xcconfig */, 6CDC4DA5940705A6E7671616 /* Pods-RunnerTests.debug.xcconfig */, 2A2EA522BDC492279A91AB75 /* Pods-RunnerTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; 23104BB9DCF267F65AD246F9 /* Frameworks */ = { isa = PBXGroup; children = ( 20721C28387E1F78689EC502 /* libPods-Runner.a */, 7BD232FD3BD3343A5F52AF50 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, F7151F3B26603ECA0028CB91 /* RunnerTests */, F7151F2D26603EBD0028CB91 /* RunnerUITests */, 97C146EF1CF9000F007C117D /* Products */, 05E898481BC29A7FA83AA441 /* Pods */, 23104BB9DCF267F65AD246F9 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, F7151F2C26603EBD0028CB91 /* RunnerUITests.xctest */, F7151F3A26603ECA0028CB91 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; F7151F2D26603EBD0028CB91 /* RunnerUITests */ = { isa = PBXGroup; children = ( F7151F2E26603EBD0028CB91 /* VideoPlayerUITests.m */, F7151F3026603EBD0028CB91 /* Info.plist */, ); path = RunnerUITests; sourceTree = ""; }; F7151F3B26603ECA0028CB91 /* RunnerTests */ = { isa = PBXGroup; children = ( F7151F3C26603ECA0028CB91 /* VideoPlayerTests.m */, F7151F3E26603ECA0028CB91 /* Info.plist */, ); path = RunnerTests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( F31A669BD45D5A7C940BF077 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; F7151F2B26603EBD0028CB91 /* RunnerUITests */ = { isa = PBXNativeTarget; buildConfigurationList = F7151F3526603EBD0028CB91 /* Build configuration list for PBXNativeTarget "RunnerUITests" */; buildPhases = ( F7151F2826603EBD0028CB91 /* Sources */, F7151F2926603EBD0028CB91 /* Frameworks */, F7151F2A26603EBD0028CB91 /* Resources */, ); buildRules = ( ); dependencies = ( F7151F3226603EBD0028CB91 /* PBXTargetDependency */, ); name = RunnerUITests; productName = RunnerUITests; productReference = F7151F2C26603EBD0028CB91 /* RunnerUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; F7151F3926603ECA0028CB91 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = F7151F4126603ECB0028CB91 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( E9F7B01F913C69934A6629F6 /* [CP] Check Pods Manifest.lock */, F7151F3626603ECA0028CB91 /* Sources */, F7151F3726603ECA0028CB91 /* Frameworks */, F7151F3826603ECA0028CB91 /* Resources */, ); buildRules = ( ); dependencies = ( F7151F4026603ECA0028CB91 /* PBXTargetDependency */, ); name = RunnerTests; productName = RunnerTests; productReference = F7151F3A26603ECA0028CB91 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1320; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; }; F7151F2B26603EBD0028CB91 = { CreatedOnToolsVersion = 12.5; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; F7151F3926603ECA0028CB91 = { CreatedOnToolsVersion = 12.5; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, F7151F3926603ECA0028CB91 /* RunnerTests */, F7151F2B26603EBD0028CB91 /* RunnerUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F2A26603EBD0028CB91 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; F7151F3826603ECA0028CB91 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; 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"; }; E9F7B01F913C69934A6629F6 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; F31A669BD45D5A7C940BF077 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F2826603EBD0028CB91 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F7151F2F26603EBD0028CB91 /* VideoPlayerUITests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F3626603ECA0028CB91 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F7151F3D26603ECA0028CB91 /* VideoPlayerTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ F7151F3226603EBD0028CB91 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = F7151F3126603EBD0028CB91 /* PBXContainerItemProxy */; }; F7151F4026603ECA0028CB91 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = F7151F3F26603ECA0028CB91 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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 = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.videoPlayerExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.videoPlayerExample; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; F7151F3326603EBD0028CB91 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = Runner; }; name = Debug; }; F7151F3426603EBD0028CB91 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = Runner; }; name = Release; }; F7151F4226603ECB0028CB91 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 6CDC4DA5940705A6E7671616 /* 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"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Debug; }; F7151F4326603ECB0028CB91 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 2A2EA522BDC492279A91AB75 /* 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"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F7151F3526603EBD0028CB91 /* Build configuration list for PBXNativeTarget "RunnerUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( F7151F3326603EBD0028CB91 /* Debug */, F7151F3426603EBD0028CB91 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F7151F4126603ECB0028CB91 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( F7151F4226603ECB0028CB91 /* Debug */, F7151F4326603ECB0028CB91 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/RunnerTests/Info.plist ================================================ 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 ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/RunnerTests/VideoPlayerTests.m ================================================ // Copyright 2013 The Flutter 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 AVFoundation; @import video_player_avfoundation; @import XCTest; #import #import @interface FLTVideoPlayer : NSObject @property(readonly, nonatomic) AVPlayer *player; @property(readonly, nonatomic) AVPlayerLayer *playerLayer; @end @interface FLTVideoPlayerPlugin (Test) @property(readonly, strong, nonatomic) NSMutableDictionary *playersByTextureId; @end @interface FakeAVAssetTrack : AVAssetTrack @property(readonly, nonatomic) CGAffineTransform preferredTransform; @property(readonly, nonatomic) CGSize naturalSize; @property(readonly, nonatomic) UIImageOrientation orientation; - (instancetype)initWithOrientation:(UIImageOrientation)orientation; @end @implementation FakeAVAssetTrack - (instancetype)initWithOrientation:(UIImageOrientation)orientation { _orientation = orientation; _naturalSize = CGSizeMake(800, 600); return self; } - (CGAffineTransform)preferredTransform { switch (_orientation) { case UIImageOrientationUp: return CGAffineTransformMake(1, 0, 0, 1, 0, 0); case UIImageOrientationDown: return CGAffineTransformMake(-1, 0, 0, -1, 0, 0); case UIImageOrientationLeft: return CGAffineTransformMake(0, -1, 1, 0, 0, 0); case UIImageOrientationRight: return CGAffineTransformMake(0, 1, -1, 0, 0, 0); case UIImageOrientationUpMirrored: return CGAffineTransformMake(-1, 0, 0, 1, 0, 0); case UIImageOrientationDownMirrored: return CGAffineTransformMake(1, 0, 0, -1, 0, 0); case UIImageOrientationLeftMirrored: return CGAffineTransformMake(0, -1, -1, 0, 0, 0); case UIImageOrientationRightMirrored: return CGAffineTransformMake(0, 1, 1, 0, 0, 0); } } @end @interface VideoPlayerTests : XCTestCase @end @implementation VideoPlayerTests - (void)testBlankVideoBugWithEncryptedVideoStreamAndInvertedAspectRatioBugForSomeVideoStream { // This is to fix 2 bugs: 1. blank video for encrypted video streams on iOS 16 // (https://github.com/flutter/flutter/issues/111457) and 2. swapped width and height for some // video streams (not just iOS 16). (https://github.com/flutter/flutter/issues/109116). An // invisible AVPlayerLayer is used to overwrite the protection of pixel buffers in those streams // for issue #1, and restore the correct width and height for issue #2. NSObject *registry = (NSObject *)[[UIApplication sharedApplication] delegate]; NSObject *registrar = [registry registrarForPlugin:@"testPlayerLayerWorkaround"]; FLTVideoPlayerPlugin *videoPlayerPlugin = [[FLTVideoPlayerPlugin alloc] initWithRegistrar:registrar]; FlutterError *error; [videoPlayerPlugin initialize:&error]; XCTAssertNil(error); FLTCreateMessage *create = [FLTCreateMessage makeWithAsset:nil uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4" packageName:nil formatHint:nil httpHeaders:@{}]; FLTTextureMessage *textureMessage = [videoPlayerPlugin create:create error:&error]; XCTAssertNil(error); XCTAssertNotNil(textureMessage); FLTVideoPlayer *player = videoPlayerPlugin.playersByTextureId[textureMessage.textureId]; XCTAssertNotNil(player); XCTAssertNotNil(player.playerLayer, @"AVPlayerLayer should be present."); XCTAssertNotNil(player.playerLayer.superlayer, @"AVPlayerLayer should be added on screen."); } - (void)testSeekToInvokesTextureFrameAvailableOnTextureRegistry { NSObject *mockTextureRegistry = OCMProtocolMock(@protocol(FlutterTextureRegistry)); NSObject *registry = (NSObject *)[[UIApplication sharedApplication] delegate]; NSObject *registrar = [registry registrarForPlugin:@"SeekToInvokestextureFrameAvailable"]; NSObject *partialRegistrar = OCMPartialMock(registrar); OCMStub([partialRegistrar textures]).andReturn(mockTextureRegistry); FLTVideoPlayerPlugin *videoPlayerPlugin = (FLTVideoPlayerPlugin *)[[FLTVideoPlayerPlugin alloc] initWithRegistrar:partialRegistrar]; FLTPositionMessage *message = [FLTPositionMessage makeWithTextureId:@101 position:@0]; FlutterError *error; [videoPlayerPlugin seekTo:message error:&error]; OCMVerify([mockTextureRegistry textureFrameAvailable:message.textureId.intValue]); } - (void)testDeregistersFromPlayer { NSObject *registry = (NSObject *)[[UIApplication sharedApplication] delegate]; NSObject *registrar = [registry registrarForPlugin:@"testDeregistersFromPlayer"]; FLTVideoPlayerPlugin *videoPlayerPlugin = (FLTVideoPlayerPlugin *)[[FLTVideoPlayerPlugin alloc] initWithRegistrar:registrar]; FlutterError *error; [videoPlayerPlugin initialize:&error]; XCTAssertNil(error); FLTCreateMessage *create = [FLTCreateMessage makeWithAsset:nil uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4" packageName:nil formatHint:nil httpHeaders:@{}]; FLTTextureMessage *textureMessage = [videoPlayerPlugin create:create error:&error]; XCTAssertNil(error); XCTAssertNotNil(textureMessage); FLTVideoPlayer *player = videoPlayerPlugin.playersByTextureId[textureMessage.textureId]; XCTAssertNotNil(player); AVPlayer *avPlayer = player.player; [videoPlayerPlugin dispose:textureMessage error:&error]; XCTAssertEqual(videoPlayerPlugin.playersByTextureId.count, 0); XCTAssertNil(error); [self keyValueObservingExpectationForObject:avPlayer keyPath:@"currentItem" expectedValue:nil]; [self waitForExpectationsWithTimeout:30.0 handler:nil]; } - (void)testVideoControls { NSObject *registry = (NSObject *)[[UIApplication sharedApplication] delegate]; NSObject *registrar = [registry registrarForPlugin:@"TestVideoControls"]; FLTVideoPlayerPlugin *videoPlayerPlugin = (FLTVideoPlayerPlugin *)[[FLTVideoPlayerPlugin alloc] initWithRegistrar:registrar]; NSDictionary *videoInitialization = [self testPlugin:videoPlayerPlugin uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4"]; XCTAssertEqualObjects(videoInitialization[@"height"], @720); XCTAssertEqualObjects(videoInitialization[@"width"], @1280); XCTAssertEqualWithAccuracy([videoInitialization[@"duration"] intValue], 4000, 200); } - (void)testAudioControls { NSObject *registry = (NSObject *)[[UIApplication sharedApplication] delegate]; NSObject *registrar = [registry registrarForPlugin:@"TestAudioControls"]; FLTVideoPlayerPlugin *videoPlayerPlugin = (FLTVideoPlayerPlugin *)[[FLTVideoPlayerPlugin alloc] initWithRegistrar:registrar]; NSDictionary *audioInitialization = [self testPlugin:videoPlayerPlugin uri:@"https://flutter.github.io/assets-for-api-docs/assets/audio/rooster.mp3"]; XCTAssertEqualObjects(audioInitialization[@"height"], @0); XCTAssertEqualObjects(audioInitialization[@"width"], @0); // Perfect precision not guaranteed. XCTAssertEqualWithAccuracy([audioInitialization[@"duration"] intValue], 5400, 200); } - (void)testHLSControls { NSObject *registry = (NSObject *)[[UIApplication sharedApplication] delegate]; NSObject *registrar = [registry registrarForPlugin:@"TestHLSControls"]; FLTVideoPlayerPlugin *videoPlayerPlugin = (FLTVideoPlayerPlugin *)[[FLTVideoPlayerPlugin alloc] initWithRegistrar:registrar]; NSDictionary *videoInitialization = [self testPlugin:videoPlayerPlugin uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8"]; XCTAssertEqualObjects(videoInitialization[@"height"], @720); XCTAssertEqualObjects(videoInitialization[@"width"], @1280); XCTAssertEqualWithAccuracy([videoInitialization[@"duration"] intValue], 4000, 200); } - (void)testTransformFix { [self validateTransformFixForOrientation:UIImageOrientationUp]; [self validateTransformFixForOrientation:UIImageOrientationDown]; [self validateTransformFixForOrientation:UIImageOrientationLeft]; [self validateTransformFixForOrientation:UIImageOrientationRight]; [self validateTransformFixForOrientation:UIImageOrientationUpMirrored]; [self validateTransformFixForOrientation:UIImageOrientationDownMirrored]; [self validateTransformFixForOrientation:UIImageOrientationLeftMirrored]; [self validateTransformFixForOrientation:UIImageOrientationRightMirrored]; } - (NSDictionary *)testPlugin:(FLTVideoPlayerPlugin *)videoPlayerPlugin uri:(NSString *)uri { FlutterError *error; [videoPlayerPlugin initialize:&error]; XCTAssertNil(error); FLTCreateMessage *create = [FLTCreateMessage makeWithAsset:nil uri:uri packageName:nil formatHint:nil httpHeaders:@{}]; FLTTextureMessage *textureMessage = [videoPlayerPlugin create:create error:&error]; NSNumber *textureId = textureMessage.textureId; FLTVideoPlayer *player = videoPlayerPlugin.playersByTextureId[textureId]; XCTAssertNotNil(player); XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"initialized"]; __block NSDictionary *initializationEvent; [player onListenWithArguments:nil eventSink:^(NSDictionary *event) { if ([event[@"event"] isEqualToString:@"initialized"]) { initializationEvent = event; XCTAssertEqual(event.count, 4); [initializedExpectation fulfill]; } }]; [self waitForExpectationsWithTimeout:30.0 handler:nil]; // Starts paused. AVPlayer *avPlayer = player.player; XCTAssertEqual(avPlayer.rate, 0); XCTAssertEqual(avPlayer.volume, 1); XCTAssertEqual(avPlayer.timeControlStatus, AVPlayerTimeControlStatusPaused); // Change playback speed. FLTPlaybackSpeedMessage *playback = [FLTPlaybackSpeedMessage makeWithTextureId:textureId speed:@2]; [videoPlayerPlugin setPlaybackSpeed:playback error:&error]; XCTAssertNil(error); XCTAssertEqual(avPlayer.rate, 2); XCTAssertEqual(avPlayer.timeControlStatus, AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate); // Volume FLTVolumeMessage *volume = [FLTVolumeMessage makeWithTextureId:textureId volume:@0.1]; [videoPlayerPlugin setVolume:volume error:&error]; XCTAssertNil(error); XCTAssertEqual(avPlayer.volume, 0.1f); [player onCancelWithArguments:nil]; return initializationEvent; } - (void)validateTransformFixForOrientation:(UIImageOrientation)orientation { AVAssetTrack *track = [[FakeAVAssetTrack alloc] initWithOrientation:orientation]; CGAffineTransform t = FLTGetStandardizedTransformForTrack(track); CGSize size = track.naturalSize; CGFloat expectX, expectY; switch (orientation) { case UIImageOrientationUp: expectX = 0; expectY = 0; break; case UIImageOrientationDown: expectX = size.width; expectY = size.height; break; case UIImageOrientationLeft: expectX = 0; expectY = size.width; break; case UIImageOrientationRight: expectX = size.height; expectY = 0; break; case UIImageOrientationUpMirrored: expectX = size.width; expectY = 0; break; case UIImageOrientationDownMirrored: expectX = 0; expectY = size.height; break; case UIImageOrientationLeftMirrored: expectX = size.height; expectY = size.width; break; case UIImageOrientationRightMirrored: expectX = 0; expectY = 0; break; } XCTAssertEqual(t.tx, expectX); XCTAssertEqual(t.ty, expectY); } @end ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/RunnerUITests/Info.plist ================================================ 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 ================================================ FILE: packages/video_player/video_player_avfoundation/example/ios/RunnerUITests/VideoPlayerUITests.m ================================================ // Copyright 2013 The Flutter 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 os.log; @import XCTest; @import CoreGraphics; @interface VideoPlayerUITests : XCTestCase @property(nonatomic, strong) XCUIApplication *app; @end @implementation VideoPlayerUITests - (void)setUp { self.continueAfterFailure = NO; self.app = [[XCUIApplication alloc] init]; [self.app launch]; } - (void)testPlayVideo { XCUIApplication *app = self.app; XCUIElement *remoteTab = [app.otherElements elementMatchingPredicate:[NSPredicate predicateWithFormat:@"selected == YES"]]; XCTAssertTrue([remoteTab waitForExistenceWithTimeout:30.0]); XCTAssertTrue([remoteTab.label containsString:@"Remote"]); XCUIElement *playButton = app.staticTexts[@"Play"]; XCTAssertTrue([playButton waitForExistenceWithTimeout:30.0]); [playButton tap]; NSPredicate *find1xButton = [NSPredicate predicateWithFormat:@"label CONTAINS '1.0x'"]; XCUIElement *playbackSpeed1x = [app.staticTexts elementMatchingPredicate:find1xButton]; BOOL foundPlaybackSpeed1x = [playbackSpeed1x waitForExistenceWithTimeout:30.0]; XCTAssertTrue(foundPlaybackSpeed1x); [playbackSpeed1x tap]; XCUIElement *playbackSpeed5xButton = app.buttons[@"5.0x"]; XCTAssertTrue([playbackSpeed5xButton waitForExistenceWithTimeout:30.0]); [playbackSpeed5xButton tap]; NSPredicate *find5xButton = [NSPredicate predicateWithFormat:@"label CONTAINS '5.0x'"]; XCUIElement *playbackSpeed5x = [app.staticTexts elementMatchingPredicate:find5xButton]; BOOL foundPlaybackSpeed5x = [playbackSpeed5x waitForExistenceWithTimeout:30.0]; XCTAssertTrue(foundPlaybackSpeed5x); // Cycle through tabs. for (NSString *tabName in @[ @"Asset mp4", @"Remote mp4" ]) { NSPredicate *predicate = [NSPredicate predicateWithFormat:@"label BEGINSWITH %@", tabName]; XCUIElement *unselectedTab = [app.staticTexts elementMatchingPredicate:predicate]; XCTAssertTrue([unselectedTab waitForExistenceWithTimeout:30.0]); XCTAssertFalse(unselectedTab.isSelected); [unselectedTab tap]; XCUIElement *selectedTab = [app.otherElements elementMatchingPredicate:[NSPredicate predicateWithFormat:@"label BEGINSWITH %@", tabName]]; XCTAssertTrue([selectedTab waitForExistenceWithTimeout:30.0]); XCTAssertTrue(selectedTab.isSelected); } } @end ================================================ FILE: packages/video_player/video_player_avfoundation/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'mini_controller.dart'; void main() { runApp( MaterialApp( home: _App(), ), ); } class _App extends StatelessWidget { @override Widget build(BuildContext context) { return DefaultTabController( length: 3, child: Scaffold( key: const ValueKey('home_page'), appBar: AppBar( title: const Text('Video player example'), bottom: const TabBar( isScrollable: true, tabs: [ Tab( icon: Icon(Icons.cloud), text: 'Remote mp4', ), Tab( icon: Icon(Icons.favorite), text: 'Remote enc m3u8', ), Tab(icon: Icon(Icons.insert_drive_file), text: 'Asset mp4'), ], ), ), body: TabBarView( children: [ _BumbleBeeRemoteVideo(), _BumbleBeeEncryptedLiveStream(), _ButterFlyAssetVideo(), ], ), ), ); } } class _ButterFlyAssetVideo extends StatefulWidget { @override _ButterFlyAssetVideoState createState() => _ButterFlyAssetVideoState(); } class _ButterFlyAssetVideoState extends State<_ButterFlyAssetVideo> { late MiniController _controller; @override void initState() { super.initState(); _controller = MiniController.asset('assets/Butterfly-209.mp4'); _controller.addListener(() { setState(() {}); }); _controller.initialize().then((_) => setState(() {})); _controller.play(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SingleChildScrollView( child: Column( children: [ Container( padding: const EdgeInsets.only(top: 20.0), ), const Text('With assets mp4'), Container( padding: const EdgeInsets.all(20), child: AspectRatio( aspectRatio: _controller.value.aspectRatio, child: Stack( alignment: Alignment.bottomCenter, children: [ VideoPlayer(_controller), _ControlsOverlay(controller: _controller), VideoProgressIndicator(_controller), ], ), ), ), ], ), ); } } class _BumbleBeeRemoteVideo extends StatefulWidget { @override _BumbleBeeRemoteVideoState createState() => _BumbleBeeRemoteVideoState(); } class _BumbleBeeRemoteVideoState extends State<_BumbleBeeRemoteVideo> { late MiniController _controller; @override void initState() { super.initState(); _controller = MiniController.network( 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', ); _controller.addListener(() { setState(() {}); }); _controller.initialize(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SingleChildScrollView( child: Column( children: [ Container(padding: const EdgeInsets.only(top: 20.0)), const Text('With remote mp4'), Container( padding: const EdgeInsets.all(20), child: AspectRatio( aspectRatio: _controller.value.aspectRatio, child: Stack( alignment: Alignment.bottomCenter, children: [ VideoPlayer(_controller), _ControlsOverlay(controller: _controller), VideoProgressIndicator(_controller), ], ), ), ), ], ), ); } } class _BumbleBeeEncryptedLiveStream extends StatefulWidget { @override _BumbleBeeEncryptedLiveStreamState createState() => _BumbleBeeEncryptedLiveStreamState(); } class _BumbleBeeEncryptedLiveStreamState extends State<_BumbleBeeEncryptedLiveStream> { late MiniController _controller; @override void initState() { super.initState(); _controller = MiniController.network( 'https://flutter.github.io/assets-for-api-docs/assets/videos/hls/encrypted_bee.m3u8', ); _controller.addListener(() { setState(() {}); }); _controller.initialize(); _controller.play(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SingleChildScrollView( child: Column( children: [ Container(padding: const EdgeInsets.only(top: 20.0)), const Text('With remote encrypted m3u8'), Container( padding: const EdgeInsets.all(20), child: _controller.value.isInitialized ? AspectRatio( aspectRatio: _controller.value.aspectRatio, child: VideoPlayer(_controller), ) : const Text('loading...'), ), ], ), ); } } class _ControlsOverlay extends StatelessWidget { const _ControlsOverlay({Key? key, required this.controller}) : super(key: key); static const List _examplePlaybackRates = [ 0.25, 0.5, 1.0, 1.5, 2.0, 3.0, 5.0, 10.0, ]; final MiniController controller; @override Widget build(BuildContext context) { return Stack( children: [ AnimatedSwitcher( duration: const Duration(milliseconds: 50), reverseDuration: const Duration(milliseconds: 200), child: controller.value.isPlaying ? const SizedBox.shrink() : Container( color: Colors.black26, child: const Center( child: Icon( Icons.play_arrow, color: Colors.white, size: 100.0, semanticLabel: 'Play', ), ), ), ), GestureDetector( onTap: () { controller.value.isPlaying ? controller.pause() : controller.play(); }, ), Align( alignment: Alignment.topRight, child: PopupMenuButton( initialValue: controller.value.playbackSpeed, tooltip: 'Playback speed', onSelected: (double speed) { controller.setPlaybackSpeed(speed); }, itemBuilder: (BuildContext context) { return >[ for (final double speed in _examplePlaybackRates) PopupMenuItem( value: speed, child: Text('${speed}x'), ) ]; }, child: Padding( padding: const EdgeInsets.symmetric( // Using less vertical padding as the text is also longer // horizontally, so it feels like it would need more spacing // horizontally (matching the aspect ratio of the video). vertical: 12, horizontal: 16, ), child: Text('${controller.value.playbackSpeed}x'), ), ), ), ], ); } } ================================================ FILE: packages/video_player/video_player_avfoundation/example/lib/mini_controller.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(stuartmorgan): Consider extracting this to a shared local (path-based) // package for use in all implementation packages. import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; VideoPlayerPlatform? _cachedPlatform; VideoPlayerPlatform get _platform { if (_cachedPlatform == null) { _cachedPlatform = VideoPlayerPlatform.instance; _cachedPlatform!.init(); } return _cachedPlatform!; } /// The duration, current position, buffering state, error state and settings /// of a [MiniController]. class VideoPlayerValue { /// Constructs a video with the given values. Only [duration] is required. The /// rest will initialize with default values when unset. VideoPlayerValue({ required this.duration, this.size = Size.zero, this.position = Duration.zero, this.buffered = const [], this.isInitialized = false, this.isPlaying = false, this.isBuffering = false, this.playbackSpeed = 1.0, this.errorDescription, }); /// Returns an instance for a video that hasn't been loaded. VideoPlayerValue.uninitialized() : this(duration: Duration.zero, isInitialized: false); /// Returns an instance with the given [errorDescription]. VideoPlayerValue.erroneous(String errorDescription) : this( duration: Duration.zero, isInitialized: false, errorDescription: errorDescription); /// The total duration of the video. /// /// The duration is [Duration.zero] if the video hasn't been initialized. final Duration duration; /// The current playback position. final Duration position; /// The currently buffered ranges. final List buffered; /// True if the video is playing. False if it's paused. final bool isPlaying; /// True if the video is currently buffering. final bool isBuffering; /// The current speed of the playback. final double playbackSpeed; /// A description of the error if present. /// /// If [hasError] is false this is `null`. final String? errorDescription; /// The [size] of the currently loaded video. final Size size; /// Indicates whether or not the video has been loaded and is ready to play. final bool isInitialized; /// Indicates whether or not the video is in an error state. If this is true /// [errorDescription] should have information about the problem. bool get hasError => errorDescription != null; /// Returns [size.width] / [size.height]. /// /// Will return `1.0` if: /// * [isInitialized] is `false` /// * [size.width], or [size.height] is equal to `0.0` /// * aspect ratio would be less than or equal to `0.0` double get aspectRatio { if (!isInitialized || size.width == 0 || size.height == 0) { return 1.0; } final double aspectRatio = size.width / size.height; if (aspectRatio <= 0) { return 1.0; } return aspectRatio; } /// Returns a new instance that has the same values as this current instance, /// except for any overrides passed in as arguments to [copyWidth]. VideoPlayerValue copyWith({ Duration? duration, Size? size, Duration? position, List? buffered, bool? isInitialized, bool? isPlaying, bool? isBuffering, double? playbackSpeed, String? errorDescription, }) { return VideoPlayerValue( duration: duration ?? this.duration, size: size ?? this.size, position: position ?? this.position, buffered: buffered ?? this.buffered, isInitialized: isInitialized ?? this.isInitialized, isPlaying: isPlaying ?? this.isPlaying, isBuffering: isBuffering ?? this.isBuffering, playbackSpeed: playbackSpeed ?? this.playbackSpeed, errorDescription: errorDescription ?? this.errorDescription, ); } } /// A very minimal version of `VideoPlayerController` for running the example /// without relying on `video_player`. class MiniController extends ValueNotifier { /// Constructs a [MiniController] playing a video from an asset. /// /// The name of the asset is given by the [dataSource] argument and must not be /// null. The [package] argument must be non-null when the asset comes from a /// package and null otherwise. MiniController.asset(this.dataSource, {this.package}) : dataSourceType = DataSourceType.asset, super(VideoPlayerValue(duration: Duration.zero)); /// Constructs a [MiniController] playing a video from obtained from /// the network. MiniController.network(this.dataSource) : dataSourceType = DataSourceType.network, package = null, super(VideoPlayerValue(duration: Duration.zero)); /// Constructs a [MiniController] playing a video from obtained from a file. MiniController.file(File file) : dataSource = Uri.file(file.absolute.path).toString(), dataSourceType = DataSourceType.file, package = null, super(VideoPlayerValue(duration: Duration.zero)); /// The URI to the video file. This will be in different formats depending on /// the [DataSourceType] of the original video. final String dataSource; /// Describes the type of data source this [MiniController] /// is constructed with. final DataSourceType dataSourceType; /// Only set for [asset] videos. The package that the asset was loaded from. final String? package; Timer? _timer; Completer? _creatingCompleter; StreamSubscription? _eventSubscription; /// The id of a texture that hasn't been initialized. @visibleForTesting static const int kUninitializedTextureId = -1; int _textureId = kUninitializedTextureId; /// This is just exposed for testing. It shouldn't be used by anyone depending /// on the plugin. @visibleForTesting int get textureId => _textureId; /// Attempts to open the given [dataSource] and load metadata about the video. Future initialize() async { _creatingCompleter = Completer(); late DataSource dataSourceDescription; switch (dataSourceType) { case DataSourceType.asset: dataSourceDescription = DataSource( sourceType: DataSourceType.asset, asset: dataSource, package: package, ); break; case DataSourceType.network: dataSourceDescription = DataSource( sourceType: DataSourceType.network, uri: dataSource, ); break; case DataSourceType.file: dataSourceDescription = DataSource( sourceType: DataSourceType.file, uri: dataSource, ); break; case DataSourceType.contentUri: dataSourceDescription = DataSource( sourceType: DataSourceType.contentUri, uri: dataSource, ); break; } _textureId = (await _platform.create(dataSourceDescription)) ?? kUninitializedTextureId; _creatingCompleter!.complete(null); final Completer initializingCompleter = Completer(); void eventListener(VideoEvent event) { switch (event.eventType) { case VideoEventType.initialized: value = value.copyWith( duration: event.duration, size: event.size, isInitialized: event.duration != null, ); initializingCompleter.complete(null); _platform.setVolume(_textureId, 1.0); _platform.setLooping(_textureId, true); _applyPlayPause(); break; case VideoEventType.completed: pause().then((void pauseResult) => seekTo(value.duration)); break; case VideoEventType.bufferingUpdate: value = value.copyWith(buffered: event.buffered); break; case VideoEventType.bufferingStart: value = value.copyWith(isBuffering: true); break; case VideoEventType.bufferingEnd: value = value.copyWith(isBuffering: false); break; case VideoEventType.unknown: break; } } void errorListener(Object obj) { final PlatformException e = obj as PlatformException; value = VideoPlayerValue.erroneous(e.message!); _timer?.cancel(); if (!initializingCompleter.isCompleted) { initializingCompleter.completeError(obj); } } _eventSubscription = _platform .videoEventsFor(_textureId) .listen(eventListener, onError: errorListener); return initializingCompleter.future; } @override Future dispose() async { if (_creatingCompleter != null) { await _creatingCompleter!.future; _timer?.cancel(); await _eventSubscription?.cancel(); await _platform.dispose(_textureId); } super.dispose(); } /// Starts playing the video. Future play() async { value = value.copyWith(isPlaying: true); await _applyPlayPause(); } /// Pauses the video. Future pause() async { value = value.copyWith(isPlaying: false); await _applyPlayPause(); } Future _applyPlayPause() async { _timer?.cancel(); if (value.isPlaying) { await _platform.play(_textureId); _timer = Timer.periodic( const Duration(milliseconds: 500), (Timer timer) async { final Duration? newPosition = await position; if (newPosition == null) { return; } _updatePosition(newPosition); }, ); await _applyPlaybackSpeed(); } else { await _platform.pause(_textureId); } } Future _applyPlaybackSpeed() async { if (value.isPlaying) { await _platform.setPlaybackSpeed( _textureId, value.playbackSpeed, ); } } /// The position in the current video. Future get position async { return _platform.getPosition(_textureId); } /// Sets the video's current timestamp to be at [position]. Future seekTo(Duration position) async { if (position > value.duration) { position = value.duration; } else if (position < Duration.zero) { position = Duration.zero; } await _platform.seekTo(_textureId, position); _updatePosition(position); } /// Sets the playback speed. Future setPlaybackSpeed(double speed) async { value = value.copyWith(playbackSpeed: speed); await _applyPlaybackSpeed(); } void _updatePosition(Duration position) { value = value.copyWith(position: position); } } /// Widget that displays the video controlled by [controller]. class VideoPlayer extends StatefulWidget { /// Uses the given [controller] for all video rendered in this widget. const VideoPlayer(this.controller, {Key? key}) : super(key: key); /// The [MiniController] responsible for the video being rendered in /// this widget. final MiniController controller; @override State createState() => _VideoPlayerState(); } class _VideoPlayerState extends State { _VideoPlayerState() { _listener = () { final int newTextureId = widget.controller.textureId; if (newTextureId != _textureId) { setState(() { _textureId = newTextureId; }); } }; } late VoidCallback _listener; late int _textureId; @override void initState() { super.initState(); _textureId = widget.controller.textureId; // Need to listen for initialization events since the actual texture ID // becomes available after asynchronous initialization finishes. widget.controller.addListener(_listener); } @override void didUpdateWidget(VideoPlayer oldWidget) { super.didUpdateWidget(oldWidget); oldWidget.controller.removeListener(_listener); _textureId = widget.controller.textureId; widget.controller.addListener(_listener); } @override void deactivate() { super.deactivate(); widget.controller.removeListener(_listener); } @override Widget build(BuildContext context) { return _textureId == MiniController.kUninitializedTextureId ? Container() : _platform.buildView(_textureId); } } class _VideoScrubber extends StatefulWidget { const _VideoScrubber({ required this.child, required this.controller, }); final Widget child; final MiniController controller; @override _VideoScrubberState createState() => _VideoScrubberState(); } class _VideoScrubberState extends State<_VideoScrubber> { MiniController get controller => widget.controller; @override Widget build(BuildContext context) { void seekToRelativePosition(Offset globalPosition) { final RenderBox box = context.findRenderObject()! as RenderBox; final Offset tapPos = box.globalToLocal(globalPosition); final double relative = tapPos.dx / box.size.width; final Duration position = controller.value.duration * relative; controller.seekTo(position); } return GestureDetector( behavior: HitTestBehavior.opaque, child: widget.child, onTapDown: (TapDownDetails details) { if (controller.value.isInitialized) { seekToRelativePosition(details.globalPosition); } }, ); } } /// Displays the play/buffering status of the video controlled by [controller]. class VideoProgressIndicator extends StatefulWidget { /// Construct an instance that displays the play/buffering status of the video /// controlled by [controller]. const VideoProgressIndicator(this.controller, {Key? key}) : super(key: key); /// The [MiniController] that actually associates a video with this /// widget. final MiniController controller; @override State createState() => _VideoProgressIndicatorState(); } class _VideoProgressIndicatorState extends State { _VideoProgressIndicatorState() { listener = () { if (mounted) { setState(() {}); } }; } late VoidCallback listener; MiniController get controller => widget.controller; @override void initState() { super.initState(); controller.addListener(listener); } @override void deactivate() { controller.removeListener(listener); super.deactivate(); } @override Widget build(BuildContext context) { const Color playedColor = Color.fromRGBO(255, 0, 0, 0.7); const Color bufferedColor = Color.fromRGBO(50, 50, 200, 0.2); const Color backgroundColor = Color.fromRGBO(200, 200, 200, 0.5); Widget progressIndicator; if (controller.value.isInitialized) { final int duration = controller.value.duration.inMilliseconds; final int position = controller.value.position.inMilliseconds; int maxBuffering = 0; for (final DurationRange range in controller.value.buffered) { final int end = range.end.inMilliseconds; if (end > maxBuffering) { maxBuffering = end; } } progressIndicator = Stack( fit: StackFit.passthrough, children: [ LinearProgressIndicator( value: maxBuffering / duration, valueColor: const AlwaysStoppedAnimation(bufferedColor), backgroundColor: backgroundColor, ), LinearProgressIndicator( value: position / duration, valueColor: const AlwaysStoppedAnimation(playedColor), backgroundColor: Colors.transparent, ), ], ); } else { progressIndicator = const LinearProgressIndicator( valueColor: AlwaysStoppedAnimation(playedColor), backgroundColor: backgroundColor, ); } return _VideoScrubber( controller: controller, child: Padding( padding: const EdgeInsets.only(top: 5.0), child: progressIndicator, ), ); } } ================================================ FILE: packages/video_player/video_player_avfoundation/example/pubspec.yaml ================================================ name: video_player_example description: Demonstrates how to use the video_player plugin. publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter video_player_avfoundation: # When depending on this package from a real application you should use: # video_player_avfoundation: ^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: ../ video_player_platform_interface: ">=4.2.0 <7.0.0" dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter path_provider: ^2.0.6 test: any flutter: uses-material-design: true assets: - assets/flutter-mark-square-64.png - assets/Butterfly-209.mp4 ================================================ FILE: packages/video_player/video_player_avfoundation/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/video_player/video_player_avfoundation/example/test_driver/video_player.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_driver/driver_extension.dart'; import 'package:video_player_example/main.dart' as app; void main() { enableFlutterDriverExtension(); app.main(); } ================================================ FILE: packages/video_player/video_player_avfoundation/ios/Assets/.gitkeep ================================================ ================================================ FILE: packages/video_player/video_player_avfoundation/ios/Classes/AVAssetTrackUtils.h ================================================ // Copyright 2013 The Flutter 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 /** * Returns a standardized transform * according to the orientation of the track. * * Note: https://stackoverflow.com/questions/64161544 * `AVAssetTrack.preferredTransform` can have wrong `tx` and `ty`. */ CGAffineTransform FLTGetStandardizedTransformForTrack(AVAssetTrack* track); ================================================ FILE: packages/video_player/video_player_avfoundation/ios/Classes/AVAssetTrackUtils.m ================================================ // Copyright 2013 The Flutter 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 CGAffineTransform FLTGetStandardizedTransformForTrack(AVAssetTrack *track) { CGAffineTransform t = track.preferredTransform; CGSize size = track.naturalSize; // Each case of control flows corresponds to a specific // `UIImageOrientation`, with 8 cases in total. if (t.a == 1 && t.b == 0 && t.c == 0 && t.d == 1) { // UIImageOrientationUp t.tx = 0; t.ty = 0; } else if (t.a == -1 && t.b == 0 && t.c == 0 && t.d == -1) { // UIImageOrientationDown t.tx = size.width; t.ty = size.height; } else if (t.a == 0 && t.b == -1 && t.c == 1 && t.d == 0) { // UIImageOrientationLeft t.tx = 0; t.ty = size.width; } else if (t.a == 0 && t.b == 1 && t.c == -1 && t.d == 0) { // UIImageOrientationRight t.tx = size.height; t.ty = 0; } else if (t.a == -1 && t.b == 0 && t.c == 0 && t.d == 1) { // UIImageOrientationUpMirrored t.tx = size.width; t.ty = 0; } else if (t.a == 1 && t.b == 0 && t.c == 0 && t.d == -1) { // UIImageOrientationDownMirrored t.tx = 0; t.ty = size.height; } else if (t.a == 0 && t.b == -1 && t.c == -1 && t.d == 0) { // UIImageOrientationLeftMirrored t.tx = size.height; t.ty = size.width; } else if (t.a == 0 && t.b == 1 && t.c == 1 && t.d == 0) { // UIImageOrientationRightMirrored t.tx = 0; t.ty = 0; } return t; } ================================================ FILE: packages/video_player/video_player_avfoundation/ios/Classes/FLTVideoPlayerPlugin.h ================================================ // Copyright 2013 The Flutter 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 FLTVideoPlayerPlugin : NSObject - (instancetype)initWithRegistrar:(NSObject *)registrar; @end ================================================ FILE: packages/video_player/video_player_avfoundation/ios/Classes/FLTVideoPlayerPlugin.m ================================================ // Copyright 2013 The Flutter 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 "FLTVideoPlayerPlugin.h" #import #import #import "AVAssetTrackUtils.h" #import "messages.g.h" #if !__has_feature(objc_arc) #error Code Requires ARC. #endif @interface FLTFrameUpdater : NSObject @property(nonatomic) int64_t textureId; @property(nonatomic, weak, readonly) NSObject *registry; - (void)onDisplayLink:(CADisplayLink *)link; @end @implementation FLTFrameUpdater - (FLTFrameUpdater *)initWithRegistry:(NSObject *)registry { NSAssert(self, @"super init cannot be nil"); if (self == nil) return nil; _registry = registry; return self; } - (void)onDisplayLink:(CADisplayLink *)link { [_registry textureFrameAvailable:_textureId]; } @end @interface FLTVideoPlayer : NSObject @property(readonly, nonatomic) AVPlayer *player; @property(readonly, nonatomic) AVPlayerItemVideoOutput *videoOutput; // This is to fix 2 bugs: 1. blank video for encrypted video streams on iOS 16 // (https://github.com/flutter/flutter/issues/111457) and 2. swapped width and height for some video // streams (not just iOS 16). (https://github.com/flutter/flutter/issues/109116). // An invisible AVPlayerLayer is used to overwrite the protection of pixel buffers in those streams // for issue #1, and restore the correct width and height for issue #2. @property(readonly, nonatomic) AVPlayerLayer *playerLayer; @property(readonly, nonatomic) CADisplayLink *displayLink; @property(nonatomic) FlutterEventChannel *eventChannel; @property(nonatomic) FlutterEventSink eventSink; @property(nonatomic) CGAffineTransform preferredTransform; @property(nonatomic, readonly) BOOL disposed; @property(nonatomic, readonly) BOOL isPlaying; @property(nonatomic) BOOL isLooping; @property(nonatomic, readonly) BOOL isInitialized; - (instancetype)initWithURL:(NSURL *)url frameUpdater:(FLTFrameUpdater *)frameUpdater httpHeaders:(nonnull NSDictionary *)headers; @end static void *timeRangeContext = &timeRangeContext; static void *statusContext = &statusContext; static void *presentationSizeContext = &presentationSizeContext; static void *durationContext = &durationContext; static void *playbackLikelyToKeepUpContext = &playbackLikelyToKeepUpContext; static void *playbackBufferEmptyContext = &playbackBufferEmptyContext; static void *playbackBufferFullContext = &playbackBufferFullContext; @implementation FLTVideoPlayer - (instancetype)initWithAsset:(NSString *)asset frameUpdater:(FLTFrameUpdater *)frameUpdater { NSString *path = [[NSBundle mainBundle] pathForResource:asset ofType:nil]; return [self initWithURL:[NSURL fileURLWithPath:path] frameUpdater:frameUpdater httpHeaders:@{}]; } - (void)addObservers:(AVPlayerItem *)item { [item addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:timeRangeContext]; [item addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:statusContext]; [item addObserver:self forKeyPath:@"presentationSize" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:presentationSizeContext]; [item addObserver:self forKeyPath:@"duration" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:durationContext]; [item addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:playbackLikelyToKeepUpContext]; [item addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:playbackBufferEmptyContext]; [item addObserver:self forKeyPath:@"playbackBufferFull" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:playbackBufferFullContext]; // Add an observer that will respond to itemDidPlayToEndTime [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(itemDidPlayToEndTime:) name:AVPlayerItemDidPlayToEndTimeNotification object:item]; } - (void)itemDidPlayToEndTime:(NSNotification *)notification { if (_isLooping) { AVPlayerItem *p = [notification object]; [p seekToTime:kCMTimeZero completionHandler:nil]; } else { if (_eventSink) { _eventSink(@{@"event" : @"completed"}); } } } const int64_t TIME_UNSET = -9223372036854775807; NS_INLINE int64_t FLTCMTimeToMillis(CMTime time) { // When CMTIME_IS_INDEFINITE return a value that matches TIME_UNSET from ExoPlayer2 on Android. // Fixes https://github.com/flutter/flutter/issues/48670 if (CMTIME_IS_INDEFINITE(time)) return TIME_UNSET; if (time.timescale == 0) return 0; return time.value * 1000 / time.timescale; } NS_INLINE CGFloat radiansToDegrees(CGFloat radians) { // Input range [-pi, pi] or [-180, 180] CGFloat degrees = GLKMathRadiansToDegrees((float)radians); if (degrees < 0) { // Convert -90 to 270 and -180 to 180 return degrees + 360; } // Output degrees in between [0, 360] return degrees; }; NS_INLINE UIViewController *rootViewController() { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" // TODO: (hellohuanlin) Provide a non-deprecated codepath. See // https://github.com/flutter/flutter/issues/104117 return UIApplication.sharedApplication.keyWindow.rootViewController; #pragma clang diagnostic pop } - (AVMutableVideoComposition *)getVideoCompositionWithTransform:(CGAffineTransform)transform withAsset:(AVAsset *)asset withVideoTrack:(AVAssetTrack *)videoTrack { AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; instruction.timeRange = CMTimeRangeMake(kCMTimeZero, [asset duration]); AVMutableVideoCompositionLayerInstruction *layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack]; [layerInstruction setTransform:_preferredTransform atTime:kCMTimeZero]; AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition]; instruction.layerInstructions = @[ layerInstruction ]; videoComposition.instructions = @[ instruction ]; // If in portrait mode, switch the width and height of the video CGFloat width = videoTrack.naturalSize.width; CGFloat height = videoTrack.naturalSize.height; NSInteger rotationDegrees = (NSInteger)round(radiansToDegrees(atan2(_preferredTransform.b, _preferredTransform.a))); if (rotationDegrees == 90 || rotationDegrees == 270) { width = videoTrack.naturalSize.height; height = videoTrack.naturalSize.width; } videoComposition.renderSize = CGSizeMake(width, height); // TODO(@recastrodiaz): should we use videoTrack.nominalFrameRate ? // Currently set at a constant 30 FPS videoComposition.frameDuration = CMTimeMake(1, 30); return videoComposition; } - (void)createVideoOutputAndDisplayLink:(FLTFrameUpdater *)frameUpdater { NSDictionary *pixBuffAttributes = @{ (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA), (id)kCVPixelBufferIOSurfacePropertiesKey : @{} }; _videoOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:pixBuffAttributes]; _displayLink = [CADisplayLink displayLinkWithTarget:frameUpdater selector:@selector(onDisplayLink:)]; [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; _displayLink.paused = YES; } - (instancetype)initWithURL:(NSURL *)url frameUpdater:(FLTFrameUpdater *)frameUpdater httpHeaders:(nonnull NSDictionary *)headers { NSDictionary *options = nil; if ([headers count] != 0) { options = @{@"AVURLAssetHTTPHeaderFieldsKey" : headers}; } AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:url options:options]; AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:urlAsset]; return [self initWithPlayerItem:item frameUpdater:frameUpdater]; } - (instancetype)initWithPlayerItem:(AVPlayerItem *)item frameUpdater:(FLTFrameUpdater *)frameUpdater { self = [super init]; NSAssert(self, @"super init cannot be nil"); AVAsset *asset = [item asset]; void (^assetCompletionHandler)(void) = ^{ if ([asset statusOfValueForKey:@"tracks" error:nil] == AVKeyValueStatusLoaded) { NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo]; if ([tracks count] > 0) { AVAssetTrack *videoTrack = tracks[0]; void (^trackCompletionHandler)(void) = ^{ if (self->_disposed) return; if ([videoTrack statusOfValueForKey:@"preferredTransform" error:nil] == AVKeyValueStatusLoaded) { // Rotate the video by using a videoComposition and the preferredTransform self->_preferredTransform = FLTGetStandardizedTransformForTrack(videoTrack); // Note: // https://developer.apple.com/documentation/avfoundation/avplayeritem/1388818-videocomposition // Video composition can only be used with file-based media and is not supported for // use with media served using HTTP Live Streaming. AVMutableVideoComposition *videoComposition = [self getVideoCompositionWithTransform:self->_preferredTransform withAsset:asset withVideoTrack:videoTrack]; item.videoComposition = videoComposition; } }; [videoTrack loadValuesAsynchronouslyForKeys:@[ @"preferredTransform" ] completionHandler:trackCompletionHandler]; } } }; _player = [AVPlayer playerWithPlayerItem:item]; _player.actionAtItemEnd = AVPlayerActionAtItemEndNone; // This is to fix 2 bugs: 1. blank video for encrypted video streams on iOS 16 // (https://github.com/flutter/flutter/issues/111457) and 2. swapped width and height for some // video streams (not just iOS 16). (https://github.com/flutter/flutter/issues/109116). An // invisible AVPlayerLayer is used to overwrite the protection of pixel buffers in those streams // for issue #1, and restore the correct width and height for issue #2. _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; [rootViewController().view.layer addSublayer:_playerLayer]; [self createVideoOutputAndDisplayLink:frameUpdater]; [self addObservers:item]; [asset loadValuesAsynchronouslyForKeys:@[ @"tracks" ] completionHandler:assetCompletionHandler]; return self; } - (void)observeValueForKeyPath:(NSString *)path ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == timeRangeContext) { if (_eventSink != nil) { NSMutableArray *> *values = [[NSMutableArray alloc] init]; for (NSValue *rangeValue in [object loadedTimeRanges]) { CMTimeRange range = [rangeValue CMTimeRangeValue]; int64_t start = FLTCMTimeToMillis(range.start); [values addObject:@[ @(start), @(start + FLTCMTimeToMillis(range.duration)) ]]; } _eventSink(@{@"event" : @"bufferingUpdate", @"values" : values}); } } else if (context == statusContext) { AVPlayerItem *item = (AVPlayerItem *)object; switch (item.status) { case AVPlayerItemStatusFailed: if (_eventSink != nil) { _eventSink([FlutterError errorWithCode:@"VideoError" message:[@"Failed to load video: " stringByAppendingString:[item.error localizedDescription]] details:nil]); } break; case AVPlayerItemStatusUnknown: break; case AVPlayerItemStatusReadyToPlay: [item addOutput:_videoOutput]; [self setupEventSinkIfReadyToPlay]; [self updatePlayingState]; break; } } else if (context == presentationSizeContext || context == durationContext) { AVPlayerItem *item = (AVPlayerItem *)object; if (item.status == AVPlayerItemStatusReadyToPlay) { // Due to an apparent bug, when the player item is ready, it still may not have determined // its presentation size or duration. When these properties are finally set, re-check if // all required properties and instantiate the event sink if it is not already set up. [self setupEventSinkIfReadyToPlay]; [self updatePlayingState]; } } else if (context == playbackLikelyToKeepUpContext) { if ([[_player currentItem] isPlaybackLikelyToKeepUp]) { [self updatePlayingState]; if (_eventSink != nil) { _eventSink(@{@"event" : @"bufferingEnd"}); } } } else if (context == playbackBufferEmptyContext) { if (_eventSink != nil) { _eventSink(@{@"event" : @"bufferingStart"}); } } else if (context == playbackBufferFullContext) { if (_eventSink != nil) { _eventSink(@{@"event" : @"bufferingEnd"}); } } } - (void)updatePlayingState { if (!_isInitialized) { return; } if (_isPlaying) { [_player play]; } else { [_player pause]; } _displayLink.paused = !_isPlaying; } - (void)setupEventSinkIfReadyToPlay { if (_eventSink && !_isInitialized) { AVPlayerItem *currentItem = self.player.currentItem; CGSize size = currentItem.presentationSize; CGFloat width = size.width; CGFloat height = size.height; // Wait until tracks are loaded to check duration or if there are any videos. AVAsset *asset = currentItem.asset; if ([asset statusOfValueForKey:@"tracks" error:nil] != AVKeyValueStatusLoaded) { void (^trackCompletionHandler)(void) = ^{ if ([asset statusOfValueForKey:@"tracks" error:nil] != AVKeyValueStatusLoaded) { // Cancelled, or something failed. return; } // This completion block will run on an AVFoundation background queue. // Hop back to the main thread to set up event sink. [self performSelector:_cmd onThread:NSThread.mainThread withObject:self waitUntilDone:NO]; }; [asset loadValuesAsynchronouslyForKeys:@[ @"tracks" ] completionHandler:trackCompletionHandler]; return; } BOOL hasVideoTracks = [asset tracksWithMediaType:AVMediaTypeVideo].count != 0; BOOL hasNoTracks = asset.tracks.count == 0; // The player has not yet initialized when it has no size, unless it is an audio-only track. // HLS m3u8 video files never load any tracks, and are also not yet initialized until they have // a size. if ((hasVideoTracks || hasNoTracks) && height == CGSizeZero.height && width == CGSizeZero.width) { return; } // The player may be initialized but still needs to determine the duration. int64_t duration = [self duration]; if (duration == 0) { return; } _isInitialized = YES; _eventSink(@{ @"event" : @"initialized", @"duration" : @(duration), @"width" : @(width), @"height" : @(height) }); } } - (void)play { _isPlaying = YES; [self updatePlayingState]; } - (void)pause { _isPlaying = NO; [self updatePlayingState]; } - (int64_t)position { return FLTCMTimeToMillis([_player currentTime]); } - (int64_t)duration { // Note: https://openradar.appspot.com/radar?id=4968600712511488 // `[AVPlayerItem duration]` can be `kCMTimeIndefinite`, // use `[[AVPlayerItem asset] duration]` instead. return FLTCMTimeToMillis([[[_player currentItem] asset] duration]); } - (void)seekTo:(int)location { // TODO(stuartmorgan): Update this to use completionHandler: to only return // once the seek operation is complete once the Pigeon API is updated to a // version that handles async calls. [_player seekToTime:CMTimeMake(location, 1000) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; } - (void)setIsLooping:(BOOL)isLooping { _isLooping = isLooping; } - (void)setVolume:(double)volume { _player.volume = (float)((volume < 0.0) ? 0.0 : ((volume > 1.0) ? 1.0 : volume)); } - (void)setPlaybackSpeed:(double)speed { // See https://developer.apple.com/library/archive/qa/qa1772/_index.html for an explanation of // these checks. if (speed > 2.0 && !_player.currentItem.canPlayFastForward) { if (_eventSink != nil) { _eventSink([FlutterError errorWithCode:@"VideoError" message:@"Video cannot be fast-forwarded beyond 2.0x" details:nil]); } return; } if (speed < 1.0 && !_player.currentItem.canPlaySlowForward) { if (_eventSink != nil) { _eventSink([FlutterError errorWithCode:@"VideoError" message:@"Video cannot be slow-forwarded" details:nil]); } return; } _player.rate = speed; } - (CVPixelBufferRef)copyPixelBuffer { CMTime outputItemTime = [_videoOutput itemTimeForHostTime:CACurrentMediaTime()]; if ([_videoOutput hasNewPixelBufferForItemTime:outputItemTime]) { return [_videoOutput copyPixelBufferForItemTime:outputItemTime itemTimeForDisplay:NULL]; } else { return NULL; } } - (void)onTextureUnregistered:(NSObject *)texture { dispatch_async(dispatch_get_main_queue(), ^{ [self dispose]; }); } - (FlutterError *_Nullable)onCancelWithArguments:(id _Nullable)arguments { _eventSink = nil; return nil; } - (FlutterError *_Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events { _eventSink = events; // TODO(@recastrodiaz): remove the line below when the race condition is resolved: // https://github.com/flutter/flutter/issues/21483 // This line ensures the 'initialized' event is sent when the event // 'AVPlayerItemStatusReadyToPlay' fires before _eventSink is set (this function // onListenWithArguments is called) [self setupEventSinkIfReadyToPlay]; return nil; } /// This method allows you to dispose without touching the event channel. This /// is useful for the case where the Engine is in the process of deconstruction /// so the channel is going to die or is already dead. - (void)disposeSansEventChannel { _disposed = YES; [_playerLayer removeFromSuperlayer]; [_displayLink invalidate]; AVPlayerItem *currentItem = self.player.currentItem; [currentItem removeObserver:self forKeyPath:@"status"]; [currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"]; [currentItem removeObserver:self forKeyPath:@"presentationSize"]; [currentItem removeObserver:self forKeyPath:@"duration"]; [currentItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"]; [currentItem removeObserver:self forKeyPath:@"playbackBufferEmpty"]; [currentItem removeObserver:self forKeyPath:@"playbackBufferFull"]; [self.player replaceCurrentItemWithPlayerItem:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)dispose { [self disposeSansEventChannel]; [_eventChannel setStreamHandler:nil]; } @end @interface FLTVideoPlayerPlugin () @property(readonly, weak, nonatomic) NSObject *registry; @property(readonly, weak, nonatomic) NSObject *messenger; @property(readonly, strong, nonatomic) NSMutableDictionary *playersByTextureId; @property(readonly, strong, nonatomic) NSObject *registrar; @end @implementation FLTVideoPlayerPlugin + (void)registerWithRegistrar:(NSObject *)registrar { FLTVideoPlayerPlugin *instance = [[FLTVideoPlayerPlugin alloc] initWithRegistrar:registrar]; [registrar publish:instance]; FLTAVFoundationVideoPlayerApiSetup(registrar.messenger, instance); } - (instancetype)initWithRegistrar:(NSObject *)registrar { self = [super init]; NSAssert(self, @"super init cannot be nil"); _registry = [registrar textures]; _messenger = [registrar messenger]; _registrar = registrar; _playersByTextureId = [NSMutableDictionary dictionaryWithCapacity:1]; return self; } - (void)detachFromEngineForRegistrar:(NSObject *)registrar { [self.playersByTextureId.allValues makeObjectsPerformSelector:@selector(disposeSansEventChannel)]; [self.playersByTextureId removeAllObjects]; // TODO(57151): This should be commented out when 57151's fix lands on stable. // This is the correct behavior we never did it in the past and the engine // doesn't currently support it. // FLTAVFoundationVideoPlayerApiSetup(registrar.messenger, nil); } - (FLTTextureMessage *)onPlayerSetup:(FLTVideoPlayer *)player frameUpdater:(FLTFrameUpdater *)frameUpdater { int64_t textureId = [self.registry registerTexture:player]; frameUpdater.textureId = textureId; FlutterEventChannel *eventChannel = [FlutterEventChannel eventChannelWithName:[NSString stringWithFormat:@"flutter.io/videoPlayer/videoEvents%lld", textureId] binaryMessenger:_messenger]; [eventChannel setStreamHandler:player]; player.eventChannel = eventChannel; self.playersByTextureId[@(textureId)] = player; FLTTextureMessage *result = [FLTTextureMessage makeWithTextureId:@(textureId)]; return result; } - (void)initialize:(FlutterError *__autoreleasing *)error { // Allow audio playback when the Ring/Silent switch is set to silent [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; [self.playersByTextureId enumerateKeysAndObjectsUsingBlock:^(NSNumber *textureId, FLTVideoPlayer *player, BOOL *stop) { [self.registry unregisterTexture:textureId.unsignedIntegerValue]; [player dispose]; }]; [self.playersByTextureId removeAllObjects]; } - (FLTTextureMessage *)create:(FLTCreateMessage *)input error:(FlutterError **)error { FLTFrameUpdater *frameUpdater = [[FLTFrameUpdater alloc] initWithRegistry:_registry]; FLTVideoPlayer *player; if (input.asset) { NSString *assetPath; if (input.packageName) { assetPath = [_registrar lookupKeyForAsset:input.asset fromPackage:input.packageName]; } else { assetPath = [_registrar lookupKeyForAsset:input.asset]; } player = [[FLTVideoPlayer alloc] initWithAsset:assetPath frameUpdater:frameUpdater]; return [self onPlayerSetup:player frameUpdater:frameUpdater]; } else if (input.uri) { player = [[FLTVideoPlayer alloc] initWithURL:[NSURL URLWithString:input.uri] frameUpdater:frameUpdater httpHeaders:input.httpHeaders]; return [self onPlayerSetup:player frameUpdater:frameUpdater]; } else { *error = [FlutterError errorWithCode:@"video_player" message:@"not implemented" details:nil]; return nil; } } - (void)dispose:(FLTTextureMessage *)input error:(FlutterError **)error { FLTVideoPlayer *player = self.playersByTextureId[input.textureId]; [self.registry unregisterTexture:input.textureId.intValue]; [self.playersByTextureId removeObjectForKey:input.textureId]; // If the Flutter contains https://github.com/flutter/engine/pull/12695, // the `player` is disposed via `onTextureUnregistered` at the right time. // Without https://github.com/flutter/engine/pull/12695, there is no guarantee that the // texture has completed the un-reregistration. It may leads a crash if we dispose the // `player` before the texture is unregistered. We add a dispatch_after hack to make sure the // texture is unregistered before we dispose the `player`. // // TODO(cyanglaz): Remove this dispatch block when // https://github.com/flutter/flutter/commit/8159a9906095efc9af8b223f5e232cb63542ad0b is in // stable And update the min flutter version of the plugin to the stable version. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (!player.disposed) { [player dispose]; } }); } - (void)setLooping:(FLTLoopingMessage *)input error:(FlutterError **)error { FLTVideoPlayer *player = self.playersByTextureId[input.textureId]; player.isLooping = input.isLooping.boolValue; } - (void)setVolume:(FLTVolumeMessage *)input error:(FlutterError **)error { FLTVideoPlayer *player = self.playersByTextureId[input.textureId]; [player setVolume:input.volume.doubleValue]; } - (void)setPlaybackSpeed:(FLTPlaybackSpeedMessage *)input error:(FlutterError **)error { FLTVideoPlayer *player = self.playersByTextureId[input.textureId]; [player setPlaybackSpeed:input.speed.doubleValue]; } - (void)play:(FLTTextureMessage *)input error:(FlutterError **)error { FLTVideoPlayer *player = self.playersByTextureId[input.textureId]; [player play]; } - (FLTPositionMessage *)position:(FLTTextureMessage *)input error:(FlutterError **)error { FLTVideoPlayer *player = self.playersByTextureId[input.textureId]; FLTPositionMessage *result = [FLTPositionMessage makeWithTextureId:input.textureId position:@([player position])]; return result; } - (void)seekTo:(FLTPositionMessage *)input error:(FlutterError **)error { FLTVideoPlayer *player = self.playersByTextureId[input.textureId]; [player seekTo:input.position.intValue]; [self.registry textureFrameAvailable:input.textureId.intValue]; } - (void)pause:(FLTTextureMessage *)input error:(FlutterError **)error { FLTVideoPlayer *player = self.playersByTextureId[input.textureId]; [player pause]; } - (void)setMixWithOthers:(FLTMixWithOthersMessage *)input error:(FlutterError *_Nullable __autoreleasing *)error { if (input.mixWithOthers.boolValue) { [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil]; } else { [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; } } @end ================================================ FILE: packages/video_player/video_player_avfoundation/ios/Classes/messages.g.h ================================================ // Copyright 2013 The Flutter 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 (v2.0.1), do not edit directly. // See also: https://pub.dev/packages/pigeon #import @protocol FlutterBinaryMessenger; @protocol FlutterMessageCodec; @class FlutterError; @class FlutterStandardTypedData; NS_ASSUME_NONNULL_BEGIN @class FLTTextureMessage; @class FLTLoopingMessage; @class FLTVolumeMessage; @class FLTPlaybackSpeedMessage; @class FLTPositionMessage; @class FLTCreateMessage; @class FLTMixWithOthersMessage; @interface FLTTextureMessage : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithTextureId:(NSNumber *)textureId; @property(nonatomic, strong) NSNumber *textureId; @end @interface FLTLoopingMessage : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithTextureId:(NSNumber *)textureId isLooping:(NSNumber *)isLooping; @property(nonatomic, strong) NSNumber *textureId; @property(nonatomic, strong) NSNumber *isLooping; @end @interface FLTVolumeMessage : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithTextureId:(NSNumber *)textureId volume:(NSNumber *)volume; @property(nonatomic, strong) NSNumber *textureId; @property(nonatomic, strong) NSNumber *volume; @end @interface FLTPlaybackSpeedMessage : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithTextureId:(NSNumber *)textureId speed:(NSNumber *)speed; @property(nonatomic, strong) NSNumber *textureId; @property(nonatomic, strong) NSNumber *speed; @end @interface FLTPositionMessage : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithTextureId:(NSNumber *)textureId position:(NSNumber *)position; @property(nonatomic, strong) NSNumber *textureId; @property(nonatomic, strong) NSNumber *position; @end @interface FLTCreateMessage : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithAsset:(nullable NSString *)asset uri:(nullable NSString *)uri packageName:(nullable NSString *)packageName formatHint:(nullable NSString *)formatHint httpHeaders:(NSDictionary *)httpHeaders; @property(nonatomic, copy, nullable) NSString *asset; @property(nonatomic, copy, nullable) NSString *uri; @property(nonatomic, copy, nullable) NSString *packageName; @property(nonatomic, copy, nullable) NSString *formatHint; @property(nonatomic, strong) NSDictionary *httpHeaders; @end @interface FLTMixWithOthersMessage : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithMixWithOthers:(NSNumber *)mixWithOthers; @property(nonatomic, strong) NSNumber *mixWithOthers; @end /// The codec used by FLTAVFoundationVideoPlayerApi. NSObject *FLTAVFoundationVideoPlayerApiGetCodec(void); @protocol FLTAVFoundationVideoPlayerApi - (void)initialize:(FlutterError *_Nullable *_Nonnull)error; /// @return `nil` only when `error != nil`. - (nullable FLTTextureMessage *)create:(FLTCreateMessage *)msg error:(FlutterError *_Nullable *_Nonnull)error; - (void)dispose:(FLTTextureMessage *)msg error:(FlutterError *_Nullable *_Nonnull)error; - (void)setLooping:(FLTLoopingMessage *)msg error:(FlutterError *_Nullable *_Nonnull)error; - (void)setVolume:(FLTVolumeMessage *)msg error:(FlutterError *_Nullable *_Nonnull)error; - (void)setPlaybackSpeed:(FLTPlaybackSpeedMessage *)msg error:(FlutterError *_Nullable *_Nonnull)error; - (void)play:(FLTTextureMessage *)msg error:(FlutterError *_Nullable *_Nonnull)error; /// @return `nil` only when `error != nil`. - (nullable FLTPositionMessage *)position:(FLTTextureMessage *)msg error:(FlutterError *_Nullable *_Nonnull)error; - (void)seekTo:(FLTPositionMessage *)msg error:(FlutterError *_Nullable *_Nonnull)error; - (void)pause:(FLTTextureMessage *)msg error:(FlutterError *_Nullable *_Nonnull)error; - (void)setMixWithOthers:(FLTMixWithOthersMessage *)msg error:(FlutterError *_Nullable *_Nonnull)error; @end extern void FLTAVFoundationVideoPlayerApiSetup( id binaryMessenger, NSObject *_Nullable api); NS_ASSUME_NONNULL_END ================================================ FILE: packages/video_player/video_player_avfoundation/ios/Classes/messages.g.m ================================================ // Copyright 2013 The Flutter 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 (v2.0.1), 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, }; } static id GetNullableObject(NSDictionary *dict, id key) { id result = dict[key]; return (result == [NSNull null]) ? nil : result; } static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { id result = array[key]; return (result == [NSNull null]) ? nil : result; } @interface FLTTextureMessage () + (FLTTextureMessage *)fromMap:(NSDictionary *)dict; - (NSDictionary *)toMap; @end @interface FLTLoopingMessage () + (FLTLoopingMessage *)fromMap:(NSDictionary *)dict; - (NSDictionary *)toMap; @end @interface FLTVolumeMessage () + (FLTVolumeMessage *)fromMap:(NSDictionary *)dict; - (NSDictionary *)toMap; @end @interface FLTPlaybackSpeedMessage () + (FLTPlaybackSpeedMessage *)fromMap:(NSDictionary *)dict; - (NSDictionary *)toMap; @end @interface FLTPositionMessage () + (FLTPositionMessage *)fromMap:(NSDictionary *)dict; - (NSDictionary *)toMap; @end @interface FLTCreateMessage () + (FLTCreateMessage *)fromMap:(NSDictionary *)dict; - (NSDictionary *)toMap; @end @interface FLTMixWithOthersMessage () + (FLTMixWithOthersMessage *)fromMap:(NSDictionary *)dict; - (NSDictionary *)toMap; @end @implementation FLTTextureMessage + (instancetype)makeWithTextureId:(NSNumber *)textureId { FLTTextureMessage *pigeonResult = [[FLTTextureMessage alloc] init]; pigeonResult.textureId = textureId; return pigeonResult; } + (FLTTextureMessage *)fromMap:(NSDictionary *)dict { FLTTextureMessage *pigeonResult = [[FLTTextureMessage alloc] init]; pigeonResult.textureId = GetNullableObject(dict, @"textureId"); NSAssert(pigeonResult.textureId != nil, @""); return pigeonResult; } - (NSDictionary *)toMap { return [NSDictionary dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), @"textureId", nil]; } @end @implementation FLTLoopingMessage + (instancetype)makeWithTextureId:(NSNumber *)textureId isLooping:(NSNumber *)isLooping { FLTLoopingMessage *pigeonResult = [[FLTLoopingMessage alloc] init]; pigeonResult.textureId = textureId; pigeonResult.isLooping = isLooping; return pigeonResult; } + (FLTLoopingMessage *)fromMap:(NSDictionary *)dict { FLTLoopingMessage *pigeonResult = [[FLTLoopingMessage alloc] init]; pigeonResult.textureId = GetNullableObject(dict, @"textureId"); NSAssert(pigeonResult.textureId != nil, @""); pigeonResult.isLooping = GetNullableObject(dict, @"isLooping"); NSAssert(pigeonResult.isLooping != nil, @""); return pigeonResult; } - (NSDictionary *)toMap { return [NSDictionary dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), @"textureId", (self.isLooping ? self.isLooping : [NSNull null]), @"isLooping", nil]; } @end @implementation FLTVolumeMessage + (instancetype)makeWithTextureId:(NSNumber *)textureId volume:(NSNumber *)volume { FLTVolumeMessage *pigeonResult = [[FLTVolumeMessage alloc] init]; pigeonResult.textureId = textureId; pigeonResult.volume = volume; return pigeonResult; } + (FLTVolumeMessage *)fromMap:(NSDictionary *)dict { FLTVolumeMessage *pigeonResult = [[FLTVolumeMessage alloc] init]; pigeonResult.textureId = GetNullableObject(dict, @"textureId"); NSAssert(pigeonResult.textureId != nil, @""); pigeonResult.volume = GetNullableObject(dict, @"volume"); NSAssert(pigeonResult.volume != nil, @""); return pigeonResult; } - (NSDictionary *)toMap { return [NSDictionary dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), @"textureId", (self.volume ? self.volume : [NSNull null]), @"volume", nil]; } @end @implementation FLTPlaybackSpeedMessage + (instancetype)makeWithTextureId:(NSNumber *)textureId speed:(NSNumber *)speed { FLTPlaybackSpeedMessage *pigeonResult = [[FLTPlaybackSpeedMessage alloc] init]; pigeonResult.textureId = textureId; pigeonResult.speed = speed; return pigeonResult; } + (FLTPlaybackSpeedMessage *)fromMap:(NSDictionary *)dict { FLTPlaybackSpeedMessage *pigeonResult = [[FLTPlaybackSpeedMessage alloc] init]; pigeonResult.textureId = GetNullableObject(dict, @"textureId"); NSAssert(pigeonResult.textureId != nil, @""); pigeonResult.speed = GetNullableObject(dict, @"speed"); NSAssert(pigeonResult.speed != nil, @""); return pigeonResult; } - (NSDictionary *)toMap { return [NSDictionary dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), @"textureId", (self.speed ? self.speed : [NSNull null]), @"speed", nil]; } @end @implementation FLTPositionMessage + (instancetype)makeWithTextureId:(NSNumber *)textureId position:(NSNumber *)position { FLTPositionMessage *pigeonResult = [[FLTPositionMessage alloc] init]; pigeonResult.textureId = textureId; pigeonResult.position = position; return pigeonResult; } + (FLTPositionMessage *)fromMap:(NSDictionary *)dict { FLTPositionMessage *pigeonResult = [[FLTPositionMessage alloc] init]; pigeonResult.textureId = GetNullableObject(dict, @"textureId"); NSAssert(pigeonResult.textureId != nil, @""); pigeonResult.position = GetNullableObject(dict, @"position"); NSAssert(pigeonResult.position != nil, @""); return pigeonResult; } - (NSDictionary *)toMap { return [NSDictionary dictionaryWithObjectsAndKeys:(self.textureId ? self.textureId : [NSNull null]), @"textureId", (self.position ? self.position : [NSNull null]), @"position", nil]; } @end @implementation FLTCreateMessage + (instancetype)makeWithAsset:(nullable NSString *)asset uri:(nullable NSString *)uri packageName:(nullable NSString *)packageName formatHint:(nullable NSString *)formatHint httpHeaders:(NSDictionary *)httpHeaders { FLTCreateMessage *pigeonResult = [[FLTCreateMessage alloc] init]; pigeonResult.asset = asset; pigeonResult.uri = uri; pigeonResult.packageName = packageName; pigeonResult.formatHint = formatHint; pigeonResult.httpHeaders = httpHeaders; return pigeonResult; } + (FLTCreateMessage *)fromMap:(NSDictionary *)dict { FLTCreateMessage *pigeonResult = [[FLTCreateMessage alloc] init]; pigeonResult.asset = GetNullableObject(dict, @"asset"); pigeonResult.uri = GetNullableObject(dict, @"uri"); pigeonResult.packageName = GetNullableObject(dict, @"packageName"); pigeonResult.formatHint = GetNullableObject(dict, @"formatHint"); pigeonResult.httpHeaders = GetNullableObject(dict, @"httpHeaders"); NSAssert(pigeonResult.httpHeaders != nil, @""); return pigeonResult; } - (NSDictionary *)toMap { return [NSDictionary dictionaryWithObjectsAndKeys:(self.asset ? self.asset : [NSNull null]), @"asset", (self.uri ? self.uri : [NSNull null]), @"uri", (self.packageName ? self.packageName : [NSNull null]), @"packageName", (self.formatHint ? self.formatHint : [NSNull null]), @"formatHint", (self.httpHeaders ? self.httpHeaders : [NSNull null]), @"httpHeaders", nil]; } @end @implementation FLTMixWithOthersMessage + (instancetype)makeWithMixWithOthers:(NSNumber *)mixWithOthers { FLTMixWithOthersMessage *pigeonResult = [[FLTMixWithOthersMessage alloc] init]; pigeonResult.mixWithOthers = mixWithOthers; return pigeonResult; } + (FLTMixWithOthersMessage *)fromMap:(NSDictionary *)dict { FLTMixWithOthersMessage *pigeonResult = [[FLTMixWithOthersMessage alloc] init]; pigeonResult.mixWithOthers = GetNullableObject(dict, @"mixWithOthers"); NSAssert(pigeonResult.mixWithOthers != nil, @""); return pigeonResult; } - (NSDictionary *)toMap { return [NSDictionary dictionaryWithObjectsAndKeys:(self.mixWithOthers ? self.mixWithOthers : [NSNull null]), @"mixWithOthers", nil]; } @end @interface FLTAVFoundationVideoPlayerApiCodecReader : FlutterStandardReader @end @implementation FLTAVFoundationVideoPlayerApiCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { case 128: return [FLTCreateMessage fromMap:[self readValue]]; case 129: return [FLTLoopingMessage fromMap:[self readValue]]; case 130: return [FLTMixWithOthersMessage fromMap:[self readValue]]; case 131: return [FLTPlaybackSpeedMessage fromMap:[self readValue]]; case 132: return [FLTPositionMessage fromMap:[self readValue]]; case 133: return [FLTTextureMessage fromMap:[self readValue]]; case 134: return [FLTVolumeMessage fromMap:[self readValue]]; default: return [super readValueOfType:type]; } } @end @interface FLTAVFoundationVideoPlayerApiCodecWriter : FlutterStandardWriter @end @implementation FLTAVFoundationVideoPlayerApiCodecWriter - (void)writeValue:(id)value { if ([value isKindOfClass:[FLTCreateMessage class]]) { [self writeByte:128]; [self writeValue:[value toMap]]; } else if ([value isKindOfClass:[FLTLoopingMessage class]]) { [self writeByte:129]; [self writeValue:[value toMap]]; } else if ([value isKindOfClass:[FLTMixWithOthersMessage class]]) { [self writeByte:130]; [self writeValue:[value toMap]]; } else if ([value isKindOfClass:[FLTPlaybackSpeedMessage class]]) { [self writeByte:131]; [self writeValue:[value toMap]]; } else if ([value isKindOfClass:[FLTPositionMessage class]]) { [self writeByte:132]; [self writeValue:[value toMap]]; } else if ([value isKindOfClass:[FLTTextureMessage class]]) { [self writeByte:133]; [self writeValue:[value toMap]]; } else if ([value isKindOfClass:[FLTVolumeMessage class]]) { [self writeByte:134]; [self writeValue:[value toMap]]; } else { [super writeValue:value]; } } @end @interface FLTAVFoundationVideoPlayerApiCodecReaderWriter : FlutterStandardReaderWriter @end @implementation FLTAVFoundationVideoPlayerApiCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { return [[FLTAVFoundationVideoPlayerApiCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { return [[FLTAVFoundationVideoPlayerApiCodecReader alloc] initWithData:data]; } @end NSObject *FLTAVFoundationVideoPlayerApiGetCodec() { static dispatch_once_t sPred = 0; static FlutterStandardMessageCodec *sSharedObject = nil; dispatch_once(&sPred, ^{ FLTAVFoundationVideoPlayerApiCodecReaderWriter *readerWriter = [[FLTAVFoundationVideoPlayerApiCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } void FLTAVFoundationVideoPlayerApiSetup(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.AVFoundationVideoPlayerApi.initialize" binaryMessenger:binaryMessenger codec:FLTAVFoundationVideoPlayerApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(initialize:)], @"FLTAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(initialize:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { FlutterError *error; [api initialize:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.AVFoundationVideoPlayerApi.create" binaryMessenger:binaryMessenger codec:FLTAVFoundationVideoPlayerApiGetCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(create:error:)], @"FLTAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(create:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FLTCreateMessage *arg_msg = GetNullableObjectAtIndex(args, 0); FlutterError *error; FLTTextureMessage *output = [api create:arg_msg error:&error]; callback(wrapResult(output, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.AVFoundationVideoPlayerApi.dispose" binaryMessenger:binaryMessenger codec:FLTAVFoundationVideoPlayerApiGetCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(dispose:error:)], @"FLTAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(dispose:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FLTTextureMessage *arg_msg = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api dispose:arg_msg error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.AVFoundationVideoPlayerApi.setLooping" binaryMessenger:binaryMessenger codec:FLTAVFoundationVideoPlayerApiGetCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(setLooping:error:)], @"FLTAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(setLooping:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FLTLoopingMessage *arg_msg = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api setLooping:arg_msg error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.AVFoundationVideoPlayerApi.setVolume" binaryMessenger:binaryMessenger codec:FLTAVFoundationVideoPlayerApiGetCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(setVolume:error:)], @"FLTAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(setVolume:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FLTVolumeMessage *arg_msg = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api setVolume:arg_msg error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.AVFoundationVideoPlayerApi.setPlaybackSpeed" binaryMessenger:binaryMessenger codec:FLTAVFoundationVideoPlayerApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(setPlaybackSpeed:error:)], @"FLTAVFoundationVideoPlayerApi api (%@) doesn't respond to " @"@selector(setPlaybackSpeed:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FLTPlaybackSpeedMessage *arg_msg = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api setPlaybackSpeed:arg_msg error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.AVFoundationVideoPlayerApi.play" binaryMessenger:binaryMessenger codec:FLTAVFoundationVideoPlayerApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(play:error:)], @"FLTAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(play:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FLTTextureMessage *arg_msg = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api play:arg_msg error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.AVFoundationVideoPlayerApi.position" binaryMessenger:binaryMessenger codec:FLTAVFoundationVideoPlayerApiGetCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(position:error:)], @"FLTAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(position:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FLTTextureMessage *arg_msg = GetNullableObjectAtIndex(args, 0); FlutterError *error; FLTPositionMessage *output = [api position:arg_msg error:&error]; callback(wrapResult(output, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.AVFoundationVideoPlayerApi.seekTo" binaryMessenger:binaryMessenger codec:FLTAVFoundationVideoPlayerApiGetCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(seekTo:error:)], @"FLTAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(seekTo:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FLTPositionMessage *arg_msg = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api seekTo:arg_msg error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.AVFoundationVideoPlayerApi.pause" binaryMessenger:binaryMessenger codec:FLTAVFoundationVideoPlayerApiGetCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(pause:error:)], @"FLTAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(pause:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FLTTextureMessage *arg_msg = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api pause:arg_msg error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.AVFoundationVideoPlayerApi.setMixWithOthers" binaryMessenger:binaryMessenger codec:FLTAVFoundationVideoPlayerApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(setMixWithOthers:error:)], @"FLTAVFoundationVideoPlayerApi api (%@) doesn't respond to " @"@selector(setMixWithOthers:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FLTMixWithOthersMessage *arg_msg = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api setMixWithOthers:arg_msg error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } } ================================================ FILE: packages/video_player/video_player_avfoundation/ios/video_player_avfoundation.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| s.name = 'video_player_avfoundation' s.version = '0.0.1' s.summary = 'Flutter Video Player' s.description = <<-DESC A Flutter plugin for playing back video on a Widget surface. 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/video_player/video_player_avfoundation' } s.documentation_url = 'https://pub.dev/packages/video_player' 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 ================================================ FILE: packages/video_player/video_player_avfoundation/lib/src/avfoundation_video_player.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; import 'messages.g.dart'; /// An iOS implementation of [VideoPlayerPlatform] that uses the /// Pigeon-generated [VideoPlayerApi]. class AVFoundationVideoPlayer extends VideoPlayerPlatform { final AVFoundationVideoPlayerApi _api = AVFoundationVideoPlayerApi(); /// Registers this class as the default instance of [VideoPlayerPlatform]. static void registerWith() { VideoPlayerPlatform.instance = AVFoundationVideoPlayer(); } @override Future init() { return _api.initialize(); } @override Future dispose(int textureId) { return _api.dispose(TextureMessage(textureId: textureId)); } @override Future create(DataSource dataSource) async { String? asset; String? packageName; String? uri; String? formatHint; Map httpHeaders = {}; switch (dataSource.sourceType) { case DataSourceType.asset: asset = dataSource.asset; packageName = dataSource.package; break; case DataSourceType.network: uri = dataSource.uri; formatHint = _videoFormatStringMap[dataSource.formatHint]; httpHeaders = dataSource.httpHeaders; break; case DataSourceType.file: uri = dataSource.uri; break; case DataSourceType.contentUri: uri = dataSource.uri; break; } final CreateMessage message = CreateMessage( asset: asset, packageName: packageName, uri: uri, httpHeaders: httpHeaders, formatHint: formatHint, ); final TextureMessage response = await _api.create(message); return response.textureId; } @override Future setLooping(int textureId, bool looping) { return _api.setLooping(LoopingMessage( textureId: textureId, isLooping: looping, )); } @override Future play(int textureId) { return _api.play(TextureMessage(textureId: textureId)); } @override Future pause(int textureId) { return _api.pause(TextureMessage(textureId: textureId)); } @override Future setVolume(int textureId, double volume) { return _api.setVolume(VolumeMessage( textureId: textureId, volume: volume, )); } @override Future setPlaybackSpeed(int textureId, double speed) { assert(speed > 0); return _api.setPlaybackSpeed(PlaybackSpeedMessage( textureId: textureId, speed: speed, )); } @override Future seekTo(int textureId, Duration position) { return _api.seekTo(PositionMessage( textureId: textureId, position: position.inMilliseconds, )); } @override Future getPosition(int textureId) async { final PositionMessage response = await _api.position(TextureMessage(textureId: textureId)); return Duration(milliseconds: response.position); } @override Stream videoEventsFor(int textureId) { return _eventChannelFor(textureId) .receiveBroadcastStream() .map((dynamic event) { final Map map = event as Map; switch (map['event']) { case 'initialized': return VideoEvent( eventType: VideoEventType.initialized, duration: Duration(milliseconds: map['duration'] as int), size: Size((map['width'] as num?)?.toDouble() ?? 0.0, (map['height'] as num?)?.toDouble() ?? 0.0), ); case 'completed': return VideoEvent( eventType: VideoEventType.completed, ); case 'bufferingUpdate': final List values = map['values'] as List; return VideoEvent( buffered: values.map(_toDurationRange).toList(), eventType: VideoEventType.bufferingUpdate, ); case 'bufferingStart': return VideoEvent(eventType: VideoEventType.bufferingStart); case 'bufferingEnd': return VideoEvent(eventType: VideoEventType.bufferingEnd); default: return VideoEvent(eventType: VideoEventType.unknown); } }); } @override Widget buildView(int textureId) { return Texture(textureId: textureId); } @override Future setMixWithOthers(bool mixWithOthers) { return _api .setMixWithOthers(MixWithOthersMessage(mixWithOthers: mixWithOthers)); } EventChannel _eventChannelFor(int textureId) { return EventChannel('flutter.io/videoPlayer/videoEvents$textureId'); } static const Map _videoFormatStringMap = { VideoFormat.ss: 'ss', VideoFormat.hls: 'hls', VideoFormat.dash: 'dash', VideoFormat.other: 'other', }; DurationRange _toDurationRange(dynamic value) { final List pair = value as List; return DurationRange( Duration(milliseconds: pair[0] as int), Duration(milliseconds: pair[1] as int), ); } } ================================================ FILE: packages/video_player/video_player_avfoundation/lib/src/messages.g.dart ================================================ // Copyright 2013 The Flutter 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 (v2.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 // @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 TextureMessage { TextureMessage({ required this.textureId, }); int textureId; Object encode() { final Map pigeonMap = {}; pigeonMap['textureId'] = textureId; return pigeonMap; } static TextureMessage decode(Object message) { final Map pigeonMap = message as Map; return TextureMessage( textureId: pigeonMap['textureId']! as int, ); } } class LoopingMessage { LoopingMessage({ required this.textureId, required this.isLooping, }); int textureId; bool isLooping; Object encode() { final Map pigeonMap = {}; pigeonMap['textureId'] = textureId; pigeonMap['isLooping'] = isLooping; return pigeonMap; } static LoopingMessage decode(Object message) { final Map pigeonMap = message as Map; return LoopingMessage( textureId: pigeonMap['textureId']! as int, isLooping: pigeonMap['isLooping']! as bool, ); } } class VolumeMessage { VolumeMessage({ required this.textureId, required this.volume, }); int textureId; double volume; Object encode() { final Map pigeonMap = {}; pigeonMap['textureId'] = textureId; pigeonMap['volume'] = volume; return pigeonMap; } static VolumeMessage decode(Object message) { final Map pigeonMap = message as Map; return VolumeMessage( textureId: pigeonMap['textureId']! as int, volume: pigeonMap['volume']! as double, ); } } class PlaybackSpeedMessage { PlaybackSpeedMessage({ required this.textureId, required this.speed, }); int textureId; double speed; Object encode() { final Map pigeonMap = {}; pigeonMap['textureId'] = textureId; pigeonMap['speed'] = speed; return pigeonMap; } static PlaybackSpeedMessage decode(Object message) { final Map pigeonMap = message as Map; return PlaybackSpeedMessage( textureId: pigeonMap['textureId']! as int, speed: pigeonMap['speed']! as double, ); } } class PositionMessage { PositionMessage({ required this.textureId, required this.position, }); int textureId; int position; Object encode() { final Map pigeonMap = {}; pigeonMap['textureId'] = textureId; pigeonMap['position'] = position; return pigeonMap; } static PositionMessage decode(Object message) { final Map pigeonMap = message as Map; return PositionMessage( textureId: pigeonMap['textureId']! as int, position: pigeonMap['position']! as int, ); } } class CreateMessage { CreateMessage({ this.asset, this.uri, this.packageName, this.formatHint, required this.httpHeaders, }); String? asset; String? uri; String? packageName; String? formatHint; Map httpHeaders; Object encode() { final Map pigeonMap = {}; pigeonMap['asset'] = asset; pigeonMap['uri'] = uri; pigeonMap['packageName'] = packageName; pigeonMap['formatHint'] = formatHint; pigeonMap['httpHeaders'] = httpHeaders; return pigeonMap; } static CreateMessage decode(Object message) { final Map pigeonMap = message as Map; return CreateMessage( asset: pigeonMap['asset'] as String?, uri: pigeonMap['uri'] as String?, packageName: pigeonMap['packageName'] as String?, formatHint: pigeonMap['formatHint'] as String?, httpHeaders: (pigeonMap['httpHeaders'] as Map?)! .cast(), ); } } class MixWithOthersMessage { MixWithOthersMessage({ required this.mixWithOthers, }); bool mixWithOthers; Object encode() { final Map pigeonMap = {}; pigeonMap['mixWithOthers'] = mixWithOthers; return pigeonMap; } static MixWithOthersMessage decode(Object message) { final Map pigeonMap = message as Map; return MixWithOthersMessage( mixWithOthers: pigeonMap['mixWithOthers']! as bool, ); } } class _AVFoundationVideoPlayerApiCodec extends StandardMessageCodec { const _AVFoundationVideoPlayerApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is CreateMessage) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is LoopingMessage) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else if (value is MixWithOthersMessage) { buffer.putUint8(130); writeValue(buffer, value.encode()); } else if (value is PlaybackSpeedMessage) { buffer.putUint8(131); writeValue(buffer, value.encode()); } else if (value is PositionMessage) { buffer.putUint8(132); writeValue(buffer, value.encode()); } else if (value is TextureMessage) { buffer.putUint8(133); writeValue(buffer, value.encode()); } else if (value is VolumeMessage) { buffer.putUint8(134); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return CreateMessage.decode(readValue(buffer)!); case 129: return LoopingMessage.decode(readValue(buffer)!); case 130: return MixWithOthersMessage.decode(readValue(buffer)!); case 131: return PlaybackSpeedMessage.decode(readValue(buffer)!); case 132: return PositionMessage.decode(readValue(buffer)!); case 133: return TextureMessage.decode(readValue(buffer)!); case 134: return VolumeMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } class AVFoundationVideoPlayerApi { /// Constructor for [AVFoundationVideoPlayerApi]. 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. AVFoundationVideoPlayerApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = _AVFoundationVideoPlayerApiCodec(); Future initialize() async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.initialize', 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; } } Future create(CreateMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.create', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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 TextureMessage?)!; } } Future dispose(TextureMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.dispose', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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 setLooping(LoopingMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.setLooping', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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 setVolume(VolumeMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.setVolume', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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 setPlaybackSpeed(PlaybackSpeedMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.setPlaybackSpeed', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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 play(TextureMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.play', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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 position(TextureMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.position', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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 PositionMessage?)!; } } Future seekTo(PositionMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.seekTo', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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 pause(TextureMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.pause', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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 setMixWithOthers(MixWithOthersMessage arg_msg) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.setMixWithOthers', codec, binaryMessenger: _binaryMessenger); final Map? replyMap = await channel.send([arg_msg]) 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; } } } ================================================ FILE: packages/video_player/video_player_avfoundation/lib/video_player_avfoundation.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'src/avfoundation_video_player.dart'; ================================================ FILE: packages/video_player/video_player_avfoundation/pigeons/copyright.txt ================================================ Copyright 2013 The Flutter Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. ================================================ FILE: packages/video_player/video_player_avfoundation/pigeons/messages.dart ================================================ // Copyright 2013 The Flutter 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', dartTestOut: 'test/test_api.g.dart', objcHeaderOut: 'ios/Classes/messages.g.h', objcSourceOut: 'ios/Classes/messages.g.m', objcOptions: ObjcOptions( prefix: 'FLT', ), copyrightHeader: 'pigeons/copyright.txt', )) class TextureMessage { TextureMessage(this.textureId); int textureId; } class LoopingMessage { LoopingMessage(this.textureId, this.isLooping); int textureId; bool isLooping; } class VolumeMessage { VolumeMessage(this.textureId, this.volume); int textureId; double volume; } class PlaybackSpeedMessage { PlaybackSpeedMessage(this.textureId, this.speed); int textureId; double speed; } class PositionMessage { PositionMessage(this.textureId, this.position); int textureId; int position; } class CreateMessage { CreateMessage({required this.httpHeaders}); String? asset; String? uri; String? packageName; String? formatHint; Map httpHeaders; } class MixWithOthersMessage { MixWithOthersMessage(this.mixWithOthers); bool mixWithOthers; } @HostApi(dartHostTestHandler: 'TestHostVideoPlayerApi') abstract class AVFoundationVideoPlayerApi { @ObjCSelector('initialize') void initialize(); @ObjCSelector('create:') TextureMessage create(CreateMessage msg); @ObjCSelector('dispose:') void dispose(TextureMessage msg); @ObjCSelector('setLooping:') void setLooping(LoopingMessage msg); @ObjCSelector('setVolume:') void setVolume(VolumeMessage msg); @ObjCSelector('setPlaybackSpeed:') void setPlaybackSpeed(PlaybackSpeedMessage msg); @ObjCSelector('play:') void play(TextureMessage msg); @ObjCSelector('position:') PositionMessage position(TextureMessage msg); @ObjCSelector('seekTo:') void seekTo(PositionMessage msg); @ObjCSelector('pause:') void pause(TextureMessage msg); @ObjCSelector('setMixWithOthers:') void setMixWithOthers(MixWithOthersMessage msg); } ================================================ FILE: packages/video_player/video_player_avfoundation/pubspec.yaml ================================================ name: video_player_avfoundation description: iOS implementation of the video_player plugin. repository: https://github.com/flutter/plugins/tree/main/packages/video_player/video_player_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 version: 2.3.8 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: video_player platforms: ios: dartPluginClass: AVFoundationVideoPlayer pluginClass: FLTVideoPlayerPlugin dependencies: flutter: sdk: flutter video_player_platform_interface: ">=4.2.0 <7.0.0" dev_dependencies: flutter_test: sdk: flutter pigeon: ^2.0.1 ================================================ FILE: packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#106316) // ignore: unnecessary_import import 'dart:ui'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; 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.g.dart'; class _ApiLogger implements TestHostVideoPlayerApi { final List log = []; TextureMessage? textureMessage; CreateMessage? createMessage; PositionMessage? positionMessage; LoopingMessage? loopingMessage; VolumeMessage? volumeMessage; PlaybackSpeedMessage? playbackSpeedMessage; MixWithOthersMessage? mixWithOthersMessage; @override TextureMessage create(CreateMessage arg) { log.add('create'); createMessage = arg; return TextureMessage(textureId: 3); } @override void dispose(TextureMessage arg) { log.add('dispose'); textureMessage = arg; } @override void initialize() { log.add('init'); } @override void pause(TextureMessage arg) { log.add('pause'); textureMessage = arg; } @override void play(TextureMessage arg) { log.add('play'); textureMessage = arg; } @override void setMixWithOthers(MixWithOthersMessage arg) { log.add('setMixWithOthers'); mixWithOthersMessage = arg; } @override PositionMessage position(TextureMessage arg) { log.add('position'); textureMessage = arg; return PositionMessage(textureId: arg.textureId, position: 234); } @override void seekTo(PositionMessage arg) { log.add('seekTo'); positionMessage = arg; } @override void setLooping(LoopingMessage arg) { log.add('setLooping'); loopingMessage = arg; } @override void setVolume(VolumeMessage arg) { log.add('setVolume'); volumeMessage = arg; } @override void setPlaybackSpeed(PlaybackSpeedMessage arg) { log.add('setPlaybackSpeed'); playbackSpeedMessage = arg; } } void main() { TestWidgetsFlutterBinding.ensureInitialized(); test('registration', () async { AVFoundationVideoPlayer.registerWith(); expect(VideoPlayerPlatform.instance, isA()); }); group('$AVFoundationVideoPlayer', () { final AVFoundationVideoPlayer player = AVFoundationVideoPlayer(); late _ApiLogger log; setUp(() { log = _ApiLogger(); TestHostVideoPlayerApi.setup(log); }); test('init', () async { await player.init(); expect( log.log.last, 'init', ); }); test('dispose', () async { await player.dispose(1); expect(log.log.last, 'dispose'); expect(log.textureMessage?.textureId, 1); }); test('create with asset', () async { final int? textureId = await player.create(DataSource( sourceType: DataSourceType.asset, asset: 'someAsset', package: 'somePackage', )); expect(log.log.last, 'create'); expect(log.createMessage?.asset, 'someAsset'); expect(log.createMessage?.packageName, 'somePackage'); expect(textureId, 3); }); test('create with network', () async { final int? textureId = await player.create(DataSource( sourceType: DataSourceType.network, uri: 'someUri', formatHint: VideoFormat.dash, )); expect(log.log.last, 'create'); expect(log.createMessage?.asset, null); expect(log.createMessage?.uri, 'someUri'); expect(log.createMessage?.packageName, null); expect(log.createMessage?.formatHint, 'dash'); expect(log.createMessage?.httpHeaders, {}); expect(textureId, 3); }); test('create with network (some headers)', () async { final int? textureId = await player.create(DataSource( sourceType: DataSourceType.network, uri: 'someUri', httpHeaders: {'Authorization': 'Bearer token'}, )); expect(log.log.last, 'create'); expect(log.createMessage?.asset, null); expect(log.createMessage?.uri, 'someUri'); expect(log.createMessage?.packageName, null); expect(log.createMessage?.formatHint, null); expect(log.createMessage?.httpHeaders, {'Authorization': 'Bearer token'}); expect(textureId, 3); }); test('create with file', () async { final int? textureId = await player.create(DataSource( sourceType: DataSourceType.file, uri: 'someUri', )); expect(log.log.last, 'create'); expect(log.createMessage?.uri, 'someUri'); expect(textureId, 3); }); test('setLooping', () async { await player.setLooping(1, true); expect(log.log.last, 'setLooping'); expect(log.loopingMessage?.textureId, 1); expect(log.loopingMessage?.isLooping, true); }); test('play', () async { await player.play(1); expect(log.log.last, 'play'); expect(log.textureMessage?.textureId, 1); }); test('pause', () async { await player.pause(1); expect(log.log.last, 'pause'); expect(log.textureMessage?.textureId, 1); }); test('setMixWithOthers', () async { await player.setMixWithOthers(true); expect(log.log.last, 'setMixWithOthers'); expect(log.mixWithOthersMessage?.mixWithOthers, true); await player.setMixWithOthers(false); expect(log.log.last, 'setMixWithOthers'); expect(log.mixWithOthersMessage?.mixWithOthers, false); }); test('setVolume', () async { await player.setVolume(1, 0.7); expect(log.log.last, 'setVolume'); expect(log.volumeMessage?.textureId, 1); expect(log.volumeMessage?.volume, 0.7); }); test('setPlaybackSpeed', () async { await player.setPlaybackSpeed(1, 1.5); expect(log.log.last, 'setPlaybackSpeed'); expect(log.playbackSpeedMessage?.textureId, 1); expect(log.playbackSpeedMessage?.speed, 1.5); }); test('seekTo', () async { await player.seekTo(1, const Duration(milliseconds: 12345)); expect(log.log.last, 'seekTo'); expect(log.positionMessage?.textureId, 1); expect(log.positionMessage?.position, 12345); }); test('getPosition', () async { final Duration position = await player.getPosition(1); expect(log.log.last, 'position'); expect(log.textureMessage?.textureId, 1); expect(position, const Duration(milliseconds: 234)); }); test('videoEventsFor', () async { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMessageHandler( 'flutter.io/videoPlayer/videoEvents123', (ByteData? message) async { final MethodCall methodCall = const StandardMethodCodec().decodeMethodCall(message); if (methodCall.method == 'listen') { await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() .encodeSuccessEnvelope({ 'event': 'initialized', 'duration': 98765, 'width': 1920, 'height': 1080, }), (ByteData? data) {}); await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() .encodeSuccessEnvelope({ 'event': 'completed', }), (ByteData? data) {}); await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() .encodeSuccessEnvelope({ 'event': 'bufferingUpdate', 'values': >[ [0, 1234], [1235, 4000], ], }), (ByteData? data) {}); await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() .encodeSuccessEnvelope({ 'event': 'bufferingStart', }), (ByteData? data) {}); await _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .handlePlatformMessage( 'flutter.io/videoPlayer/videoEvents123', const StandardMethodCodec() .encodeSuccessEnvelope({ 'event': 'bufferingEnd', }), (ByteData? data) {}); return const StandardMethodCodec().encodeSuccessEnvelope(null); } else if (methodCall.method == 'cancel') { return const StandardMethodCodec().encodeSuccessEnvelope(null); } else { fail('Expected listen or cancel'); } }, ); expect( player.videoEventsFor(123), emitsInOrder([ VideoEvent( eventType: VideoEventType.initialized, duration: const Duration(milliseconds: 98765), size: const Size(1920, 1080), ), VideoEvent(eventType: VideoEventType.completed), VideoEvent( eventType: VideoEventType.bufferingUpdate, buffered: [ DurationRange( Duration.zero, const Duration(milliseconds: 1234), ), DurationRange( const Duration(milliseconds: 1235), const Duration(milliseconds: 4000), ), ]), VideoEvent(eventType: VideoEventType.bufferingStart), VideoEvent(eventType: VideoEventType.bufferingEnd), ])); }); }); } /// 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; ================================================ FILE: packages/video_player/video_player_avfoundation/test/test_api.g.dart ================================================ // Copyright 2013 The Flutter 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 (v2.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 // ignore_for_file: avoid_relative_lib_imports // @dart = 2.12 import 'dart:async'; import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#106316) // ignore: unnecessary_import import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; // TODO(gaaclarke): The following output had to be tweaked from a relative path to a uri. import 'package:video_player_avfoundation/src/messages.g.dart'; class _TestHostVideoPlayerApiCodec extends StandardMessageCodec { const _TestHostVideoPlayerApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is CreateMessage) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is LoopingMessage) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else if (value is MixWithOthersMessage) { buffer.putUint8(130); writeValue(buffer, value.encode()); } else if (value is PlaybackSpeedMessage) { buffer.putUint8(131); writeValue(buffer, value.encode()); } else if (value is PositionMessage) { buffer.putUint8(132); writeValue(buffer, value.encode()); } else if (value is TextureMessage) { buffer.putUint8(133); writeValue(buffer, value.encode()); } else if (value is VolumeMessage) { buffer.putUint8(134); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return CreateMessage.decode(readValue(buffer)!); case 129: return LoopingMessage.decode(readValue(buffer)!); case 130: return MixWithOthersMessage.decode(readValue(buffer)!); case 131: return PlaybackSpeedMessage.decode(readValue(buffer)!); case 132: return PositionMessage.decode(readValue(buffer)!); case 133: return TextureMessage.decode(readValue(buffer)!); case 134: return VolumeMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } abstract class TestHostVideoPlayerApi { static const MessageCodec codec = _TestHostVideoPlayerApiCodec(); void initialize(); TextureMessage create(CreateMessage msg); void dispose(TextureMessage msg); void setLooping(LoopingMessage msg); void setVolume(VolumeMessage msg); void setPlaybackSpeed(PlaybackSpeedMessage msg); void play(TextureMessage msg); PositionMessage position(TextureMessage msg); void seekTo(PositionMessage msg); void pause(TextureMessage msg); void setMixWithOthers(MixWithOthersMessage msg); static void setup(TestHostVideoPlayerApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.initialize', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { // ignore message api.initialize(); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.create was null.'); final List args = (message as List?)!; final CreateMessage? arg_msg = (args[0] as CreateMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.create was null, expected non-null CreateMessage.'); final TextureMessage output = api.create(arg_msg!); return {'result': output}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.dispose', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.dispose was null.'); final List args = (message as List?)!; final TextureMessage? arg_msg = (args[0] as TextureMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.dispose was null, expected non-null TextureMessage.'); api.dispose(arg_msg!); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.setLooping', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.setLooping was null.'); final List args = (message as List?)!; final LoopingMessage? arg_msg = (args[0] as LoopingMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.setLooping was null, expected non-null LoopingMessage.'); api.setLooping(arg_msg!); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.setVolume', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.setVolume was null.'); final List args = (message as List?)!; final VolumeMessage? arg_msg = (args[0] as VolumeMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.setVolume was null, expected non-null VolumeMessage.'); api.setVolume(arg_msg!); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.setPlaybackSpeed', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.setPlaybackSpeed was null.'); final List args = (message as List?)!; final PlaybackSpeedMessage? arg_msg = (args[0] as PlaybackSpeedMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.setPlaybackSpeed was null, expected non-null PlaybackSpeedMessage.'); api.setPlaybackSpeed(arg_msg!); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.play', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.play was null.'); final List args = (message as List?)!; final TextureMessage? arg_msg = (args[0] as TextureMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.play was null, expected non-null TextureMessage.'); api.play(arg_msg!); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.position', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.position was null.'); final List args = (message as List?)!; final TextureMessage? arg_msg = (args[0] as TextureMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.position was null, expected non-null TextureMessage.'); final PositionMessage output = api.position(arg_msg!); return {'result': output}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.seekTo', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.seekTo was null.'); final List args = (message as List?)!; final PositionMessage? arg_msg = (args[0] as PositionMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.seekTo was null, expected non-null PositionMessage.'); api.seekTo(arg_msg!); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.pause', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.pause was null.'); final List args = (message as List?)!; final TextureMessage? arg_msg = (args[0] as TextureMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.pause was null, expected non-null TextureMessage.'); api.pause(arg_msg!); return {}; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.AVFoundationVideoPlayerApi.setMixWithOthers', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.setMixWithOthers was null.'); final List args = (message as List?)!; final MixWithOthersMessage? arg_msg = (args[0] as MixWithOthersMessage?); assert(arg_msg != null, 'Argument for dev.flutter.pigeon.AVFoundationVideoPlayerApi.setMixWithOthers was null, expected non-null MixWithOthersMessage.'); api.setMixWithOthers(arg_msg!); return {}; }); } } } } ================================================ FILE: packages/video_player/video_player_platform_interface/AUTHORS ================================================ # 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> ================================================ FILE: packages/video_player/video_player_platform_interface/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 6.0.1 * Fixes comment describing file URI construction. ## 6.0.0 * **BREAKING CHANGE**: Removes `MethodChannelVideoPlayer`. The default implementation is now only a placeholder with no functionality; implementations of `video_player` must include their own `VideoPlayerPlatform` Dart implementation. * Updates minimum Flutter version to 2.10. * Fixes violations of new analysis option use_named_constants. ## 5.1.4 * Fixes avoid_redundant_argument_values lint warnings and minor typos. * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/106316). ## 5.1.3 * Updates references to the obsolete master branch. * Removes unnecessary imports. ## 5.1.2 * Adopts `Object.hash`. * Removes obsolete dependency on `pedantic`. ## 5.1.1 * Adds `rotationCorrection` (for Android playing videos recorded in landscapeRight [#60327](https://github.com/flutter/flutter/issues/60327)). ## 5.1.0 * Adds `allowBackgroundPlayback` to `VideoPlayerOptions`. ## 5.0.2 * Adds the Pigeon definitions used to create the method channel implementation. * Internal code cleanup for stricter analysis options. ## 5.0.1 * Update to use the `verify` method introduced in platform_plugin_interface 2.1.0. ## 5.0.0 * **BREAKING CHANGES**: * Updates to extending `PlatformInterface`. Removes `isMock`, in favor of the now-standard `MockPlatformInterfaceMixin`. * Removes test.dart from the public interface. Tests in other packages should mock `VideoPlatformInterface` rather than the method channel. ## 4.2.0 * Add `contentUri` to `DataSourceType`. ## 4.1.0 * Add `httpHeaders` to `DataSource` ## 4.0.0 * **Breaking Changes**: * Migrate to null-safety * Update to latest Pigeon. This includes a breaking change to how the test logic is exposed. * Add note about the `mixWithOthers` option being ignored on the web. * Make DataSource's `uri` parameter nullable. * `messages.dart` sets Dart `2.12`. ## 3.0.0 * Version 3 only was published as nullsafety "previews". ## 2.2.1 * Update Flutter SDK constraint. ## 2.2.0 * Added option to set the video playback speed on the video controller. ## 2.1.1 * Fix mixWithOthers test channel. ## 2.1.0 * Add VideoPlayerOptions with audio mix mode ## 2.0.2 * Migrated tests to use pigeon correctly. ## 2.0.1 * Updated minimum Dart version. * Added class to help testing Pigeon communication. ## 2.0.0 * Migrated to [pigeon](https://pub.dev/packages/pigeon). ## 1.0.5 * Make the pedantic dev_dependency explicit. ## 1.0.4 * Remove the deprecated `author:` field from pubspec.yaml * Require Flutter SDK 1.10.0 or greater. ## 1.0.3 * Document public API. ## 1.0.2 * Fix unawaited futures in the tests. ## 1.0.1 * Return correct platform event type when buffering ## 1.0.0 * Initial release. ================================================ FILE: packages/video_player/video_player_platform_interface/LICENSE ================================================ 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. ================================================ FILE: packages/video_player/video_player_platform_interface/README.md ================================================ # video_player_platform_interface A common platform interface for the [`video_player`][1] plugin. This interface allows platform-specific implementations of the `video_player` plugin, as well as the plugin itself, to ensure they are supporting the same interface. # Usage To implement a new platform-specific implementation of `video_player`, extend [`VideoPlayerPlatform`][2] with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `VideoPlayerPlatform` by calling `VideoPlayerPlatform.instance = MyPlatformVideoPlayer()`. # Note on breaking changes Strongly prefer non-breaking changes (such as adding a method to the interface) over breaking changes for this package. See https://flutter.dev/go/platform-interface-breaking-changes for a discussion on why a less-clean interface is preferable to a breaking change. [1]: ../video_player [2]: lib/video_player_platform_interface.dart ================================================ FILE: packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; /// The interface that implementations of video_player must implement. /// /// Platform implementations should extend this class rather than implement it as `video_player` /// does not consider newly added methods to be breaking changes. Extending this class /// (using `extends`) ensures that the subclass will get the default implementation, while /// platform implementations that `implements` this interface will be broken by newly added /// [VideoPlayerPlatform] methods. abstract class VideoPlayerPlatform extends PlatformInterface { /// Constructs a VideoPlayerPlatform. VideoPlayerPlatform() : super(token: _token); static final Object _token = Object(); static VideoPlayerPlatform _instance = _PlaceholderImplementation(); /// The instance of [VideoPlayerPlatform] to use. /// /// Defaults to a placeholder that does not override any methods, and thus /// throws `UnimplementedError` in most cases. static VideoPlayerPlatform get instance => _instance; /// Platform-specific plugins should override this with their own /// platform-specific class that extends [VideoPlayerPlatform] when they /// register themselves. static set instance(VideoPlayerPlatform instance) { PlatformInterface.verify(instance, _token); _instance = instance; } /// Initializes the platform interface and disposes all existing players. /// /// This method is called when the plugin is first initialized /// and on every full restart. Future init() { throw UnimplementedError('init() has not been implemented.'); } /// Clears one video. Future dispose(int textureId) { throw UnimplementedError('dispose() has not been implemented.'); } /// Creates an instance of a video player and returns its textureId. Future create(DataSource dataSource) { throw UnimplementedError('create() has not been implemented.'); } /// Returns a Stream of [VideoEventType]s. Stream videoEventsFor(int textureId) { throw UnimplementedError('videoEventsFor() has not been implemented.'); } /// Sets the looping attribute of the video. Future setLooping(int textureId, bool looping) { throw UnimplementedError('setLooping() has not been implemented.'); } /// Starts the video playback. Future play(int textureId) { throw UnimplementedError('play() has not been implemented.'); } /// Stops the video playback. Future pause(int textureId) { throw UnimplementedError('pause() has not been implemented.'); } /// Sets the volume to a range between 0.0 and 1.0. Future setVolume(int textureId, double volume) { throw UnimplementedError('setVolume() has not been implemented.'); } /// Sets the video position to a [Duration] from the start. Future seekTo(int textureId, Duration position) { throw UnimplementedError('seekTo() has not been implemented.'); } /// Sets the playback speed to a [speed] value indicating the playback rate. Future setPlaybackSpeed(int textureId, double speed) { throw UnimplementedError('setPlaybackSpeed() has not been implemented.'); } /// Gets the video position as [Duration] from the start. Future getPosition(int textureId) { throw UnimplementedError('getPosition() has not been implemented.'); } /// Returns a widget displaying the video with a given textureID. Widget buildView(int textureId) { throw UnimplementedError('buildView() has not been implemented.'); } /// Sets the audio mode to mix with other sources Future setMixWithOthers(bool mixWithOthers) { throw UnimplementedError('setMixWithOthers() has not been implemented.'); } } class _PlaceholderImplementation extends VideoPlayerPlatform {} /// Description of the data source used to create an instance of /// the video player. class DataSource { /// Constructs an instance of [DataSource]. /// /// The [sourceType] is always required. /// /// The [uri] argument takes the form of `'https://example.com/video.mp4'` or /// `'file:///absolute/path/to/local/video.mp4`. /// /// The [formatHint] argument can be null. /// /// The [asset] argument takes the form of `'assets/video.mp4'`. /// /// The [package] argument must be non-null when the asset comes from a /// package and null otherwise. DataSource({ required this.sourceType, this.uri, this.formatHint, this.asset, this.package, this.httpHeaders = const {}, }); /// The way in which the video was originally loaded. /// /// This has nothing to do with the video's file type. It's just the place /// from which the video is fetched from. final DataSourceType sourceType; /// The URI to the video file. /// /// This will be in different formats depending on the [DataSourceType] of /// the original video. final String? uri; /// **Android only**. Will override the platform's generic file format /// detection with whatever is set here. final VideoFormat? formatHint; /// HTTP headers used for the request to the [uri]. /// Only for [DataSourceType.network] videos. /// Always empty for other video types. Map httpHeaders; /// The name of the asset. Only set for [DataSourceType.asset] videos. final String? asset; /// The package that the asset was loaded from. Only set for /// [DataSourceType.asset] videos. final String? package; } /// The way in which the video was originally loaded. /// /// This has nothing to do with the video's file type. It's just the place /// from which the video is fetched from. enum DataSourceType { /// The video was included in the app's asset files. asset, /// The video was downloaded from the internet. network, /// The video was loaded off of the local filesystem. file, /// The video is available via contentUri. Android only. contentUri, } /// The file format of the given video. enum VideoFormat { /// Dynamic Adaptive Streaming over HTTP, also known as MPEG-DASH. dash, /// HTTP Live Streaming. hls, /// Smooth Streaming. ss, /// Any format other than the other ones defined in this enum. other, } /// Event emitted from the platform implementation. @immutable class VideoEvent { /// Creates an instance of [VideoEvent]. /// /// The [eventType] argument is required. /// /// Depending on the [eventType], the [duration], [size], /// [rotationCorrection], and [buffered] arguments can be null. // TODO(stuartmorgan): Temporarily suppress warnings about not using const // in all of the other video player packages, fix this, and then update // the other packages to use const. // ignore: prefer_const_constructors_in_immutables VideoEvent({ required this.eventType, this.duration, this.size, this.rotationCorrection, this.buffered, }); /// The type of the event. final VideoEventType eventType; /// Duration of the video. /// /// Only used if [eventType] is [VideoEventType.initialized]. final Duration? duration; /// Size of the video. /// /// Only used if [eventType] is [VideoEventType.initialized]. final Size? size; /// Degrees to rotate the video (clockwise) so it is displayed correctly. /// /// Only used if [eventType] is [VideoEventType.initialized]. final int? rotationCorrection; /// Buffered parts of the video. /// /// Only used if [eventType] is [VideoEventType.bufferingUpdate]. final List? buffered; @override bool operator ==(Object other) { return identical(this, other) || other is VideoEvent && runtimeType == other.runtimeType && eventType == other.eventType && duration == other.duration && size == other.size && rotationCorrection == other.rotationCorrection && listEquals(buffered, other.buffered); } @override int get hashCode => Object.hash( eventType, duration, size, rotationCorrection, buffered, ); } /// Type of the event. /// /// Emitted by the platform implementation when the video is initialized or /// completed or to communicate buffering events. enum VideoEventType { /// The video has been initialized. initialized, /// The playback has ended. completed, /// Updated information on the buffering state. bufferingUpdate, /// The video started to buffer. bufferingStart, /// The video stopped to buffer. bufferingEnd, /// An unknown event has been received. unknown, } /// Describes a discrete segment of time within a video using a [start] and /// [end] [Duration]. @immutable class DurationRange { /// Trusts that the given [start] and [end] are actually in order. They should /// both be non-null. // TODO(stuartmorgan): Temporarily suppress warnings about not using const // in all of the other video player packages, fix this, and then update // the other packages to use const. // ignore: prefer_const_constructors_in_immutables DurationRange(this.start, this.end); /// The beginning of the segment described relative to the beginning of the /// entire video. Should be shorter than or equal to [end]. /// /// For example, if the entire video is 4 minutes long and the range is from /// 1:00-2:00, this should be a `Duration` of one minute. final Duration start; /// The end of the segment described as a duration relative to the beginning of /// the entire video. This is expected to be non-null and longer than or equal /// to [start]. /// /// For example, if the entire video is 4 minutes long and the range is from /// 1:00-2:00, this should be a `Duration` of two minutes. final Duration end; /// Assumes that [duration] is the total length of the video that this /// DurationRange is a segment form. It returns the percentage that [start] is /// through the entire video. /// /// For example, assume that the entire video is 4 minutes long. If [start] has /// a duration of one minute, this will return `0.25` since the DurationRange /// starts 25% of the way through the video's total length. double startFraction(Duration duration) { return start.inMilliseconds / duration.inMilliseconds; } /// Assumes that [duration] is the total length of the video that this /// DurationRange is a segment form. It returns the percentage that [start] is /// through the entire video. /// /// For example, assume that the entire video is 4 minutes long. If [end] has a /// duration of two minutes, this will return `0.5` since the DurationRange /// ends 50% of the way through the video's total length. double endFraction(Duration duration) { return end.inMilliseconds / duration.inMilliseconds; } @override String toString() => '${objectRuntimeType(this, 'DurationRange')}(start: $start, end: $end)'; @override bool operator ==(Object other) => identical(this, other) || other is DurationRange && runtimeType == other.runtimeType && start == other.start && end == other.end; @override int get hashCode => Object.hash(start, end); } /// [VideoPlayerOptions] can be optionally used to set additional player settings @immutable class VideoPlayerOptions { /// set additional optional player settings // TODO(stuartmorgan): Temporarily suppress warnings about not using const // in all of the other video player packages, fix this, and then update // the other packages to use const. // ignore: prefer_const_constructors_in_immutables VideoPlayerOptions({ this.mixWithOthers = false, this.allowBackgroundPlayback = false, }); /// Set this to true to keep playing video in background, when app goes in background. /// The default value is false. final bool allowBackgroundPlayback; /// Set this to true to mix the video players audio with other audio sources. /// The default value is false /// /// Note: This option will be silently ignored in the web platform (there is /// currently no way to implement this feature in this platform). final bool mixWithOthers; } ================================================ FILE: packages/video_player/video_player_platform_interface/pubspec.yaml ================================================ name: video_player_platform_interface description: A common platform interface for the video_player plugin. repository: https://github.com/flutter/plugins/tree/main/packages/video_player/video_player_platform_interface issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%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: 6.0.1 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.1.0 dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/video_player/video_player_platform_interface/test/video_player_options_test.dart ================================================ // Copyright 2013 The Flutter 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:video_player_platform_interface/video_player_platform_interface.dart'; void main() { test( 'VideoPlayerOptions allowBackgroundPlayback defaults to false', () { final VideoPlayerOptions options = VideoPlayerOptions(); expect(options.allowBackgroundPlayback, false); }, ); test( 'VideoPlayerOptions mixWithOthers defaults to false', () { final VideoPlayerOptions options = VideoPlayerOptions(); expect(options.mixWithOthers, false); }, ); } ================================================ FILE: packages/video_player/video_player_platform_interface/test/video_player_platform_interface_test.dart ================================================ // Copyright 2013 The Flutter 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:video_player_platform_interface/video_player_platform_interface.dart'; void main() { // Store the initial instance before any tests change it. final VideoPlayerPlatform initialInstance = VideoPlayerPlatform.instance; test('default implementation throws uninimpletemented', () async { await expectLater(() => initialInstance.init(), throwsUnimplementedError); }); } ================================================ FILE: packages/video_player/video_player_web/AUTHORS ================================================ # 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> ================================================ FILE: packages/video_player/video_player_web/CHANGELOG.md ================================================ ## NEXT * Updates minimum Flutter version to 3.0. ## 2.0.13 * Adds compatibilty with version 6.0 of the platform interface. * Updates minimum Flutter version to 2.10. ## 2.0.12 * Updates the `README` with: * Information about a common known issue: "Some videos restart when using the seek bar/progress bar/scrubber" (Issue [#49630](https://github.com/flutter/flutter/issues/49360)) * Links to the Autoplay information of all major browsers (Chrome/Edge, Firefox, Safari). ## 2.0.11 * Improves handling of videos with `Infinity` duration. ## 2.0.10 * Minor fixes for new analysis options. ## 2.0.9 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 2.0.8 * Ensures `buffering` state is only removed when the browser reports enough data has been buffered so that the video can likely play through without stopping (`onCanPlayThrough`). Issue [#94630](https://github.com/flutter/flutter/issues/94630). * Improves testability of the `_VideoPlayer` private class. * Ensures that tests that listen to a Stream fail "fast" (1 second max timeout). ## 2.0.7 * Internal code cleanup for stricter analysis options. ## 2.0.6 * Removes dependency on `meta`. ## 2.0.5 * Adds compatibility with `video_player_platform_interface` 5.0, which does not include non-dev test dependencies. ## 2.0.4 * Adopt `video_player_platform_interface` 4.2 and opt out of `contentUri` data source. ## 2.0.3 * Add `implements` to pubspec. ## 2.0.2 * Updated installation instructions in README. ## 2.0.1 * Fix videos not playing in Safari/Chrome on iOS by setting autoplay to false * Change sizing code of `Video` widget's `HtmlElementView` so it works well when slotted. * Move tests to `example` directory, so they run as integration_tests with `flutter drive`. ## 2.0.0 * Migrate to null safety. * Calling `setMixWithOthers()` now is silently ignored instead of throwing an exception. * Fixed an issue where `isBuffering` was not updating on Web. ## 0.1.4+2 * Update Flutter SDK constraint. ## 0.1.4+1 * Substitute `undefined_prefixed_name: ignore` analyzer setting by a `dart:ui` shim with conditional exports. [Issue](https://github.com/flutter/flutter/issues/69309). ## 0.1.4 * Added option to set the video playback speed on the video controller. ## 0.1.3+2 * Allow users to set the 'muted' attribute on video elements by setting their volume to 0. * Do not parse URIs on 'network' videos to not break blobs (Safari). ## 0.1.3+1 * Remove Android folder from `video_player_web`. ## 0.1.3 * Updated video_player_platform_interface, bumped minimum Dart version to 2.1.0. ## 0.1.2+3 * 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.1.2+2 * Add `analysis_options.yaml` to the package, so we can ignore `undefined_prefixed_name` errors. Works around https://github.com/flutter/flutter/issues/41563. ## 0.1.2+1 * Make the pedantic dev_dependency explicit. ## 0.1.2 * Add a `PlatformException` to the player's `eventController` when there's a `videoElement.onError`. Fixes https://github.com/flutter/flutter/issues/48884. * Handle DomExceptions on videoElement.play() and turn them into `PlatformException` as well, so we don't end up with unhandled Futures. * Update setup instructions in the README. ## 0.1.1+1 * Add an android/ folder with no-op implementation to workaround https://github.com/flutter/flutter/issues/46898. ## 0.1.1 * Support videos from assets. ## 0.1.0+1 * Remove the deprecated `author:` field from pubspec.yaml * Require Flutter SDK 1.10.0 or greater. ## 0.1.0 * Initial release ================================================ FILE: packages/video_player/video_player_web/LICENSE ================================================ 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. ================================================ FILE: packages/video_player/video_player_web/README.md ================================================ # video_player_web The web implementation of [`video_player`][1]. ## Usage This package is [endorsed](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin), which means you can simply use `video_player` normally. This package will be automatically included in your app when you do. ## Limitations on the Web platform Video playback on the Web platform has some limitations that might surprise developers more familiar with mobile/desktop targets. In no particular order: ### dart:io The web platform does **not** suppport `dart:io`, so attempts to create a `VideoPlayerController.file` will throw an `UnimplementedError`. ### Autoplay Attempts to start playing videos with an audio track (or not muted) without user interaction with the site ("user activation") will be prohibited by the browser and cause runtime errors in JS. See also: * [Autoplay policy in Chrome](https://developer.chrome.com/blog/autoplay/) * MDN > [Autoplay guide for media and Web Audio APIs](https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide) * Delivering Video Content for Safari > [Enable Video Autoplay](https://developer.apple.com/documentation/webkit/delivering_video_content_for_safari#3030251) * More info about "user activation", in general: * [Making user activation consistent across APIs](https://developer.chrome.com/blog/user-activation) * HTML Spec: [Tracking user activation](https://html.spec.whatwg.org/multipage/interaction.html#sticky-activation) ### Some videos restart when using the seek bar/progress bar/scrubber Certain videos will rewind to the beginning when users attempt to `seekTo` (change the progress/scrub to) another position, instead of jumping to the desired position. Once the video is fully stored in the browser cache, seeking will work fine after a full page reload. The most common explanation for this issue is that the server where the video is stored doesn't support [HTTP range requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests). > **NOTE:** Flutter web's local server (the one that powers `flutter run`) **DOES NOT** support > range requests, so all video **assets** in `debug` mode will exhibit this behavior. See [Issue #49360](https://github.com/flutter/flutter/issues/49360) for more information on how to diagnose if a server supports range requests or not. ### Mixing audio with other audio sources The `VideoPlayerOptions.mixWithOthers` option can't be implemented in web, at least at the moment. If you use this option it will be silently ignored. ## Supported Formats **Different web browsers support different sets of video codecs.** ### Video codecs? Check MDN's [**Web video codec guide**](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Video_codecs) to learn more about the pros and cons of each video codec. ### What codecs are supported? Visit [**caniuse.com: 'video format'**](https://caniuse.com/#search=video%20format) for a breakdown of which browsers support what codecs. You can customize charts there for the users of your particular website(s). Here's an abridged version of the data from caniuse, for a Global audience: #### MPEG-4/H.264 [![Data on Global support for the MPEG-4/H.264 video format](https://caniuse.bitsofco.de/image/mpeg4.png)](https://caniuse.com/#feat=mpeg4) #### WebM [![Data on Global support for the WebM video format](https://caniuse.bitsofco.de/image/webm.png)](https://caniuse.com/#feat=webm) #### Ogg/Theora [![Data on Global support for the Ogg/Theora video format](https://caniuse.bitsofco.de/image/ogv.png)](https://caniuse.com/#feat=ogv) #### AV1 [![Data on Global support for the AV1 video format](https://caniuse.bitsofco.de/image/av1.png)](https://caniuse.com/#feat=av1) #### HEVC/H.265 [![Data on Global support for the HEVC/H.265 video format](https://caniuse.bitsofco.de/image/hevc.png)](https://caniuse.com/#feat=hevc) [1]: ../video_player ================================================ FILE: packages/video_player/video_player_web/example/README.md ================================================ # 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. ## Testing This package uses `package:integration_test` to run its tests in a web browser. See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/wiki/Plugin-Tests#web-tests) in the Flutter wiki for instructions to setup and run the tests in this package. Check [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) for more info. ================================================ FILE: packages/video_player/video_player_web/example/integration_test/duration_utils_test.dart ================================================ // Copyright 2013 The Flutter 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:video_player_web/src/duration_utils.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('convertNumVideoDurationToPluginDuration', () { testWidgets('Finite value converts to milliseconds', (WidgetTester _) async { final Duration? result = convertNumVideoDurationToPluginDuration(1.5); final Duration? zero = convertNumVideoDurationToPluginDuration(0.0001); expect(result, isNotNull); expect(result!.inMilliseconds, equals(1500)); expect(zero, equals(Duration.zero)); }); testWidgets('Finite value rounds 3rd decimal value', (WidgetTester _) async { final Duration? result = convertNumVideoDurationToPluginDuration(1.567899089087); final Duration? another = convertNumVideoDurationToPluginDuration(1.567199089087); expect(result, isNotNull); expect(result!.inMilliseconds, equals(1568)); expect(another!.inMilliseconds, equals(1567)); }); testWidgets('Infinite value returns magic constant', (WidgetTester _) async { final Duration? result = convertNumVideoDurationToPluginDuration(double.infinity); expect(result, isNotNull); expect(result, equals(jsCompatibleTimeUnset)); expect(result!.inMilliseconds, equals(-9007199254740990)); }); testWidgets('NaN value returns null', (WidgetTester _) async { final Duration? result = convertNumVideoDurationToPluginDuration(double.nan); expect(result, isNull); }); }); } ================================================ FILE: packages/video_player/video_player_web/example/integration_test/utils.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @JS() library integration_test_utils; import 'dart:html'; import 'package:js/js.dart'; // Returns the URL to load an asset from this example app as a network source. // // TODO(stuartmorgan): Convert this to a local `HttpServer` that vends the // assets directly, https://github.com/flutter/flutter/issues/95420 String getUrlForAssetAsNetworkSource(String assetKey) { return 'https://github.com/flutter/plugins/blob/' // This hash can be rolled forward to pick up newly-added assets. 'cb381ced070d356799dddf24aca38ce0579d3d7b' '/packages/video_player/video_player/example/' '$assetKey' '?raw=true'; } @JS() @anonymous class _Descriptor { // May also contain "configurable" and "enumerable" bools. // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#description external factory _Descriptor({ // bool configurable, // bool enumerable, bool writable, Object value, }); } @JS('Object.defineProperty') external void _defineProperty( Object object, String property, _Descriptor description, ); /// Forces a VideoElement to report "Infinity" duration. /// /// Uses JS Object.defineProperty to set the value of a readonly property. void setInfinityDuration(VideoElement element) { _defineProperty( element, 'duration', _Descriptor( writable: true, value: double.infinity, )); } ================================================ FILE: packages/video_player/video_player_web/example/integration_test/video_player_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:html' as html; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; import 'package:video_player_web/src/duration_utils.dart'; import 'package:video_player_web/src/video_player.dart'; import 'utils.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('VideoPlayer', () { late html.VideoElement video; setUp(() { // Never set "src" on the video, so this test doesn't hit the network! video = html.VideoElement() ..controls = true ..setAttribute('playsinline', 'false'); }); testWidgets('fixes critical video element config', (WidgetTester _) async { VideoPlayer(videoElement: video).initialize(); expect(video.controls, isFalse, reason: 'Video is controlled through code'); expect(video.getAttribute('autoplay'), 'false', reason: 'Cannot autoplay on the web'); expect(video.getAttribute('playsinline'), 'true', reason: 'Needed by safari iOS'); }); testWidgets('setVolume', (WidgetTester tester) async { final VideoPlayer player = VideoPlayer(videoElement: video)..initialize(); player.setVolume(0); expect(video.volume, isZero, reason: 'Volume should be zero'); expect(video.muted, isTrue, reason: 'muted attribute should be true'); expect(() { player.setVolume(-0.0001); }, throwsAssertionError, reason: 'Volume cannot be < 0'); expect(() { player.setVolume(1.0001); }, throwsAssertionError, reason: 'Volume cannot be > 1'); }); testWidgets('setPlaybackSpeed', (WidgetTester tester) async { final VideoPlayer player = VideoPlayer(videoElement: video)..initialize(); expect(() { player.setPlaybackSpeed(-1); }, throwsAssertionError, reason: 'Playback speed cannot be < 0'); expect(() { player.setPlaybackSpeed(0); }, throwsAssertionError, reason: 'Playback speed cannot be == 0'); }); testWidgets('seekTo', (WidgetTester tester) async { final VideoPlayer player = VideoPlayer(videoElement: video)..initialize(); expect(() { player.seekTo(const Duration(seconds: -1)); }, throwsAssertionError, reason: 'Cannot seek into negative numbers'); }); // The events tested in this group do *not* represent the actual sequence // of events from a real "video" element. They're crafted to test the // behavior of the VideoPlayer in different states with different events. group('events', () { late StreamController streamController; late VideoPlayer player; late Stream timedStream; final Set bufferingEvents = { VideoEventType.bufferingStart, VideoEventType.bufferingEnd, }; setUp(() { streamController = StreamController(); player = VideoPlayer(videoElement: video, eventController: streamController) ..initialize(); // This stream will automatically close after 100 ms without seeing any events timedStream = streamController.stream.timeout( const Duration(milliseconds: 100), onTimeout: (EventSink sink) { sink.close(); }, ); }); testWidgets('buffering dispatches only when it changes', (WidgetTester tester) async { // Take all the "buffering" events that we see during the next few seconds final Future> stream = timedStream .where( (VideoEvent event) => bufferingEvents.contains(event.eventType)) .map((VideoEvent event) => event.eventType == VideoEventType.bufferingStart) .toList(); // Simulate some events coming from the player... player.setBuffering(true); player.setBuffering(true); player.setBuffering(true); player.setBuffering(false); player.setBuffering(false); player.setBuffering(true); player.setBuffering(false); player.setBuffering(true); player.setBuffering(false); final List events = await stream; expect(events, hasLength(6)); expect(events, [true, false, true, false, true, false]); }); testWidgets('canplay event does not change buffering state', (WidgetTester tester) async { // Take all the "buffering" events that we see during the next few seconds final Future> stream = timedStream .where( (VideoEvent event) => bufferingEvents.contains(event.eventType)) .map((VideoEvent event) => event.eventType == VideoEventType.bufferingStart) .toList(); player.setBuffering(true); // Simulate "canplay" event... video.dispatchEvent(html.Event('canplay')); final List events = await stream; expect(events, hasLength(1)); expect(events, [true]); }); testWidgets('canplaythrough event does change buffering state', (WidgetTester tester) async { // Take all the "buffering" events that we see during the next few seconds final Future> stream = timedStream .where( (VideoEvent event) => bufferingEvents.contains(event.eventType)) .map((VideoEvent event) => event.eventType == VideoEventType.bufferingStart) .toList(); player.setBuffering(true); // Simulate "canplaythrough" event... video.dispatchEvent(html.Event('canplaythrough')); final List events = await stream; expect(events, hasLength(2)); expect(events, [true, false]); }); testWidgets('initialized dispatches only once', (WidgetTester tester) async { // Dispatch some bogus "canplay" events from the video object video.dispatchEvent(html.Event('canplay')); video.dispatchEvent(html.Event('canplay')); video.dispatchEvent(html.Event('canplay')); // Take all the "initialized" events that we see during the next few seconds final Future> stream = timedStream .where((VideoEvent event) => event.eventType == VideoEventType.initialized) .toList(); video.dispatchEvent(html.Event('canplay')); video.dispatchEvent(html.Event('canplay')); video.dispatchEvent(html.Event('canplay')); final List events = await stream; expect(events, hasLength(1)); expect(events[0].eventType, VideoEventType.initialized); }); // Issue: https://github.com/flutter/flutter/issues/105649 testWidgets('supports `Infinity` duration', (WidgetTester _) async { setInfinityDuration(video); expect(video.duration.isInfinite, isTrue); final Future> stream = timedStream .where((VideoEvent event) => event.eventType == VideoEventType.initialized) .toList(); video.dispatchEvent(html.Event('canplay')); final List events = await stream; expect(events, hasLength(1)); expect(events[0].eventType, VideoEventType.initialized); expect(events[0].duration, equals(jsCompatibleTimeUnset)); }); }); }); } ================================================ FILE: packages/video_player/video_player_web/example/integration_test/video_player_web_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; import 'package:video_player_web/video_player_web.dart'; import 'utils.dart'; // Use WebM to allow CI to run tests in Chromium. const String _videoAssetKey = 'assets/Butterfly-209.webm'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('VideoPlayerWeb plugin (hits network)', () { late Future textureId; setUp(() { VideoPlayerPlatform.instance = VideoPlayerPlugin(); textureId = VideoPlayerPlatform.instance .create( DataSource( sourceType: DataSourceType.network, uri: getUrlForAssetAsNetworkSource(_videoAssetKey), ), ) .then((int? textureId) => textureId!); }); testWidgets('can init', (WidgetTester tester) async { expect(VideoPlayerPlatform.instance.init(), completes); }); testWidgets('can create from network', (WidgetTester tester) async { expect( VideoPlayerPlatform.instance.create( DataSource( sourceType: DataSourceType.network, uri: getUrlForAssetAsNetworkSource(_videoAssetKey), ), ), completion(isNonZero)); }); testWidgets('can create from asset', (WidgetTester tester) async { expect( VideoPlayerPlatform.instance.create( DataSource( sourceType: DataSourceType.asset, asset: 'videos/bee.mp4', package: 'bee_vids', ), ), completion(isNonZero)); }); testWidgets('cannot create from file', (WidgetTester tester) async { expect( VideoPlayerPlatform.instance.create( DataSource( sourceType: DataSourceType.file, uri: '/videos/bee.mp4', ), ), throwsUnimplementedError); }); testWidgets('cannot create from content URI', (WidgetTester tester) async { expect( VideoPlayerPlatform.instance.create( DataSource( sourceType: DataSourceType.contentUri, uri: 'content://video', ), ), throwsUnimplementedError); }); testWidgets('can dispose', (WidgetTester tester) async { expect(VideoPlayerPlatform.instance.dispose(await textureId), completes); }); testWidgets('can set looping', (WidgetTester tester) async { expect( VideoPlayerPlatform.instance.setLooping(await textureId, true), completes, ); }); testWidgets('can play', (WidgetTester tester) async { // Mute video to allow autoplay (See https://goo.gl/xX8pDD) await VideoPlayerPlatform.instance.setVolume(await textureId, 0); expect(VideoPlayerPlatform.instance.play(await textureId), completes); }); testWidgets('throws PlatformException when playing bad media', (WidgetTester tester) async { final int videoPlayerId = (await VideoPlayerPlatform.instance.create( DataSource( sourceType: DataSourceType.network, uri: getUrlForAssetAsNetworkSource('assets/__non_existent.webm'), ), ))!; final Stream eventStream = VideoPlayerPlatform.instance.videoEventsFor(videoPlayerId); // Mute video to allow autoplay (See https://goo.gl/xX8pDD) await VideoPlayerPlatform.instance.setVolume(videoPlayerId, 0); await VideoPlayerPlatform.instance.play(videoPlayerId); expect(() async { await eventStream.timeout(const Duration(seconds: 5)).last; }, throwsA(isA())); }); testWidgets('can pause', (WidgetTester tester) async { expect(VideoPlayerPlatform.instance.pause(await textureId), completes); }); testWidgets('can set volume', (WidgetTester tester) async { expect( VideoPlayerPlatform.instance.setVolume(await textureId, 0.8), completes, ); }); testWidgets('can set playback speed', (WidgetTester tester) async { expect( VideoPlayerPlatform.instance.setPlaybackSpeed(await textureId, 2.0), completes, ); }); testWidgets('can seek to position', (WidgetTester tester) async { expect( VideoPlayerPlatform.instance.seekTo( await textureId, const Duration(seconds: 1), ), completes, ); }); testWidgets('can get position', (WidgetTester tester) async { expect(VideoPlayerPlatform.instance.getPosition(await textureId), completion(isInstanceOf())); }); testWidgets('can get video event stream', (WidgetTester tester) async { expect(VideoPlayerPlatform.instance.videoEventsFor(await textureId), isInstanceOf>()); }); testWidgets('can build view', (WidgetTester tester) async { expect(VideoPlayerPlatform.instance.buildView(await textureId), isInstanceOf()); }); testWidgets('ignores setting mixWithOthers', (WidgetTester tester) async { expect(VideoPlayerPlatform.instance.setMixWithOthers(true), completes); expect(VideoPlayerPlatform.instance.setMixWithOthers(false), completes); }); testWidgets('video playback lifecycle', (WidgetTester tester) async { final int videoPlayerId = await textureId; final Stream eventStream = VideoPlayerPlatform.instance.videoEventsFor(videoPlayerId); final Future> stream = eventStream.timeout( const Duration(seconds: 1), onTimeout: (EventSink sink) { sink.close(); }, ).toList(); await VideoPlayerPlatform.instance.setVolume(videoPlayerId, 0); await VideoPlayerPlatform.instance.play(videoPlayerId); // Let the video play, until we stop seeing events for a second final List events = await stream; await VideoPlayerPlatform.instance.pause(videoPlayerId); // The expected list of event types should look like this: // 1. bufferingStart, // 2. bufferingUpdate (videoElement.onWaiting), // 3. initialized (videoElement.onCanPlay), // 4. bufferingEnd (videoElement.onCanPlayThrough), expect( events.map((VideoEvent e) => e.eventType), equals([ VideoEventType.bufferingStart, VideoEventType.bufferingUpdate, VideoEventType.initialized, VideoEventType.bufferingEnd ])); }); }); } ================================================ FILE: packages/video_player/video_player_web/example/lib/main.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } /// App for testing class MyApp extends StatefulWidget { /// Default Constructor const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override Widget build(BuildContext context) { return const Directionality( textDirection: TextDirection.ltr, child: Text('Testing... Look at the console output for results!'), ); } } ================================================ FILE: packages/video_player/video_player_web/example/pubspec.yaml ================================================ name: video_player_for_web_integration_tests publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter js: ^0.6.0 video_player_platform_interface: ">=4.2.0 <7.0.0" video_player_web: path: ../ dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter ================================================ FILE: packages/video_player/video_player_web/example/run_test.sh ================================================ #!/usr/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. if pgrep -lf chromedriver > /dev/null; then echo "chromedriver is running." if [ $# -eq 0 ]; then echo "No target specified, running all tests..." find integration_test/ -iname *_test.dart | xargs -n1 -i -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target='{}' else echo "Running test target: $1..." set -x flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target=$1 fi else echo "chromedriver is not running." echo "Please, check the README.md for instructions on how to use run_test.sh" fi ================================================ FILE: packages/video_player/video_player_web/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/video_player/video_player_web/example/web/index.html ================================================ example ================================================ FILE: packages/video_player/video_player_web/lib/src/duration_utils.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// The "length" of a video which doesn't have finite duration. // See: https://github.com/flutter/flutter/issues/107882 const Duration jsCompatibleTimeUnset = Duration( milliseconds: -9007199254740990, // Number.MIN_SAFE_INTEGER + 1. -(2^53 - 1) ); /// Converts a `num` duration coming from a [VideoElement] into a [Duration] that /// the plugin can use. /// /// From the documentation, `videoDuration` is "a double-precision floating-point /// value indicating the duration of the media in seconds. /// If no media data is available, the value `NaN` is returned. /// If the element's media doesn't have a known duration —such as for live media /// streams— the value of duration is `+Infinity`." /// /// If the `videoDuration` is finite, this method returns it as a `Duration`. /// If the `videoDuration` is `Infinity`, the duration will be /// `-9007199254740990` milliseconds. (See https://github.com/flutter/flutter/issues/107882) /// If the `videoDuration` is `NaN`, this will return null. Duration? convertNumVideoDurationToPluginDuration(num duration) { if (duration.isFinite) { return Duration( milliseconds: (duration * 1000).round(), ); } else if (duration.isInfinite) { return jsCompatibleTimeUnset; } return null; } ================================================ FILE: packages/video_player/video_player_web/lib/src/shims/dart_ui.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// This file shims dart:ui in web-only scenarios, getting rid of the need to /// suppress analyzer warnings. // TODO(ditman): Remove this file once web-only dart:ui APIs are exposed from // a dedicated place, https://github.com/flutter/flutter/issues/55000 export 'dart_ui_fake.dart' if (dart.library.html) 'dart_ui_real.dart'; ================================================ FILE: packages/video_player/video_player_web/lib/src/shims/dart_ui_fake.dart ================================================ // Copyright 2013 The Flutter 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:html' as html; // Fake interface for the logic that this package needs from (web-only) dart:ui. // This is conditionally exported so the analyzer sees these methods as available. // ignore_for_file: avoid_classes_with_only_static_members // ignore_for_file: camel_case_types /// Shim for web_ui engine.PlatformViewRegistry /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/ui.dart#L62 class platformViewRegistry { /// Shim for registerViewFactory /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/ui.dart#L72 static bool registerViewFactory( String viewTypeId, html.Element Function(int viewId) viewFactory) { return false; } } /// Shim for web_ui engine.AssetManager. /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/src/engine/assets.dart#L12 class webOnlyAssetManager { /// Shim for getAssetUrl. /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/src/engine/assets.dart#L45 static String getAssetUrl(String asset) => ''; } /// Signature of callbacks that have no arguments and return no data. typedef VoidCallback = void Function(); ================================================ FILE: packages/video_player/video_player_web/lib/src/shims/dart_ui_real.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'dart:ui'; ================================================ FILE: packages/video_player/video_player_web/lib/src/video_player.dart ================================================ // Copyright 2013 The Flutter 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 'dart:html' as html; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; import 'duration_utils.dart'; // An error code value to error name Map. // See: https://developer.mozilla.org/en-US/docs/Web/API/MediaError/code const Map _kErrorValueToErrorName = { 1: 'MEDIA_ERR_ABORTED', 2: 'MEDIA_ERR_NETWORK', 3: 'MEDIA_ERR_DECODE', 4: 'MEDIA_ERR_SRC_NOT_SUPPORTED', }; // An error code value to description Map. // See: https://developer.mozilla.org/en-US/docs/Web/API/MediaError/code const Map _kErrorValueToErrorDescription = { 1: 'The user canceled the fetching of the video.', 2: 'A network error occurred while fetching the video, despite having previously been available.', 3: 'An error occurred while trying to decode the video, despite having previously been determined to be usable.', 4: 'The video has been found to be unsuitable (missing or in a format not supported by your browser).', }; // The default error message, when the error is an empty string // See: https://developer.mozilla.org/en-US/docs/Web/API/MediaError/message const String _kDefaultErrorMessage = 'No further diagnostic information can be determined or provided.'; /// Wraps a [html.VideoElement] so its API complies with what is expected by the plugin. class VideoPlayer { /// Create a [VideoPlayer] from a [html.VideoElement] instance. VideoPlayer({ required html.VideoElement videoElement, @visibleForTesting StreamController? eventController, }) : _videoElement = videoElement, _eventController = eventController ?? StreamController(); final StreamController _eventController; final html.VideoElement _videoElement; bool _isInitialized = false; bool _isBuffering = false; /// Returns the [Stream] of [VideoEvent]s from the inner [html.VideoElement]. Stream get events => _eventController.stream; /// Initializes the wrapped [html.VideoElement]. /// /// This method sets the required DOM attributes so videos can [play] programmatically, /// and attaches listeners to the internal events from the [html.VideoElement] /// to react to them / expose them through the [VideoPlayer.events] stream. void initialize() { _videoElement ..autoplay = false ..controls = false; // Allows Safari iOS to play the video inline _videoElement.setAttribute('playsinline', 'true'); // Set autoplay to false since most browsers won't autoplay a video unless it is muted _videoElement.setAttribute('autoplay', 'false'); _videoElement.onCanPlay.listen((dynamic _) { if (!_isInitialized) { _isInitialized = true; _sendInitialized(); } }); _videoElement.onCanPlayThrough.listen((dynamic _) { setBuffering(false); }); _videoElement.onPlaying.listen((dynamic _) { setBuffering(false); }); _videoElement.onWaiting.listen((dynamic _) { setBuffering(true); _sendBufferingRangesUpdate(); }); // The error event fires when some form of error occurs while attempting to load or perform the media. _videoElement.onError.listen((html.Event _) { setBuffering(false); // The Event itself (_) doesn't contain info about the actual error. // We need to look at the HTMLMediaElement.error. // See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/error final html.MediaError error = _videoElement.error!; _eventController.addError(PlatformException( code: _kErrorValueToErrorName[error.code]!, message: error.message != '' ? error.message : _kDefaultErrorMessage, details: _kErrorValueToErrorDescription[error.code], )); }); _videoElement.onEnded.listen((dynamic _) { setBuffering(false); _eventController.add(VideoEvent(eventType: VideoEventType.completed)); }); } /// Attempts to play the video. /// /// If this method is called programmatically (without user interaction), it /// might fail unless the video is completely muted (or it has no Audio tracks). /// /// When called from some user interaction (a tap on a button), the above /// limitation should disappear. Future play() { return _videoElement.play().catchError((Object e) { // play() attempts to begin playback of the media. It returns // a Promise which can get rejected in case of failure to begin // playback for any reason, such as permission issues. // The rejection handler is called with a DomException. // See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play final html.DomException exception = e as html.DomException; _eventController.addError(PlatformException( code: exception.name, message: exception.message, )); }, test: (Object e) => e is html.DomException); } /// Pauses the video in the current position. void pause() { _videoElement.pause(); } /// Controls whether the video should start again after it finishes. // ignore: use_setters_to_change_properties void setLooping(bool value) { _videoElement.loop = value; } /// Sets the volume at which the media will be played. /// /// Values must fall between 0 and 1, where 0 is muted and 1 is the loudest. /// /// When volume is set to 0, the `muted` property is also applied to the /// [html.VideoElement]. This is required for auto-play on the web. void setVolume(double volume) { assert(volume >= 0 && volume <= 1); // TODO(ditman): Do we need to expose a "muted" API? // https://github.com/flutter/flutter/issues/60721 _videoElement.muted = !(volume > 0.0); _videoElement.volume = volume; } /// Sets the playback `speed`. /// /// A `speed` of 1.0 is "normal speed," values lower than 1.0 make the media /// play slower than normal, higher values make it play faster. /// /// `speed` cannot be negative. /// /// The audio is muted when the fast forward or slow motion is outside a useful /// range (for example, Gecko mutes the sound outside the range 0.25 to 4.0). /// /// The pitch of the audio is corrected by default. void setPlaybackSpeed(double speed) { assert(speed > 0); _videoElement.playbackRate = speed; } /// Moves the playback head to a new `position`. /// /// `position` cannot be negative. void seekTo(Duration position) { assert(!position.isNegative); _videoElement.currentTime = position.inMilliseconds.toDouble() / 1000; } /// Returns the current playback head position as a [Duration]. Duration getPosition() { _sendBufferingRangesUpdate(); return Duration(milliseconds: (_videoElement.currentTime * 1000).round()); } /// Disposes of the current [html.VideoElement]. void dispose() { _videoElement.removeAttribute('src'); _videoElement.load(); } // Sends an [VideoEventType.initialized] [VideoEvent] with info about the wrapped video. void _sendInitialized() { final Duration? duration = convertNumVideoDurationToPluginDuration(_videoElement.duration); final Size? size = _videoElement.videoHeight.isFinite ? Size( _videoElement.videoWidth.toDouble(), _videoElement.videoHeight.toDouble(), ) : null; _eventController.add( VideoEvent( eventType: VideoEventType.initialized, duration: duration, size: size, ), ); } /// Caches the current "buffering" state of the video. /// /// If the current buffering state is different from the previous one /// ([_isBuffering]), this dispatches a [VideoEvent]. @visibleForTesting void setBuffering(bool buffering) { if (_isBuffering != buffering) { _isBuffering = buffering; _eventController.add(VideoEvent( eventType: _isBuffering ? VideoEventType.bufferingStart : VideoEventType.bufferingEnd, )); } } // Broadcasts the [html.VideoElement.buffered] status through the [events] stream. void _sendBufferingRangesUpdate() { _eventController.add(VideoEvent( buffered: _toDurationRange(_videoElement.buffered), eventType: VideoEventType.bufferingUpdate, )); } // Converts from [html.TimeRanges] to our own List. List _toDurationRange(html.TimeRanges buffered) { final List durationRange = []; for (int i = 0; i < buffered.length; i++) { durationRange.add(DurationRange( Duration(milliseconds: (buffered.start(i) * 1000).round()), Duration(milliseconds: (buffered.end(i) * 1000).round()), )); } return durationRange; } } ================================================ FILE: packages/video_player/video_player_web/lib/video_player_web.dart ================================================ // Copyright 2013 The Flutter 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 'dart:html'; import 'package:flutter/material.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; import 'src/shims/dart_ui.dart' as ui; import 'src/video_player.dart'; /// The web implementation of [VideoPlayerPlatform]. /// /// This class implements the `package:video_player` functionality for the web. class VideoPlayerPlugin extends VideoPlayerPlatform { /// Registers this class as the default instance of [VideoPlayerPlatform]. static void registerWith(Registrar registrar) { VideoPlayerPlatform.instance = VideoPlayerPlugin(); } // Map of textureId -> VideoPlayer instances final Map _videoPlayers = {}; // Simulate the native "textureId". int _textureCounter = 1; @override Future init() async { return _disposeAllPlayers(); } @override Future dispose(int textureId) async { _player(textureId).dispose(); _videoPlayers.remove(textureId); return; } void _disposeAllPlayers() { for (final VideoPlayer videoPlayer in _videoPlayers.values) { videoPlayer.dispose(); } _videoPlayers.clear(); } @override Future create(DataSource dataSource) async { final int textureId = _textureCounter++; late String uri; switch (dataSource.sourceType) { case DataSourceType.network: // Do NOT modify the incoming uri, it can be a Blob, and Safari doesn't // like blobs that have changed. uri = dataSource.uri ?? ''; break; case DataSourceType.asset: String assetUrl = dataSource.asset!; if (dataSource.package != null && dataSource.package!.isNotEmpty) { assetUrl = 'packages/${dataSource.package}/$assetUrl'; } assetUrl = ui.webOnlyAssetManager.getAssetUrl(assetUrl); uri = assetUrl; break; case DataSourceType.file: return Future.error(UnimplementedError( 'web implementation of video_player cannot play local files')); case DataSourceType.contentUri: return Future.error(UnimplementedError( 'web implementation of video_player cannot play content uri')); } final VideoElement videoElement = VideoElement() ..id = 'videoElement-$textureId' ..src = uri ..style.border = 'none' ..style.height = '100%' ..style.width = '100%'; // TODO(hterkelsen): Use initialization parameters once they are available ui.platformViewRegistry.registerViewFactory( 'videoPlayer-$textureId', (int viewId) => videoElement); final VideoPlayer player = VideoPlayer(videoElement: videoElement) ..initialize(); _videoPlayers[textureId] = player; return textureId; } @override Future setLooping(int textureId, bool looping) async { return _player(textureId).setLooping(looping); } @override Future play(int textureId) async { return _player(textureId).play(); } @override Future pause(int textureId) async { return _player(textureId).pause(); } @override Future setVolume(int textureId, double volume) async { return _player(textureId).setVolume(volume); } @override Future setPlaybackSpeed(int textureId, double speed) async { return _player(textureId).setPlaybackSpeed(speed); } @override Future seekTo(int textureId, Duration position) async { return _player(textureId).seekTo(position); } @override Future getPosition(int textureId) async { return _player(textureId).getPosition(); } @override Stream videoEventsFor(int textureId) { return _player(textureId).events; } // Retrieves a [VideoPlayer] by its internal `id`. // It must have been created earlier from the [create] method. VideoPlayer _player(int id) { return _videoPlayers[id]!; } @override Widget buildView(int textureId) { return HtmlElementView(viewType: 'videoPlayer-$textureId'); } /// Sets the audio mode to mix with other sources (ignored) @override Future setMixWithOthers(bool mixWithOthers) => Future.value(); } ================================================ FILE: packages/video_player/video_player_web/pubspec.yaml ================================================ name: video_player_web description: Web platform implementation of video_player. repository: https://github.com/flutter/plugins/tree/main/packages/video_player/video_player_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 version: 2.0.13 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: video_player platforms: web: pluginClass: VideoPlayerPlugin fileName: video_player_web.dart dependencies: flutter: sdk: flutter flutter_web_plugins: sdk: flutter video_player_platform_interface: ">=4.2.0 <7.0.0" dev_dependencies: flutter_test: sdk: flutter ================================================ FILE: packages/video_player/video_player_web/test/README.md ================================================ ## test This package uses integration tests for testing. See `example/README.md` for more info. ================================================ FILE: packages/video_player/video_player_web/test/tests_exist_elsewhere_test.dart ================================================ // Copyright 2013 The Flutter 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: avoid_print import 'package:flutter_test/flutter_test.dart'; void main() { test('Tell the user where to find the real tests', () { print('---'); print('This package uses integration_test for its tests.'); print('See `example/README.md` for more info.'); print('---'); }); } ================================================ FILE: packages/webview_flutter/webview_flutter/AUTHORS ================================================ # 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> Nick Bradshaw Antonino Di Natale ================================================ FILE: packages/webview_flutter/webview_flutter/CHANGELOG.md ================================================ ## 4.0.4 * Adds examples of accessing platform-specific features for each class. ## 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 `webview_flutter_platform_interface`. See `Usage` section in the README for updated usage. See `Migrating from 3.0 to 4.0` section in the README for details on migrating to this version. * Updates minimum Flutter version to 3.0.0. * Updates code for new analysis options. * Updates references to the obsolete master branch. ## 3.0.4 * Minor fixes for new analysis options. ## 3.0.3 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 3.0.2 * Migrates deprecated `Scaffold.showSnackBar` to `ScaffoldMessenger` in example app. * Adds OS version support information to README. ## 3.0.1 * Removes a duplicate Android-specific integration test. * Fixes an integration test race condition. * Fixes comments (accidentally mixed // with ///). ## 3.0.0 * **BREAKING CHANGE**: On Android, hybrid composition (SurfaceAndroidWebView) is now the default. The previous default, virtual display, can be specified with `WebView.platform = AndroidWebView()` ## 2.8.0 * Adds support for the `loadFlutterAsset` method. ## 2.7.0 * Adds `setCookie` to CookieManager. * CreationParams now supports setting `initialCookies`. ## 2.6.0 * Adds support for the `loadRequest` method. ## 2.5.0 * Adds an option to set the background color of the webview. ## 2.4.0 * Adds support for the `loadFile` and `loadHtmlString` methods. * Updates example app Android compileSdkVersion to 31. * Integration test fixes. * Updates code for new analysis options. ## 2.3.1 * Add iOS-specific note to set `JavascriptMode.unrestricted` in order to set `zoomEnabled: false`. ## 2.3.0 * Add ability to enable/disable zoom functionality. ## 2.2.0 * Added `runJavascript` and `runJavascriptForResult` to supersede `evaluateJavascript`. * Deprecated `evaluateJavascript`. ## 2.1.2 * Fix typos in the README. ## 2.1.1 * Fixed `_CastError` that was thrown when running the example App. ## 2.1.0 * Migrated to fully federated architecture. ## 2.0.14 * Update minimum Flutter SDK to 2.5 and iOS deployment target to 9.0. ## 2.0.13 * Send URL of File to download to the NavigationDelegate on Android just like it is already done on iOS. * Updated Android lint settings. ## 2.0.12 * Improved the documentation on using the different Android Platform View modes. * So that Android and iOS behave the same, `onWebResourceError` is now only called for the main page. ## 2.0.11 * Remove references to the Android V1 embedding. ## 2.0.10 * Fix keyboard issues link in the README. ## 2.0.9 * Add iOS UI integration test target. * Suppress deprecation warning for iOS APIs deprecated in iOS 9. ## 2.0.8 * Migrate maven repository from jcenter to mavenCentral. ## 2.0.7 * Republished 2.0.6 with Flutter 2.2 to avoid https://github.com/dart-lang/pub/issues/3001 ## 2.0.6 * WebView requires at least Android 19 if you are using hybrid composition ([flutter/issues/59894](https://github.com/flutter/flutter/issues/59894)). ## 2.0.5 * Example app observes `uiMode`, so the WebView isn't reattached when the UI mode changes. (e.g. switching to Dark mode). ## 2.0.4 * Fix a bug where `allowsInlineMediaPlayback` is not respected on iOS. ## 2.0.3 * Fixes bug where scroll bars on the Android non-hybrid WebView are rendered on the wrong side of the screen. ## 2.0.2 * Fixes bug where text fields are hidden behind the keyboard when hybrid composition is used [flutter/issues/75667](https://github.com/flutter/flutter/issues/75667). ## 2.0.1 * Run CocoaPods iOS tests in RunnerUITests target ## 2.0.0 * Migration to null-safety. * Added support for progress tracking. * Add section to the wiki explaining how to use Material components. * Update integration test to workaround an iOS 14 issue with `evaluateJavascript`. * Fix `onWebResourceError` on iOS. * Fix outdated links across a number of markdown files ([#3276](https://github.com/flutter/plugins/pull/3276)) * Added `allowsInlineMediaPlayback` property. ## 1.0.8 * Update Flutter SDK constraint. ## 1.0.7 * Minor documentation update to indicate known issue on iOS 13.4 and 13.5. * See: https://github.com/flutter/flutter/issues/53490 ## 1.0.6 * Invoke the WebView.onWebResourceError on iOS when the webview content process crashes. ## 1.0.5 * Fix example in the readme. ## 1.0.4 * Suppress the `deprecated_member_use` warning in the example app for `ScaffoldMessenger.showSnackBar`. ## 1.0.3 * Update android compileSdkVersion to 29. ## 1.0.2 * Android Code Inspection and Clean up. ## 1.0.1 * Add documentation for `WebViewPlatformCreatedCallback`. ## 1.0.0 - Out of developer preview 🎉. * Bumped the minimal Flutter SDK to 1.22 where platform views are out of developer preview, and performing better on iOS. Flutter 1.22 no longer requires adding the `io.flutter.embedded_views_preview` flag to `Info.plist`. * Added support for Hybrid Composition on Android (see opt-in instructions in [README](https://github.com/flutter/plugins/blob/main/packages/webview_flutter/README.md#android)) * Lowered the required Android API to 19 (was previously 20): [#23728](https://github.com/flutter/flutter/issues/23728). * Fixed the following issues: * 🎹 Keyboard: [#41089](https://github.com/flutter/flutter/issues/41089), [#36478](https://github.com/flutter/flutter/issues/36478), [#51254](https://github.com/flutter/flutter/issues/51254), [#50716](https://github.com/flutter/flutter/issues/50716), [#55724](https://github.com/flutter/flutter/issues/55724), [#56513](https://github.com/flutter/flutter/issues/56513), [#56515](https://github.com/flutter/flutter/issues/56515), [#61085](https://github.com/flutter/flutter/issues/61085), [#62205](https://github.com/flutter/flutter/issues/62205), [#62547](https://github.com/flutter/flutter/issues/62547), [#58943](https://github.com/flutter/flutter/issues/58943), [#56361](https://github.com/flutter/flutter/issues/56361), [#56361](https://github.com/flutter/flutter/issues/42902), [#40716](https://github.com/flutter/flutter/issues/40716), [#37989](https://github.com/flutter/flutter/issues/37989), [#27924](https://github.com/flutter/flutter/issues/27924). * ♿️ Accessibility: [#50716](https://github.com/flutter/flutter/issues/50716). * ⚡️ Performance: [#61280](https://github.com/flutter/flutter/issues/61280), [#31243](https://github.com/flutter/flutter/issues/31243), [#52211](https://github.com/flutter/flutter/issues/52211). * 📹 Video: [#5191](https://github.com/flutter/flutter/issues/5191). ## 0.3.24 * Keep handling deprecated Android v1 classes for backward compatibility. ## 0.3.23 * Handle WebView multi-window support. ## 0.3.22+2 * Update package:e2e reference to use the local version in the flutter/plugins repository. ## 0.3.22+1 * Update the `setAndGetScrollPosition` to use hard coded values and add a `pumpAndSettle` call. ## 0.3.22 * Add support for passing a failing url. ## 0.3.21 * Enable programmatic scrolling using Android's WebView.scrollTo & iOS WKWebView.scrollView.contentOffset. ## 0.3.20+2 * Fix CocoaPods podspec lint warnings. ## 0.3.20+1 * OCMock module import -> #import, unit tests compile generated as library. * Fix select drop down crash on old Android tablets (https://github.com/flutter/flutter/issues/54164). ## 0.3.20 * Added support for receiving web resource loading errors. See `WebView.onWebResourceError`. ## 0.3.19+10 * Replace deprecated `getFlutterEngine` call on Android. ## 0.3.19+9 * Remove example app's iOS workspace settings. ## 0.3.19+8 * Make the pedantic dev_dependency explicit. ## 0.3.19+7 * Remove the Flutter SDK constraint upper bound. ## 0.3.19+6 * Enable opening links that target the "_blank" window (links open in same window). ## 0.3.19+5 * On iOS, always keep contentInsets of the WebView to be 0. * Fix XCTest case to follow XCTest naming convention. ## 0.3.19+4 * On iOS, fix the scroll view content inset is automatically adjusted. After the fix, the content position of the WebView is customizable by Flutter. * Fix an iOS 13 bug where the scroll indicator shows at random location. ## 0.3.19+3 * Setup XCTests. ## 0.3.19+2 * Migrate from deprecated BinaryMessages to ServicesBinding.instance.defaultBinaryMessenger. ## 0.3.19+1 * Raise min Flutter SDK requirement to the latest stable. v2 embedding apps no longer need to special case their Flutter SDK requirement like they have since v0.3.15+3. ## 0.3.19 * Add setting for iOS to allow gesture based navigation. ## 0.3.18+1 * Be explicit that keyboard is not ready for production in README.md. ## 0.3.18 * Add support for onPageStarted event. * Remove the deprecated `author:` field from pubspec.yaml * Migrate to the new pubspec platforms manifest. * Require Flutter SDK 1.10.0 or greater. ## 0.3.17 * Fix pedantic lint errors. Added missing documentation and awaited some futures in tests and the example app. ## 0.3.16 * Add support for async NavigationDelegates. Synchronous NavigationDelegates should still continue to function without any change in behavior. ## 0.3.15+3 * Re-land support for the v2 Android embedding. This correctly sets the minimum SDK to the latest stable and avoid any compile errors. *WARNING:* the V2 embedding itself still requires the current Flutter master channel (flutter/flutter@1d4d63a) for text input to work properly on all Android versions. ## 0.3.15+2 * Remove AndroidX warnings. ## 0.3.15+1 * Revert the prior embedding support add since it requires an API that hasn't rolled to stable. ## 0.3.15 * Add support for the v2 Android embedding. This shouldn't affect existing functionality. Plugin authors who use the V2 embedding can now register the plugin and expect that it correctly responds to app lifecycle changes. ## 0.3.14+2 * Define clang module for iOS. ## 0.3.14+1 * Allow underscores anywhere for Javascript Channel name. ## 0.3.14 * Added a getTitle getter to WebViewController. ## 0.3.13 * Add an optional `userAgent` property to set a custom User Agent. ## 0.3.12+1 * Temporarily revert getTitle (doing this as a patch bump shortly after publishing). ## 0.3.12 * Added a getTitle getter to WebViewController. ## 0.3.11+6 * Calling destroy on Android webview when flutter webview is getting disposed. ## 0.3.11+5 * Reduce compiler warnings regarding iOS9 compatibility by moving a single method back into a `@available` block. ## 0.3.11+4 * Removed noisy log messages on iOS. ## 0.3.11+3 * Apply the display listeners workaround that was shipped in 0.3.11+1 on all Android versions prior to P. ## 0.3.11+2 * Add fix for input connection being dropped after a screen resize on certain Android devices. ## 0.3.11+1 * Work around a bug in old Android WebView versions that was causing a crash when resizing the webview on old devices. ## 0.3.11 * Add an initialAutoMediaPlaybackPolicy setting for controlling how auto media playback is restricted. ## 0.3.10+5 * Add dependency on `androidx.annotation:annotation:1.0.0`. ## 0.3.10+4 * Add keyboard text to README. ## 0.3.10+3 * Don't log an unknown setting key error for 'debuggingEnabled' on iOS. ## 0.3.10+2 * Fix InputConnection being lost when combined with route transitions. ## 0.3.10+1 * Add support for simultaenous Flutter `TextInput` and WebView text fields. ## 0.3.10 * Add partial WebView keyboard support for Android versions prior to N. Support for UIs that also have Flutter `TextInput` fields is still pending. This basic support currently only works with Flutter `master`. The keyboard will still appear when it previously did not when run with older versions of Flutter. But if the WebView is resized while showing the keyboard the text field will need to be focused multiple times for any input to be registered. ## 0.3.9+2 * Update Dart code to conform to current Dart formatter. ## 0.3.9+1 * Add missing template type parameter to `invokeMethod` calls. * Bump minimum Flutter version to 1.5.0. * Replace invokeMethod with invokeMapMethod wherever necessary. ## 0.3.9 * Allow external packages to provide webview implementations for new platforms. ## 0.3.8+1 * Suppress deprecation warning for BinaryMessages. See: https://github.com/flutter/flutter/issues/33446 ## 0.3.8 * Add `debuggingEnabled` property. ## 0.3.7+1 * Fix an issue where JavaScriptChannel messages weren't sent from the platform thread on Android. ## 0.3.7 * Fix loadUrlWithHeaders flaky test. ## 0.3.6+1 * Remove un-used method params in webview\_flutter ## 0.3.6 * Add an optional `headers` field to the controller. ## 0.3.5+5 * Fixed error in documentation of `javascriptChannels`. ## 0.3.5+4 * Fix bugs in the example app by updating it to use a `StatefulWidget`. ## 0.3.5+3 * Make sure to post javascript channel messages from the platform thread. ## 0.3.5+2 * Fix crash from `NavigationDelegate` on later versions of Android. ## 0.3.5+1 * Fix a bug where updates to onPageFinished were ignored. ## 0.3.5 * Added an onPageFinished callback. ## 0.3.4 * Support specifying navigation delegates that can prevent navigations from being executed. ## 0.3.3+2 * Exclude LongPress handler from semantics tree since it does nothing. ## 0.3.3+1 * Fixed a memory leak on Android - the WebView was not properly disposed. ## 0.3.3 * Add clearCache method to WebView controller. ## 0.3.2+1 * Log a more detailed warning at build time about the previous AndroidX migration. ## 0.3.2 * Added CookieManager to interface with WebView cookies. Currently has the ability to clear cookies. ## 0.3.1 * Added JavaScript channels to facilitate message passing from JavaScript code running inside the WebView to the Flutter app's Dart code. ## 0.3.0 * **Breaking change**. Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to [also migrate](https://developer.android.com/jetpack/androidx/migrate) if they're using the original support library. ## 0.2.0 * Added a evaluateJavascript method to WebView controller. * (BREAKING CHANGE) Renamed the `JavaScriptMode` enum to `JavascriptMode`, and the WebView `javasScriptMode` parameter to `javascriptMode`. ## 0.1.2 * Added a reload method to the WebView controller. ## 0.1.1 * Added a `currentUrl` accessor for the WebView controller to look up what URL is being displayed. ## 0.1.0+1 * Fix null crash when initialUrl is unset on iOS. ## 0.1.0 * Add goBack, goForward, canGoBack, and canGoForward methods to the WebView controller. ## 0.0.1+1 * Fix case for "FLTWebViewFlutterPlugin" (iOS was failing to buld on case-sensitive file systems). ## 0.0.1 * Initial release. ================================================ FILE: packages/webview_flutter/webview_flutter/LICENSE ================================================ 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. ================================================ FILE: packages/webview_flutter/webview_flutter/README.md ================================================ # WebView for Flutter [![pub package](https://img.shields.io/pub/v/webview_flutter.svg)](https://pub.dev/packages/webview_flutter) A Flutter plugin that provides a WebView widget. On iOS the WebView widget is backed by a [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview). On Android the WebView widget is backed by a [WebView](https://developer.android.com/reference/android/webkit/WebView). | | Android | iOS | |-------------|----------------|------| | **Support** | SDK 19+ or 20+ | 9.0+ | ## Usage Add `webview_flutter` as a [dependency in your pubspec.yaml file](https://pub.dev/packages/webview_flutter/install). You can now display a WebView by: 1. Instantiating a [WebViewController](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController-class.html). ```dart controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setBackgroundColor(const Color(0x00000000)) ..setNavigationDelegate( NavigationDelegate( onProgress: (int progress) { // Update loading bar. }, onPageStarted: (String url) {}, onPageFinished: (String url) {}, onWebResourceError: (WebResourceError error) {}, onNavigationRequest: (NavigationRequest request) { if (request.url.startsWith('https://www.youtube.com/')) { return NavigationDecision.prevent; } return NavigationDecision.navigate; }, ), ) ..loadRequest(Uri.parse('https://flutter.dev')); ``` 2. Passing the controller to a [WebViewWidget](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewWidget-class.html). ```dart @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Flutter Simple Example')), body: WebViewWidget(controller: controller), ); } ``` See the Dartdocs for [WebViewController](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController-class.html) and [WebViewWidget](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewWidget-class.html) for more details. ### Android Platform Views This plugin uses [Platform Views](https://flutter.dev/docs/development/platform-integration/platform-views) to embed the Android’s WebView within the Flutter app. You should however make sure to set the correct `minSdkVersion` in `android/app/build.gradle` if it was previously lower than 19: ```groovy android { defaultConfig { minSdkVersion 19 } } ``` ### Platform-Specific Features Many classes have a subclass or an underlying implementation that provides access to platform-specific features. To access platform-specific features, start by adding the platform implementation packages to your app or package: * **Android**: [webview_flutter_android](https://pub.dev/packages/webview_flutter_android/install) * **iOS**: [webview_flutter_wkwebview](https://pub.dev/packages/webview_flutter_wkwebview/install) Next, add the imports of the implementation packages to your app or package: ```dart // Import for Android features. import 'package:webview_flutter_android/webview_flutter_android.dart'; // Import for iOS features. import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; ``` Now, additional features can be accessed through the platform implementations. Classes [WebViewController], [WebViewWidget], [NavigationDelegate], and [WebViewCookieManager] pass their functionality to a class provided by the current platform. Below are a couple of ways to access additional functionality provided by the platform and is followed by an example. 1. Pass a creation params class provided by a platform implementation to a `fromPlatformCreationParams` constructor (e.g. `WebViewController.fromPlatformCreationParams`, `WebViewWidget.fromPlatformCreationParams`, etc.). 2. Call methods on a platform implementation of a class by using the `platform` field (e.g. `WebViewController.platform`, `WebViewWidget.platform`, etc.). Below is an example of setting additional iOS and Android parameters on the `WebViewController`. ```dart late final PlatformWebViewControllerCreationParams params; if (WebViewPlatform.instance is WebKitWebViewPlatform) { params = WebKitWebViewControllerCreationParams( allowsInlineMediaPlayback: true, mediaTypesRequiringUserAction: const {}, ); } else { params = const PlatformWebViewControllerCreationParams(); } final WebViewController controller = WebViewController.fromPlatformCreationParams(params); // ··· if (controller.platform is AndroidWebViewController) { AndroidWebViewController.enableDebugging(true); (controller.platform as AndroidWebViewController) .setMediaPlaybackRequiresUserGesture(false); } ``` See https://pub.dev/documentation/webview_flutter_android/latest/webview_flutter_android/webview_flutter_android-library.html for more details on Android features. See https://pub.dev/documentation/webview_flutter_wkwebview/latest/webview_flutter_wkwebview/webview_flutter_wkwebview-library.html for more details on iOS features. ### Enable Material Components for Android To use Material Components when the user interacts with input elements in the WebView, follow the steps described in the [Enabling Material Components instructions](https://flutter.dev/docs/deployment/android#enabling-material-components). ### Setting custom headers on POST requests Currently, setting custom headers when making a post request with the WebViewController's `loadRequest` method is not supported on Android. If you require this functionality, a workaround is to make the request manually, and then load the response data using `loadHtmlString` instead. ## Migrating from 3.0 to 4.0 ### Instantiating WebViewController In version 3.0 and below, `WebViewController` could only be retrieved in a callback after the `WebView` was added to the widget tree. Now, `WebViewController` must be instantiated and can be used before it is added to the widget tree. See `Usage` section above for an example. ### Replacing WebView Functionality The `WebView` class has been removed and its functionality has been split into `WebViewController` and `WebViewWidget`. `WebViewController` handles all functionality that is associated with the underlying web view provided by each platform. (e.g., loading a url, setting the background color of the underlying platform view, or clearing the cache). `WebViewWidget` takes a `WebViewController` and handles all Flutter widget related functionality (e.g., layout direction, gesture recognizers). See the Dartdocs for [WebViewController](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController-class.html) and [WebViewWidget](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewWidget-class.html) for more details. ### PlatformView Implementation on Android The PlatformView implementation for Android is currently no longer configurable. It uses Texture Layer Hybrid Composition on versions 23+ and automatically fallbacks to Hybrid Composition for version 19-23. See https://github.com/flutter/flutter/issues/108106 for progress on manually switching to Hybrid Composition on versions 23+. ### API Changes Below is a non-exhaustive list of changes to the API: * `WebViewController.clearCache` no longer clears local storage. Please use `WebViewController.clearLocalStorage`. * `WebViewController.clearCache` no longer reloads the page. * `WebViewController.loadUrl` has been removed. Please use `WebViewController.loadRequest`. * `WebViewController.evaluateJavascript` has been removed. Please use `WebViewController.runJavaScript` or `WebViewController.runJavaScriptReturningResult`. * `WebViewController.getScrollX` and `WebViewController.getScrollY` have been removed and have been replaced by `WebViewController.getScrollPosition`. * `WebViewController.runJavaScriptReturningResult` now returns an `Object` and not a `String`. This will attempt to return a `bool` or `num` if the return value can be parsed. * `CookieManager` is replaced by `WebViewCookieManager`. * `NavigationDelegate.onWebResourceError` callback includes errors that are not from the main frame. Use the `WebResourceError.isForMainFrame` field to filter errors. * The following fields from `WebView` have been moved to `NavigationDelegate`. They can be added to a WebView with `WebViewController.setNavigationDelegate`. * `WebView.navigationDelegate` -> `NavigationDelegate.onNavigationRequest` * `WebView.onPageStarted` -> `NavigationDelegate.onPageStarted` * `WebView.onPageFinished` -> `NavigationDelegate.onPageFinished` * `WebView.onProgress` -> `NavigationDelegate.onProgress` * `WebView.onWebResourceError` -> `NavigationDelegate.onWebResourceError` * The following fields from `WebView` have been moved to `WebViewController`: * `WebView.javascriptMode` -> `WebViewController.setJavaScriptMode` * `WebView.javascriptChannels` -> `WebViewController.addJavaScriptChannel`/`WebViewController.removeJavaScriptChannel` * `WebView.zoomEnabled` -> `WebViewController.enableZoom` * `WebView.userAgent` -> `WebViewController.setUserAgent` * `WebView.backgroundColor` -> `WebViewController.setBackgroundColor` * The following features have been moved to an Android implementation class. See section `Platform-Specific Features` for details on accessing Android platform-specific features. * `WebView.debuggingEnabled` -> `static AndroidWebViewController.enableDebugging` * `WebView.initialMediaPlaybackPolicy` -> `AndroidWebViewController.setMediaPlaybackRequiresUserGesture` * The following features have been moved to an iOS implementation class. See section `Platform-Specific Features` for details on accessing iOS platform-specific features. * `WebView.gestureNavigationEnabled` -> `WebKitWebViewController.setAllowsBackForwardNavigationGestures` * `WebView.initialMediaPlaybackPolicy` -> `WebKitWebViewControllerCreationParams.mediaTypesRequiringUserAction` * `WebView.allowsInlineMediaPlayback` -> `WebKitWebViewControllerCreationParams.allowsInlineMediaPlayback` [WebViewController]: https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewController-class.html [WebViewWidget]: https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewWidget-class.html [NavigationDelegate]: https://pub.dev/documentation/webview_flutter/latest/webview_flutter/NavigationDelegate-class.html [WebViewCookieManager]: https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebViewCookieManager-class.html ================================================ FILE: packages/webview_flutter/webview_flutter/example/.metadata ================================================ # 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: 1e5cb2d87f8542f9fbbd0f22d528823274be0acb channel: master ================================================ FILE: packages/webview_flutter/webview_flutter/example/README.md ================================================ # webview_flutter_example Demonstrates how to use the webview_flutter plugin. ================================================ FILE: packages/webview_flutter/webview_flutter/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 32 lintOptions { disable 'InvalidPackage' } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "io.flutter.plugins.webviewflutterexample" minSdkVersion 21 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' api 'androidx.test:core:1.2.0' } ================================================ FILE: packages/webview_flutter/webview_flutter/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/webview_flutter/webview_flutter/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/webview_flutter/webview_flutter/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/MainActivityTest.java ================================================ // Copyright 2013 The Flutter 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.webviewflutterexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class MainActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/webview_flutter/webview_flutter/example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/WebViewTestActivity.java ================================================ // Copyright 2013 The Flutter 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.webviewflutterexample; import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; // Extends FlutterActivity to make the FlutterEngine accessible for testing. public class WebViewTestActivity extends FlutterActivity { public FlutterEngine engine; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); engine = flutterEngine; } } ================================================ FILE: packages/webview_flutter/webview_flutter/example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter/example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.1' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/webview_flutter/webview_flutter/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip ================================================ FILE: packages/webview_flutter/webview_flutter/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true android.enableR8=true ================================================ FILE: packages/webview_flutter/webview_flutter/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/webview_flutter/webview_flutter/example/assets/www/index.html ================================================ Load file or HTML string example

Local demo page

This is an example page used to demonstrate how to load a local file or HTML string using the Flutter webview plugin.

================================================ FILE: packages/webview_flutter/webview_flutter/example/assets/www/styles/style.css ================================================ h1 { color: blue; } ================================================ FILE: packages/webview_flutter/webview_flutter/example/build.excerpt.yaml ================================================ targets: $default: sources: include: - lib/** # Some default includes that aren't really used here but will prevent # false-negative warnings: - $package$ - lib/$lib$ exclude: - '**/.*/**' - '**/build/**' builders: code_excerpter|code_excerpter: enabled: true ================================================ FILE: packages/webview_flutter/webview_flutter/example/integration_test/legacy/webview_flutter_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This test is run using `flutter drive` by the CI (see /script/tool/README.md // in this repository for details on driving that tooling manually), but can // also be run using `flutter test` directly during development. import 'dart:async'; import 'dart:convert'; import 'dart:io'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/foundation.dart'; 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/src/webview_flutter_legacy.dart'; Future main() async { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); final HttpServer server = await HttpServer.bind(InternetAddress.anyIPv4, 0); server.forEach((HttpRequest request) { if (request.uri.path == '/hello.txt') { request.response.writeln('Hello, world.'); } else if (request.uri.path == '/secondary.txt') { request.response.writeln('How are you today?'); } else if (request.uri.path == '/headers') { request.response.writeln('${request.headers}'); } else if (request.uri.path == '/favicon.ico') { request.response.statusCode = HttpStatus.notFound; } else { fail('unexpected request: ${request.method} ${request.uri}'); } request.response.close(); }); final String prefixUrl = 'http://${server.address.address}:${server.port}'; final String primaryUrl = '$prefixUrl/hello.txt'; final String secondaryUrl = '$prefixUrl/secondary.txt'; final String headersUrl = '$prefixUrl/headers'; testWidgets('initialUrl', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageFinishedCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, onPageFinished: pageFinishedCompleter.complete, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageFinishedCompleter.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); testWidgets('loadUrl', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final StreamController pageLoads = StreamController(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, onPageFinished: (String url) { pageLoads.add(url); }, ), ), ); final WebViewController controller = await controllerCompleter.future; await controller.loadUrl(secondaryUrl); await expectLater( pageLoads.stream.firstWhere((String url) => url == secondaryUrl), completion(secondaryUrl), ); }); testWidgets('evaluateJavascript', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, ), ), ); final WebViewController controller = await controllerCompleter.future; // ignore: deprecated_member_use final String result = await controller.evaluateJavascript('1 + 1'); expect(result, equals('2')); }); testWidgets('loadUrl with headers', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final StreamController pageStarts = StreamController(); final StreamController pageLoads = StreamController(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageStarted: (String url) { pageStarts.add(url); }, onPageFinished: (String url) { pageLoads.add(url); }, ), ), ); final WebViewController controller = await controllerCompleter.future; final Map headers = { 'test_header': 'flutter_test_header' }; await controller.loadUrl(headersUrl, headers: headers); final String? currentUrl = await controller.currentUrl(); expect(currentUrl, headersUrl); await pageStarts.stream.firstWhere((String url) => url == currentUrl); await pageLoads.stream.firstWhere((String url) => url == currentUrl); final String content = await controller .runJavascriptReturningResult('document.documentElement.innerText'); expect(content.contains('flutter_test_header'), isTrue); }); testWidgets('JavascriptChannel', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageStarted = Completer(); final Completer pageLoaded = Completer(); final Completer channelCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), // This is the data URL for: '' initialUrl: 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, javascriptChannels: { JavascriptChannel( name: 'Echo', onMessageReceived: (JavascriptMessage message) { channelCompleter.complete(message.message); }, ), }, onPageStarted: (String url) { pageStarted.complete(null); }, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageStarted.future; await pageLoaded.future; expect(channelCompleter.isCompleted, isFalse); await controller.runJavascript('Echo.postMessage("hello");'); await expectLater(channelCompleter.future, completion('hello')); }); testWidgets('resize webview', (WidgetTester tester) async { final Completer initialResizeCompleter = Completer(); final Completer buttonTapResizeCompleter = Completer(); final Completer onPageFinished = Completer(); bool resizeButtonTapped = false; await tester.pumpWidget(ResizableWebView( onResize: (_) { if (resizeButtonTapped) { buttonTapResizeCompleter.complete(); } else { initialResizeCompleter.complete(); } }, onPageFinished: () => onPageFinished.complete(), )); await onPageFinished.future; // Wait for a potential call to resize after page is loaded. await initialResizeCompleter.future.timeout( const Duration(seconds: 3), onTimeout: () => null, ); resizeButtonTapped = true; await tester.tap(find.byKey(const ValueKey('resizeButton'))); await tester.pumpAndSettle(); expect(buttonTapResizeCompleter.future, completes); }); testWidgets('set custom userAgent', (WidgetTester tester) async { final Completer controllerCompleter1 = Completer(); final GlobalKey globalKey = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: globalKey, initialUrl: 'about:blank', javascriptMode: JavascriptMode.unrestricted, userAgent: 'Custom_User_Agent1', onWebViewCreated: (WebViewController controller) { controllerCompleter1.complete(controller); }, ), ), ); final WebViewController controller1 = await controllerCompleter1.future; final String customUserAgent1 = await _getUserAgent(controller1); expect(customUserAgent1, 'Custom_User_Agent1'); // rebuild the WebView with a different user agent. await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: globalKey, initialUrl: 'about:blank', javascriptMode: JavascriptMode.unrestricted, userAgent: 'Custom_User_Agent2', ), ), ); final String customUserAgent2 = await _getUserAgent(controller1); expect(customUserAgent2, 'Custom_User_Agent2'); }); testWidgets('use default platform userAgent after webView is rebuilt', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final GlobalKey globalKey = GlobalKey(); // Build the webView with no user agent to get the default platform user agent. await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: globalKey, initialUrl: primaryUrl, javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, ), ), ); final WebViewController controller = await controllerCompleter.future; final String defaultPlatformUserAgent = await _getUserAgent(controller); // rebuild the WebView with a custom user agent. await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: globalKey, initialUrl: 'about:blank', javascriptMode: JavascriptMode.unrestricted, userAgent: 'Custom_User_Agent', ), ), ); final String customUserAgent = await _getUserAgent(controller); expect(customUserAgent, 'Custom_User_Agent'); // rebuilds the WebView with no user agent. await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: globalKey, initialUrl: 'about:blank', javascriptMode: JavascriptMode.unrestricted, ), ), ); final String customUserAgent2 = await _getUserAgent(controller); expect(customUserAgent2, defaultPlatformUserAgent); }); group('Video playback policy', () { late String videoTestBase64; setUpAll(() async { final ByteData videoData = await rootBundle.load('assets/sample_video.mp4'); final String base64VideoData = base64Encode(Uint8List.view(videoData.buffer)); final String videoTest = ''' Video auto play '''; videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest)); }); testWidgets('Auto media playback', (WidgetTester tester) async { Completer controllerCompleter = Completer(); Completer pageLoaded = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (String url) { pageLoaded.complete(null); }, initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, ), ), ); WebViewController controller = await controllerCompleter.future; await pageLoaded.future; String isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(false)); controllerCompleter = Completer(); pageLoaded = Completer(); // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); controller = await controllerCompleter.future; await pageLoaded.future; isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(true)); }); testWidgets('Changes to initialMediaPlaybackPolicy are ignored', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); Completer pageLoaded = Completer(); final GlobalKey key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: key, initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (String url) { pageLoaded.complete(null); }, initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; String isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(false)); pageLoaded = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: key, initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); await controller.reload(); await pageLoaded.future; isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(false)); }); testWidgets('Video plays inline when allowsInlineMediaPlayback is true', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageLoaded = Completer(); final Completer videoPlaying = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, javascriptChannels: { JavascriptChannel( name: 'VideoTestTime', onMessageReceived: (JavascriptMessage message) { final double currentTime = double.parse(message.message); // Let it play for at least 1 second to make sure the related video's properties are set. if (currentTime > 1 && !videoPlaying.isCompleted) { videoPlaying.complete(null); } }, ), }, onPageFinished: (String url) { pageLoaded.complete(null); }, initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, allowsInlineMediaPlayback: true, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; // Pump once to trigger the video play. await tester.pump(); // Makes sure we get the correct event that indicates the video is actually playing. await videoPlaying.future; final String fullScreen = await controller.runJavascriptReturningResult('isFullScreen();'); expect(fullScreen, _webviewBool(false)); }); // allowsInlineMediaPlayback is a noop on Android, so it is skipped. testWidgets( 'Video plays full screen when allowsInlineMediaPlayback is false', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageLoaded = Completer(); final Completer videoPlaying = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, javascriptChannels: { JavascriptChannel( name: 'VideoTestTime', onMessageReceived: (JavascriptMessage message) { final double currentTime = double.parse(message.message); // Let it play for at least 1 second to make sure the related video's properties are set. if (currentTime > 1 && !videoPlaying.isCompleted) { videoPlaying.complete(null); } }, ), }, onPageFinished: (String url) { pageLoaded.complete(null); }, initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; // Pump once to trigger the video play. await tester.pump(); // Makes sure we get the correct event that indicates the video is actually playing. await videoPlaying.future; final String fullScreen = await controller.runJavascriptReturningResult('isFullScreen();'); expect(fullScreen, _webviewBool(true)); }, skip: Platform.isAndroid); }); group('Audio playback policy', () { late String audioTestBase64; setUpAll(() async { final ByteData audioData = await rootBundle.load('assets/sample_audio.ogg'); final String base64AudioData = base64Encode(Uint8List.view(audioData.buffer)); final String audioTest = ''' Audio auto play '''; audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest)); }); testWidgets('Auto media playback', (WidgetTester tester) async { Completer controllerCompleter = Completer(); Completer pageStarted = Completer(); Completer pageLoaded = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageStarted: (String url) { pageStarted.complete(null); }, onPageFinished: (String url) { pageLoaded.complete(null); }, initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, ), ), ); WebViewController controller = await controllerCompleter.future; await pageStarted.future; await pageLoaded.future; String isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(false)); controllerCompleter = Completer(); pageStarted = Completer(); pageLoaded = Completer(); // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageStarted: (String url) { pageStarted.complete(null); }, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); controller = await controllerCompleter.future; await pageStarted.future; await pageLoaded.future; isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(true)); }); testWidgets('Changes to initialMediaPlaybackPolicy are ignored', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); Completer pageStarted = Completer(); Completer pageLoaded = Completer(); final GlobalKey key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: key, initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageStarted: (String url) { pageStarted.complete(null); }, onPageFinished: (String url) { pageLoaded.complete(null); }, initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageStarted.future; await pageLoaded.future; String isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(false)); pageStarted = Completer(); pageLoaded = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: key, initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageStarted: (String url) { pageStarted.complete(null); }, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); await controller.reload(); await pageStarted.future; await pageLoaded.future; isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(false)); }); }); testWidgets('getTitle', (WidgetTester tester) async { const String getTitleTest = ''' Some title '''; final String getTitleTestBase64 = base64Encode(const Utf8Encoder().convert(getTitleTest)); final Completer pageStarted = Completer(); final Completer pageLoaded = Completer(); final Completer controllerCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( initialUrl: 'data:text/html;charset=utf-8;base64,$getTitleTestBase64', javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, onPageStarted: (String url) { pageStarted.complete(null); }, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageStarted.future; await pageLoaded.future; // On at least iOS, it does not appear to be guaranteed that the native // code has the title when the page load completes. Execute some JavaScript // before checking the title to ensure that the page has been fully parsed // and processed. await controller.runJavascript('1;'); final String? title = await controller.getTitle(); expect(title, 'Some title'); }); group('Programmatic Scroll', () { testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { const String scrollTestPage = '''
'''; final String scrollTestPageBase64 = base64Encode(const Utf8Encoder().convert(scrollTestPage)); final Completer pageLoaded = Completer(); final Completer controllerCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( initialUrl: 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; await tester.pumpAndSettle(const Duration(seconds: 3)); int scrollPosX = await controller.getScrollX(); int scrollPosY = await controller.getScrollY(); // Check scrollTo() const int X_SCROLL = 123; const int Y_SCROLL = 321; // Get the initial position; this ensures that scrollTo is actually // changing something, but also gives the native view's scroll position // time to settle. expect(scrollPosX, isNot(X_SCROLL)); expect(scrollPosX, isNot(Y_SCROLL)); await controller.scrollTo(X_SCROLL, Y_SCROLL); scrollPosX = await controller.getScrollX(); scrollPosY = await controller.getScrollY(); expect(scrollPosX, X_SCROLL); expect(scrollPosY, Y_SCROLL); // Check scrollBy() (on top of scrollTo()) await controller.scrollBy(X_SCROLL, Y_SCROLL); scrollPosX = await controller.getScrollX(); scrollPosY = await controller.getScrollY(); expect(scrollPosX, X_SCROLL * 2); expect(scrollPosY, Y_SCROLL * 2); }); }); // Minimal end-to-end testing of the legacy Android implementation. group('AndroidWebView (virtual display)', () { setUpAll(() { WebView.platform = AndroidWebView(); }); tearDownAll(() { WebView.platform = null; }); testWidgets('initialUrl', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageFinishedCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, onPageFinished: pageFinishedCompleter.complete, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageFinishedCompleter.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); }, skip: !Platform.isAndroid); group('NavigationDelegate', () { const String blankPage = ''; final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' '${base64Encode(const Utf8Encoder().convert(blankPage))}'; testWidgets('can allow requests', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final StreamController pageLoads = StreamController.broadcast(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: blankPageEncoded, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, navigationDelegate: (NavigationRequest request) { return (request.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; }, onPageFinished: (String url) => pageLoads.add(url), ), ), ); await pageLoads.stream.first; // Wait for initial page load. final WebViewController controller = await controllerCompleter.future; await controller.runJavascript('location.href = "$secondaryUrl"'); await pageLoads.stream.first; // Wait for the next page load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); testWidgets('onWebResourceError', (WidgetTester tester) async { final Completer errorCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'https://www.notawebsite..com', onWebResourceError: (WebResourceError error) { errorCompleter.complete(error); }, ), ), ); final WebResourceError error = await errorCompleter.future; expect(error, isNotNull); if (Platform.isIOS) { expect(error.domain, isNotNull); expect(error.failingUrl, isNull); } else if (Platform.isAndroid) { expect(error.errorType, isNotNull); expect(error.failingUrl?.startsWith('https://www.notawebsite..com'), isTrue); } }); testWidgets('onWebResourceError is not called with valid url', (WidgetTester tester) async { final Completer errorCompleter = Completer(); final Completer pageFinishCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', onWebResourceError: (WebResourceError error) { errorCompleter.complete(error); }, onPageFinished: (_) => pageFinishCompleter.complete(), ), ), ); expect(errorCompleter.future, doesNotComplete); await pageFinishCompleter.future; }); testWidgets( 'onWebResourceError only called for main frame', (WidgetTester tester) async { const String iframeTest = ''' WebResourceError test '''; final String iframeTestBase64 = base64Encode(const Utf8Encoder().convert(iframeTest)); final Completer errorCompleter = Completer(); final Completer pageFinishCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'data:text/html;charset=utf-8;base64,$iframeTestBase64', onWebResourceError: (WebResourceError error) { errorCompleter.complete(error); }, onPageFinished: (_) => pageFinishCompleter.complete(), ), ), ); expect(errorCompleter.future, doesNotComplete); await pageFinishCompleter.future; }, ); testWidgets('can block requests', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final StreamController pageLoads = StreamController.broadcast(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: blankPageEncoded, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, navigationDelegate: (NavigationRequest request) { return (request.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; }, onPageFinished: (String url) => pageLoads.add(url), ), ), ); await pageLoads.stream.first; // Wait for initial page load. final WebViewController controller = await controllerCompleter.future; 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 .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 Completer controllerCompleter = Completer(); final StreamController pageLoads = StreamController.broadcast(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: blankPageEncoded, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, navigationDelegate: (NavigationRequest request) async { NavigationDecision decision = NavigationDecision.prevent; decision = await Future.delayed( const Duration(milliseconds: 10), () => NavigationDecision.navigate); return decision; }, onPageFinished: (String url) => pageLoads.add(url), ), ), ); await pageLoads.stream.first; // Wait for initial page load. final WebViewController controller = await controllerCompleter.future; await controller.runJavascript('location.href = "$secondaryUrl"'); await pageLoads.stream.first; // Wait for second page to load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); }); testWidgets('launches with gestureNavigationEnabled on iOS', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: SizedBox( width: 400, height: 300, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, gestureNavigationEnabled: true, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, ), ), ), ); final WebViewController controller = await controllerCompleter.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); testWidgets('target _blank opens in same window', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageLoaded = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); final WebViewController controller = await controllerCompleter.future; await controller.runJavascript('window.open("$primaryUrl", "_blank")'); await pageLoaded.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); testWidgets( 'can open new window and go back', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); Completer pageLoaded = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (String url) { pageLoaded.complete(); }, initialUrl: primaryUrl, ), ), ); final WebViewController controller = await controllerCompleter.future; expect(controller.currentUrl(), completion(primaryUrl)); await pageLoaded.future; pageLoaded = Completer(); await controller.runJavascript('window.open("$secondaryUrl")'); await pageLoaded.future; pageLoaded = Completer(); expect(controller.currentUrl(), completion(secondaryUrl)); expect(controller.canGoBack(), completion(true)); await controller.goBack(); await pageLoaded.future; await expectLater(controller.currentUrl(), completion(primaryUrl)); }, ); testWidgets( 'clearCache should clear local storage', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); Completer pageLoadCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (_) => pageLoadCompleter.complete(), onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, ), ), ); await pageLoadCompleter.future; pageLoadCompleter = Completer(); final WebViewController controller = await controllerCompleter.future; await controller.runJavascript('localStorage.setItem("myCat", "Tom");'); final String myCatItem = await controller.runJavascriptReturningResult( 'localStorage.getItem("myCat");', ); expect(myCatItem, _webviewString('Tom')); await controller.clearCache(); await pageLoadCompleter.future; late final String? nullItem; try { nullItem = await controller.runJavascriptReturningResult( 'localStorage.getItem("myCat");', ); } catch (exception) { if (defaultTargetPlatform == TargetPlatform.iOS && exception is ArgumentError && (exception.message as String).contains( 'Result of JavaScript execution returned a `null` value.')) { nullItem = ''; } } expect(nullItem, _webviewNull()); }, ); } // JavaScript booleans evaluate to different string values on Android and iOS. // This utility method returns the string boolean value of the current platform. String _webviewBool(bool value) { if (defaultTargetPlatform == TargetPlatform.iOS) { return value ? '1' : '0'; } return value ? 'true' : 'false'; } // JavaScript `null` evaluate to different string values on Android and iOS. // This utility method returns the string boolean value of the current platform. String _webviewNull() { if (defaultTargetPlatform == TargetPlatform.iOS) { return ''; } return 'null'; } // JavaScript String evaluate to different string values on Android and iOS. // This utility method returns the string boolean value of the current platform. String _webviewString(String value) { if (defaultTargetPlatform == TargetPlatform.iOS) { return value; } return '"$value"'; } /// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests. Future _getUserAgent(WebViewController controller) async { return _runJavascriptReturningResult(controller, 'navigator.userAgent;'); } Future _runJavascriptReturningResult( WebViewController controller, String js) async { if (defaultTargetPlatform == TargetPlatform.iOS) { return controller.runJavascriptReturningResult(js); } return jsonDecode(await controller.runJavascriptReturningResult(js)) as String; } class ResizableWebView extends StatefulWidget { const ResizableWebView({ super.key, required this.onResize, required this.onPageFinished, }); final JavascriptMessageHandler onResize; final VoidCallback onPageFinished; @override State createState() => ResizableWebViewState(); } class ResizableWebViewState extends State { double webViewWidth = 200; double webViewHeight = 200; static const String resizePage = ''' Resize test '''; @override Widget build(BuildContext context) { final String resizeTestBase64 = base64Encode(const Utf8Encoder().convert(resizePage)); return Directionality( textDirection: TextDirection.ltr, child: Column( children: [ SizedBox( width: webViewWidth, height: webViewHeight, child: WebView( initialUrl: 'data:text/html;charset=utf-8;base64,$resizeTestBase64', javascriptChannels: { JavascriptChannel( name: 'Resize', onMessageReceived: widget.onResize, ), }, onPageFinished: (_) => widget.onPageFinished(), javascriptMode: JavascriptMode.unrestricted, ), ), TextButton( key: const Key('resizeButton'), onPressed: () { setState(() { webViewWidth += 100.0; webViewHeight += 100.0; }); }, child: const Text('ResizeButton'), ), ], ), ); } } ================================================ FILE: packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This test is run using `flutter drive` by the CI (see /script/tool/README.md // in this repository for details on driving that tooling manually), but can // also be run using `flutter test` directly during development. import 'dart:async'; import 'dart:convert'; import 'dart:io'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/foundation.dart'; 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/webview_flutter.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; Future main() async { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); final HttpServer server = await HttpServer.bind(InternetAddress.anyIPv4, 0); server.forEach((HttpRequest request) { if (request.uri.path == '/hello.txt') { request.response.writeln('Hello, world.'); } else if (request.uri.path == '/secondary.txt') { request.response.writeln('How are you today?'); } else if (request.uri.path == '/headers') { request.response.writeln('${request.headers}'); } else if (request.uri.path == '/favicon.ico') { request.response.statusCode = HttpStatus.notFound; } else { fail('unexpected request: ${request.method} ${request.uri}'); } request.response.close(); }); final String prefixUrl = 'http://${server.address.address}:${server.port}'; final String primaryUrl = '$prefixUrl/hello.txt'; final String secondaryUrl = '$prefixUrl/secondary.txt'; final String headersUrl = '$prefixUrl/headers'; testWidgets('loadRequest', (WidgetTester tester) async { final Completer pageFinished = Completer(); final WebViewController controller = WebViewController() ..setNavigationDelegate( NavigationDelegate(onPageFinished: (_) => pageFinished.complete()), ) ..loadRequest(Uri.parse(primaryUrl)); await tester.pumpWidget(WebViewWidget(controller: controller)); await pageFinished.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); testWidgets('runJavaScriptReturningResult', (WidgetTester tester) async { final Completer pageFinished = Completer(); final WebViewController controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( NavigationDelegate(onPageFinished: (_) => pageFinished.complete()), ) ..loadRequest(Uri.parse(primaryUrl)); await tester.pumpWidget(WebViewWidget(controller: controller)); await pageFinished.future; await expectLater( controller.runJavaScriptReturningResult('1 + 1'), completion(2), ); }); testWidgets('loadRequest with headers', (WidgetTester tester) async { final Map headers = { 'test_header': 'flutter_test_header' }; final StreamController pageLoads = StreamController(); final WebViewController controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( NavigationDelegate(onPageFinished: (String url) => pageLoads.add(url)), ); await tester.pumpWidget(WebViewWidget(controller: controller)); controller.loadRequest(Uri.parse(headersUrl), headers: headers); await pageLoads.stream.firstWhere((String url) => url == headersUrl); final String content = await controller.runJavaScriptReturningResult( 'document.documentElement.innerText', ) as String; expect(content.contains('flutter_test_header'), isTrue); }); testWidgets('JavascriptChannel', (WidgetTester tester) async { final Completer pageFinished = Completer(); final WebViewController controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( NavigationDelegate(onPageFinished: (_) => pageFinished.complete()), ); final Completer channelCompleter = Completer(); await controller.addJavaScriptChannel( 'Echo', onMessageReceived: (JavaScriptMessage message) { channelCompleter.complete(message.message); }, ); await controller.loadHtmlString( 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', ); await tester.pumpWidget(WebViewWidget(controller: controller)); await pageFinished.future; await controller.runJavaScript('Echo.postMessage("hello");'); await expectLater(channelCompleter.future, completion('hello')); }); testWidgets('resize webview', (WidgetTester tester) async { final Completer initialResizeCompleter = Completer(); final Completer buttonTapResizeCompleter = Completer(); final Completer onPageFinished = Completer(); bool resizeButtonTapped = false; await tester.pumpWidget(ResizableWebView( onResize: () { if (resizeButtonTapped) { buttonTapResizeCompleter.complete(); } else { initialResizeCompleter.complete(); } }, onPageFinished: () => onPageFinished.complete(), )); await onPageFinished.future; // Wait for a potential call to resize after page is loaded. await initialResizeCompleter.future.timeout( const Duration(seconds: 3), onTimeout: () => null, ); resizeButtonTapped = true; await tester.tap(find.byKey(const ValueKey('resizeButton'))); await tester.pumpAndSettle(); await expectLater(buttonTapResizeCompleter.future, completes); }); testWidgets('set custom userAgent', (WidgetTester tester) async { final Completer pageFinished = Completer(); final WebViewController controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageFinished.complete(), )) ..setUserAgent('Custom_User_Agent1') ..loadRequest(Uri.parse('about:blank')); await tester.pumpWidget(WebViewWidget(controller: controller)); await pageFinished.future; final String customUserAgent = await _getUserAgent(controller); expect(customUserAgent, 'Custom_User_Agent1'); }); group('Video playback policy', () { late String videoTestBase64; setUpAll(() async { final ByteData videoData = await rootBundle.load('assets/sample_video.mp4'); final String base64VideoData = base64Encode(Uint8List.view(videoData.buffer)); final String videoTest = ''' Video auto play '''; videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest)); }); testWidgets('Auto media playback', (WidgetTester tester) async { Completer pageLoaded = Completer(); late PlatformWebViewControllerCreationParams params; if (defaultTargetPlatform == TargetPlatform.iOS) { params = WebKitWebViewControllerCreationParams( mediaTypesRequiringUserAction: const {}, ); } else { params = const PlatformWebViewControllerCreationParams(); } WebViewController controller = WebViewController.fromPlatformCreationParams(params) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), ); if (controller.platform is AndroidWebViewController) { (controller.platform as AndroidWebViewController) .setMediaPlaybackRequiresUserGesture(false); } await controller.loadRequest( Uri.parse('data:text/html;charset=utf-8;base64,$videoTestBase64'), ); await tester.pumpWidget(WebViewWidget(controller: controller)); await pageLoaded.future; bool isPaused = await controller.runJavaScriptReturningResult('isPaused();') as bool; expect(isPaused, false); pageLoaded = Completer(); controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), ) ..loadRequest( Uri.parse('data:text/html;charset=utf-8;base64,$videoTestBase64'), ); await tester.pumpWidget(WebViewWidget(controller: controller)); await pageLoaded.future; isPaused = await controller.runJavaScriptReturningResult('isPaused();') as bool; expect(isPaused, true); }); testWidgets('Video plays inline', (WidgetTester tester) async { final Completer pageLoaded = Completer(); final Completer videoPlaying = Completer(); late PlatformWebViewControllerCreationParams params; if (defaultTargetPlatform == TargetPlatform.iOS) { params = WebKitWebViewControllerCreationParams( mediaTypesRequiringUserAction: const {}, allowsInlineMediaPlayback: true, ); } else { params = const PlatformWebViewControllerCreationParams(); } final WebViewController controller = WebViewController.fromPlatformCreationParams(params) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), ) ..addJavaScriptChannel( 'VideoTestTime', onMessageReceived: (JavaScriptMessage message) { final double currentTime = double.parse(message.message); // Let it play for at least 1 second to make sure the related video's properties are set. if (currentTime > 1 && !videoPlaying.isCompleted) { videoPlaying.complete(null); } }, ); if (controller.platform is AndroidWebViewController) { (controller.platform as AndroidWebViewController) .setMediaPlaybackRequiresUserGesture(false); } await controller.loadRequest( Uri.parse('data:text/html;charset=utf-8;base64,$videoTestBase64'), ); await tester.pumpWidget(WebViewWidget(controller: controller)); await tester.pumpAndSettle(); await pageLoaded.future; // Makes sure we get the correct event that indicates the video is actually playing. await videoPlaying.future; final bool fullScreen = await controller .runJavaScriptReturningResult('isFullScreen();') as bool; expect(fullScreen, false); }); // allowsInlineMediaPlayback is a noop on Android, so it is skipped. testWidgets( 'Video plays full screen when allowsInlineMediaPlayback is false', (WidgetTester tester) async { final Completer pageLoaded = Completer(); final Completer videoPlaying = Completer(); final WebViewController controller = WebViewController.fromPlatformCreationParams( WebKitWebViewControllerCreationParams( mediaTypesRequiringUserAction: const {}, ), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), ) ..addJavaScriptChannel( 'VideoTestTime', onMessageReceived: (JavaScriptMessage message) { final double currentTime = double.parse(message.message); // Let it play for at least 1 second to make sure the related video's properties are set. if (currentTime > 1 && !videoPlaying.isCompleted) { videoPlaying.complete(null); } }, ) ..loadRequest( Uri.parse( 'data:text/html;charset=utf-8;base64,$videoTestBase64', ), ); await tester.pumpWidget(WebViewWidget(controller: controller)); await tester.pumpAndSettle(); await pageLoaded.future; // Makes sure we get the correct event that indicates the video is actually playing. await videoPlaying.future; final bool fullScreen = await controller .runJavaScriptReturningResult('isFullScreen();') as bool; expect(fullScreen, true); }, skip: Platform.isAndroid); }); group('Audio playback policy', () { late String audioTestBase64; setUpAll(() async { final ByteData audioData = await rootBundle.load('assets/sample_audio.ogg'); final String base64AudioData = base64Encode(Uint8List.view(audioData.buffer)); final String audioTest = ''' Audio auto play '''; audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest)); }); testWidgets('Auto media playback', (WidgetTester tester) async { Completer pageLoaded = Completer(); late PlatformWebViewControllerCreationParams params; if (defaultTargetPlatform == TargetPlatform.iOS) { params = WebKitWebViewControllerCreationParams( mediaTypesRequiringUserAction: const {}, ); } else { params = const PlatformWebViewControllerCreationParams(); } WebViewController controller = WebViewController.fromPlatformCreationParams(params) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), ); if (controller.platform is AndroidWebViewController) { (controller.platform as AndroidWebViewController) .setMediaPlaybackRequiresUserGesture(false); } await controller.loadRequest( Uri.parse('data:text/html;charset=utf-8;base64,$audioTestBase64'), ); await tester.pumpWidget(WebViewWidget(controller: controller)); await tester.pumpAndSettle(); await pageLoaded.future; bool isPaused = await controller.runJavaScriptReturningResult('isPaused();') as bool; expect(isPaused, false); pageLoaded = Completer(); controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( NavigationDelegate(onPageFinished: (_) => pageLoaded.complete()), ) ..loadRequest( Uri.parse('data:text/html;charset=utf-8;base64,$audioTestBase64'), ); await tester.pumpWidget(WebViewWidget(controller: controller)); await tester.pumpAndSettle(); await pageLoaded.future; isPaused = await controller.runJavaScriptReturningResult('isPaused();') as bool; expect(isPaused, true); }); }); testWidgets('getTitle', (WidgetTester tester) async { const String getTitleTest = ''' Some title '''; final String getTitleTestBase64 = base64Encode(const Utf8Encoder().convert(getTitleTest)); final Completer pageLoaded = Completer(); final WebViewController controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageLoaded.complete(), )) ..loadRequest( Uri.parse('data:text/html;charset=utf-8;base64,$getTitleTestBase64'), ); await tester.pumpWidget(WebViewWidget(controller: controller)); await pageLoaded.future; // On at least iOS, it does not appear to be guaranteed that the native // code has the title when the page load completes. Execute some JavaScript // before checking the title to ensure that the page has been fully parsed // and processed. await controller.runJavaScript('1;'); final String? title = await controller.getTitle(); expect(title, 'Some title'); }); group('Programmatic Scroll', () { testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { const String scrollTestPage = '''
'''; final String scrollTestPageBase64 = base64Encode(const Utf8Encoder().convert(scrollTestPage)); final Completer pageLoaded = Completer(); final WebViewController controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageLoaded.complete(), )) ..loadRequest(Uri.parse( 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', )); await tester.pumpWidget(WebViewWidget(controller: controller)); await pageLoaded.future; await tester.pumpAndSettle(const Duration(seconds: 3)); Offset scrollPos = await controller.getScrollPosition(); // Check scrollTo() const int X_SCROLL = 123; const int Y_SCROLL = 321; // Get the initial position; this ensures that scrollTo is actually // changing something, but also gives the native view's scroll position // time to settle. expect(scrollPos.dx, isNot(X_SCROLL)); expect(scrollPos.dy, isNot(Y_SCROLL)); await controller.scrollTo(X_SCROLL, Y_SCROLL); scrollPos = await controller.getScrollPosition(); expect(scrollPos.dx, X_SCROLL); expect(scrollPos.dy, Y_SCROLL); // Check scrollBy() (on top of scrollTo()) await controller.scrollBy(X_SCROLL, Y_SCROLL); scrollPos = await controller.getScrollPosition(); expect(scrollPos.dx, X_SCROLL * 2); expect(scrollPos.dy, Y_SCROLL * 2); }); }); group('NavigationDelegate', () { const String blankPage = ''; final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' '${base64Encode(const Utf8Encoder().convert(blankPage))}'; testWidgets('can allow requests', (WidgetTester tester) async { Completer pageLoaded = Completer(); final WebViewController controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageLoaded.complete(), onNavigationRequest: (NavigationRequest navigationRequest) { return (navigationRequest.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; }, )); await tester.pumpWidget(WebViewWidget(controller: controller)); controller.loadRequest(Uri.parse(blankPageEncoded)); await pageLoaded.future; // Wait for initial page load. pageLoaded = Completer(); await controller.runJavaScript('location.href = "$secondaryUrl"'); await pageLoaded.future; // Wait for the next page load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); testWidgets('onWebResourceError', (WidgetTester tester) async { final Completer errorCompleter = Completer(); final WebViewController controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( NavigationDelegate(onWebResourceError: (WebResourceError error) { errorCompleter.complete(error); })) ..loadRequest(Uri.parse('https://www.notawebsite..com')); await tester.pumpWidget(WebViewWidget(controller: controller)); final WebResourceError error = await errorCompleter.future; expect(error, isNotNull); }); testWidgets('onWebResourceError is not called with valid url', (WidgetTester tester) async { final Completer errorCompleter = Completer(); final Completer pageFinishCompleter = Completer(); final WebViewController controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageFinishCompleter.complete(), onWebResourceError: (WebResourceError error) { errorCompleter.complete(error); }, )) ..loadRequest( Uri.parse('data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+'), ); await tester.pumpWidget(WebViewWidget(controller: controller)); expect(errorCompleter.future, doesNotComplete); await pageFinishCompleter.future; }); testWidgets('can block requests', (WidgetTester tester) async { Completer pageLoaded = Completer(); final WebViewController controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageLoaded.complete(), onNavigationRequest: (NavigationRequest navigationRequest) { return (navigationRequest.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; })); await tester.pumpWidget(WebViewWidget(controller: controller)); controller.loadRequest(Uri.parse(blankPageEncoded)); 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 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 { Completer pageLoaded = Completer(); final WebViewController controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageLoaded.complete(), onNavigationRequest: (NavigationRequest navigationRequest) async { NavigationDecision decision = NavigationDecision.prevent; decision = await Future.delayed( const Duration(milliseconds: 10), () => NavigationDecision.navigate); return decision; })); await tester.pumpWidget(WebViewWidget(controller: controller)); controller.loadRequest(Uri.parse(blankPageEncoded)); await pageLoaded.future; // Wait for initial page load. pageLoaded = Completer(); await controller.runJavaScript('location.href = "$secondaryUrl"'); await pageLoaded.future; // Wait for second page to load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); }); testWidgets('target _blank opens in same window', (WidgetTester tester) async { final Completer pageLoaded = Completer(); final WebViewController controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageLoaded.complete(), )); await tester.pumpWidget(WebViewWidget(controller: controller)); await controller.runJavaScript('window.open("$primaryUrl", "_blank")'); await pageLoaded.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); testWidgets( 'can open new window and go back', (WidgetTester tester) async { Completer pageLoaded = Completer(); final WebViewController controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageLoaded.complete(), )) ..loadRequest(Uri.parse(primaryUrl)); await tester.pumpWidget(WebViewWidget(controller: controller)); expect(controller.currentUrl(), completion(primaryUrl)); await pageLoaded.future; pageLoaded = Completer(); await controller.runJavaScript('window.open("$secondaryUrl")'); await pageLoaded.future; pageLoaded = Completer(); expect(controller.currentUrl(), completion(secondaryUrl)); expect(controller.canGoBack(), completion(true)); await controller.goBack(); await pageLoaded.future; await expectLater(controller.currentUrl(), completion(primaryUrl)); }, ); testWidgets( 'clearLocalStorage', (WidgetTester tester) async { Completer pageLoadCompleter = Completer(); final WebViewController controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => pageLoadCompleter.complete(), )) ..loadRequest(Uri.parse(primaryUrl)); await tester.pumpWidget(WebViewWidget(controller: controller)); await pageLoadCompleter.future; pageLoadCompleter = Completer(); await controller.runJavaScript('localStorage.setItem("myCat", "Tom");'); final String myCatItem = await controller.runJavaScriptReturningResult( 'localStorage.getItem("myCat");', ) as String; expect(myCatItem, _webViewString('Tom')); await controller.clearLocalStorage(); // Reload page to have changes take effect. await controller.reload(); await pageLoadCompleter.future; late final String? nullItem; try { nullItem = await controller.runJavaScriptReturningResult( 'localStorage.getItem("myCat");', ) as String; } catch (exception) { if (defaultTargetPlatform == TargetPlatform.iOS && exception is ArgumentError && (exception.message as String).contains( 'Result of JavaScript execution returned a `null` value.')) { nullItem = ''; } } expect(nullItem, _webViewNull()); }, ); } // JavaScript `null` evaluate to different string values on Android and iOS. // This utility method returns the string boolean value of the current platform. String _webViewNull() { if (defaultTargetPlatform == TargetPlatform.iOS) { return ''; } return 'null'; } // JavaScript String evaluate to different string values on Android and iOS. // This utility method returns the string boolean value of the current platform. String _webViewString(String value) { if (defaultTargetPlatform == TargetPlatform.iOS) { return value; } return '"$value"'; } /// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests. Future _getUserAgent(WebViewController controller) async { return _runJavascriptReturningResult(controller, 'navigator.userAgent;'); } Future _runJavascriptReturningResult( WebViewController controller, String js, ) async { if (defaultTargetPlatform == TargetPlatform.iOS) { return await controller.runJavaScriptReturningResult(js) as String; } return jsonDecode(await controller.runJavaScriptReturningResult(js) as String) as String; } class ResizableWebView extends StatefulWidget { const ResizableWebView({ super.key, required this.onResize, required this.onPageFinished, }); final VoidCallback onResize; final VoidCallback onPageFinished; @override State createState() => ResizableWebViewState(); } class ResizableWebViewState extends State { late final WebViewController controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate(NavigationDelegate( onPageFinished: (_) => widget.onPageFinished(), )) ..addJavaScriptChannel( 'Resize', onMessageReceived: (_) { widget.onResize(); }, ) ..loadRequest( Uri.parse( 'data:text/html;charset=utf-8;base64,${base64Encode(const Utf8Encoder().convert(resizePage))}', ), ); double webViewWidth = 200; double webViewHeight = 200; static const String resizePage = ''' Resize test '''; @override Widget build(BuildContext context) { return Directionality( textDirection: TextDirection.ltr, child: Column( children: [ SizedBox( width: webViewWidth, height: webViewHeight, child: WebViewWidget(controller: controller)), TextButton( key: const Key('resizeButton'), onPressed: () { setState(() { webViewWidth += 100.0; webViewHeight += 100.0; }); }, child: const Text('ResizeButton'), ), ], ), ); } } ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 9.0 ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/Flutter/Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/Flutter/Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths # Matches test_spec dependency. pod 'OCMock', '3.5' end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "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" } } ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName webview_flutter_example CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 334734012669319100DCC49E /* FLTWebViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 68BDCAF523C3F97800D9C032 /* FLTWebViewTests.m */; }; 334734022669319400DCC49E /* FLTWKNavigationDelegateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 686B4BF82548DBC7000AEA36 /* FLTWKNavigationDelegateTests.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 63D2F2FB307F1F037702C198 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BEC8CD326B252E47ABE6C037 /* libPods-RunnerTests.a */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; E6159E2B6496F35B1D4F4096 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C0ABA59F25635F077C9EA161 /* libPods-Runner.a */; }; F7151F77266057800028CB91 /* FLTWebViewUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F76266057800028CB91 /* FLTWebViewUITests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 68BDCAEE23C3F7CB00D9C032 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; F7151F79266057800028CB91 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 11DF059E983DF25F078B44CC /* 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 = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 3CEFE8F0E91B9792E4EE427B /* 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 = ""; }; 4D2B3F45D8E6CA81EA52591E /* 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 = ""; }; 5D19D984A61169BB95DB0FED /* 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 = ""; }; 686B4BF82548DBC7000AEA36 /* FLTWKNavigationDelegateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTWKNavigationDelegateTests.m; sourceTree = ""; }; 68BDCAE923C3F7CB00D9C032 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 68BDCAED23C3F7CB00D9C032 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 68BDCAF523C3F97800D9C032 /* FLTWebViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTWebViewTests.m; 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 = ""; }; 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 = ""; }; BEC8CD326B252E47ABE6C037 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; C0ABA59F25635F077C9EA161 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; F7151F74266057800028CB91 /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F7151F76266057800028CB91 /* FLTWebViewUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTWebViewUITests.m; sourceTree = ""; }; F7151F78266057800028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 68BDCAE623C3F7CB00D9C032 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 63D2F2FB307F1F037702C198 /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( E6159E2B6496F35B1D4F4096 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F71266057800028CB91 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 00D2395F7DDFEE571DF3C0B1 /* Frameworks */ = { isa = PBXGroup; children = ( C0ABA59F25635F077C9EA161 /* libPods-Runner.a */, BEC8CD326B252E47ABE6C037 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; 68BDCAEA23C3F7CB00D9C032 /* RunnerTests */ = { isa = PBXGroup; children = ( 686B4BF82548DBC7000AEA36 /* FLTWKNavigationDelegateTests.m */, 68BDCAF523C3F97800D9C032 /* FLTWebViewTests.m */, 68BDCAED23C3F7CB00D9C032 /* Info.plist */, ); path = RunnerTests; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 68BDCAEA23C3F7CB00D9C032 /* RunnerTests */, F7151F75266057800028CB91 /* RunnerUITests */, 97C146EF1CF9000F007C117D /* Products */, EA36D6F90B795550E32A139A /* Pods */, 00D2395F7DDFEE571DF3C0B1 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, 68BDCAE923C3F7CB00D9C032 /* RunnerTests.xctest */, F7151F74266057800028CB91 /* RunnerUITests.xctest */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; EA36D6F90B795550E32A139A /* Pods */ = { isa = PBXGroup; children = ( 4D2B3F45D8E6CA81EA52591E /* Pods-Runner.debug.xcconfig */, 11DF059E983DF25F078B44CC /* Pods-Runner.release.xcconfig */, 3CEFE8F0E91B9792E4EE427B /* Pods-RunnerTests.debug.xcconfig */, 5D19D984A61169BB95DB0FED /* Pods-RunnerTests.release.xcconfig */, ); name = Pods; path = Pods; sourceTree = ""; }; F7151F75266057800028CB91 /* RunnerUITests */ = { isa = PBXGroup; children = ( F7151F76266057800028CB91 /* FLTWebViewUITests.m */, F7151F78266057800028CB91 /* Info.plist */, ); path = RunnerUITests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 68BDCAE823C3F7CB00D9C032 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = 68BDCAF223C3F7CB00D9C032 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( EA0C9BB56C9A98B4F095051B /* [CP] Check Pods Manifest.lock */, 68BDCAE523C3F7CB00D9C032 /* Sources */, 68BDCAE623C3F7CB00D9C032 /* Frameworks */, 68BDCAE723C3F7CB00D9C032 /* Resources */, ); buildRules = ( ); dependencies = ( 68BDCAEF23C3F7CB00D9C032 /* PBXTargetDependency */, ); name = RunnerTests; productName = webview_flutter_exampleTests; productReference = 68BDCAE923C3F7CB00D9C032 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 1B3EA6BF26F6D525A8503093 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; F7151F73266057800028CB91 /* RunnerUITests */ = { isa = PBXNativeTarget; buildConfigurationList = F7151F7B266057800028CB91 /* Build configuration list for PBXNativeTarget "RunnerUITests" */; buildPhases = ( F7151F70266057800028CB91 /* Sources */, F7151F71266057800028CB91 /* Frameworks */, F7151F72266057800028CB91 /* Resources */, ); buildRules = ( ); dependencies = ( F7151F7A266057800028CB91 /* PBXTargetDependency */, ); name = RunnerUITests; productName = RunnerUITests; productReference = F7151F74266057800028CB91 /* RunnerUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { DefaultBuildSystemTypeForWorkspace = Original; LastUpgradeCheck = 1030; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 68BDCAE823C3F7CB00D9C032 = { ProvisioningStyle = Automatic; }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; }; F7151F73266057800028CB91 = { CreatedOnToolsVersion = 12.5; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, 68BDCAE823C3F7CB00D9C032 /* RunnerTests */, F7151F73266057800028CB91 /* RunnerUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 68BDCAE723C3F7CB00D9C032 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F72266057800028CB91 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 1B3EA6BF26F6D525A8503093 /* [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", ); 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; }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n"; }; 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\n"; }; EA0C9BB56C9A98B4F095051B /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 68BDCAE523C3F7CB00D9C032 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 334734012669319100DCC49E /* FLTWebViewTests.m in Sources */, 334734022669319400DCC49E /* FLTWKNavigationDelegateTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F70266057800028CB91 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F7151F77266057800028CB91 /* FLTWebViewUITests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 68BDCAEF23C3F7CB00D9C032 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = 68BDCAEE23C3F7CB00D9C032 /* PBXContainerItemProxy */; }; F7151F7A266057800028CB91 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = F7151F79266057800028CB91 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 68BDCAF023C3F7CB00D9C032 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 3CEFE8F0E91B9792E4EE427B /* 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; }; 68BDCAF123C3F7CB00D9C032 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D19D984A61169BB95DB0FED /* 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_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; }; 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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 = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.webviewFlutterExample; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.webviewFlutterExample; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; F7151F7C266057800028CB91 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = Runner; }; name = Debug; }; F7151F7D266057800028CB91 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = Runner; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 68BDCAF223C3F7CB00D9C032 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 68BDCAF023C3F7CB00D9C032 /* Debug */, 68BDCAF123C3F7CB00D9C032 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F7151F7B266057800028CB91 /* Build configuration list for PBXNativeTarget "RunnerUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( F7151F7C266057800028CB91 /* Debug */, F7151F7D266057800028CB91 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/RunnerTests/FLTWKNavigationDelegateTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import Flutter; @import XCTest; @import webview_flutter; // OCMock library doesn't generate a valid modulemap. #import @interface FLTWKNavigationDelegateTests : XCTestCase @property(strong, nonatomic) FlutterMethodChannel *mockMethodChannel; @property(strong, nonatomic) FLTWKNavigationDelegate *navigationDelegate; @end @implementation FLTWKNavigationDelegateTests - (void)setUp { self.mockMethodChannel = OCMClassMock(FlutterMethodChannel.class); self.navigationDelegate = [[FLTWKNavigationDelegate alloc] initWithChannel:self.mockMethodChannel]; } - (void)testWebViewWebContentProcessDidTerminateCallsRecourseErrorChannel { WKWebView *webview = OCMClassMock(WKWebView.class); [self.navigationDelegate webViewWebContentProcessDidTerminate:webview]; OCMVerify([self.mockMethodChannel invokeMethod:@"onWebResourceError" arguments:[OCMArg checkWithBlock:^BOOL(NSDictionary *args) { XCTAssertEqualObjects(args[@"errorType"], @"webContentProcessTerminated"); return true; }]]); } @end ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/RunnerTests/FLTWebViewTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import Flutter; @import XCTest; @import webview_flutter; // OCMock library doesn't generate a valid modulemap. #import static bool feq(CGFloat a, CGFloat b) { return fabs(b - a) < FLT_EPSILON; } @interface FLTWebViewTests : XCTestCase @property(strong, nonatomic) NSObject *mockBinaryMessenger; @end @implementation FLTWebViewTests - (void)setUp { [super setUp]; self.mockBinaryMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); } - (void)testCanInitFLTWebViewController { FLTWebViewController *controller = [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400) viewIdentifier:1 arguments:nil binaryMessenger:self.mockBinaryMessenger]; XCTAssertNotNil(controller); } - (void)testCanInitFLTWebViewFactory { FLTWebViewFactory *factory = [[FLTWebViewFactory alloc] initWithMessenger:self.mockBinaryMessenger]; XCTAssertNotNil(factory); } - (void)webViewContentInsetBehaviorShouldBeNeverOnIOS11 { if (@available(iOS 11, *)) { FLTWebViewController *controller = [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400) viewIdentifier:1 arguments:nil binaryMessenger:self.mockBinaryMessenger]; UIView *view = controller.view; XCTAssertTrue([view isKindOfClass:WKWebView.class]); WKWebView *webView = (WKWebView *)view; XCTAssertEqual(webView.scrollView.contentInsetAdjustmentBehavior, UIScrollViewContentInsetAdjustmentNever); } } - (void)testWebViewScrollIndicatorAticautomaticallyAdjustsScrollIndicatorInsetsShouldbeNoOnIOS13 { if (@available(iOS 13, *)) { FLTWebViewController *controller = [[FLTWebViewController alloc] initWithFrame:CGRectMake(0, 0, 300, 400) viewIdentifier:1 arguments:nil binaryMessenger:self.mockBinaryMessenger]; UIView *view = controller.view; XCTAssertTrue([view isKindOfClass:WKWebView.class]); WKWebView *webView = (WKWebView *)view; XCTAssertFalse(webView.scrollView.automaticallyAdjustsScrollIndicatorInsets); } } - (void)testContentInsetsSumAlwaysZeroAfterSetFrame { FLTWKWebView *webView = [[FLTWKWebView alloc] initWithFrame:CGRectMake(0, 0, 300, 400)]; webView.scrollView.contentInset = UIEdgeInsetsMake(0, 0, 300, 0); XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(webView.scrollView.contentInset, UIEdgeInsetsZero)); webView.frame = CGRectMake(0, 0, 300, 200); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(webView.scrollView.contentInset, UIEdgeInsetsZero)); XCTAssertTrue(CGRectEqualToRect(webView.frame, CGRectMake(0, 0, 300, 200))); if (@available(iOS 11, *)) { // After iOS 11, we need to make sure the contentInset compensates the adjustedContentInset. UIScrollView *partialMockScrollView = OCMPartialMock(webView.scrollView); UIEdgeInsets insetToAdjust = UIEdgeInsetsMake(0, 0, 300, 0); OCMStub(partialMockScrollView.adjustedContentInset).andReturn(insetToAdjust); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(webView.scrollView.contentInset, UIEdgeInsetsZero)); webView.frame = CGRectMake(0, 0, 300, 100); XCTAssertTrue(feq(webView.scrollView.contentInset.bottom, -insetToAdjust.bottom)); XCTAssertTrue(CGRectEqualToRect(webView.frame, CGRectMake(0, 0, 300, 100))); } } @end ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/RunnerTests/Info.plist ================================================ 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 ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/RunnerUITests/FLTWebViewUITests.m ================================================ // Copyright 2013 The Flutter 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; @import os.log; @interface FLTWebViewUITests : XCTestCase @property(nonatomic, strong) XCUIApplication *app; @end @implementation FLTWebViewUITests - (void)setUp { self.continueAfterFailure = NO; self.app = [[XCUIApplication alloc] init]; [self.app launch]; } - (void)testUserAgent { XCUIApplication *app = self.app; XCUIElement *menu = app.buttons[@"Show menu"]; if (![menu waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find menu"); } [menu tap]; XCUIElement *userAgent = app.buttons[@"Show user agent"]; if (![userAgent waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find Show user agent"); } NSPredicate *userAgentPredicate = [NSPredicate predicateWithFormat:@"label BEGINSWITH 'User Agent: Mozilla/5.0 (iPhone; '"]; XCUIElement *userAgentPopUp = [app.otherElements elementMatchingPredicate:userAgentPredicate]; XCTAssertFalse(userAgentPopUp.exists); [userAgent tap]; if (![userAgentPopUp waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find user agent pop up"); } } - (void)testCache { XCUIApplication *app = self.app; XCUIElement *menu = app.buttons[@"Show menu"]; if (![menu waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find menu"); } [menu tap]; XCUIElement *clearCache = app.buttons[@"Clear cache"]; if (![clearCache waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find Clear cache"); } [clearCache tap]; [menu tap]; XCUIElement *listCache = app.buttons[@"List cache"]; if (![listCache waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find List cache"); } [listCache tap]; XCUIElement *emptyCachePopup = app.otherElements[@"{\"cacheKeys\":[],\"localStorage\":{}}"]; if (![emptyCachePopup waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find empty cache pop up"); } [menu tap]; XCUIElement *addCache = app.buttons[@"Add to cache"]; if (![addCache waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find Add to cache"); } [addCache tap]; [menu tap]; if (![listCache waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find List cache"); } [listCache tap]; XCUIElement *cachePopup = app.otherElements[@"{\"cacheKeys\":[\"test_caches_entry\"],\"localStorage\":{\"test_" @"localStorage\":\"dummy_entry\"}}"]; if (![cachePopup waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find cache pop up"); } } @end ================================================ FILE: packages/webview_flutter/webview_flutter/example/ios/RunnerUITests/Info.plist ================================================ 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 ================================================ FILE: packages/webview_flutter/webview_flutter/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:webview_flutter/webview_flutter.dart'; // #docregion platform_imports // Import for Android features. import 'package:webview_flutter_android/webview_flutter_android.dart'; // Import for iOS features. import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; // #enddocregion platform_imports void main() => runApp(const MaterialApp(home: WebViewExample())); const String kNavigationExamplePage = ''' Navigation Delegate Example

The navigation delegate is set to block navigation to the youtube website.

'''; const String kLocalExamplePage = ''' Load file or HTML string example

Local demo page

This is an example page used to demonstrate how to load a local file or HTML string using the Flutter webview plugin.

'''; const String kTransparentBackgroundPage = ''' Transparent background test

Transparent background test

'''; class WebViewExample extends StatefulWidget { const WebViewExample({super.key}); @override State createState() => _WebViewExampleState(); } class _WebViewExampleState extends State { late final WebViewController _controller; @override void initState() { super.initState(); // #docregion platform_features late final PlatformWebViewControllerCreationParams params; if (WebViewPlatform.instance is WebKitWebViewPlatform) { params = WebKitWebViewControllerCreationParams( allowsInlineMediaPlayback: true, mediaTypesRequiringUserAction: const {}, ); } else { params = const PlatformWebViewControllerCreationParams(); } final WebViewController controller = WebViewController.fromPlatformCreationParams(params); // #enddocregion platform_features controller ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setBackgroundColor(const Color(0x00000000)) ..setNavigationDelegate( NavigationDelegate( onProgress: (int progress) { debugPrint('WebView is loading (progress : $progress%)'); }, onPageStarted: (String url) { debugPrint('Page started loading: $url'); }, onPageFinished: (String url) { debugPrint('Page finished loading: $url'); }, onWebResourceError: (WebResourceError error) { debugPrint(''' Page resource error: code: ${error.errorCode} description: ${error.description} errorType: ${error.errorType} isForMainFrame: ${error.isForMainFrame} '''); }, onNavigationRequest: (NavigationRequest request) { if (request.url.startsWith('https://www.youtube.com/')) { debugPrint('blocking navigation to ${request.url}'); return NavigationDecision.prevent; } debugPrint('allowing navigation to ${request.url}'); return NavigationDecision.navigate; }, ), ) ..addJavaScriptChannel( 'Toaster', onMessageReceived: (JavaScriptMessage message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message.message)), ); }, ) ..loadRequest(Uri.parse('https://flutter.dev')); // #docregion platform_features if (controller.platform is AndroidWebViewController) { AndroidWebViewController.enableDebugging(true); (controller.platform as AndroidWebViewController) .setMediaPlaybackRequiresUserGesture(false); } // #enddocregion platform_features _controller = controller; } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.green, appBar: AppBar( title: const Text('Flutter WebView example'), // This drop down menu demonstrates that Flutter widgets can be shown over the web view. actions: [ NavigationControls(webViewController: _controller), SampleMenu(webViewController: _controller), ], ), body: WebViewWidget(controller: _controller), floatingActionButton: favoriteButton(), ); } Widget favoriteButton() { return FloatingActionButton( onPressed: () async { final String? url = await _controller.currentUrl(); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Favorited $url')), ); } }, child: const Icon(Icons.favorite), ); } } enum MenuOptions { showUserAgent, listCookies, clearCookies, addToCache, listCache, clearCache, navigationDelegate, doPostRequest, loadLocalFile, loadFlutterAsset, loadHtmlString, transparentBackground, setCookie, } class SampleMenu extends StatelessWidget { SampleMenu({ super.key, required this.webViewController, }); final WebViewController webViewController; late final WebViewCookieManager cookieManager = WebViewCookieManager(); @override Widget build(BuildContext context) { return PopupMenuButton( key: const ValueKey('ShowPopupMenu'), onSelected: (MenuOptions value) { switch (value) { case MenuOptions.showUserAgent: _onShowUserAgent(); break; case MenuOptions.listCookies: _onListCookies(context); break; case MenuOptions.clearCookies: _onClearCookies(context); break; case MenuOptions.addToCache: _onAddToCache(context); break; case MenuOptions.listCache: _onListCache(); break; case MenuOptions.clearCache: _onClearCache(context); break; case MenuOptions.navigationDelegate: _onNavigationDelegateExample(); break; case MenuOptions.doPostRequest: _onDoPostRequest(); break; case MenuOptions.loadLocalFile: _onLoadLocalFileExample(); break; case MenuOptions.loadFlutterAsset: _onLoadFlutterAssetExample(); break; case MenuOptions.loadHtmlString: _onLoadHtmlStringExample(); break; case MenuOptions.transparentBackground: _onTransparentBackground(); break; case MenuOptions.setCookie: _onSetCookie(); break; } }, itemBuilder: (BuildContext context) => >[ const PopupMenuItem( value: MenuOptions.showUserAgent, child: Text('Show user agent'), ), const PopupMenuItem( value: MenuOptions.listCookies, child: Text('List cookies'), ), const PopupMenuItem( value: MenuOptions.clearCookies, child: Text('Clear cookies'), ), const PopupMenuItem( value: MenuOptions.addToCache, child: Text('Add to cache'), ), const PopupMenuItem( value: MenuOptions.listCache, child: Text('List cache'), ), const PopupMenuItem( value: MenuOptions.clearCache, child: Text('Clear cache'), ), const PopupMenuItem( value: MenuOptions.navigationDelegate, child: Text('Navigation Delegate example'), ), const PopupMenuItem( value: MenuOptions.doPostRequest, child: Text('Post Request'), ), const PopupMenuItem( value: MenuOptions.loadHtmlString, child: Text('Load HTML string'), ), const PopupMenuItem( value: MenuOptions.loadLocalFile, child: Text('Load local file'), ), const PopupMenuItem( value: MenuOptions.loadFlutterAsset, child: Text('Load Flutter Asset'), ), const PopupMenuItem( key: ValueKey('ShowTransparentBackgroundExample'), value: MenuOptions.transparentBackground, child: Text('Transparent background example'), ), const PopupMenuItem( value: MenuOptions.setCookie, child: Text('Set cookie'), ), ], ); } Future _onShowUserAgent() { // Send a message with the user agent string to the Toaster JavaScript channel we registered // with the WebView. return webViewController.runJavaScript( 'Toaster.postMessage("User Agent: " + navigator.userAgent);', ); } Future _onListCookies(BuildContext context) async { final String cookies = await webViewController .runJavaScriptReturningResult('document.cookie') as String; 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";', ); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Added a test entry to cache.'), )); } } Future _onListCache() { return webViewController.runJavaScript('caches.keys()' // ignore: missing_whitespace_between_adjacent_strings '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))' '.then((caches) => Toaster.postMessage(caches))'); } Future _onClearCache(BuildContext context) async { await webViewController.clearCache(); await webViewController.clearLocalStorage(); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Cache cleared.'), )); } } Future _onClearCookies(BuildContext context) async { final bool hadCookies = await cookieManager.clearCookies(); String message = 'There were cookies. Now, they are gone!'; if (!hadCookies) { message = 'There are no cookies.'; } if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(message), )); } } Future _onNavigationDelegateExample() { final String contentBase64 = base64Encode( const Utf8Encoder().convert(kNavigationExamplePage), ); return webViewController.loadRequest( Uri.parse('data:text/html;base64,$contentBase64'), ); } Future _onSetCookie() async { await cookieManager.setCookie( const WebViewCookie( name: 'foo', value: 'bar', domain: 'httpbin.org', path: '/anything', ), ); await webViewController.loadRequest(Uri.parse( 'https://httpbin.org/anything', )); } Future _onDoPostRequest() { return webViewController.loadRequest( Uri.parse('https://httpbin.org/post'), method: LoadRequestMethod.post, headers: {'foo': 'bar', 'Content-Type': 'text/plain'}, body: Uint8List.fromList('Test Body'.codeUnits), ); } Future _onLoadLocalFileExample() async { final String pathToIndex = await _prepareLocalFile(); await webViewController.loadFile(pathToIndex); } Future _onLoadFlutterAssetExample() { return webViewController.loadFlutterAsset('assets/www/index.html'); } Future _onLoadHtmlStringExample() { return webViewController.loadHtmlString(kLocalExamplePage); } Future _onTransparentBackground() { return webViewController.loadHtmlString(kTransparentBackgroundPage); } Widget _getCookieList(String cookies) { if (cookies == null || cookies == '""') { return Container(); } final List cookieList = cookies.split(';'); final Iterable cookieWidgets = cookieList.map((String cookie) => Text(cookie)); return Column( mainAxisAlignment: MainAxisAlignment.end, mainAxisSize: MainAxisSize.min, children: cookieWidgets.toList(), ); } static Future _prepareLocalFile() async { final String tmpDir = (await getTemporaryDirectory()).path; final File indexFile = File( {tmpDir, 'www', 'index.html'}.join(Platform.pathSeparator)); await indexFile.create(recursive: true); await indexFile.writeAsString(kLocalExamplePage); return indexFile.path; } } class NavigationControls extends StatelessWidget { const NavigationControls({super.key, required this.webViewController}); final WebViewController webViewController; @override Widget build(BuildContext context) { return Row( children: [ IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () async { if (await webViewController.canGoBack()) { await webViewController.goBack(); } else { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('No back history item')), ); } } }, ), IconButton( icon: const Icon(Icons.arrow_forward_ios), onPressed: () async { if (await webViewController.canGoForward()) { await webViewController.goForward(); } else { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('No forward history item')), ); } } }, ), IconButton( icon: const Icon(Icons.replay), onPressed: () => webViewController.reload(), ), ], ); } } ================================================ FILE: packages/webview_flutter/webview_flutter/example/lib/simple_example.dart ================================================ // Copyright 2013 The Flutter 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:webview_flutter/webview_flutter.dart'; void main() => runApp(const MaterialApp(home: WebViewExample())); class WebViewExample extends StatefulWidget { const WebViewExample({super.key}); @override State createState() => _WebViewExampleState(); } class _WebViewExampleState extends State { late final WebViewController controller; @override void initState() { super.initState(); // #docregion webview_controller controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setBackgroundColor(const Color(0x00000000)) ..setNavigationDelegate( NavigationDelegate( onProgress: (int progress) { // Update loading bar. }, onPageStarted: (String url) {}, onPageFinished: (String url) {}, onWebResourceError: (WebResourceError error) {}, onNavigationRequest: (NavigationRequest request) { if (request.url.startsWith('https://www.youtube.com/')) { return NavigationDecision.prevent; } return NavigationDecision.navigate; }, ), ) ..loadRequest(Uri.parse('https://flutter.dev')); // #enddocregion webview_controller } // #docregion webview_widget @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Flutter Simple Example')), body: WebViewWidget(controller: controller), ); } // #enddocregion webview_widget } ================================================ FILE: packages/webview_flutter/webview_flutter/example/pubspec.yaml ================================================ name: webview_flutter_example description: Demonstrates how to use the webview_flutter plugin. publish_to: none environment: sdk: ">=2.17.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter path_provider: ^2.0.6 webview_flutter: # When depending on this package from a real application you should use: # webview_flutter: ^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: ../ webview_flutter_android: ^3.0.0 webview_flutter_wkwebview: ^3.0.0 dev_dependencies: build_runner: ^2.1.5 espresso: ^0.2.0 flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter webview_flutter_platform_interface: ^2.0.0 flutter: uses-material-design: true assets: - assets/sample_audio.ogg - assets/sample_video.mp4 - assets/www/index.html - assets/www/styles/style.css ================================================ FILE: packages/webview_flutter/webview_flutter/example/test/main_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:webview_flutter_example/main.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; void main() { setUp(() { WebViewPlatform.instance = FakeWebViewPlatform(); }); testWidgets('Test snackbar from ScaffoldMessenger', (WidgetTester tester) async { await tester.pumpWidget(const MaterialApp(home: WebViewExample())); expect(find.byIcon(Icons.favorite), findsOneWidget); await tester.tap(find.byIcon(Icons.favorite)); await tester.pump(); expect(find.byType(SnackBar), findsOneWidget); }); } class FakeWebViewPlatform extends WebViewPlatform { @override PlatformWebViewController createPlatformWebViewController( PlatformWebViewControllerCreationParams params, ) { return FakeWebViewController(params); } @override PlatformWebViewWidget createPlatformWebViewWidget( PlatformWebViewWidgetCreationParams params, ) { return FakeWebViewWidget(params); } @override PlatformWebViewCookieManager createPlatformCookieManager( PlatformWebViewCookieManagerCreationParams params, ) { return FakeCookieManager(params); } @override PlatformNavigationDelegate createPlatformNavigationDelegate( PlatformNavigationDelegateCreationParams params, ) { return FakeNavigationDelegate(params); } } class FakeWebViewController extends PlatformWebViewController { FakeWebViewController(super.params) : super.implementation(); @override Future setJavaScriptMode(JavaScriptMode javaScriptMode) async {} @override Future setBackgroundColor(Color color) async {} @override Future setPlatformNavigationDelegate( PlatformNavigationDelegate handler, ) async {} @override Future addJavaScriptChannel( JavaScriptChannelParams javaScriptChannelParams) async {} @override Future loadRequest(LoadRequestParams params) async {} @override Future currentUrl() async { return 'https://www.google.com'; } } class FakeCookieManager extends PlatformWebViewCookieManager { FakeCookieManager(super.params) : super.implementation(); } class FakeWebViewWidget extends PlatformWebViewWidget { FakeWebViewWidget(super.params) : super.implementation(); @override Widget build(BuildContext context) { return Container(); } } class FakeNavigationDelegate extends PlatformNavigationDelegate { FakeNavigationDelegate(super.params) : super.implementation(); @override Future setOnNavigationRequest( NavigationRequestCallback onNavigationRequest, ) async {} @override Future setOnPageFinished(PageEventCallback onPageFinished) async {} @override Future setOnPageStarted(PageEventCallback onPageStarted) async {} @override Future setOnProgress(ProgressCallback onProgress) async {} @override Future setOnWebResourceError( WebResourceErrorCallback onWebResourceError, ) async {} } ================================================ FILE: packages/webview_flutter/webview_flutter/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/webview_flutter/webview_flutter/lib/src/legacy/platform_interface.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Re-export the classes from the webview_flutter_platform_interface through /// the `platform_interface.dart` file so we don't accidentally break any /// non-endorsed existing implementations of the interface. export 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart' show AutoMediaPlaybackPolicy, CreationParams, JavascriptChannel, JavascriptChannelRegistry, JavascriptMessage, JavascriptMode, JavascriptMessageHandler, WebViewPlatform, WebViewPlatformCallbacksHandler, WebViewPlatformController, WebViewPlatformCreatedCallback, WebSetting, WebSettings, WebResourceError, WebResourceErrorType, WebViewCookie, WebViewRequest, WebViewRequestMethod; ================================================ FILE: packages/webview_flutter/webview_flutter/lib/src/legacy/webview.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; // ignore: implementation_imports import 'package:webview_flutter_android/src/webview_flutter_android_legacy.dart'; // ignore: implementation_imports import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; // ignore: implementation_imports import 'package:webview_flutter_wkwebview/src/webview_flutter_wkwebview_legacy.dart'; /// Optional callback invoked when a web view is first created. [controller] is /// the [WebViewController] for the created web view. typedef WebViewCreatedCallback = void Function(WebViewController controller); /// Information about a navigation action that is about to be executed. class NavigationRequest { NavigationRequest._({required this.url, required this.isForMainFrame}); /// The URL that will be loaded if the navigation is executed. final String url; /// Whether the navigation request is to be loaded as the main frame. final bool isForMainFrame; @override String toString() { return 'NavigationRequest(url: $url, isForMainFrame: $isForMainFrame)'; } } /// A decision on how to handle a navigation request. enum NavigationDecision { /// Prevent the navigation from taking place. prevent, /// Allow the navigation to take place. navigate, } /// Decides how to handle a specific navigation request. /// /// The returned [NavigationDecision] determines how the navigation described by /// `navigation` should be handled. /// /// See also: [WebView.navigationDelegate]. typedef NavigationDelegate = FutureOr Function( NavigationRequest navigation); /// Signature for when a [WebView] has started loading a page. typedef PageStartedCallback = void Function(String url); /// Signature for when a [WebView] has finished loading a page. typedef PageFinishedCallback = void Function(String url); /// Signature for when a [WebView] is loading a page. typedef PageLoadingCallback = void Function(int progress); /// Signature for when a [WebView] has failed to load a resource. typedef WebResourceErrorCallback = void Function(WebResourceError error); /// A web view widget for showing html content. /// /// There is a known issue that on iOS 13.4 and 13.5, other flutter widgets covering /// the `WebView` is not able to block the `WebView` from receiving touch events. /// See https://github.com/flutter/flutter/issues/53490. class WebView extends StatefulWidget { /// Creates a new web view. /// /// The web view can be controlled using a `WebViewController` that is passed to the /// `onWebViewCreated` callback once the web view is created. /// /// The `javascriptMode` and `autoMediaPlaybackPolicy` parameters must not be null. const WebView({ super.key, this.onWebViewCreated, this.initialUrl, this.initialCookies = const [], this.javascriptMode = JavascriptMode.disabled, this.javascriptChannels, this.navigationDelegate, this.gestureRecognizers, this.onPageStarted, this.onPageFinished, this.onProgress, this.onWebResourceError, this.debuggingEnabled = false, this.gestureNavigationEnabled = false, this.userAgent, this.zoomEnabled = true, this.initialMediaPlaybackPolicy = AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, this.allowsInlineMediaPlayback = false, this.backgroundColor, }) : assert(javascriptMode != null), assert(initialMediaPlaybackPolicy != null), assert(allowsInlineMediaPlayback != null); static WebViewPlatform? _platform; /// Sets a custom [WebViewPlatform]. /// /// This property can be set to use a custom platform implementation for WebViews. /// /// Setting `platform` doesn't affect [WebView]s that were already created. /// /// The default value is [AndroidWebView] on Android and [CupertinoWebView] on iOS. static set platform(WebViewPlatform? platform) { _platform = platform; } /// The WebView platform that's used by this WebView. /// /// The default value is [SurfaceAndroidWebView] on Android and [CupertinoWebView] on iOS. static WebViewPlatform get platform { if (_platform == null) { switch (defaultTargetPlatform) { case TargetPlatform.android: _platform = SurfaceAndroidWebView(); break; 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"); } } return _platform!; } /// If not null invoked once the web view is created. final WebViewCreatedCallback? onWebViewCreated; /// Which gestures should be consumed by the web view. /// /// It is possible for other gesture recognizers to be competing with the web view on pointer /// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle /// vertical drags. The web view will claim gestures that are recognized by any of the /// recognizers on this list. /// /// When this set is empty or null, the web view will only handle pointer events for gestures that /// were not claimed by any other gesture recognizer. final Set>? gestureRecognizers; /// The initial URL to load. final String? initialUrl; /// The initial cookies to set. final List initialCookies; /// Whether JavaScript execution is enabled. final JavascriptMode javascriptMode; /// The set of [JavascriptChannel]s available to JavaScript code running in the web view. /// /// For each [JavascriptChannel] in the set, a channel object is made available for the /// JavaScript code in a window property named [JavascriptChannel.name]. /// The JavaScript code can then call `postMessage` on that object to send a message that will be /// passed to [JavascriptChannel.onMessageReceived]. /// /// For example for the following JavascriptChannel: /// /// ```dart /// JavascriptChannel(name: 'Print', onMessageReceived: (JavascriptMessage message) { print(message.message); }); /// ``` /// /// JavaScript code can call: /// /// ```javascript /// Print.postMessage('Hello'); /// ``` /// /// To asynchronously invoke the message handler which will print the message to standard output. /// /// Adding a new JavaScript channel only takes affect after the next page is loaded. /// /// Set values must not be null. A [JavascriptChannel.name] cannot be the same for multiple /// channels in the list. /// /// A null value is equivalent to an empty set. final Set? javascriptChannels; /// A delegate function that decides how to handle navigation actions. /// /// When a navigation is initiated by the WebView (e.g when a user clicks a link) /// this delegate is called and has to decide how to proceed with the navigation. /// /// See [NavigationDecision] for possible decisions the delegate can take. /// /// When null all navigation actions are allowed. /// /// Caveats on Android: /// /// * Navigation actions targeted to the main frame can be intercepted, /// navigation actions targeted to subframes are allowed regardless of the value /// returned by this delegate. /// * Setting a navigationDelegate makes the WebView treat all navigations as if they were /// triggered by a user gesture, this disables some of Chromium's security mechanisms. /// A navigationDelegate should only be set when loading trusted content. /// * On Android WebView versions earlier than 67(most devices running at least Android L+ should have /// a later version): /// * When a navigationDelegate is set pages with frames are not properly handled by the /// webview, and frames will be opened in the main frame. /// * When a navigationDelegate is set HTTP requests do not include the HTTP referer header. final NavigationDelegate? navigationDelegate; /// Controls whether inline playback of HTML5 videos is allowed on iOS. /// /// This field is ignored on Android because Android allows it by default. /// /// By default `allowsInlineMediaPlayback` is false. final bool allowsInlineMediaPlayback; /// Invoked when a page starts loading. final PageStartedCallback? onPageStarted; /// Invoked when a page has finished loading. /// /// This is invoked only for the main frame. /// /// When [onPageFinished] is invoked on Android, the page being rendered may /// not be updated yet. /// /// When invoked on iOS or Android, any JavaScript code that is embedded /// directly in the HTML has been loaded and code injected with /// [WebViewController.runJavascript] or [WebViewController.runJavascriptReturningResult] can assume this. final PageFinishedCallback? onPageFinished; /// Invoked when a page is loading. final PageLoadingCallback? onProgress; /// Invoked when a web resource has failed to load. /// /// This callback is only called for the main page. final WebResourceErrorCallback? onWebResourceError; /// Controls whether WebView debugging is enabled. /// /// Setting this to true enables [WebView debugging on Android](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/). /// /// WebView debugging is enabled by default in dev builds on iOS. /// /// To debug WebViews on iOS: /// - Enable developer options (Open Safari, go to Preferences -> Advanced and make sure "Show Develop Menu in Menubar" is on.) /// - From the Menu-bar (of Safari) select Develop -> iPhone Simulator -> /// /// By default `debuggingEnabled` is false. final bool debuggingEnabled; /// A Boolean value indicating whether horizontal swipe gestures will trigger back-forward list navigations. /// /// This only works on iOS. /// /// By default `gestureNavigationEnabled` is false. final bool gestureNavigationEnabled; /// The value used for the HTTP User-Agent: request header. /// /// When null the platform's webview default is used for the User-Agent header. /// /// When the [WebView] is rebuilt with a different `userAgent`, the page reloads and the request uses the new User Agent. /// /// When [WebViewController.goBack] is called after changing `userAgent` the previous `userAgent` value is used until the page is reloaded. /// /// This field is ignored on iOS versions prior to 9 as the platform does not support a custom /// user agent. /// /// By default `userAgent` is null. final String? userAgent; /// A Boolean value indicating whether the WebView should support zooming /// using its on-screen zoom controls and gestures. /// /// *Note: On iOS [javascriptMode] must be set to /// [JavascriptMode.unrestricted] in order to set [zoomEnabled] to false /// /// By default 'zoomEnabled' is true final bool zoomEnabled; /// Which restrictions apply on automatic media playback. /// /// This initial value is applied to the platform's webview upon creation. Any following /// changes to this parameter are ignored (as long as the state of the [WebView] is preserved). /// /// The default policy is [AutoMediaPlaybackPolicy.require_user_action_for_all_media_types]. final AutoMediaPlaybackPolicy initialMediaPlaybackPolicy; /// The background color of the [WebView]. /// /// When `null` the platform's webview default background color is used. By /// default [backgroundColor] is `null`. final Color? backgroundColor; @override State createState() => _WebViewState(); } class _WebViewState extends State { final Completer _controller = Completer(); late JavascriptChannelRegistry _javascriptChannelRegistry; late _PlatformCallbacksHandler _platformCallbacksHandler; @override Widget build(BuildContext context) { return WebView.platform.build( context: context, onWebViewPlatformCreated: _onWebViewPlatformCreated, webViewPlatformCallbacksHandler: _platformCallbacksHandler, javascriptChannelRegistry: _javascriptChannelRegistry, gestureRecognizers: widget.gestureRecognizers, creationParams: _creationParamsFromWidget(widget), ); } @override void initState() { super.initState(); _assertJavascriptChannelNamesAreUnique(); _platformCallbacksHandler = _PlatformCallbacksHandler(widget); _javascriptChannelRegistry = JavascriptChannelRegistry(widget.javascriptChannels); } @override void didUpdateWidget(WebView oldWidget) { super.didUpdateWidget(oldWidget); _assertJavascriptChannelNamesAreUnique(); _controller.future.then((WebViewController controller) { _platformCallbacksHandler._widget = widget; controller._updateWidget(widget); }); } void _onWebViewPlatformCreated(WebViewPlatformController? webViewPlatform) { final WebViewController controller = WebViewController._( widget, webViewPlatform!, _javascriptChannelRegistry, ); _controller.complete(controller); if (widget.onWebViewCreated != null) { widget.onWebViewCreated!(controller); } } void _assertJavascriptChannelNamesAreUnique() { if (widget.javascriptChannels == null || widget.javascriptChannels!.isEmpty) { return; } assert(_extractChannelNames(widget.javascriptChannels).length == widget.javascriptChannels!.length); } } CreationParams _creationParamsFromWidget(WebView widget) { return CreationParams( initialUrl: widget.initialUrl, webSettings: _webSettingsFromWidget(widget), javascriptChannelNames: _extractChannelNames(widget.javascriptChannels), userAgent: widget.userAgent, autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, backgroundColor: widget.backgroundColor, cookies: widget.initialCookies, ); } WebSettings _webSettingsFromWidget(WebView widget) { return WebSettings( javascriptMode: widget.javascriptMode, hasNavigationDelegate: widget.navigationDelegate != null, hasProgressTracking: widget.onProgress != null, debuggingEnabled: widget.debuggingEnabled, gestureNavigationEnabled: widget.gestureNavigationEnabled, allowsInlineMediaPlayback: widget.allowsInlineMediaPlayback, userAgent: WebSetting.of(widget.userAgent), zoomEnabled: widget.zoomEnabled, ); } // This method assumes that no fields in `currentValue` are null. WebSettings _clearUnchangedWebSettings( WebSettings currentValue, WebSettings newValue) { assert(currentValue.javascriptMode != null); assert(currentValue.hasNavigationDelegate != null); assert(currentValue.hasProgressTracking != null); assert(currentValue.debuggingEnabled != null); assert(currentValue.userAgent != null); assert(newValue.javascriptMode != null); assert(newValue.hasNavigationDelegate != null); assert(newValue.debuggingEnabled != null); assert(newValue.userAgent != null); assert(newValue.zoomEnabled != null); JavascriptMode? javascriptMode; bool? hasNavigationDelegate; bool? hasProgressTracking; bool? debuggingEnabled; WebSetting userAgent = const WebSetting.absent(); bool? zoomEnabled; if (currentValue.javascriptMode != newValue.javascriptMode) { javascriptMode = newValue.javascriptMode; } if (currentValue.hasNavigationDelegate != newValue.hasNavigationDelegate) { hasNavigationDelegate = newValue.hasNavigationDelegate; } if (currentValue.hasProgressTracking != newValue.hasProgressTracking) { hasProgressTracking = newValue.hasProgressTracking; } if (currentValue.debuggingEnabled != newValue.debuggingEnabled) { debuggingEnabled = newValue.debuggingEnabled; } if (currentValue.userAgent != newValue.userAgent) { userAgent = newValue.userAgent; } if (currentValue.zoomEnabled != newValue.zoomEnabled) { zoomEnabled = newValue.zoomEnabled; } return WebSettings( javascriptMode: javascriptMode, hasNavigationDelegate: hasNavigationDelegate, hasProgressTracking: hasProgressTracking, debuggingEnabled: debuggingEnabled, userAgent: userAgent, zoomEnabled: zoomEnabled, ); } Set _extractChannelNames(Set? channels) { final Set channelNames = channels == null ? {} : channels.map((JavascriptChannel channel) => channel.name).toSet(); return channelNames; } class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { _PlatformCallbacksHandler(this._widget); WebView _widget; @override FutureOr onNavigationRequest({ required String url, required bool isForMainFrame, }) async { final NavigationRequest request = NavigationRequest._(url: url, isForMainFrame: isForMainFrame); final bool allowNavigation = _widget.navigationDelegate == null || await _widget.navigationDelegate!(request) == NavigationDecision.navigate; return allowNavigation; } @override void onPageStarted(String url) { if (_widget.onPageStarted != null) { _widget.onPageStarted!(url); } } @override void onPageFinished(String url) { if (_widget.onPageFinished != null) { _widget.onPageFinished!(url); } } @override void onProgress(int progress) { if (_widget.onProgress != null) { _widget.onProgress!(progress); } } @override void onWebResourceError(WebResourceError error) { if (_widget.onWebResourceError != null) { _widget.onWebResourceError!(error); } } } /// Controls a [WebView]. /// /// A [WebViewController] instance can be obtained by setting the [WebView.onWebViewCreated] /// callback for a [WebView] widget. class WebViewController { WebViewController._( this._widget, this._webViewPlatformController, this._javascriptChannelRegistry, ) : assert(_webViewPlatformController != null) { _settings = _webSettingsFromWidget(_widget); } final WebViewPlatformController _webViewPlatformController; final JavascriptChannelRegistry _javascriptChannelRegistry; late WebSettings _settings; WebView _widget; /// Loads the file located at the specified [absoluteFilePath]. /// /// The [absoluteFilePath] parameter should contain the absolute path to the /// file as it is stored on the device. For example: /// `/Users/username/Documents/www/index.html`. /// /// Throws an ArgumentError if the [absoluteFilePath] does not exist. Future loadFile( String absoluteFilePath, ) { assert(absoluteFilePath.isNotEmpty); return _webViewPlatformController.loadFile(absoluteFilePath); } /// Loads the Flutter asset specified in the pubspec.yaml file. /// /// Throws an ArgumentError if [key] is not part of the specified assets /// in the pubspec.yaml file. Future loadFlutterAsset(String key) { assert(key.isNotEmpty); return _webViewPlatformController.loadFlutterAsset(key); } /// Loads the supplied HTML string. /// /// The [baseUrl] parameter is used when resolving relative URLs within the /// HTML string. Future loadHtmlString( String html, { String? baseUrl, }) { assert(html.isNotEmpty); return _webViewPlatformController.loadHtmlString( html, baseUrl: baseUrl, ); } /// Loads the specified URL. /// /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will /// be added as key value pairs of HTTP headers for the request. /// /// `url` must not be null. /// /// Throws an ArgumentError if `url` is not a valid URL string. Future loadUrl( String url, { Map? headers, }) async { assert(url != null); _validateUrlString(url); return _webViewPlatformController.loadUrl(url, headers); } /// Makes a specific HTTP request and loads the response in the webview. /// /// [WebViewRequest.method] must be one of the supported HTTP methods /// in [WebViewRequestMethod]. /// /// If [WebViewRequest.headers] is not empty, its key-value pairs will be /// added as the headers for the request. /// /// If [WebViewRequest.body] is not null, it will be added as the body /// for the request. /// /// Throws an ArgumentError if [WebViewRequest.uri] has empty scheme. /// /// Android only: /// When making a POST request, headers are ignored. As a workaround, make /// the request manually and load the response data using [loadHTMLString]. Future loadRequest(WebViewRequest request) async { return _webViewPlatformController.loadRequest(request); } /// Accessor to the current URL that the WebView is displaying. /// /// If [WebView.initialUrl] was never specified, returns `null`. /// Note that this operation is asynchronous, and it is possible that the /// current URL changes again by the time this function returns (in other /// words, by the time this future completes, the WebView may be displaying a /// different URL). Future currentUrl() { return _webViewPlatformController.currentUrl(); } /// Checks whether there's a back history item. /// /// Note that this operation is asynchronous, and it is possible that the "canGoBack" state has /// changed by the time the future completed. Future canGoBack() { return _webViewPlatformController.canGoBack(); } /// Checks whether there's a forward history item. /// /// Note that this operation is asynchronous, and it is possible that the "canGoForward" state has /// changed by the time the future completed. Future canGoForward() { return _webViewPlatformController.canGoForward(); } /// Goes back in the history of this WebView. /// /// If there is no back history item this is a no-op. Future goBack() { return _webViewPlatformController.goBack(); } /// Goes forward in the history of this WebView. /// /// If there is no forward history item this is a no-op. Future goForward() { return _webViewPlatformController.goForward(); } /// Reloads the current URL. Future reload() { return _webViewPlatformController.reload(); } /// Clears all caches used by the [WebView]. /// /// The following caches are cleared: /// 1. Browser HTTP Cache. /// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches. /// These are not yet supported in iOS WkWebView. Service workers tend to use this cache. /// 3. Application cache. /// 4. Local Storage. /// /// Note: Calling this method also triggers a reload. Future clearCache() async { await _webViewPlatformController.clearCache(); return reload(); } Future _updateJavascriptChannels( Set? newChannels) async { final Set currentChannels = _javascriptChannelRegistry.channels.keys.toSet(); final Set newChannelNames = _extractChannelNames(newChannels); final Set channelsToAdd = newChannelNames.difference(currentChannels); final Set channelsToRemove = currentChannels.difference(newChannelNames); if (channelsToRemove.isNotEmpty) { await _webViewPlatformController .removeJavascriptChannels(channelsToRemove); } if (channelsToAdd.isNotEmpty) { await _webViewPlatformController.addJavascriptChannels(channelsToAdd); } _javascriptChannelRegistry.updateJavascriptChannelsFromSet(newChannels); } Future _updateWidget(WebView widget) async { _widget = widget; await _updateSettings(_webSettingsFromWidget(widget)); await _updateJavascriptChannels(widget.javascriptChannels); } Future _updateSettings(WebSettings newSettings) { final WebSettings update = _clearUnchangedWebSettings(_settings, newSettings); _settings = newSettings; return _webViewPlatformController.updateSettings(update); } /// Evaluates a JavaScript expression in the context of the current page. /// /// On Android returns the evaluation result as a JSON formatted string. /// /// On iOS depending on the value type the return value would be one of: /// /// - For primitive JavaScript types: the value string formatted /// (e.g JavaScript 100 returns '100'). /// - For JavaScript arrays of supported types: a string formatted NSArray /// (e.g '(1,2,3), note that the string for NSArray is formatted and might /// contain newlines and extra spaces.'). /// - Other non-primitive types are not supported on iOS and will complete /// the Future with an error. /// /// The Future completes with an error if a JavaScript error occurred, /// or on iOS, if the type of the evaluated expression is /// not supported as described above. /// /// When evaluating JavaScript in a [WebView], it is best practice to wait for /// the [WebView.onPageFinished] callback. This guarantees all the JavaScript /// embedded in the main frame HTML has been loaded. @Deprecated('Use [runJavascript] or [runJavascriptReturningResult]') Future evaluateJavascript(String javascriptString) { if (_settings.javascriptMode == JavascriptMode.disabled) { return Future.error(FlutterError( 'JavaScript mode must be enabled/unrestricted when calling evaluateJavascript.')); } return _webViewPlatformController.evaluateJavascript(javascriptString); } /// Runs the given JavaScript in the context of the current page. /// If you are looking for the result, use [runJavascriptReturningResult] instead. /// The Future completes with an error if a JavaScript error occurred. /// /// When running JavaScript in a [WebView], it is best practice to wait for /// the [WebView.onPageFinished] callback. This guarantees all the JavaScript /// embedded in the main frame HTML has been loaded. Future runJavascript(String javaScriptString) { if (_settings.javascriptMode == JavascriptMode.disabled) { return Future.error(FlutterError( 'JavaScript mode must be enabled/unrestricted when calling runJavascript.')); } return _webViewPlatformController.runJavascript(javaScriptString); } /// Runs the given JavaScript in the context of the current page, /// and returns the result. /// /// On Android returns the evaluation result as a JSON formatted string. /// /// On iOS depending on the value type the return value would be one of: /// /// - For primitive JavaScript types: the value string formatted /// (e.g JavaScript 100 returns '100'). /// - For JavaScript arrays of supported types: a string formatted NSArray /// (e.g '(1,2,3), note that the string for NSArray is formatted and might /// contain newlines and extra spaces.'). /// /// The Future completes with an error if a JavaScript error occurred, /// or if the type the given expression evaluates to is unsupported. /// Unsupported values include certain non primitive types on iOS, as well as /// `undefined` or `null` on iOS 14+. /// /// When evaluating JavaScript in a [WebView], it is best practice to wait /// for the [WebView.onPageFinished] callback. This guarantees all the /// JavaScript embedded in the main frame HTML has been loaded. Future runJavascriptReturningResult(String javaScriptString) { if (_settings.javascriptMode == JavascriptMode.disabled) { return Future.error(FlutterError( 'JavaScript mode must be enabled/unrestricted when calling runJavascriptReturningResult.')); } return _webViewPlatformController .runJavascriptReturningResult(javaScriptString); } /// Returns the title of the currently loaded page. Future getTitle() { return _webViewPlatformController.getTitle(); } /// Sets the WebView's content scroll position. /// /// The parameters `x` and `y` specify the scroll position in WebView pixels. Future scrollTo(int x, int y) { return _webViewPlatformController.scrollTo(x, y); } /// Move the scrolled position of this view. /// /// The parameters `x` and `y` specify the amount of WebView pixels to scroll by horizontally and vertically respectively. Future scrollBy(int x, int y) { return _webViewPlatformController.scrollBy(x, y); } /// Return the horizontal scroll position, in WebView pixels, of this view. /// /// Scroll position is measured from left. Future getScrollX() { return _webViewPlatformController.getScrollX(); } /// Return the vertical scroll position, in WebView pixels, of this view. /// /// Scroll position is measured from top. Future getScrollY() { return _webViewPlatformController.getScrollY(); } } /// Manages cookies pertaining to all [WebView]s. class CookieManager { /// Creates a [CookieManager] -- returns the instance if it's already been called. factory CookieManager() { return _instance ??= CookieManager._(); } CookieManager._() { if (WebViewCookieManagerPlatform.instance == null) { if (Platform.isAndroid) { WebViewCookieManagerPlatform.instance = WebViewAndroidCookieManager(); } else if (Platform.isIOS) { WebViewCookieManagerPlatform.instance = WKWebViewCookieManager(); } else { throw AssertionError( 'This platform is currently unsupported by webview_flutter.'); } } } static CookieManager? _instance; /// Clears all cookies for all [WebView] instances. /// /// Returns true if cookies were present before clearing, else false. Future clearCookies() => WebViewCookieManagerPlatform.instance!.clearCookies(); /// Sets a cookie for all [WebView] instances. /// /// This is a no op on iOS versions below 11. Future setCookie(WebViewCookie cookie) => WebViewCookieManagerPlatform.instance!.setCookie(cookie); } // Throws an ArgumentError if `url` is not a valid URL string. void _validateUrlString(String url) { try { final Uri uri = Uri.parse(url); if (uri.scheme.isEmpty) { throw ArgumentError('Missing scheme in URL string: "$url"'); } } on FormatException catch (e) { throw ArgumentError(e); } } ================================================ FILE: packages/webview_flutter/webview_flutter/lib/src/navigation_delegate.dart ================================================ // Copyright 2013 The Flutter 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:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'webview_controller.dart'; /// Callbacks for accepting or rejecting navigation changes, and for tracking /// the progress of navigation requests. /// /// See [WebViewController.setNavigationDelegate]. /// /// ## Platform-Specific Features /// This class contains an underlying implementation provided by the current /// platform. Once a platform implementation is imported, the examples below /// can be followed to use features provided by a platform's implementation. /// /// {@macro webview_flutter.NavigationDelegate.fromPlatformCreationParams} /// /// Below is an example of accessing the platform-specific implementation for /// iOS and Android: /// /// ```dart /// final NavigationDelegate navigationDelegate = NavigationDelegate(); /// /// if (WebViewPlatform.instance is WebKitWebViewPlatform) { /// final WebKitNavigationDelegate webKitDelegate = /// navigationDelegate.platform as WebKitNavigationDelegate; /// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { /// final AndroidNavigationDelegate androidDelegate = /// navigationDelegate.platform as AndroidNavigationDelegate; /// } /// ``` class NavigationDelegate { /// Constructs a [NavigationDelegate]. NavigationDelegate({ FutureOr Function(NavigationRequest request)? onNavigationRequest, void Function(String url)? onPageStarted, void Function(String url)? onPageFinished, void Function(int progress)? onProgress, void Function(WebResourceError error)? onWebResourceError, }) : this.fromPlatformCreationParams( const PlatformNavigationDelegateCreationParams(), onNavigationRequest: onNavigationRequest, onPageStarted: onPageStarted, onPageFinished: onPageFinished, onProgress: onProgress, onWebResourceError: onWebResourceError, ); /// Constructs a [NavigationDelegate] from creation params for a specific /// platform. /// /// {@template webview_flutter.NavigationDelegate.fromPlatformCreationParams} /// Below is an example of setting platform-specific creation parameters for /// iOS and Android: /// /// ```dart /// PlatformNavigationDelegateCreationParams params = /// const PlatformNavigationDelegateCreationParams(); /// /// if (WebViewPlatform.instance is WebKitWebViewPlatform) { /// params = WebKitNavigationDelegateCreationParams /// .fromPlatformNavigationDelegateCreationParams( /// params, /// ); /// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { /// params = AndroidNavigationDelegateCreationParams /// .fromPlatformNavigationDelegateCreationParams( /// params, /// ); /// } /// /// final NavigationDelegate navigationDelegate = /// NavigationDelegate.fromPlatformCreationParams( /// params, /// ); /// ``` /// {@endtemplate} NavigationDelegate.fromPlatformCreationParams( PlatformNavigationDelegateCreationParams params, { FutureOr Function(NavigationRequest request)? onNavigationRequest, void Function(String url)? onPageStarted, void Function(String url)? onPageFinished, void Function(int progress)? onProgress, void Function(WebResourceError error)? onWebResourceError, }) : this.fromPlatform( PlatformNavigationDelegate(params), onNavigationRequest: onNavigationRequest, onPageStarted: onPageStarted, onPageFinished: onPageFinished, onProgress: onProgress, onWebResourceError: onWebResourceError, ); /// Constructs a [NavigationDelegate] from a specific platform implementation. NavigationDelegate.fromPlatform( this.platform, { this.onNavigationRequest, this.onPageStarted, this.onPageFinished, this.onProgress, this.onWebResourceError, }) { if (onNavigationRequest != null) { platform.setOnNavigationRequest(onNavigationRequest!); } if (onPageStarted != null) { platform.setOnPageStarted(onPageStarted!); } if (onPageFinished != null) { platform.setOnPageFinished(onPageFinished!); } if (onProgress != null) { platform.setOnProgress(onProgress!); } if (onWebResourceError != null) { platform.setOnWebResourceError(onWebResourceError!); } } /// Implementation of [PlatformNavigationDelegate] for the current platform. final PlatformNavigationDelegate platform; /// Invoked when a decision for a navigation request is pending. /// /// When a navigation is initiated by the WebView (e.g when a user clicks a /// link) this delegate is called and has to decide how to proceed with the /// navigation. /// /// *Important*: Some platforms may also trigger this callback from calls to /// [WebViewController.loadRequest]. /// /// See [NavigationDecision]. final NavigationRequestCallback? onNavigationRequest; /// Invoked when a page has started loading. final PageEventCallback? onPageStarted; /// Invoked when a page has finished loading. final PageEventCallback? onPageFinished; /// Invoked when a page is loading to report the progress. final ProgressCallback? onProgress; /// Invoked when a resource loading error occurred. final WebResourceErrorCallback? onWebResourceError; } ================================================ FILE: packages/webview_flutter/webview_flutter/lib/src/webview_controller.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'navigation_delegate.dart'; import 'webview_widget.dart'; /// Controls a WebView provided by the host platform. /// /// Pass this to a [WebViewWidget] to display the WebView. /// /// A [WebViewController] can only be used by a single [WebViewWidget] at a /// time. /// /// ## Platform-Specific Features /// This class contains an underlying implementation provided by the current /// platform. Once a platform implementation is imported, the examples below /// can be followed to use features provided by a platform's implementation. /// /// {@macro webview_flutter.WebViewController.fromPlatformCreationParams} /// /// Below is an example of accessing the platform-specific implementation for /// iOS and Android: /// /// ```dart /// final WebViewController webViewController = WebViewController(); /// /// if (WebViewPlatform.instance is WebKitWebViewPlatform) { /// final WebKitWebViewController webKitController = /// webViewController.platform as WebKitWebViewController; /// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { /// final AndroidWebViewController androidController = /// webViewController.platform as AndroidWebViewController; /// } /// ``` class WebViewController { /// Constructs a [WebViewController]. /// /// See [WebViewController.fromPlatformCreationParams] for setting parameters /// for a specific platform. WebViewController() : this.fromPlatformCreationParams( const PlatformWebViewControllerCreationParams(), ); /// Constructs a [WebViewController] from creation params for a specific /// platform. /// /// {@template webview_flutter.WebViewCookieManager.fromPlatformCreationParams} /// Below is an example of setting platform-specific creation parameters for /// iOS and Android: /// /// ```dart /// PlatformWebViewControllerCreationParams params = /// const PlatformWebViewControllerCreationParams(); /// /// if (WebViewPlatform.instance is WebKitWebViewPlatform) { /// params = WebKitWebViewControllerCreationParams /// .fromPlatformWebViewControllerCreationParams( /// params, /// ); /// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { /// params = AndroidWebViewControllerCreationParams /// .fromPlatformWebViewControllerCreationParams( /// params, /// ); /// } /// /// final WebViewController webViewController = /// WebViewController.fromPlatformCreationParams( /// params, /// ); /// ``` /// {@endtemplate} WebViewController.fromPlatformCreationParams( PlatformWebViewControllerCreationParams params, ) : this.fromPlatform(PlatformWebViewController(params)); /// Constructs a [WebViewController] from a specific platform implementation. WebViewController.fromPlatform(this.platform); /// Implementation of [PlatformWebViewController] for the current platform. final PlatformWebViewController platform; /// Loads the file located on the specified [absoluteFilePath]. /// /// The [absoluteFilePath] parameter should contain the absolute path to the /// file as it is stored on the device. For example: /// `/Users/username/Documents/www/index.html`. /// /// Throws a `PlatformException` if the [absoluteFilePath] does not exist. Future loadFile(String absoluteFilePath) { return platform.loadFile(absoluteFilePath); } /// Loads the Flutter asset specified in the pubspec.yaml file. /// /// Throws a `PlatformException` if [key] is not part of the specified assets /// in the pubspec.yaml file. Future loadFlutterAsset(String key) { assert(key.isNotEmpty); return platform.loadFlutterAsset(key); } /// Loads the supplied HTML string. /// /// The [baseUrl] parameter is used when resolving relative URLs within the /// HTML string. Future loadHtmlString(String html, {String? baseUrl}) { assert(html.isNotEmpty); return platform.loadHtmlString(html, baseUrl: baseUrl); } /// Makes a specific HTTP request ands loads the response in the webview. /// /// [method] must be one of the supported HTTP methods in [LoadRequestMethod]. /// /// If [headers] is not empty, its key-value pairs will be added as the /// headers for the request. /// /// If [body] is not null, it will be added as the body for the request. /// /// Throws an ArgumentError if [uri] has an empty scheme. Future loadRequest( Uri uri, { LoadRequestMethod method = LoadRequestMethod.get, Map headers = const {}, Uint8List? body, }) { if (uri.scheme.isEmpty) { throw ArgumentError('Missing scheme in uri: $uri'); } return platform.loadRequest(LoadRequestParams( uri: uri, method: method, headers: headers, body: body, )); } /// Returns the current URL that the WebView is displaying. /// /// If no URL was ever loaded, returns `null`. Future currentUrl() { return platform.currentUrl(); } /// Checks whether there's a back history item. Future canGoBack() { return platform.canGoBack(); } /// Checks whether there's a forward history item. Future canGoForward() { return platform.canGoForward(); } /// Goes back in the history of this WebView. /// /// If there is no back history item this is a no-op. Future goBack() { return platform.goBack(); } /// Goes forward in the history of this WebView. /// /// If there is no forward history item this is a no-op. Future goForward() { return platform.goForward(); } /// Reloads the current URL. Future reload() { return platform.reload(); } /// Sets the [NavigationDelegate] containing the callback methods that are /// called during navigation events. Future setNavigationDelegate(NavigationDelegate delegate) { return platform.setPlatformNavigationDelegate(delegate.platform); } /// Clears all caches used by the WebView. /// /// The following caches are cleared: /// 1. Browser HTTP Cache. /// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) /// caches. Service workers tend to use this cache. /// 3. Application cache. Future clearCache() { return platform.clearCache(); } /// Clears the local storage used by the WebView. Future clearLocalStorage() { return platform.clearLocalStorage(); } /// Runs the given JavaScript in the context of the current page. /// /// The Future completes with an error if a JavaScript error occurred. Future runJavaScript(String javaScript) { return platform.runJavaScript(javaScript); } /// Runs the given JavaScript in the context of the current page, and returns /// the result. /// /// The Future completes with an error if a JavaScript error occurred, or if /// the type the given expression evaluates to is unsupported. Unsupported /// values include certain non-primitive types on iOS, as well as `undefined` /// or `null` on iOS 14+. Future runJavaScriptReturningResult(String javaScript) { return platform.runJavaScriptReturningResult(javaScript); } /// Adds a new JavaScript channel to the set of enabled channels. /// /// The JavaScript code can then call `postMessage` on that object to send a /// message that will be passed to [onMessageReceived]. /// /// For example, after adding the following JavaScript channel: /// /// ```dart /// final WebViewController controller = WebViewController(); /// controller.addJavaScriptChannel( /// name: 'Print', /// onMessageReceived: (JavascriptMessage message) { /// print(message.message); /// }, /// ); /// ``` /// /// JavaScript code can call: /// /// ```javascript /// Print.postMessage('Hello'); /// ``` /// /// to asynchronously invoke the message handler which will print the message /// to standard output. /// /// Adding a new JavaScript channel only takes affect after the next page is /// loaded. /// /// A channel [name] cannot be the same for multiple channels. Future addJavaScriptChannel( String name, { required void Function(JavaScriptMessage) onMessageReceived, }) { assert(name.isNotEmpty); return platform.addJavaScriptChannel(JavaScriptChannelParams( name: name, onMessageReceived: onMessageReceived, )); } /// Removes the JavaScript channel with the matching name from the set of /// enabled channels. /// /// This disables the channel with the matching name if it was previously /// enabled through the [addJavaScriptChannel]. Future removeJavaScriptChannel(String javaScriptChannelName) { return platform.removeJavaScriptChannel(javaScriptChannelName); } /// The title of the currently loaded page. Future getTitle() { return platform.getTitle(); } /// Sets the scrolled position of this view. /// /// The parameters `x` and `y` specify the position to scroll to in WebView /// pixels. Future scrollTo(int x, int y) { return platform.scrollTo(x, y); } /// Moves the scrolled position of this view. /// /// The parameters `x` and `y` specify the amount of WebView pixels to scroll /// by. Future scrollBy(int x, int y) { return platform.scrollBy(x, y); } /// Returns the current scroll position of this view. /// /// Scroll position is measured from the top left. Future getScrollPosition() { return platform.getScrollPosition(); } /// Whether to support zooming using the on-screen zoom controls and gestures. Future enableZoom(bool enabled) { return platform.enableZoom(enabled); } /// Sets the current background color of this view. Future setBackgroundColor(Color color) { return platform.setBackgroundColor(color); } /// Sets the JavaScript execution mode to be used by the WebView. Future setJavaScriptMode(JavaScriptMode javaScriptMode) { return platform.setJavaScriptMode(javaScriptMode); } /// Sets the value used for the HTTP `User-Agent:` request header. Future setUserAgent(String? userAgent) { return platform.setUserAgent(userAgent); } } ================================================ FILE: packages/webview_flutter/webview_flutter/lib/src/webview_cookie_manager.dart ================================================ // Copyright 2013 The Flutter 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:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; /// Manages cookies pertaining to all WebViews. /// /// ## Platform-Specific Features /// This class contains an underlying implementation provided by the current /// platform. Once a platform implementation is imported, the examples below /// can be followed to use features provided by a platform's implementation. /// /// {@macro webview_flutter.WebViewCookieManager.fromPlatformCreationParams} /// /// Below is an example of accessing the platform-specific implementation for /// iOS and Android: /// /// ```dart /// final WebViewCookieManager cookieManager = WebViewCookieManager(); /// /// if (WebViewPlatform.instance is WebKitWebViewPlatform) { /// final WebKitWebViewCookieManager webKitManager = /// cookieManager.platform as WebKitWebViewCookieManager; /// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { /// final AndroidWebViewCookieManager androidManager = /// cookieManager.platform as AndroidWebViewCookieManager; /// } /// ``` class WebViewCookieManager { /// Constructs a [WebViewCookieManager]. /// /// See [WebViewCookieManager.fromPlatformCreationParams] for setting /// parameters for a specific platform. WebViewCookieManager() : this.fromPlatformCreationParams( const PlatformWebViewCookieManagerCreationParams(), ); /// Constructs a [WebViewCookieManager] from creation params for a specific /// platform. /// /// {@template webview_flutter.WebViewCookieManager.fromPlatformCreationParams} /// Below is an example of setting platform-specific creation parameters for /// iOS and Android: /// /// ```dart /// PlatformWebViewCookieManagerCreationParams params = /// const PlatformWebViewCookieManagerCreationParams(); /// /// if (WebViewPlatform.instance is WebKitWebViewPlatform) { /// params = WebKitWebViewCookieManagerCreationParams /// .fromPlatformWebViewCookieManagerCreationParams( /// params, /// ); /// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { /// params = AndroidWebViewCookieManagerCreationParams /// .fromPlatformWebViewCookieManagerCreationParams( /// params, /// ); /// } /// /// final WebViewCookieManager webViewCookieManager = /// WebViewCookieManager.fromPlatformCreationParams( /// params, /// ); /// ``` /// {@endtemplate} WebViewCookieManager.fromPlatformCreationParams( PlatformWebViewCookieManagerCreationParams params, ) : this.fromPlatform(PlatformWebViewCookieManager(params)); /// Constructs a [WebViewCookieManager] from a specific platform /// implementation. WebViewCookieManager.fromPlatform(this.platform); /// Implementation of [PlatformWebViewCookieManager] for the current platform. final PlatformWebViewCookieManager platform; /// Clears all cookies for all WebViews. /// /// Returns true if cookies were present before clearing, else false. Future clearCookies() => platform.clearCookies(); /// Sets a cookie for all WebView instances. /// /// This is a no op on iOS versions below 11. Future setCookie(WebViewCookie cookie) => platform.setCookie(cookie); } ================================================ FILE: packages/webview_flutter/webview_flutter/lib/src/webview_flutter_legacy.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'package:webview_flutter_android/src/webview_flutter_android_legacy.dart'; export 'package:webview_flutter_wkwebview/src/webview_flutter_wkwebview_legacy.dart'; export 'legacy/platform_interface.dart'; export 'legacy/webview.dart'; ================================================ FILE: packages/webview_flutter/webview_flutter/lib/src/webview_widget.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'webview_controller.dart'; /// Displays a native WebView as a Widget. /// /// ## Platform-Specific Features /// This class contains an underlying implementation provided by the current /// platform. Once a platform implementation is imported, the examples below /// can be followed to use features provided by a platform's implementation. /// /// {@macro webview_flutter.WebViewWidget.fromPlatformCreationParams} /// /// Below is an example of accessing the platform-specific implementation for /// iOS and Android: /// /// ```dart /// final WebViewController controller = WebViewController(); /// /// final WebViewWidget webViewWidget = WebViewWidget(controller: controller); /// /// if (WebViewPlatform.instance is WebKitWebViewPlatform) { /// final WebKitWebViewWidget webKitWidget = /// webViewWidget.platform as WebKitWebViewWidget; /// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { /// final AndroidWebViewWidget androidWidget = /// webViewWidget.platform as AndroidWebViewWidget; /// } /// ``` class WebViewWidget extends StatelessWidget { /// Constructs a [WebViewWidget]. /// /// See [WebViewWidget.fromPlatformCreationParams] for setting parameters for /// a specific platform. WebViewWidget({ Key? key, required WebViewController controller, TextDirection layoutDirection = TextDirection.ltr, Set> gestureRecognizers = const >{}, }) : this.fromPlatformCreationParams( key: key, params: PlatformWebViewWidgetCreationParams( controller: controller.platform, layoutDirection: layoutDirection, gestureRecognizers: gestureRecognizers, ), ); /// Constructs a [WebViewWidget] from creation params for a specific platform. /// /// {@template webview_flutter.WebViewWidget.fromPlatformCreationParams} /// Below is an example of setting platform-specific creation parameters for /// iOS and Android: /// /// ```dart /// final WebViewController controller = WebViewController(); /// /// PlatformWebViewWidgetCreationParams params = /// PlatformWebViewWidgetCreationParams( /// controller: controller.platform, /// layoutDirection: TextDirection.ltr, /// gestureRecognizers: const >{}, /// ); /// /// if (WebViewPlatform.instance is WebKitWebViewPlatform) { /// params = WebKitWebViewWidgetCreationParams /// .fromPlatformWebViewWidgetCreationParams( /// params, /// ); /// } else if (WebViewPlatform.instance is AndroidWebViewPlatform) { /// params = AndroidWebViewWidgetCreationParams /// .fromPlatformWebViewWidgetCreationParams( /// params, /// ); /// } /// /// final WebViewWidget webViewWidget = /// WebViewWidget.fromPlatformCreationParams( /// params: params, /// ); /// ``` /// {@endtemplate} WebViewWidget.fromPlatformCreationParams({ Key? key, required PlatformWebViewWidgetCreationParams params, }) : this.fromPlatform(key: key, platform: PlatformWebViewWidget(params)); /// Constructs a [WebViewWidget] from a specific platform implementation. WebViewWidget.fromPlatform({super.key, required this.platform}); /// Implementation of [PlatformWebViewWidget] for the current platform. final PlatformWebViewWidget platform; /// The layout direction to use for the embedded WebView. late final TextDirection layoutDirection = platform.params.layoutDirection; /// Specifies which gestures should be consumed by the web view. /// /// It is possible for other gesture recognizers to be competing with the web /// view on pointer events, e.g if the web view is inside a [ListView] the /// [ListView] will want to handle vertical drags. The web view will claim /// gestures that are recognized by any of the recognizers on this list. /// /// When `gestureRecognizers` is empty (default), the web view will only /// handle pointer events for gestures that were not claimed by any other /// gesture recognizer. late final Set> gestureRecognizers = platform.params.gestureRecognizers; @override Widget build(BuildContext context) { return platform.build(context); } } ================================================ FILE: packages/webview_flutter/webview_flutter/lib/webview_flutter.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. library webview_flutter; export 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart' show JavaScriptMessage, JavaScriptMode, LoadRequestMethod, NavigationDecision, NavigationRequest, NavigationRequestCallback, PageEventCallback, PlatformNavigationDelegateCreationParams, PlatformWebViewControllerCreationParams, PlatformWebViewCookieManagerCreationParams, PlatformWebViewWidgetCreationParams, ProgressCallback, WebResourceError, WebResourceErrorCallback, WebResourceErrorType, WebViewCookie, WebViewPlatform; export 'src/navigation_delegate.dart'; export 'src/webview_controller.dart'; export 'src/webview_cookie_manager.dart'; export 'src/webview_widget.dart'; ================================================ FILE: packages/webview_flutter/webview_flutter/pubspec.yaml ================================================ 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.4 environment: sdk: ">=2.17.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: platforms: android: default_package: webview_flutter_android ios: default_package: webview_flutter_wkwebview dependencies: flutter: sdk: flutter webview_flutter_android: ^3.0.0 webview_flutter_platform_interface: ^2.0.0 webview_flutter_wkwebview: ^3.0.0 dev_dependencies: build_runner: ^2.1.5 flutter_driver: sdk: flutter flutter_test: sdk: flutter mockito: ^5.3.2 plugin_platform_interface: ^2.1.3 ================================================ FILE: packages/webview_flutter/webview_flutter/test/legacy/webview_flutter_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:typed_data'; import 'package:flutter/src/foundation/basic_types.dart'; import 'package:flutter/src/gestures/recognizer.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter/src/webview_flutter_legacy.dart'; import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; import 'webview_flutter_test.mocks.dart'; typedef VoidCallback = void Function(); @GenerateMocks([WebViewPlatform, WebViewPlatformController]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); late MockWebViewPlatform mockWebViewPlatform; late MockWebViewPlatformController mockWebViewPlatformController; late MockWebViewCookieManagerPlatform mockWebViewCookieManagerPlatform; setUp(() { mockWebViewPlatformController = MockWebViewPlatformController(); mockWebViewPlatform = MockWebViewPlatform(); mockWebViewCookieManagerPlatform = MockWebViewCookieManagerPlatform(); when(mockWebViewPlatform.build( context: anyNamed('context'), creationParams: anyNamed('creationParams'), webViewPlatformCallbacksHandler: anyNamed('webViewPlatformCallbacksHandler'), javascriptChannelRegistry: anyNamed('javascriptChannelRegistry'), onWebViewPlatformCreated: anyNamed('onWebViewPlatformCreated'), gestureRecognizers: anyNamed('gestureRecognizers'), )).thenAnswer((Invocation invocation) { final WebViewPlatformCreatedCallback onWebViewPlatformCreated = invocation.namedArguments[const Symbol('onWebViewPlatformCreated')] as WebViewPlatformCreatedCallback; return TestPlatformWebView( mockWebViewPlatformController: mockWebViewPlatformController, onWebViewPlatformCreated: onWebViewPlatformCreated, ); }); WebView.platform = mockWebViewPlatform; WebViewCookieManagerPlatform.instance = mockWebViewCookieManagerPlatform; }); tearDown(() { mockWebViewCookieManagerPlatform.reset(); }); testWidgets('Create WebView', (WidgetTester tester) async { await tester.pumpWidget(const WebView()); }); testWidgets('Initial url', (WidgetTester tester) async { await tester.pumpWidget(const WebView(initialUrl: 'https://youtube.com')); final CreationParams params = captureBuildArgs( mockWebViewPlatform, creationParams: true, ).single as CreationParams; expect(params.initialUrl, 'https://youtube.com'); }); testWidgets('Javascript mode', (WidgetTester tester) async { await tester.pumpWidget(const WebView( javascriptMode: JavascriptMode.unrestricted, )); final CreationParams unrestrictedparams = captureBuildArgs( mockWebViewPlatform, creationParams: true, ).single as CreationParams; expect( unrestrictedparams.webSettings!.javascriptMode, JavascriptMode.unrestricted, ); await tester.pumpWidget(const WebView()); final CreationParams disabledparams = captureBuildArgs( mockWebViewPlatform, creationParams: true, ).single as CreationParams; expect(disabledparams.webSettings!.javascriptMode, JavascriptMode.disabled); }); testWidgets('Load file', (WidgetTester tester) async { WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); await controller!.loadFile('/test/path/index.html'); verify(mockWebViewPlatformController.loadFile( '/test/path/index.html', )); }); testWidgets('Load file with empty path', (WidgetTester tester) async { WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); expect(() => controller!.loadFile(''), throwsAssertionError); }); testWidgets('Load Flutter asset', (WidgetTester tester) async { WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); await controller!.loadFlutterAsset('assets/index.html'); verify(mockWebViewPlatformController.loadFlutterAsset( 'assets/index.html', )); }); testWidgets('Load Flutter asset with empty key', (WidgetTester tester) async { WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); expect(() => controller!.loadFlutterAsset(''), throwsAssertionError); }); testWidgets('Load HTML string without base URL', (WidgetTester tester) async { WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); await controller!.loadHtmlString('

This is a test paragraph.

'); verify(mockWebViewPlatformController.loadHtmlString( '

This is a test paragraph.

', )); }); testWidgets('Load HTML string with base URL', (WidgetTester tester) async { WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); await controller!.loadHtmlString( '

This is a test paragraph.

', baseUrl: 'https://flutter.dev', ); verify(mockWebViewPlatformController.loadHtmlString( '

This is a test paragraph.

', baseUrl: 'https://flutter.dev', )); }); testWidgets('Load HTML string with empty string', (WidgetTester tester) async { WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); expect(() => controller!.loadHtmlString(''), throwsAssertionError); }); testWidgets('Load url', (WidgetTester tester) async { WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); await controller!.loadUrl('https://flutter.io'); verify(mockWebViewPlatformController.loadUrl( 'https://flutter.io', argThat(isNull), )); }); testWidgets('Invalid urls', (WidgetTester tester) async { WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); final CreationParams params = captureBuildArgs( mockWebViewPlatform, creationParams: true, ).single as CreationParams; expect(params.initialUrl, isNull); expect(() => controller!.loadUrl(''), throwsA(anything)); expect(() => controller!.loadUrl('flutter.io'), throwsA(anything)); }); testWidgets('Headers in loadUrl', (WidgetTester tester) async { WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); final Map headers = { 'CACHE-CONTROL': 'ABC' }; await controller!.loadUrl('https://flutter.io', headers: headers); verify(mockWebViewPlatformController.loadUrl( 'https://flutter.io', {'CACHE-CONTROL': 'ABC'}, )); }); testWidgets('loadRequest', (WidgetTester tester) async { WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); final WebViewRequest req = WebViewRequest( uri: Uri.parse('https://flutter.dev'), method: WebViewRequestMethod.post, headers: {'foo': 'bar'}, body: Uint8List.fromList('Test Body'.codeUnits), ); await controller!.loadRequest(req); verify(mockWebViewPlatformController.loadRequest(req)); }); testWidgets('Clear Cache', (WidgetTester tester) async { WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); await controller!.clearCache(); verify(mockWebViewPlatformController.clearCache()); }); testWidgets('Can go back', (WidgetTester tester) async { when(mockWebViewPlatformController.canGoBack()) .thenAnswer((_) => Future.value(true)); WebViewController? controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); expect(controller!.canGoBack(), completion(true)); }); testWidgets("Can't go forward", (WidgetTester tester) async { when(mockWebViewPlatformController.canGoForward()) .thenAnswer((_) => Future.value(false)); WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); expect(controller!.canGoForward(), completion(false)); }); testWidgets('Go back', (WidgetTester tester) async { WebViewController? controller; await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); await controller!.goBack(); verify(mockWebViewPlatformController.goBack()); }); testWidgets('Go forward', (WidgetTester tester) async { WebViewController? controller; await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); await controller!.goForward(); verify(mockWebViewPlatformController.goForward()); }); testWidgets('Current URL', (WidgetTester tester) async { when(mockWebViewPlatformController.currentUrl()) .thenAnswer((_) => Future.value('https://youtube.com')); WebViewController? controller; await tester.pumpWidget( WebView( onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(controller, isNotNull); expect(await controller!.currentUrl(), 'https://youtube.com'); }); testWidgets('Reload url', (WidgetTester tester) async { late WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); await controller.reload(); verify(mockWebViewPlatformController.reload()); }); testWidgets('evaluate Javascript', (WidgetTester tester) async { when(mockWebViewPlatformController.evaluateJavascript('fake js string')) .thenAnswer((_) => Future.value('fake js string')); late WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect( // ignore: deprecated_member_use_from_same_package await controller.evaluateJavascript('fake js string'), 'fake js string', reason: 'should get the argument'); }); testWidgets('evaluate Javascript with JavascriptMode disabled', (WidgetTester tester) async { late WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect( // ignore: deprecated_member_use_from_same_package () => controller.evaluateJavascript('fake js string'), throwsA(anything), ); }); testWidgets('runJavaScript', (WidgetTester tester) async { late WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); await controller.runJavascript('fake js string'); verify(mockWebViewPlatformController.runJavascript('fake js string')); }); testWidgets('runJavaScript with JavascriptMode disabled', (WidgetTester tester) async { late WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect( () => controller.runJavascript('fake js string'), throwsA(anything), ); }); testWidgets('runJavaScriptReturningResult', (WidgetTester tester) async { when(mockWebViewPlatformController .runJavascriptReturningResult('fake js string')) .thenAnswer((_) => Future.value('fake js string')); late WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect(await controller.runJavascriptReturningResult('fake js string'), 'fake js string', reason: 'should get the argument'); }); testWidgets('runJavaScriptReturningResult with JavascriptMode disabled', (WidgetTester tester) async { late WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://flutter.io', onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); expect( () => controller.runJavascriptReturningResult('fake js string'), throwsA(anything), ); }); testWidgets('Cookies can be cleared once', (WidgetTester tester) async { await tester.pumpWidget( const WebView( initialUrl: 'https://flutter.io', ), ); final CookieManager cookieManager = CookieManager(); final bool hasCookies = await cookieManager.clearCookies(); expect(hasCookies, true); }); testWidgets('Cookies can be set', (WidgetTester tester) async { const WebViewCookie cookie = WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev'); await tester.pumpWidget( const WebView( initialUrl: 'https://flutter.io', ), ); final CookieManager cookieManager = CookieManager(); await cookieManager.setCookie(cookie); expect(mockWebViewCookieManagerPlatform.setCookieCalls, [cookie]); }); testWidgets('Initial JavaScript channels', (WidgetTester tester) async { await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', javascriptChannels: { JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), }, ), ); final CreationParams params = captureBuildArgs( mockWebViewPlatform, creationParams: true, ).single as CreationParams; expect(params.javascriptChannelNames, unorderedEquals(['Tts', 'Alarm'])); }); test('Only valid JavaScript channel names are allowed', () { void noOp(JavascriptMessage msg) {} JavascriptChannel(name: 'Tts1', onMessageReceived: noOp); JavascriptChannel(name: '_Alarm', onMessageReceived: noOp); JavascriptChannel(name: 'foo_bar_', onMessageReceived: noOp); VoidCallback createChannel(String name) { return () { JavascriptChannel(name: name, onMessageReceived: noOp); }; } expect(createChannel('1Alarm'), throwsAssertionError); expect(createChannel('foo.bar'), throwsAssertionError); expect(createChannel(''), throwsAssertionError); }); testWidgets('Unique JavaScript channel names are required', (WidgetTester tester) async { await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', javascriptChannels: { JavascriptChannel( name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), }, ), ); expect(tester.takeException(), isNot(null)); }); testWidgets('JavaScript channels update', (WidgetTester tester) async { await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', javascriptChannels: { JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm', onMessageReceived: (JavascriptMessage msg) {}), }, ), ); await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', javascriptChannels: { JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm2', onMessageReceived: (JavascriptMessage msg) {}), JavascriptChannel( name: 'Alarm3', onMessageReceived: (JavascriptMessage msg) {}), }, ), ); final JavascriptChannelRegistry channelRegistry = captureBuildArgs( mockWebViewPlatform, javascriptChannelRegistry: true, ).first as JavascriptChannelRegistry; expect( channelRegistry.channels.keys, unorderedEquals(['Tts', 'Alarm2', 'Alarm3']), ); }); testWidgets('Remove all JavaScript channels and then add', (WidgetTester tester) async { // This covers a specific bug we had where after updating javascriptChannels to null, // updating it again with a subset of the previously registered channels fails as the // widget's cache of current channel wasn't properly updated when updating javascriptChannels to // null. await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', javascriptChannels: { JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), }, ), ); await tester.pumpWidget( const WebView( initialUrl: 'https://youtube.com', ), ); await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', javascriptChannels: { JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) {}), }, ), ); final JavascriptChannelRegistry channelRegistry = captureBuildArgs( mockWebViewPlatform, javascriptChannelRegistry: true, ).last as JavascriptChannelRegistry; expect(channelRegistry.channels.keys, unorderedEquals(['Tts'])); }); testWidgets('JavaScript channel messages', (WidgetTester tester) async { final List ttsMessagesReceived = []; final List alarmMessagesReceived = []; await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', javascriptChannels: { JavascriptChannel( name: 'Tts', onMessageReceived: (JavascriptMessage msg) { ttsMessagesReceived.add(msg.message); }), JavascriptChannel( name: 'Alarm', onMessageReceived: (JavascriptMessage msg) { alarmMessagesReceived.add(msg.message); }), }, ), ); final JavascriptChannelRegistry channelRegistry = captureBuildArgs( mockWebViewPlatform, javascriptChannelRegistry: true, ).single as JavascriptChannelRegistry; expect(ttsMessagesReceived, isEmpty); expect(alarmMessagesReceived, isEmpty); channelRegistry.onJavascriptChannelMessage('Tts', 'Hello'); channelRegistry.onJavascriptChannelMessage('Tts', 'World'); expect(ttsMessagesReceived, ['Hello', 'World']); }); group('$PageStartedCallback', () { testWidgets('onPageStarted is not null', (WidgetTester tester) async { String? returnedUrl; await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', onPageStarted: (String url) { returnedUrl = url; }, )); final WebViewPlatformCallbacksHandler handler = captureBuildArgs( mockWebViewPlatform, webViewPlatformCallbacksHandler: true, ).single as WebViewPlatformCallbacksHandler; handler.onPageStarted('https://youtube.com'); expect(returnedUrl, 'https://youtube.com'); }); testWidgets('onPageStarted is null', (WidgetTester tester) async { await tester.pumpWidget(const WebView( initialUrl: 'https://youtube.com', )); final WebViewPlatformCallbacksHandler handler = captureBuildArgs( mockWebViewPlatform, webViewPlatformCallbacksHandler: true, ).single as WebViewPlatformCallbacksHandler; // The platform side will always invoke a call for onPageStarted. This is // to test that it does not crash on a null callback. handler.onPageStarted('https://youtube.com'); }); testWidgets('onPageStarted changed', (WidgetTester tester) async { String? returnedUrl; await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', onPageStarted: (String url) {}, )); await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', onPageStarted: (String url) { returnedUrl = url; }, )); final WebViewPlatformCallbacksHandler handler = captureBuildArgs( mockWebViewPlatform, webViewPlatformCallbacksHandler: true, ).last as WebViewPlatformCallbacksHandler; handler.onPageStarted('https://youtube.com'); expect(returnedUrl, 'https://youtube.com'); }); }); group('$PageFinishedCallback', () { testWidgets('onPageFinished is not null', (WidgetTester tester) async { String? returnedUrl; await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', onPageFinished: (String url) { returnedUrl = url; }, )); final WebViewPlatformCallbacksHandler handler = captureBuildArgs( mockWebViewPlatform, webViewPlatformCallbacksHandler: true, ).single as WebViewPlatformCallbacksHandler; handler.onPageFinished('https://youtube.com'); expect(returnedUrl, 'https://youtube.com'); }); testWidgets('onPageFinished is null', (WidgetTester tester) async { await tester.pumpWidget(const WebView( initialUrl: 'https://youtube.com', )); final WebViewPlatformCallbacksHandler handler = captureBuildArgs( mockWebViewPlatform, webViewPlatformCallbacksHandler: true, ).single as WebViewPlatformCallbacksHandler; // The platform side will always invoke a call for onPageFinished. This is // to test that it does not crash on a null callback. handler.onPageFinished('https://youtube.com'); }); testWidgets('onPageFinished changed', (WidgetTester tester) async { String? returnedUrl; await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', onPageFinished: (String url) {}, )); await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', onPageFinished: (String url) { returnedUrl = url; }, )); final WebViewPlatformCallbacksHandler handler = captureBuildArgs( mockWebViewPlatform, webViewPlatformCallbacksHandler: true, ).last as WebViewPlatformCallbacksHandler; handler.onPageFinished('https://youtube.com'); expect(returnedUrl, 'https://youtube.com'); }); }); group('$PageLoadingCallback', () { testWidgets('onLoadingProgress is not null', (WidgetTester tester) async { int? loadingProgress; await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', onProgress: (int progress) { loadingProgress = progress; }, )); final WebViewPlatformCallbacksHandler handler = captureBuildArgs( mockWebViewPlatform, webViewPlatformCallbacksHandler: true, ).single as WebViewPlatformCallbacksHandler; handler.onProgress(50); expect(loadingProgress, 50); }); testWidgets('onLoadingProgress is null', (WidgetTester tester) async { await tester.pumpWidget(const WebView( initialUrl: 'https://youtube.com', )); final WebViewPlatformCallbacksHandler handler = captureBuildArgs( mockWebViewPlatform, webViewPlatformCallbacksHandler: true, ).single as WebViewPlatformCallbacksHandler; // This is to test that it does not crash on a null callback. handler.onProgress(50); }); testWidgets('onLoadingProgress changed', (WidgetTester tester) async { int? loadingProgress; await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', onProgress: (int progress) {}, )); await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', onProgress: (int progress) { loadingProgress = progress; }, )); final WebViewPlatformCallbacksHandler handler = captureBuildArgs( mockWebViewPlatform, webViewPlatformCallbacksHandler: true, ).last as WebViewPlatformCallbacksHandler; handler.onProgress(50); expect(loadingProgress, 50); }); }); group('navigationDelegate', () { testWidgets('hasNavigationDelegate', (WidgetTester tester) async { await tester.pumpWidget(const WebView( initialUrl: 'https://youtube.com', )); final CreationParams params = captureBuildArgs( mockWebViewPlatform, creationParams: true, ).single as CreationParams; expect(params.webSettings!.hasNavigationDelegate, false); await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', navigationDelegate: (NavigationRequest r) => NavigationDecision.navigate, )); final WebSettings updateSettings = verify(mockWebViewPlatformController.updateSettings(captureAny)) .captured .single as WebSettings; expect(updateSettings.hasNavigationDelegate, true); }); testWidgets('Block navigation', (WidgetTester tester) async { final List navigationRequests = []; await tester.pumpWidget(WebView( initialUrl: 'https://youtube.com', navigationDelegate: (NavigationRequest request) { navigationRequests.add(request); // Only allow navigating to https://flutter.dev return request.url == 'https://flutter.dev' ? NavigationDecision.navigate : NavigationDecision.prevent; })); final List args = captureBuildArgs( mockWebViewPlatform, creationParams: true, webViewPlatformCallbacksHandler: true, ); final CreationParams params = args[0] as CreationParams; expect(params.webSettings!.hasNavigationDelegate, true); final WebViewPlatformCallbacksHandler handler = args[1] as WebViewPlatformCallbacksHandler; // The navigation delegate only allows navigation to https://flutter.dev // so we should still be in https://youtube.com. expect( handler.onNavigationRequest( url: 'https://www.google.com', isForMainFrame: true, ), completion(false), ); expect(navigationRequests.length, 1); expect(navigationRequests[0].url, 'https://www.google.com'); expect(navigationRequests[0].isForMainFrame, true); expect( handler.onNavigationRequest( url: 'https://flutter.dev', isForMainFrame: true, ), completion(true), ); }); }); group('debuggingEnabled', () { testWidgets('enable debugging', (WidgetTester tester) async { await tester.pumpWidget(const WebView( debuggingEnabled: true, )); final CreationParams params = captureBuildArgs( mockWebViewPlatform, creationParams: true, ).single as CreationParams; expect(params.webSettings!.debuggingEnabled, true); }); testWidgets('defaults to false', (WidgetTester tester) async { await tester.pumpWidget(const WebView()); final CreationParams params = captureBuildArgs( mockWebViewPlatform, creationParams: true, ).single as CreationParams; expect(params.webSettings!.debuggingEnabled, false); }); testWidgets('can be changed', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget(WebView(key: key)); await tester.pumpWidget(WebView( key: key, debuggingEnabled: true, )); final WebSettings enabledSettings = verify(mockWebViewPlatformController.updateSettings(captureAny)) .captured .last as WebSettings; expect(enabledSettings.debuggingEnabled, true); await tester.pumpWidget(WebView( key: key, )); final WebSettings disabledSettings = verify(mockWebViewPlatformController.updateSettings(captureAny)) .captured .last as WebSettings; expect(disabledSettings.debuggingEnabled, false); }); }); group('zoomEnabled', () { testWidgets('Enable zoom', (WidgetTester tester) async { await tester.pumpWidget(const WebView()); final CreationParams params = captureBuildArgs( mockWebViewPlatform, creationParams: true, ).single as CreationParams; expect(params.webSettings!.zoomEnabled, isTrue); }); testWidgets('defaults to true', (WidgetTester tester) async { await tester.pumpWidget(const WebView()); final CreationParams params = captureBuildArgs( mockWebViewPlatform, creationParams: true, ).single as CreationParams; expect(params.webSettings!.zoomEnabled, isTrue); }); testWidgets('can be changed', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget(WebView(key: key)); await tester.pumpWidget(WebView( key: key, )); final WebSettings enabledSettings = verify(mockWebViewPlatformController.updateSettings(captureAny)) .captured .last as WebSettings; // Zoom defaults to true, so no changes are made to settings. expect(enabledSettings.zoomEnabled, isNull); await tester.pumpWidget(WebView( key: key, zoomEnabled: false, )); final WebSettings disabledSettings = verify(mockWebViewPlatformController.updateSettings(captureAny)) .captured .last as WebSettings; expect(disabledSettings.zoomEnabled, isFalse); }); }); group('Background color', () { testWidgets('Defaults to null', (WidgetTester tester) async { await tester.pumpWidget(const WebView()); final CreationParams params = captureBuildArgs( mockWebViewPlatform, creationParams: true, ).single as CreationParams; expect(params.backgroundColor, null); }); testWidgets('Can be transparent', (WidgetTester tester) async { const Color transparentColor = Color(0x00000000); await tester.pumpWidget(const WebView( backgroundColor: transparentColor, )); final CreationParams params = captureBuildArgs( mockWebViewPlatform, creationParams: true, ).single as CreationParams; expect(params.backgroundColor, transparentColor); }); }); group('Custom platform implementation', () { setUp(() { WebView.platform = MyWebViewPlatform(); }); tearDownAll(() { WebView.platform = null; }); testWidgets('creation', (WidgetTester tester) async { await tester.pumpWidget( const WebView( initialUrl: 'https://youtube.com', gestureNavigationEnabled: true, ), ); final MyWebViewPlatform builder = WebView.platform as MyWebViewPlatform; final MyWebViewPlatformController platform = builder.lastPlatformBuilt!; expect( platform.creationParams, MatchesCreationParams(CreationParams( initialUrl: 'https://youtube.com', webSettings: WebSettings( javascriptMode: JavascriptMode.disabled, hasNavigationDelegate: false, debuggingEnabled: false, userAgent: const WebSetting.of(null), gestureNavigationEnabled: true, zoomEnabled: true, ), ))); }); testWidgets('loadUrl', (WidgetTester tester) async { late WebViewController controller; await tester.pumpWidget( WebView( initialUrl: 'https://youtube.com', onWebViewCreated: (WebViewController webViewController) { controller = webViewController; }, ), ); final MyWebViewPlatform builder = WebView.platform as MyWebViewPlatform; final MyWebViewPlatformController platform = builder.lastPlatformBuilt!; final Map headers = { 'header': 'value', }; await controller.loadUrl('https://google.com', headers: headers); expect(platform.lastUrlLoaded, 'https://google.com'); expect(platform.lastRequestHeaders, headers); }); }); testWidgets('Set UserAgent', (WidgetTester tester) async { await tester.pumpWidget(const WebView( initialUrl: 'https://youtube.com', javascriptMode: JavascriptMode.unrestricted, )); final CreationParams params = captureBuildArgs( mockWebViewPlatform, creationParams: true, ).single as CreationParams; expect(params.webSettings!.userAgent.value, isNull); await tester.pumpWidget(const WebView( initialUrl: 'https://youtube.com', javascriptMode: JavascriptMode.unrestricted, userAgent: 'UA', )); final WebSettings settings = verify(mockWebViewPlatformController.updateSettings(captureAny)) .captured .last as WebSettings; expect(settings.userAgent.value, 'UA'); }); } List captureBuildArgs( MockWebViewPlatform mockWebViewPlatform, { bool context = false, bool creationParams = false, bool webViewPlatformCallbacksHandler = false, bool javascriptChannelRegistry = false, bool onWebViewPlatformCreated = false, bool gestureRecognizers = false, }) { return verify(mockWebViewPlatform.build( context: context ? captureAnyNamed('context') : anyNamed('context'), creationParams: creationParams ? captureAnyNamed('creationParams') : anyNamed('creationParams'), webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler ? captureAnyNamed('webViewPlatformCallbacksHandler') : anyNamed('webViewPlatformCallbacksHandler'), javascriptChannelRegistry: javascriptChannelRegistry ? captureAnyNamed('javascriptChannelRegistry') : anyNamed('javascriptChannelRegistry'), onWebViewPlatformCreated: onWebViewPlatformCreated ? captureAnyNamed('onWebViewPlatformCreated') : anyNamed('onWebViewPlatformCreated'), gestureRecognizers: gestureRecognizers ? captureAnyNamed('gestureRecognizers') : anyNamed('gestureRecognizers'), )).captured; } // This Widget ensures that onWebViewPlatformCreated is only called once when // making multiple calls to `WidgetTester.pumpWidget` with different parameters // for the WebView. class TestPlatformWebView extends StatefulWidget { const TestPlatformWebView({ super.key, required this.mockWebViewPlatformController, this.onWebViewPlatformCreated, }); final MockWebViewPlatformController mockWebViewPlatformController; final WebViewPlatformCreatedCallback? onWebViewPlatformCreated; @override State createState() => TestPlatformWebViewState(); } class TestPlatformWebViewState extends State { @override void initState() { super.initState(); final WebViewPlatformCreatedCallback? onWebViewPlatformCreated = widget.onWebViewPlatformCreated; if (onWebViewPlatformCreated != null) { onWebViewPlatformCreated(widget.mockWebViewPlatformController); } } @override Widget build(BuildContext context) { return Container(); } } class MyWebViewPlatform implements WebViewPlatform { MyWebViewPlatformController? lastPlatformBuilt; @override Widget build({ BuildContext? context, CreationParams? creationParams, required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, required JavascriptChannelRegistry javascriptChannelRegistry, WebViewPlatformCreatedCallback? onWebViewPlatformCreated, Set>? gestureRecognizers, }) { assert(onWebViewPlatformCreated != null); lastPlatformBuilt = MyWebViewPlatformController( creationParams, gestureRecognizers, webViewPlatformCallbacksHandler); onWebViewPlatformCreated!(lastPlatformBuilt); return Container(); } @override Future clearCookies() { return Future.sync(() => true); } } class MyWebViewPlatformController extends WebViewPlatformController { MyWebViewPlatformController(this.creationParams, this.gestureRecognizers, WebViewPlatformCallbacksHandler platformHandler) : super(platformHandler); CreationParams? creationParams; Set>? gestureRecognizers; String? lastUrlLoaded; Map? lastRequestHeaders; @override Future loadUrl(String url, Map? headers) async { equals(1, 1); lastUrlLoaded = url; lastRequestHeaders = headers; } } class MatchesWebSettings extends Matcher { MatchesWebSettings(this._webSettings); final WebSettings? _webSettings; @override Description describe(Description description) => description.add('$_webSettings'); @override bool matches( covariant WebSettings webSettings, Map matchState) { return _webSettings!.javascriptMode == webSettings.javascriptMode && _webSettings!.hasNavigationDelegate == webSettings.hasNavigationDelegate && _webSettings!.debuggingEnabled == webSettings.debuggingEnabled && _webSettings!.gestureNavigationEnabled == webSettings.gestureNavigationEnabled && _webSettings!.userAgent == webSettings.userAgent && _webSettings!.zoomEnabled == webSettings.zoomEnabled; } } class MatchesCreationParams extends Matcher { MatchesCreationParams(this._creationParams); final CreationParams _creationParams; @override Description describe(Description description) => description.add('$_creationParams'); @override bool matches(covariant CreationParams creationParams, Map matchState) { return _creationParams.initialUrl == creationParams.initialUrl && MatchesWebSettings(_creationParams.webSettings) .matches(creationParams.webSettings!, matchState) && orderedEquals(_creationParams.javascriptChannelNames) .matches(creationParams.javascriptChannelNames, matchState); } } class MockWebViewCookieManagerPlatform extends WebViewCookieManagerPlatform { List setCookieCalls = []; @override Future clearCookies() async => true; @override Future setCookie(WebViewCookie cookie) async { setCookieCalls.add(cookie); } void reset() { setCookieCalls = []; } } ================================================ FILE: packages/webview_flutter/webview_flutter/test/legacy/webview_flutter_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter/test/legacy/webview_flutter_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i9; import 'package:flutter/foundation.dart' as _i3; import 'package:flutter/gestures.dart' as _i8; import 'package:flutter/widgets.dart' as _i2; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_platform_interface/src/legacy/platform_interface/javascript_channel_registry.dart' as _i7; import 'package:webview_flutter_platform_interface/src/legacy/platform_interface/webview_platform.dart' as _i4; import 'package:webview_flutter_platform_interface/src/legacy/platform_interface/webview_platform_callbacks_handler.dart' as _i6; import 'package:webview_flutter_platform_interface/src/legacy/platform_interface/webview_platform_controller.dart' as _i10; import 'package:webview_flutter_platform_interface/src/legacy/types/types.dart' as _i5; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class class _FakeWidget_0 extends _i1.SmartFake implements _i2.Widget { _FakeWidget_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => super.toString(); } /// A class which mocks [WebViewPlatform]. /// /// See the documentation for Mockito's code generation for more information. class MockWebViewPlatform extends _i1.Mock implements _i4.WebViewPlatform { MockWebViewPlatform() { _i1.throwOnMissingStub(this); } @override _i2.Widget build({ required _i2.BuildContext? context, required _i5.CreationParams? creationParams, required _i6.WebViewPlatformCallbacksHandler? webViewPlatformCallbacksHandler, required _i7.JavascriptChannelRegistry? javascriptChannelRegistry, _i4.WebViewPlatformCreatedCallback? onWebViewPlatformCreated, Set<_i3.Factory<_i8.OneSequenceGestureRecognizer>>? gestureRecognizers, }) => (super.noSuchMethod( Invocation.method( #build, [], { #context: context, #creationParams: creationParams, #webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler, #javascriptChannelRegistry: javascriptChannelRegistry, #onWebViewPlatformCreated: onWebViewPlatformCreated, #gestureRecognizers: gestureRecognizers, }, ), returnValue: _FakeWidget_0( this, Invocation.method( #build, [], { #context: context, #creationParams: creationParams, #webViewPlatformCallbacksHandler: webViewPlatformCallbacksHandler, #javascriptChannelRegistry: javascriptChannelRegistry, #onWebViewPlatformCreated: onWebViewPlatformCreated, #gestureRecognizers: gestureRecognizers, }, ), ), ) as _i2.Widget); @override _i9.Future clearCookies() => (super.noSuchMethod( Invocation.method( #clearCookies, [], ), returnValue: _i9.Future.value(false), ) as _i9.Future); } /// A class which mocks [WebViewPlatformController]. /// /// See the documentation for Mockito's code generation for more information. class MockWebViewPlatformController extends _i1.Mock implements _i10.WebViewPlatformController { MockWebViewPlatformController() { _i1.throwOnMissingStub(this); } @override _i9.Future loadFile(String? absoluteFilePath) => (super.noSuchMethod( Invocation.method( #loadFile, [absoluteFilePath], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future loadFlutterAsset(String? key) => (super.noSuchMethod( Invocation.method( #loadFlutterAsset, [key], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future loadHtmlString( String? html, { String? baseUrl, }) => (super.noSuchMethod( Invocation.method( #loadHtmlString, [html], {#baseUrl: baseUrl}, ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future loadUrl( String? url, Map? headers, ) => (super.noSuchMethod( Invocation.method( #loadUrl, [ url, headers, ], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future loadRequest(_i5.WebViewRequest? request) => (super.noSuchMethod( Invocation.method( #loadRequest, [request], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future updateSettings(_i5.WebSettings? setting) => (super.noSuchMethod( Invocation.method( #updateSettings, [setting], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future currentUrl() => (super.noSuchMethod( Invocation.method( #currentUrl, [], ), returnValue: _i9.Future.value(), ) as _i9.Future); @override _i9.Future canGoBack() => (super.noSuchMethod( Invocation.method( #canGoBack, [], ), returnValue: _i9.Future.value(false), ) as _i9.Future); @override _i9.Future canGoForward() => (super.noSuchMethod( Invocation.method( #canGoForward, [], ), returnValue: _i9.Future.value(false), ) as _i9.Future); @override _i9.Future goBack() => (super.noSuchMethod( Invocation.method( #goBack, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future goForward() => (super.noSuchMethod( Invocation.method( #goForward, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future reload() => (super.noSuchMethod( Invocation.method( #reload, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future clearCache() => (super.noSuchMethod( Invocation.method( #clearCache, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future evaluateJavascript(String? javascript) => (super.noSuchMethod( Invocation.method( #evaluateJavascript, [javascript], ), returnValue: _i9.Future.value(''), ) as _i9.Future); @override _i9.Future runJavascript(String? javascript) => (super.noSuchMethod( Invocation.method( #runJavascript, [javascript], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future runJavascriptReturningResult(String? javascript) => (super.noSuchMethod( Invocation.method( #runJavascriptReturningResult, [javascript], ), returnValue: _i9.Future.value(''), ) as _i9.Future); @override _i9.Future addJavascriptChannels(Set? javascriptChannelNames) => (super.noSuchMethod( Invocation.method( #addJavascriptChannels, [javascriptChannelNames], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future removeJavascriptChannels( Set? javascriptChannelNames) => (super.noSuchMethod( Invocation.method( #removeJavascriptChannels, [javascriptChannelNames], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future getTitle() => (super.noSuchMethod( Invocation.method( #getTitle, [], ), returnValue: _i9.Future.value(), ) as _i9.Future); @override _i9.Future scrollTo( int? x, int? y, ) => (super.noSuchMethod( Invocation.method( #scrollTo, [ x, y, ], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future scrollBy( int? x, int? y, ) => (super.noSuchMethod( Invocation.method( #scrollBy, [ x, y, ], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future getScrollX() => (super.noSuchMethod( Invocation.method( #getScrollX, [], ), returnValue: _i9.Future.value(0), ) as _i9.Future); @override _i9.Future getScrollY() => (super.noSuchMethod( Invocation.method( #getScrollY, [], ), returnValue: _i9.Future.value(0), ) as _i9.Future); } ================================================ FILE: packages/webview_flutter/webview_flutter/test/navigation_delegate_test.dart ================================================ // Copyright 2013 The Flutter 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:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'navigation_delegate_test.mocks.dart'; @GenerateMocks([WebViewPlatform, PlatformNavigationDelegate]) void main() { group('NavigationDelegate', () { test('onNavigationRequest', () async { WebViewPlatform.instance = TestWebViewPlatform(); NavigationDecision onNavigationRequest(NavigationRequest request) { return NavigationDecision.navigate; } final NavigationDelegate delegate = NavigationDelegate( onNavigationRequest: onNavigationRequest, ); verify(delegate.platform.setOnNavigationRequest(onNavigationRequest)); }); test('onPageStarted', () async { WebViewPlatform.instance = TestWebViewPlatform(); void onPageStarted(String url) {} final NavigationDelegate delegate = NavigationDelegate( onPageStarted: onPageStarted, ); verify(delegate.platform.setOnPageStarted(onPageStarted)); }); test('onPageFinished', () async { WebViewPlatform.instance = TestWebViewPlatform(); void onPageFinished(String url) {} final NavigationDelegate delegate = NavigationDelegate( onPageFinished: onPageFinished, ); verify(delegate.platform.setOnPageFinished(onPageFinished)); }); test('onProgress', () async { WebViewPlatform.instance = TestWebViewPlatform(); void onProgress(int progress) {} final NavigationDelegate delegate = NavigationDelegate( onProgress: onProgress, ); verify(delegate.platform.setOnProgress(onProgress)); }); test('onWebResourceError', () async { WebViewPlatform.instance = TestWebViewPlatform(); void onWebResourceError(WebResourceError error) {} final NavigationDelegate delegate = NavigationDelegate( onWebResourceError: onWebResourceError, ); verify(delegate.platform.setOnWebResourceError(onWebResourceError)); }); }); } class TestWebViewPlatform extends WebViewPlatform { @override PlatformNavigationDelegate createPlatformNavigationDelegate( PlatformNavigationDelegateCreationParams params, ) { return TestMockPlatformNavigationDelegate(); } } class TestMockPlatformNavigationDelegate extends MockPlatformNavigationDelegate with MockPlatformInterfaceMixin {} ================================================ FILE: packages/webview_flutter/webview_flutter/test/navigation_delegate_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter/test/navigation_delegate_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i8; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_platform_interface/src/platform_navigation_delegate.dart' as _i3; import 'package:webview_flutter_platform_interface/src/platform_webview_controller.dart' as _i4; import 'package:webview_flutter_platform_interface/src/platform_webview_cookie_manager.dart' as _i2; import 'package:webview_flutter_platform_interface/src/platform_webview_widget.dart' as _i5; import 'package:webview_flutter_platform_interface/src/types/types.dart' as _i6; import 'package:webview_flutter_platform_interface/src/webview_platform.dart' as _i7; // 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 class _FakePlatformWebViewCookieManager_0 extends _i1.SmartFake implements _i2.PlatformWebViewCookieManager { _FakePlatformWebViewCookieManager_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePlatformNavigationDelegate_1 extends _i1.SmartFake implements _i3.PlatformNavigationDelegate { _FakePlatformNavigationDelegate_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePlatformWebViewController_2 extends _i1.SmartFake implements _i4.PlatformWebViewController { _FakePlatformWebViewController_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePlatformWebViewWidget_3 extends _i1.SmartFake implements _i5.PlatformWebViewWidget { _FakePlatformWebViewWidget_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePlatformNavigationDelegateCreationParams_4 extends _i1.SmartFake implements _i6.PlatformNavigationDelegateCreationParams { _FakePlatformNavigationDelegateCreationParams_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [WebViewPlatform]. /// /// See the documentation for Mockito's code generation for more information. class MockWebViewPlatform extends _i1.Mock implements _i7.WebViewPlatform { MockWebViewPlatform() { _i1.throwOnMissingStub(this); } @override _i2.PlatformWebViewCookieManager createPlatformCookieManager( _i6.PlatformWebViewCookieManagerCreationParams? params) => (super.noSuchMethod( Invocation.method( #createPlatformCookieManager, [params], ), returnValue: _FakePlatformWebViewCookieManager_0( this, Invocation.method( #createPlatformCookieManager, [params], ), ), ) as _i2.PlatformWebViewCookieManager); @override _i3.PlatformNavigationDelegate createPlatformNavigationDelegate( _i6.PlatformNavigationDelegateCreationParams? params) => (super.noSuchMethod( Invocation.method( #createPlatformNavigationDelegate, [params], ), returnValue: _FakePlatformNavigationDelegate_1( this, Invocation.method( #createPlatformNavigationDelegate, [params], ), ), ) as _i3.PlatformNavigationDelegate); @override _i4.PlatformWebViewController createPlatformWebViewController( _i6.PlatformWebViewControllerCreationParams? params) => (super.noSuchMethod( Invocation.method( #createPlatformWebViewController, [params], ), returnValue: _FakePlatformWebViewController_2( this, Invocation.method( #createPlatformWebViewController, [params], ), ), ) as _i4.PlatformWebViewController); @override _i5.PlatformWebViewWidget createPlatformWebViewWidget( _i6.PlatformWebViewWidgetCreationParams? params) => (super.noSuchMethod( Invocation.method( #createPlatformWebViewWidget, [params], ), returnValue: _FakePlatformWebViewWidget_3( this, Invocation.method( #createPlatformWebViewWidget, [params], ), ), ) as _i5.PlatformWebViewWidget); } /// A class which mocks [PlatformNavigationDelegate]. /// /// See the documentation for Mockito's code generation for more information. class MockPlatformNavigationDelegate extends _i1.Mock implements _i3.PlatformNavigationDelegate { MockPlatformNavigationDelegate() { _i1.throwOnMissingStub(this); } @override _i6.PlatformNavigationDelegateCreationParams get params => (super.noSuchMethod( Invocation.getter(#params), returnValue: _FakePlatformNavigationDelegateCreationParams_4( this, Invocation.getter(#params), ), ) as _i6.PlatformNavigationDelegateCreationParams); @override _i8.Future setOnNavigationRequest( _i3.NavigationRequestCallback? onNavigationRequest) => (super.noSuchMethod( Invocation.method( #setOnNavigationRequest, [onNavigationRequest], ), returnValue: _i8.Future.value(), returnValueForMissingStub: _i8.Future.value(), ) as _i8.Future); @override _i8.Future setOnPageStarted(_i3.PageEventCallback? onPageStarted) => (super.noSuchMethod( Invocation.method( #setOnPageStarted, [onPageStarted], ), returnValue: _i8.Future.value(), returnValueForMissingStub: _i8.Future.value(), ) as _i8.Future); @override _i8.Future setOnPageFinished(_i3.PageEventCallback? onPageFinished) => (super.noSuchMethod( Invocation.method( #setOnPageFinished, [onPageFinished], ), returnValue: _i8.Future.value(), returnValueForMissingStub: _i8.Future.value(), ) as _i8.Future); @override _i8.Future setOnProgress(_i3.ProgressCallback? onProgress) => (super.noSuchMethod( Invocation.method( #setOnProgress, [onProgress], ), returnValue: _i8.Future.value(), returnValueForMissingStub: _i8.Future.value(), ) as _i8.Future); @override _i8.Future setOnWebResourceError( _i3.WebResourceErrorCallback? onWebResourceError) => (super.noSuchMethod( Invocation.method( #setOnWebResourceError, [onWebResourceError], ), returnValue: _i8.Future.value(), returnValueForMissingStub: _i8.Future.value(), ) as _i8.Future); } ================================================ FILE: packages/webview_flutter/webview_flutter/test/webview_controller_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'webview_controller_test.mocks.dart'; @GenerateMocks([PlatformWebViewController, PlatformNavigationDelegate]) void main() { test('loadFile', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await webViewController.loadFile('file/path'); verify(mockPlatformWebViewController.loadFile('file/path')); }); test('loadFlutterAsset', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await webViewController.loadFlutterAsset('file/path'); verify(mockPlatformWebViewController.loadFlutterAsset('file/path')); }); test('loadHtmlString', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await webViewController.loadHtmlString('html', baseUrl: 'baseUrl'); verify(mockPlatformWebViewController.loadHtmlString( 'html', baseUrl: 'baseUrl', )); }); test('loadRequest', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await webViewController.loadRequest( Uri(scheme: 'https', host: 'dart.dev'), method: LoadRequestMethod.post, headers: {'a': 'header'}, body: Uint8List(0), ); final LoadRequestParams params = verify(mockPlatformWebViewController.loadRequest(captureAny)) .captured[0] as LoadRequestParams; expect(params.uri, Uri(scheme: 'https', host: 'dart.dev')); expect(params.method, LoadRequestMethod.post); expect(params.headers, {'a': 'header'}); expect(params.body, Uint8List(0)); }); test('currentUrl', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); when(mockPlatformWebViewController.currentUrl()).thenAnswer( (_) => Future.value('https://dart.dev'), ); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await expectLater( webViewController.currentUrl(), completion('https://dart.dev'), ); }); test('canGoBack', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); when(mockPlatformWebViewController.canGoBack()).thenAnswer( (_) => Future.value(false), ); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await expectLater(webViewController.canGoBack(), completion(false)); }); test('canGoForward', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); when(mockPlatformWebViewController.canGoForward()).thenAnswer( (_) => Future.value(true), ); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await expectLater(webViewController.canGoForward(), completion(true)); }); test('goBack', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await webViewController.goBack(); verify(mockPlatformWebViewController.goBack()); }); test('goForward', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await webViewController.goForward(); verify(mockPlatformWebViewController.goForward()); }); test('reload', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await webViewController.reload(); verify(mockPlatformWebViewController.reload()); }); test('clearCache', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await webViewController.clearCache(); verify(mockPlatformWebViewController.clearCache()); }); test('clearLocalStorage', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await webViewController.clearLocalStorage(); verify(mockPlatformWebViewController.clearLocalStorage()); }); test('runJavaScript', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await webViewController.runJavaScript('1 + 1'); verify(mockPlatformWebViewController.runJavaScript('1 + 1')); }); test('runJavaScriptReturningResult', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); when(mockPlatformWebViewController.runJavaScriptReturningResult('1 + 1')) .thenAnswer((_) => Future.value('2')); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await expectLater( webViewController.runJavaScriptReturningResult('1 + 1'), completion('2'), ); }); test('addJavaScriptChannel', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); void onMessageReceived(JavaScriptMessage message) {} await webViewController.addJavaScriptChannel( 'name', onMessageReceived: onMessageReceived, ); final JavaScriptChannelParams params = verify(mockPlatformWebViewController.addJavaScriptChannel(captureAny)) .captured[0] as JavaScriptChannelParams; expect(params.name, 'name'); expect(params.onMessageReceived, onMessageReceived); }); test('removeJavaScriptChannel', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await webViewController.removeJavaScriptChannel('channel'); verify(mockPlatformWebViewController.removeJavaScriptChannel('channel')); }); test('getTitle', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); when(mockPlatformWebViewController.getTitle()) .thenAnswer((_) => Future.value('myTitle')); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await expectLater(webViewController.getTitle(), completion('myTitle')); }); test('scrollTo', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await webViewController.scrollTo(2, 3); verify(mockPlatformWebViewController.scrollTo(2, 3)); }); test('scrollBy', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await webViewController.scrollBy(2, 3); verify(mockPlatformWebViewController.scrollBy(2, 3)); }); test('getScrollPosition', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); when(mockPlatformWebViewController.getScrollPosition()).thenAnswer( (_) => Future.value(const Offset(2, 3)), ); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await expectLater( webViewController.getScrollPosition(), completion(const Offset(2.0, 3.0)), ); }); test('enableZoom', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await webViewController.enableZoom(false); verify(mockPlatformWebViewController.enableZoom(false)); }); test('setBackgroundColor', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await webViewController.setBackgroundColor(Colors.green); verify(mockPlatformWebViewController.setBackgroundColor(Colors.green)); }); test('setJavaScriptMode', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await webViewController.setJavaScriptMode(JavaScriptMode.disabled); verify( mockPlatformWebViewController.setJavaScriptMode(JavaScriptMode.disabled), ); }); test('setUserAgent', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); await webViewController.setUserAgent('userAgent'); verify(mockPlatformWebViewController.setUserAgent('userAgent')); }); test('setNavigationDelegate', () async { final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); final MockPlatformNavigationDelegate mockPlatformNavigationDelegate = MockPlatformNavigationDelegate(); final NavigationDelegate navigationDelegate = NavigationDelegate.fromPlatform(mockPlatformNavigationDelegate); await webViewController.setNavigationDelegate(navigationDelegate); verify(mockPlatformWebViewController.setPlatformNavigationDelegate( mockPlatformNavigationDelegate, )); }); } ================================================ FILE: packages/webview_flutter/webview_flutter/test/webview_controller_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter/test/webview_controller_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i5; import 'dart:ui' as _i3; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_platform_interface/src/platform_navigation_delegate.dart' as _i6; import 'package:webview_flutter_platform_interface/src/platform_webview_controller.dart' as _i4; import 'package:webview_flutter_platform_interface/src/webview_platform.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 class _FakePlatformWebViewControllerCreationParams_0 extends _i1.SmartFake implements _i2.PlatformWebViewControllerCreationParams { _FakePlatformWebViewControllerCreationParams_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeObject_1 extends _i1.SmartFake implements Object { _FakeObject_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeOffset_2 extends _i1.SmartFake implements _i3.Offset { _FakeOffset_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePlatformNavigationDelegateCreationParams_3 extends _i1.SmartFake implements _i2.PlatformNavigationDelegateCreationParams { _FakePlatformNavigationDelegateCreationParams_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [PlatformWebViewController]. /// /// See the documentation for Mockito's code generation for more information. class MockPlatformWebViewController extends _i1.Mock implements _i4.PlatformWebViewController { MockPlatformWebViewController() { _i1.throwOnMissingStub(this); } @override _i2.PlatformWebViewControllerCreationParams get params => (super.noSuchMethod( Invocation.getter(#params), returnValue: _FakePlatformWebViewControllerCreationParams_0( this, Invocation.getter(#params), ), ) as _i2.PlatformWebViewControllerCreationParams); @override _i5.Future loadFile(String? absoluteFilePath) => (super.noSuchMethod( Invocation.method( #loadFile, [absoluteFilePath], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future loadFlutterAsset(String? key) => (super.noSuchMethod( Invocation.method( #loadFlutterAsset, [key], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future loadHtmlString( String? html, { String? baseUrl, }) => (super.noSuchMethod( Invocation.method( #loadHtmlString, [html], {#baseUrl: baseUrl}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future loadRequest(_i2.LoadRequestParams? params) => (super.noSuchMethod( Invocation.method( #loadRequest, [params], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future currentUrl() => (super.noSuchMethod( Invocation.method( #currentUrl, [], ), returnValue: _i5.Future.value(), ) as _i5.Future); @override _i5.Future canGoBack() => (super.noSuchMethod( Invocation.method( #canGoBack, [], ), returnValue: _i5.Future.value(false), ) as _i5.Future); @override _i5.Future canGoForward() => (super.noSuchMethod( Invocation.method( #canGoForward, [], ), returnValue: _i5.Future.value(false), ) as _i5.Future); @override _i5.Future goBack() => (super.noSuchMethod( Invocation.method( #goBack, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future goForward() => (super.noSuchMethod( Invocation.method( #goForward, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future reload() => (super.noSuchMethod( Invocation.method( #reload, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future clearCache() => (super.noSuchMethod( Invocation.method( #clearCache, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future clearLocalStorage() => (super.noSuchMethod( Invocation.method( #clearLocalStorage, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setPlatformNavigationDelegate( _i6.PlatformNavigationDelegate? handler) => (super.noSuchMethod( Invocation.method( #setPlatformNavigationDelegate, [handler], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future runJavaScript(String? javaScript) => (super.noSuchMethod( Invocation.method( #runJavaScript, [javaScript], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future runJavaScriptReturningResult(String? javaScript) => (super.noSuchMethod( Invocation.method( #runJavaScriptReturningResult, [javaScript], ), returnValue: _i5.Future.value(_FakeObject_1( this, Invocation.method( #runJavaScriptReturningResult, [javaScript], ), )), ) as _i5.Future); @override _i5.Future addJavaScriptChannel( _i4.JavaScriptChannelParams? javaScriptChannelParams) => (super.noSuchMethod( Invocation.method( #addJavaScriptChannel, [javaScriptChannelParams], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeJavaScriptChannel(String? javaScriptChannelName) => (super.noSuchMethod( Invocation.method( #removeJavaScriptChannel, [javaScriptChannelName], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future getTitle() => (super.noSuchMethod( Invocation.method( #getTitle, [], ), returnValue: _i5.Future.value(), ) as _i5.Future); @override _i5.Future scrollTo( int? x, int? y, ) => (super.noSuchMethod( Invocation.method( #scrollTo, [ x, y, ], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future scrollBy( int? x, int? y, ) => (super.noSuchMethod( Invocation.method( #scrollBy, [ x, y, ], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future<_i3.Offset> getScrollPosition() => (super.noSuchMethod( Invocation.method( #getScrollPosition, [], ), returnValue: _i5.Future<_i3.Offset>.value(_FakeOffset_2( this, Invocation.method( #getScrollPosition, [], ), )), ) as _i5.Future<_i3.Offset>); @override _i5.Future enableZoom(bool? enabled) => (super.noSuchMethod( Invocation.method( #enableZoom, [enabled], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setBackgroundColor(_i3.Color? color) => (super.noSuchMethod( Invocation.method( #setBackgroundColor, [color], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setJavaScriptMode(_i2.JavaScriptMode? javaScriptMode) => (super.noSuchMethod( Invocation.method( #setJavaScriptMode, [javaScriptMode], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setUserAgent(String? userAgent) => (super.noSuchMethod( Invocation.method( #setUserAgent, [userAgent], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } /// A class which mocks [PlatformNavigationDelegate]. /// /// See the documentation for Mockito's code generation for more information. class MockPlatformNavigationDelegate extends _i1.Mock implements _i6.PlatformNavigationDelegate { MockPlatformNavigationDelegate() { _i1.throwOnMissingStub(this); } @override _i2.PlatformNavigationDelegateCreationParams get params => (super.noSuchMethod( Invocation.getter(#params), returnValue: _FakePlatformNavigationDelegateCreationParams_3( this, Invocation.getter(#params), ), ) as _i2.PlatformNavigationDelegateCreationParams); @override _i5.Future setOnNavigationRequest( _i6.NavigationRequestCallback? onNavigationRequest) => (super.noSuchMethod( Invocation.method( #setOnNavigationRequest, [onNavigationRequest], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setOnPageStarted(_i6.PageEventCallback? onPageStarted) => (super.noSuchMethod( Invocation.method( #setOnPageStarted, [onPageStarted], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setOnPageFinished(_i6.PageEventCallback? onPageFinished) => (super.noSuchMethod( Invocation.method( #setOnPageFinished, [onPageFinished], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setOnProgress(_i6.ProgressCallback? onProgress) => (super.noSuchMethod( Invocation.method( #setOnProgress, [onProgress], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setOnWebResourceError( _i6.WebResourceErrorCallback? onWebResourceError) => (super.noSuchMethod( Invocation.method( #setOnWebResourceError, [onWebResourceError], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } ================================================ FILE: packages/webview_flutter/webview_flutter/test/webview_cookie_manager_test.dart ================================================ // Copyright 2013 The Flutter 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:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'webview_cookie_manager_test.mocks.dart'; @GenerateMocks([PlatformWebViewCookieManager]) void main() { group('WebViewCookieManager', () { test('clearCookies', () async { final MockPlatformWebViewCookieManager mockPlatformWebViewCookieManager = MockPlatformWebViewCookieManager(); when(mockPlatformWebViewCookieManager.clearCookies()).thenAnswer( (_) => Future.value(false), ); final WebViewCookieManager cookieManager = WebViewCookieManager.fromPlatform( mockPlatformWebViewCookieManager, ); await expectLater(cookieManager.clearCookies(), completion(false)); }); test('setCookie', () async { final MockPlatformWebViewCookieManager mockPlatformWebViewCookieManager = MockPlatformWebViewCookieManager(); final WebViewCookieManager cookieManager = WebViewCookieManager.fromPlatform( mockPlatformWebViewCookieManager, ); const WebViewCookie cookie = WebViewCookie( name: 'name', value: 'value', domain: 'domain', ); await cookieManager.setCookie(cookie); final WebViewCookie capturedCookie = verify( mockPlatformWebViewCookieManager.setCookie(captureAny), ).captured.single as WebViewCookie; expect(capturedCookie, cookie); }); }); } ================================================ FILE: packages/webview_flutter/webview_flutter/test/webview_cookie_manager_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter/test/webview_cookie_manager_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_platform_interface/src/platform_webview_cookie_manager.dart' as _i3; import 'package:webview_flutter_platform_interface/src/webview_platform.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 class _FakePlatformWebViewCookieManagerCreationParams_0 extends _i1.SmartFake implements _i2.PlatformWebViewCookieManagerCreationParams { _FakePlatformWebViewCookieManagerCreationParams_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [PlatformWebViewCookieManager]. /// /// See the documentation for Mockito's code generation for more information. class MockPlatformWebViewCookieManager extends _i1.Mock implements _i3.PlatformWebViewCookieManager { MockPlatformWebViewCookieManager() { _i1.throwOnMissingStub(this); } @override _i2.PlatformWebViewCookieManagerCreationParams get params => (super.noSuchMethod( Invocation.getter(#params), returnValue: _FakePlatformWebViewCookieManagerCreationParams_0( this, Invocation.getter(#params), ), ) as _i2.PlatformWebViewCookieManagerCreationParams); @override _i4.Future clearCookies() => (super.noSuchMethod( Invocation.method( #clearCookies, [], ), returnValue: _i4.Future.value(false), ) as _i4.Future); @override _i4.Future setCookie(_i2.WebViewCookie? cookie) => (super.noSuchMethod( Invocation.method( #setCookie, [cookie], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); } ================================================ FILE: packages/webview_flutter/webview_flutter/test/webview_flutter_test.dart ================================================ // Copyright 2013 The Flutter 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:webview_flutter/webview_flutter.dart' as main_file; void main() { group('webview_flutter', () { test('ensure webview_flutter.dart exports classes from platform interface', () { // ignore: unnecessary_statements main_file.JavaScriptMessage; // ignore: unnecessary_statements main_file.JavaScriptMode; // ignore: unnecessary_statements main_file.LoadRequestMethod; // ignore: unnecessary_statements main_file.NavigationDecision; // ignore: unnecessary_statements main_file.NavigationRequest; // ignore: unnecessary_statements main_file.NavigationRequestCallback; // ignore: unnecessary_statements main_file.PageEventCallback; // ignore: unnecessary_statements main_file.PlatformNavigationDelegateCreationParams; // ignore: unnecessary_statements main_file.PlatformWebViewControllerCreationParams; // ignore: unnecessary_statements main_file.PlatformWebViewCookieManagerCreationParams; // ignore: unnecessary_statements main_file.PlatformWebViewWidgetCreationParams; // ignore: unnecessary_statements main_file.ProgressCallback; // ignore: unnecessary_statements main_file.WebResourceError; // ignore: unnecessary_statements main_file.WebResourceErrorCallback; // ignore: unnecessary_statements main_file.WebViewCookie; // ignore: unnecessary_statements main_file.WebResourceErrorType; }); }); } ================================================ FILE: packages/webview_flutter/webview_flutter/test/webview_widget_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'webview_widget_test.mocks.dart'; @GenerateMocks([PlatformWebViewController, PlatformWebViewWidget]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('WebViewWidget', () { testWidgets('build', (WidgetTester tester) async { final MockPlatformWebViewWidget mockPlatformWebViewWidget = MockPlatformWebViewWidget(); when(mockPlatformWebViewWidget.build(any)).thenReturn(Container()); await tester.pumpWidget(WebViewWidget.fromPlatform( platform: mockPlatformWebViewWidget, )); expect(find.byType(Container), findsOneWidget); }); testWidgets( 'constructor parameters are correctly passed to creation params', (WidgetTester tester) async { WebViewPlatform.instance = TestWebViewPlatform(); final MockPlatformWebViewController mockPlatformWebViewController = MockPlatformWebViewController(); final WebViewController webViewController = WebViewController.fromPlatform( mockPlatformWebViewController, ); final WebViewWidget webViewWidget = WebViewWidget( key: GlobalKey(), controller: webViewController, layoutDirection: TextDirection.rtl, gestureRecognizers: >{ Factory(() => EagerGestureRecognizer()), }, ); // The key passed to the default constructor is used by the super class // and not passed to the platform implementation. expect(webViewWidget.platform.params.key, isNull); expect( webViewWidget.platform.params.controller, webViewController.platform, ); expect(webViewWidget.platform.params.layoutDirection, TextDirection.rtl); expect( webViewWidget.platform.params.gestureRecognizers.isNotEmpty, isTrue, ); }); }); } class TestWebViewPlatform extends WebViewPlatform { @override PlatformWebViewWidget createPlatformWebViewWidget( PlatformWebViewWidgetCreationParams params, ) { return TestPlatformWebViewWidget(params); } } class TestPlatformWebViewWidget extends PlatformWebViewWidget { TestPlatformWebViewWidget(super.params) : super.implementation(); @override Widget build(BuildContext context) { throw UnimplementedError(); } } ================================================ FILE: packages/webview_flutter/webview_flutter/test/webview_widget_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter/test/webview_widget_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i7; import 'dart:ui' as _i3; import 'package:flutter/foundation.dart' as _i5; import 'package:flutter/widgets.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_platform_interface/src/platform_navigation_delegate.dart' as _i8; import 'package:webview_flutter_platform_interface/src/platform_webview_controller.dart' as _i6; import 'package:webview_flutter_platform_interface/src/platform_webview_widget.dart' as _i9; import 'package:webview_flutter_platform_interface/src/webview_platform.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 class _FakePlatformWebViewControllerCreationParams_0 extends _i1.SmartFake implements _i2.PlatformWebViewControllerCreationParams { _FakePlatformWebViewControllerCreationParams_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeObject_1 extends _i1.SmartFake implements Object { _FakeObject_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeOffset_2 extends _i1.SmartFake implements _i3.Offset { _FakeOffset_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePlatformWebViewWidgetCreationParams_3 extends _i1.SmartFake implements _i2.PlatformWebViewWidgetCreationParams { _FakePlatformWebViewWidgetCreationParams_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWidget_4 extends _i1.SmartFake implements _i4.Widget { _FakeWidget_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } /// A class which mocks [PlatformWebViewController]. /// /// See the documentation for Mockito's code generation for more information. class MockPlatformWebViewController extends _i1.Mock implements _i6.PlatformWebViewController { MockPlatformWebViewController() { _i1.throwOnMissingStub(this); } @override _i2.PlatformWebViewControllerCreationParams get params => (super.noSuchMethod( Invocation.getter(#params), returnValue: _FakePlatformWebViewControllerCreationParams_0( this, Invocation.getter(#params), ), ) as _i2.PlatformWebViewControllerCreationParams); @override _i7.Future loadFile(String? absoluteFilePath) => (super.noSuchMethod( Invocation.method( #loadFile, [absoluteFilePath], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future loadFlutterAsset(String? key) => (super.noSuchMethod( Invocation.method( #loadFlutterAsset, [key], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future loadHtmlString( String? html, { String? baseUrl, }) => (super.noSuchMethod( Invocation.method( #loadHtmlString, [html], {#baseUrl: baseUrl}, ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future loadRequest(_i2.LoadRequestParams? params) => (super.noSuchMethod( Invocation.method( #loadRequest, [params], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future currentUrl() => (super.noSuchMethod( Invocation.method( #currentUrl, [], ), returnValue: _i7.Future.value(), ) as _i7.Future); @override _i7.Future canGoBack() => (super.noSuchMethod( Invocation.method( #canGoBack, [], ), returnValue: _i7.Future.value(false), ) as _i7.Future); @override _i7.Future canGoForward() => (super.noSuchMethod( Invocation.method( #canGoForward, [], ), returnValue: _i7.Future.value(false), ) as _i7.Future); @override _i7.Future goBack() => (super.noSuchMethod( Invocation.method( #goBack, [], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future goForward() => (super.noSuchMethod( Invocation.method( #goForward, [], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future reload() => (super.noSuchMethod( Invocation.method( #reload, [], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future clearCache() => (super.noSuchMethod( Invocation.method( #clearCache, [], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future clearLocalStorage() => (super.noSuchMethod( Invocation.method( #clearLocalStorage, [], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future setPlatformNavigationDelegate( _i8.PlatformNavigationDelegate? handler) => (super.noSuchMethod( Invocation.method( #setPlatformNavigationDelegate, [handler], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future runJavaScript(String? javaScript) => (super.noSuchMethod( Invocation.method( #runJavaScript, [javaScript], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future runJavaScriptReturningResult(String? javaScript) => (super.noSuchMethod( Invocation.method( #runJavaScriptReturningResult, [javaScript], ), returnValue: _i7.Future.value(_FakeObject_1( this, Invocation.method( #runJavaScriptReturningResult, [javaScript], ), )), ) as _i7.Future); @override _i7.Future addJavaScriptChannel( _i6.JavaScriptChannelParams? javaScriptChannelParams) => (super.noSuchMethod( Invocation.method( #addJavaScriptChannel, [javaScriptChannelParams], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future removeJavaScriptChannel(String? javaScriptChannelName) => (super.noSuchMethod( Invocation.method( #removeJavaScriptChannel, [javaScriptChannelName], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future getTitle() => (super.noSuchMethod( Invocation.method( #getTitle, [], ), returnValue: _i7.Future.value(), ) as _i7.Future); @override _i7.Future scrollTo( int? x, int? y, ) => (super.noSuchMethod( Invocation.method( #scrollTo, [ x, y, ], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future scrollBy( int? x, int? y, ) => (super.noSuchMethod( Invocation.method( #scrollBy, [ x, y, ], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future<_i3.Offset> getScrollPosition() => (super.noSuchMethod( Invocation.method( #getScrollPosition, [], ), returnValue: _i7.Future<_i3.Offset>.value(_FakeOffset_2( this, Invocation.method( #getScrollPosition, [], ), )), ) as _i7.Future<_i3.Offset>); @override _i7.Future enableZoom(bool? enabled) => (super.noSuchMethod( Invocation.method( #enableZoom, [enabled], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future setBackgroundColor(_i3.Color? color) => (super.noSuchMethod( Invocation.method( #setBackgroundColor, [color], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future setJavaScriptMode(_i2.JavaScriptMode? javaScriptMode) => (super.noSuchMethod( Invocation.method( #setJavaScriptMode, [javaScriptMode], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); @override _i7.Future setUserAgent(String? userAgent) => (super.noSuchMethod( Invocation.method( #setUserAgent, [userAgent], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), ) as _i7.Future); } /// A class which mocks [PlatformWebViewWidget]. /// /// See the documentation for Mockito's code generation for more information. class MockPlatformWebViewWidget extends _i1.Mock implements _i9.PlatformWebViewWidget { MockPlatformWebViewWidget() { _i1.throwOnMissingStub(this); } @override _i2.PlatformWebViewWidgetCreationParams get params => (super.noSuchMethod( Invocation.getter(#params), returnValue: _FakePlatformWebViewWidgetCreationParams_3( this, Invocation.getter(#params), ), ) as _i2.PlatformWebViewWidgetCreationParams); @override _i4.Widget build(_i4.BuildContext? context) => (super.noSuchMethod( Invocation.method( #build, [context], ), returnValue: _FakeWidget_4( this, Invocation.method( #build, [context], ), ), ) as _i4.Widget); } ================================================ FILE: packages/webview_flutter/webview_flutter_android/AUTHORS ================================================ # 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> Maurits van Beusekom Nick Bradshaw ================================================ FILE: packages/webview_flutter/webview_flutter_android/CHANGELOG.md ================================================ ## 3.3.0 * Adds support to access native `WebView`. ## 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. ## 3.1.0 * Adds support for selecting Hybrid Composition on versions 23+. Please use `AndroidWebViewControllerCreationParams.displayWithHybridComposition`. ## 3.0.0 * **BREAKING CHANGE** Updates platform implementation to `2.0.0` release of `webview_flutter_platform_interface`. See [webview_flutter](https://pub.dev/packages/webview_flutter/versions/4.0.0) for updated usage. ## 2.10.4 * Updates code for `no_leading_underscores_for_local_identifiers` lint. * Bumps androidx.annotation from 1.4.0 to 1.5.0. ## 2.10.3 * Updates imports for `prefer_relative_imports`. ## 2.10.2 * Adds a getter to expose the Java InstanceManager. ## 2.10.1 * Adds a method to the `WebView` wrapper to retrieve the X and Y positions simultaneously. * Removes reference to https://github.com/flutter/flutter/issues/97744 from `README`. ## 2.10.0 * Bumps webkit from 1.0.0 to 1.5.0. * Raises minimum `compileSdkVersion` to 32. ## 2.9.5 * Adds dispose methods for HostApi and FlutterApi of JavaObject. ## 2.9.4 * Fixes avoid_redundant_argument_values lint warnings and minor typos. * Bumps gradle from 7.2.1 to 7.2.2. ## 2.9.3 * Updates the Dart InstanceManager to take a listener for when an object is garbage collected. See https://github.com/flutter/flutter/issues/107199. ## 2.9.2 * Updates the Java InstanceManager to take a listener for when an object is garbage collected. See https://github.com/flutter/flutter/issues/107199. ## 2.9.1 * Updates Android WebView classes as Copyable. This is a part of moving the api to handle garbage collection automatically. See https://github.com/flutter/flutter/issues/107199. ## 2.9.0 * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/106316). * Fixes bug where `Directionality` from context didn't affect `SurfaceAndroidWebView`. * Fixes bug where default text direction was different for `SurfaceAndroidWebView` and `AndroidWebView`. Default is now `TextDirection.ltr` for both. * Fixes bug where setting WebView to a transparent background could cause visual errors when using `SurfaceAndroidWebView`. Hybrid composition is now used when the background color is not 100% opaque. * Raises minimum Flutter version to 3.0.0. ## 2.8.14 * Bumps androidx.annotation from 1.0.0 to 1.4.0. ## 2.8.13 * Fixes a bug which causes an exception when the `onNavigationRequestCallback` return `false`. ## 2.8.12 * Bumps mockito-inline from 3.11.1 to 4.6.1. ## 2.8.11 * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/104231). ## 2.8.10 * Updates references to the obsolete master branch. ## 2.8.9 * Updates Gradle to 7.2.1. ## 2.8.8 * Minor fixes for new analysis options. ## 2.8.7 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 2.8.6 * Updates pigeon developer dependency to the latest version which adds support for null safety. ## 2.8.5 * Migrates deprecated `Scaffold.showSnackBar` to `ScaffoldMessenger` in example app. ## 2.8.4 * Fixes bug preventing `mockito` code generation for tests. * Fixes regression where local storage wasn't cleared when `WebViewController.clearCache` was called. ## 2.8.3 * Fixes a bug causing `debuggingEnabled` to always be set to true. * Fixes an integration test race condition. ## 2.8.2 * Adds the `WebSettings.setAllowFileAccess()` method and ensure that file access is allowed when the `WebViewAndroidWidget.loadFile()` method is executed. ## 2.8.1 * Fixes bug where the default user agent string was being set for every rebuild. See https://github.com/flutter/flutter/issues/94847. ## 2.8.0 * Implements new cookie manager for setting cookies and providing initial cookies. ## 2.7.0 * Adds support for the `loadRequest` method from the platform interface. ## 2.6.0 * Adds implementation of the `loadFlutterAsset` method from the platform interface. ## 2.5.0 * Adds an option to set the background color of the webview. ## 2.4.0 * Adds support for Android's `WebView.loadData` and `WebView.loadDataWithBaseUrl` methods and implements the `loadFile` and `loadHtmlString` methods from the platform interface. * Updates to webview_flutter_platform_interface version 1.5.2. ## 2.3.1 * Adds explanation on how to generate the pigeon communication layer and mockito mock objects. * Updates compileSdkVersion to 31. ## 2.3.0 * Replaces platform implementation with API built with pigeon. ## 2.2.1 * Fix `NullPointerException` from a race condition when changing focus. This only affects `WebView` when it is created without Hybrid Composition. ## 2.2.0 * Implemented new `runJavascript` and `runJavascriptReturningResult` methods in platform interface. ## 2.1.0 * Add `zoomEnabled` functionality. ## 2.0.15 * Added Overrides in FlutterWebView.java ## 2.0.14 * Update example App so navigation menu loads immediatly but only becomes available when `WebViewController` is available (same behavior as example App in webview_flutter package). ## 2.0.13 * Extract Android implementation from `webview_flutter`. ================================================ FILE: packages/webview_flutter/webview_flutter_android/LICENSE ================================================ 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. ================================================ FILE: packages/webview_flutter/webview_flutter_android/README.md ================================================ # webview\_flutter\_android The Android implementation of [`webview_flutter`][1]. ## Usage This package is [endorsed][2], which means you can simply use `webview_flutter` normally. This package will be automatically included in your app when you do. ## Display Mode This plugin supports two different platform view display modes. The default display mode is subject to change in the future, and will not be considered a breaking change, so if you want to ensure a specific mode, you can set it explicitly. ### Texture Layer Hybrid Composition This is the current default mode for versions >=23. This is a new display mode used by most plugins starting with Flutter 3.0. This is more performant than Hybrid Composition, but has some limitations from using an Android [SurfaceTexture](https://developer.android.com/reference/android/graphics/SurfaceTexture). See: * https://github.com/flutter/flutter/issues/104889 * https://github.com/flutter/flutter/issues/116954 ### Hybrid Composition This is the current default mode for versions <23. It ensures that the WebView will display and work as expected, at the cost of some performance. See: * https://flutter.dev/docs/development/platform-integration/platform-views#performance This can be configured for versions >=23 with `AndroidWebViewWidgetCreationParams.displayWithHybridComposition`. See https://pub.dev/packages/webview_flutter#platform-specific-features for more details on setting platform-specific features in the main plugin. ### External Native API The plugin also provides a native API accessible by the native code of Android applications or packages. This API follows the convention of breaking changes of the Dart API, which means that any changes to the class that are not backwards compatible will only be made with a major version change of the plugin. Native code other than this external API does not follow breaking change conventions, so app or plugin clients should not use any other native APIs. The API can be accessed by importing the native class `WebViewFlutterAndroidExternalApi`: Java: ```java import io.flutter.plugins.webviewflutter.WebViewFlutterAndroidExternalApi; ``` ## Contributing This package uses [pigeon][3] to generate the communication layer between Flutter and the host platform (Android). The communication interface is defined in the `pigeons/android_webview.dart` file. After editing the communication interface regenerate the communication layer by running `flutter pub run pigeon --input pigeons/android_webview.dart`. Besides [pigeon][3] this package also uses [mockito][4] to generate mock objects for testing purposes. To generate the mock objects run the following command: ```bash flutter pub run build_runner build --delete-conflicting-outputs ``` If you would like to contribute to the plugin, check out our [contribution guide][5]. [1]: https://pub.dev/packages/webview_flutter [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin [3]: https://pub.dev/packages/pigeon [4]: https://pub.dev/packages/mockito [5]: https://github.com/flutter/plugins/blob/main/CONTRIBUTING.md ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/build.gradle ================================================ group 'io.flutter.plugins.webviewflutter' version '1.0-SNAPSHOT' buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.2.2' } } rootProject.allprojects { repositories { google() mavenCentral() } } apply plugin: 'com.android.library' android { compileSdkVersion 32 defaultConfig { minSdkVersion 19 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { disable 'AndroidGradlePluginVersion', 'InvalidPackage', 'GradleDependency' } dependencies { 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:5.1.0' testImplementation 'androidx.test:core:1.3.0' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } testOptions { unitTests.includeAndroidResources = true unitTests.returnDefaultValues = true unitTests.all { testLogging { events "passed", "skipped", "failed", "standardOut", "standardError" outputs.upToDateWhen {false} showStandardStreams = true } } } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/settings.gradle ================================================ rootProject.name = 'webview_flutter' ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImpl.java ================================================ // Copyright 2013 The Flutter 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.CookieManager; class CookieManagerHostApiImpl implements GeneratedAndroidWebView.CookieManagerHostApi { @Override public void clearCookies(GeneratedAndroidWebView.Result result) { CookieManager cookieManager = CookieManager.getInstance(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { cookieManager.removeAllCookies(result::success); } else { final boolean hasCookies = cookieManager.hasCookies(); if (hasCookies) { cookieManager.removeAllCookie(); } result.success(hasCookies); } } @Override public void setCookie(String url, String value) { CookieManager.getInstance().setCookie(url, value); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DisplayListenerProxy.java ================================================ // Copyright 2013 The Flutter 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 android.hardware.display.DisplayManager.DisplayListener; import android.annotation.TargetApi; import android.hardware.display.DisplayManager; import android.os.Build; import android.util.Log; import java.lang.reflect.Field; import java.util.ArrayList; /** * Works around an Android WebView bug by filtering some DisplayListener invocations. * *

Older Android WebView versions had assumed that when {@link DisplayListener#onDisplayChanged} * is invoked, the display ID it is provided is of a valid display. However it turns out that when a * display is removed Android may call onDisplayChanged with the ID of the removed display, in this * case the Android WebView code tries to fetch and use the display with this ID and crashes with an * NPE. * *

This issue was fixed in the Android WebView code in * https://chromium-review.googlesource.com/517913 which is available starting WebView version * 58.0.3029.125 however older webviews in the wild still have this issue. * *

Since Flutter removes virtual displays whenever a platform view is resized the webview crash * is more likely to happen than other apps. And users were reporting this issue see: * https://github.com/flutter/flutter/issues/30420 * *

This class works around the webview bug by unregistering the WebView's DisplayListener, and * instead registering its own DisplayListener which delegates the callbacks to the WebView's * listener unless it's a onDisplayChanged for an invalid display. * *

I did not find a clean way to get a handle of the WebView's DisplayListener so I'm using * reflection to fetch all registered listeners before and after initializing a webview. In the * first initialization of a webview within the process the difference between the lists is the * webview's display listener. */ @TargetApi(Build.VERSION_CODES.KITKAT) class DisplayListenerProxy { private static final String TAG = "DisplayListenerProxy"; private ArrayList listenersBeforeWebView; /** Should be called prior to the webview's initialization. */ void onPreWebViewInitialization(DisplayManager displayManager) { listenersBeforeWebView = yoinkDisplayListeners(displayManager); } /** Should be called after the webview's initialization. */ void onPostWebViewInitialization(final DisplayManager displayManager) { final ArrayList webViewListeners = yoinkDisplayListeners(displayManager); // We recorded the list of listeners prior to initializing webview, any new listeners we see // after initializing the webview are listeners added by the webview. webViewListeners.removeAll(listenersBeforeWebView); if (webViewListeners.isEmpty()) { // The Android WebView registers a single display listener per process (even if there // are multiple WebView instances) so this list is expected to be non-empty only the // first time a webview is initialized. // Note that in an add2app scenario if the application had instantiated a non Flutter // WebView prior to instantiating the Flutter WebView we are not able to get a reference // to the WebView's display listener and can't work around the bug. // // This means that webview resizes in add2app Flutter apps with a non Flutter WebView // running on a system with a webview prior to 58.0.3029.125 may crash (the Android's // behavior seems to be racy so it doesn't always happen). return; } for (DisplayListener webViewListener : webViewListeners) { // Note that while DisplayManager.unregisterDisplayListener throws when given an // unregistered listener, this isn't an issue as the WebView code never calls // unregisterDisplayListener. displayManager.unregisterDisplayListener(webViewListener); // We never explicitly unregister this listener as the webview's listener is never // unregistered (it's released when the process is terminated). displayManager.registerDisplayListener( new DisplayListener() { @Override public void onDisplayAdded(int displayId) { for (DisplayListener webViewListener : webViewListeners) { webViewListener.onDisplayAdded(displayId); } } @Override public void onDisplayRemoved(int displayId) { for (DisplayListener webViewListener : webViewListeners) { webViewListener.onDisplayRemoved(displayId); } } @Override public void onDisplayChanged(int displayId) { if (displayManager.getDisplay(displayId) == null) { return; } for (DisplayListener webViewListener : webViewListeners) { webViewListener.onDisplayChanged(displayId); } } }, null); } } @SuppressWarnings({"unchecked", "PrivateApi"}) private static ArrayList yoinkDisplayListeners(DisplayManager displayManager) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { // We cannot use reflection on Android P, but it shouldn't matter as it shipped // with WebView 66.0.3359.158 and the WebView version the bug this code is working around was // fixed in 61.0.3116.0. return new ArrayList<>(); } try { Field displayManagerGlobalField = DisplayManager.class.getDeclaredField("mGlobal"); displayManagerGlobalField.setAccessible(true); Object displayManagerGlobal = displayManagerGlobalField.get(displayManager); Field displayListenersField = displayManagerGlobal.getClass().getDeclaredField("mDisplayListeners"); displayListenersField.setAccessible(true); ArrayList delegates = (ArrayList) displayListenersField.get(displayManagerGlobal); Field listenerField = null; ArrayList listeners = new ArrayList<>(); for (Object delegate : delegates) { if (listenerField == null) { listenerField = delegate.getClass().getField("mListener"); listenerField.setAccessible(true); } DisplayManager.DisplayListener listener = (DisplayManager.DisplayListener) listenerField.get(delegate); listeners.add(listener); } return listeners; } catch (NoSuchFieldException | IllegalAccessException e) { Log.w(TAG, "Could not extract WebView's display listeners. " + e); return new ArrayList<>(); } } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerFlutterApiImpl.java ================================================ // Copyright 2013 The Flutter 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.webkit.DownloadListener; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.DownloadListenerFlutterApi; /** * Flutter Api implementation for {@link DownloadListener}. * *

Passes arguments of callbacks methods from a {@link DownloadListener} to Dart. */ public class DownloadListenerFlutterApiImpl extends DownloadListenerFlutterApi { 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 DownloadListenerFlutterApiImpl( BinaryMessenger binaryMessenger, InstanceManager instanceManager) { super(binaryMessenger); this.instanceManager = instanceManager; } /** Passes arguments from {@link DownloadListener#onDownloadStart} to Dart. */ public void onDownloadStart( DownloadListener downloadListener, String url, String userAgent, String contentDisposition, String mimetype, long contentLength, Reply callback) { onDownloadStart( getIdentifierForListener(downloadListener), url, userAgent, contentDisposition, mimetype, contentLength, callback); } private long getIdentifierForListener(DownloadListener listener) { final Long identifier = instanceManager.getIdentifierForStrongReference(listener); if (identifier == null) { throw new IllegalStateException("Could not find identifier for DownloadListener."); } return identifier; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerHostApiImpl.java ================================================ // Copyright 2013 The Flutter 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.webkit.DownloadListener; import androidx.annotation.NonNull; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.DownloadListenerHostApi; /** * Host api implementation for {@link DownloadListener}. * *

Handles creating {@link DownloadListener}s that intercommunicate with a paired Dart object. */ public class DownloadListenerHostApiImpl implements DownloadListenerHostApi { private final InstanceManager instanceManager; private final DownloadListenerCreator downloadListenerCreator; private final DownloadListenerFlutterApiImpl flutterApi; /** * Implementation of {@link DownloadListener} that passes arguments of callback methods to Dart. */ public static class DownloadListenerImpl implements DownloadListener { private final DownloadListenerFlutterApiImpl flutterApi; /** * Creates a {@link DownloadListenerImpl} that passes arguments of callbacks methods to Dart. * * @param flutterApi handles sending messages to Dart */ public DownloadListenerImpl(@NonNull DownloadListenerFlutterApiImpl flutterApi) { this.flutterApi = flutterApi; } @Override public void onDownloadStart( String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { flutterApi.onDownloadStart( this, url, userAgent, contentDisposition, mimetype, contentLength, reply -> {}); } } /** Handles creating {@link DownloadListenerImpl}s for a {@link DownloadListenerHostApiImpl}. */ public static class DownloadListenerCreator { /** * Creates a {@link DownloadListenerImpl}. * * @param flutterApi handles sending messages to Dart * @return the created {@link DownloadListenerImpl} */ public DownloadListenerImpl createDownloadListener(DownloadListenerFlutterApiImpl flutterApi) { return new DownloadListenerImpl(flutterApi); } } /** * Creates a host API that handles creating {@link DownloadListener}s. * * @param instanceManager maintains instances stored to communicate with Dart objects * @param downloadListenerCreator handles creating {@link DownloadListenerImpl}s * @param flutterApi handles sending messages to Dart */ public DownloadListenerHostApiImpl( InstanceManager instanceManager, DownloadListenerCreator downloadListenerCreator, DownloadListenerFlutterApiImpl flutterApi) { this.instanceManager = instanceManager; this.downloadListenerCreator = downloadListenerCreator; this.flutterApi = flutterApi; } @Override public void create(Long instanceId) { final DownloadListener downloadListener = downloadListenerCreator.createDownloadListener(flutterApi); instanceManager.addDartCreatedInstance(downloadListener, instanceId); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FileChooserParamsFlutterApiImpl.java ================================================ // Copyright 2013 The Flutter 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; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManager.java ================================================ // Copyright 2013 The Flutter 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.content.res.AssetManager; import androidx.annotation.NonNull; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.plugin.common.PluginRegistry; import java.io.IOException; /** Provides access to the assets registered as part of the App bundle. */ abstract class FlutterAssetManager { final AssetManager assetManager; /** * Constructs a new instance of the {@link FlutterAssetManager}. * * @param assetManager Instance of Android's {@link AssetManager} used to access assets within the * App bundle. */ public FlutterAssetManager(AssetManager assetManager) { this.assetManager = assetManager; } /** * Gets the relative file path to the Flutter asset with the given name, including the file's * extension, e.g., "myImage.jpg". * *

The returned file path is relative to the Android app's standard asset's directory. * Therefore, the returned path is appropriate to pass to Android's AssetManager, but the path is * not appropriate to load as an absolute path. */ abstract String getAssetFilePathByName(String name); /** * Returns a String array of all the assets at the given path. * * @param path A relative path within the assets, i.e., "docs/home.html". This value cannot be * null. * @return String[] Array of strings, one for each asset. These file names are relative to 'path'. * This value may be null. * @throws IOException Throws an IOException in case I/O operations were interrupted. */ public String[] list(@NonNull String path) throws IOException { return assetManager.list(path); } /** * Provides access to assets using the {@link PluginRegistry.Registrar} for looking up file paths * to Flutter assets. * * @deprecated The {@link RegistrarFlutterAssetManager} is for Flutter's v1 embedding. For * instructions on migrating a plugin from Flutter's v1 Android embedding to v2, visit * http://flutter.dev/go/android-plugin-migration */ @Deprecated static class RegistrarFlutterAssetManager extends FlutterAssetManager { final PluginRegistry.Registrar registrar; /** * Constructs a new instance of the {@link RegistrarFlutterAssetManager}. * * @param assetManager Instance of Android's {@link AssetManager} used to access assets within * the App bundle. * @param registrar Instance of {@link io.flutter.plugin.common.PluginRegistry.Registrar} used * to look up file paths to assets registered by Flutter. */ RegistrarFlutterAssetManager(AssetManager assetManager, PluginRegistry.Registrar registrar) { super(assetManager); this.registrar = registrar; } @Override public String getAssetFilePathByName(String name) { return registrar.lookupKeyForAsset(name); } } /** * Provides access to assets using the {@link FlutterPlugin.FlutterAssets} for looking up file * paths to Flutter assets. */ static class PluginBindingFlutterAssetManager extends FlutterAssetManager { final FlutterPlugin.FlutterAssets flutterAssets; /** * Constructs a new instance of the {@link PluginBindingFlutterAssetManager}. * * @param assetManager Instance of Android's {@link AssetManager} used to access assets within * the App bundle. * @param flutterAssets Instance of {@link * io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterAssets} used to look up file * paths to assets registered by Flutter. */ PluginBindingFlutterAssetManager( AssetManager assetManager, FlutterPlugin.FlutterAssets flutterAssets) { super(assetManager); this.flutterAssets = flutterAssets; } @Override public String getAssetFilePathByName(String name) { return flutterAssets.getAssetFilePathByName(name); } } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerHostApiImpl.java ================================================ // Copyright 2013 The Flutter 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.webkit.WebView; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.FlutterAssetManagerHostApi; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Host api implementation for {@link WebView}. * *

Handles creating {@link WebView}s that intercommunicate with a paired Dart object. */ public class FlutterAssetManagerHostApiImpl implements FlutterAssetManagerHostApi { final FlutterAssetManager flutterAssetManager; /** Constructs a new instance of {@link FlutterAssetManagerHostApiImpl}. */ public FlutterAssetManagerHostApiImpl(FlutterAssetManager flutterAssetManager) { this.flutterAssetManager = flutterAssetManager; } @Override public List list(String path) { try { String[] paths = flutterAssetManager.list(path); if (paths == null) { return new ArrayList<>(); } return Arrays.asList(paths); } catch (IOException ex) { throw new RuntimeException(ex.getMessage()); } } @Override public String getAssetFilePathByName(String name) { return flutterAssetManager.getAssetFilePathByName(name); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewFactory.java ================================================ // Copyright 2013 The Flutter 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.content.Context; import io.flutter.plugin.common.StandardMessageCodec; import io.flutter.plugin.platform.PlatformView; import io.flutter.plugin.platform.PlatformViewFactory; class FlutterWebViewFactory extends PlatformViewFactory { private final InstanceManager instanceManager; FlutterWebViewFactory(InstanceManager instanceManager) { super(StandardMessageCodec.INSTANCE); this.instanceManager = instanceManager; } @Override public PlatformView create(Context context, int id, Object args) { final PlatformView view = (PlatformView) instanceManager.getInstance((Integer) args); if (view == null) { throw new IllegalStateException("Unable to find WebView instance: " + args); } return view; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java ================================================ // Copyright 2013 The Flutter 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 package io.flutter.plugins.webviewflutter; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.plugin.common.BasicMessageChannel; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MessageCodec; import io.flutter.plugin.common.StandardMessageCodec; import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; /** Generated class from Pigeon. */ @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; public @NonNull String getUrl() { return url; } public void setUrl(@NonNull String setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"url\" is null."); } this.url = setterArg; } private @NonNull Boolean isForMainFrame; public @NonNull Boolean getIsForMainFrame() { return isForMainFrame; } public void setIsForMainFrame(@NonNull Boolean setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"isForMainFrame\" is null."); } this.isForMainFrame = setterArg; } private @Nullable Boolean isRedirect; public @Nullable Boolean getIsRedirect() { return isRedirect; } public void setIsRedirect(@Nullable Boolean setterArg) { this.isRedirect = setterArg; } private @NonNull Boolean hasGesture; public @NonNull Boolean getHasGesture() { return hasGesture; } public void setHasGesture(@NonNull Boolean setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"hasGesture\" is null."); } this.hasGesture = setterArg; } private @NonNull String method; public @NonNull String getMethod() { return method; } public void setMethod(@NonNull String setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"method\" is null."); } this.method = setterArg; } private @NonNull Map requestHeaders; public @NonNull Map getRequestHeaders() { return requestHeaders; } public void setRequestHeaders(@NonNull Map setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"requestHeaders\" is null."); } this.requestHeaders = setterArg; } /** Constructor is private to enforce null safety; use Builder. */ private WebResourceRequestData() {} public static final class Builder { private @Nullable String url; public @NonNull Builder setUrl(@NonNull String setterArg) { this.url = setterArg; return this; } private @Nullable Boolean isForMainFrame; public @NonNull Builder setIsForMainFrame(@NonNull Boolean setterArg) { this.isForMainFrame = setterArg; return this; } private @Nullable Boolean isRedirect; public @NonNull Builder setIsRedirect(@Nullable Boolean setterArg) { this.isRedirect = setterArg; return this; } private @Nullable Boolean hasGesture; public @NonNull Builder setHasGesture(@NonNull Boolean setterArg) { this.hasGesture = setterArg; return this; } private @Nullable String method; public @NonNull Builder setMethod(@NonNull String setterArg) { this.method = setterArg; return this; } private @Nullable Map requestHeaders; public @NonNull Builder setRequestHeaders(@NonNull Map setterArg) { this.requestHeaders = setterArg; return this; } public @NonNull WebResourceRequestData build() { WebResourceRequestData pigeonReturn = new WebResourceRequestData(); pigeonReturn.setUrl(url); pigeonReturn.setIsForMainFrame(isForMainFrame); pigeonReturn.setIsRedirect(isRedirect); pigeonReturn.setHasGesture(hasGesture); pigeonReturn.setMethod(method); pigeonReturn.setRequestHeaders(requestHeaders); return pigeonReturn; } } @NonNull 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 = list.get(0); pigeonResult.setUrl((String) url); Object isForMainFrame = list.get(1); pigeonResult.setIsForMainFrame((Boolean) isForMainFrame); Object isRedirect = list.get(2); pigeonResult.setIsRedirect((Boolean) isRedirect); Object hasGesture = list.get(3); pigeonResult.setHasGesture((Boolean) hasGesture); Object method = list.get(4); pigeonResult.setMethod((String) method); Object requestHeaders = list.get(5); pigeonResult.setRequestHeaders((Map) requestHeaders); return pigeonResult; } } /** Generated class from Pigeon that represents data sent in messages. */ public static class WebResourceErrorData { private @NonNull Long errorCode; public @NonNull Long getErrorCode() { return errorCode; } public void setErrorCode(@NonNull Long 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 WebResourceErrorData() {} public static final class Builder { private @Nullable Long errorCode; public @NonNull Builder setErrorCode(@NonNull Long setterArg) { this.errorCode = setterArg; return this; } private @Nullable String description; public @NonNull Builder setDescription(@NonNull String setterArg) { this.description = setterArg; return this; } public @NonNull WebResourceErrorData build() { WebResourceErrorData pigeonReturn = new WebResourceErrorData(); pigeonReturn.setErrorCode(errorCode); pigeonReturn.setDescription(description); return pigeonReturn; } } @NonNull ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(errorCode); toListResult.add(description); return toListResult; } static @NonNull WebResourceErrorData fromList(@NonNull ArrayList list) { WebResourceErrorData pigeonResult = new WebResourceErrorData(); Object errorCode = list.get(0); pigeonResult.setErrorCode( (errorCode == null) ? null : ((errorCode instanceof Integer) ? (Integer) errorCode : (Long) errorCode)); Object description = list.get(1); pigeonResult.setDescription((String) description); return pigeonResult; } } /** Generated class from Pigeon that represents data sent in messages. */ public static class WebViewPoint { private @NonNull Long x; public @NonNull Long getX() { return x; } public void setX(@NonNull Long setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"x\" is null."); } this.x = setterArg; } private @NonNull Long y; public @NonNull Long getY() { return y; } public void setY(@NonNull Long setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"y\" is null."); } this.y = setterArg; } /** Constructor is private to enforce null safety; use Builder. */ private WebViewPoint() {} public static final class Builder { private @Nullable Long x; public @NonNull Builder setX(@NonNull Long setterArg) { this.x = setterArg; return this; } private @Nullable Long y; public @NonNull Builder setY(@NonNull Long setterArg) { this.y = setterArg; return this; } public @NonNull WebViewPoint build() { WebViewPoint pigeonReturn = new WebViewPoint(); pigeonReturn.setX(x); pigeonReturn.setY(y); return pigeonReturn; } } @NonNull ArrayList toList() { ArrayList toListResult = new ArrayList(2); toListResult.add(x); toListResult.add(y); return toListResult; } static @NonNull WebViewPoint fromList(@NonNull ArrayList list) { WebViewPoint pigeonResult = new WebViewPoint(); Object x = list.get(0); pigeonResult.setX((x == null) ? null : ((x instanceof Integer) ? (Integer) x : (Long) x)); Object y = list.get(1); pigeonResult.setY((y == null) ? null : ((y instanceof Integer) ? (Integer) y : (Long) y)); return pigeonResult; } } public interface Result { void success(T result); void error(Throwable error); } /** * Handles methods calls to the native Java Object class. * *

Also handles calls to remove the reference to an instance with `dispose`. * *

See https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html. * *

Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface JavaObjectHostApi { void dispose(@NonNull Long identifier); /** The codec used by JavaObjectHostApi. */ static MessageCodec getCodec() { return new StandardMessageCodec(); } /** * Sets up an instance of `JavaObjectHostApi` to handle messages through the `binaryMessenger`. */ static void setup(BinaryMessenger binaryMessenger, JavaObjectHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.JavaObjectHostApi.dispose", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; Number identifierArg = (Number) args.get(0); if (identifierArg == null) { throw new NullPointerException("identifierArg unexpectedly null."); } api.dispose((identifierArg == null) ? null : identifierArg.longValue()); wrapped.add(0, null); } catch (Error | RuntimeException exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } } } /** * Handles callbacks methods for the native Java Object class. * *

See https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html. * *

Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class JavaObjectFlutterApi { private final BinaryMessenger binaryMessenger; public JavaObjectFlutterApi(BinaryMessenger argBinaryMessenger) { this.binaryMessenger = argBinaryMessenger; } public interface Reply { void reply(T reply); } /** The codec used by JavaObjectFlutterApi. */ static MessageCodec getCodec() { return new StandardMessageCodec(); } public void dispose(@NonNull Long identifierArg, Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.JavaObjectFlutterApi.dispose", getCodec()); channel.send( new ArrayList(Collections.singletonList(identifierArg)), channelReply -> { callback.reply(null); }); } } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface CookieManagerHostApi { void clearCookies(Result result); void setCookie(@NonNull String url, @NonNull String value); /** The codec used by CookieManagerHostApi. */ static MessageCodec getCodec() { return new StandardMessageCodec(); } /** * Sets up an instance of `CookieManagerHostApi` to handle messages through the * `binaryMessenger`. */ static void setup(BinaryMessenger binaryMessenger, CookieManagerHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.CookieManagerHostApi.clearCookies", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList<>(); try { Result resultCallback = new Result() { public void success(Boolean result) { wrapped.add(0, result); reply.reply(wrapped); } public void error(Throwable error) { ArrayList wrappedError = wrapError(error); reply.reply(wrappedError); } }; api.clearCookies(resultCallback); } catch (Error | RuntimeException exception) { ArrayList wrappedError = wrapError(exception); reply.reply(wrappedError); } }); } else { channel.setMessageHandler(null); } } { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.CookieManagerHostApi.setCookie", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; String urlArg = (String) args.get(0); if (urlArg == null) { throw new NullPointerException("urlArg unexpectedly null."); } String valueArg = (String) args.get(1); if (valueArg == null) { throw new NullPointerException("valueArg unexpectedly null."); } api.setCookie(urlArg, valueArg); wrapped.add(0, null); } catch (Error | RuntimeException exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } } } private static class WebViewHostApiCodec extends StandardMessageCodec { public static final WebViewHostApiCodec INSTANCE = new WebViewHostApiCodec(); private WebViewHostApiCodec() {} @Override protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { switch (type) { case (byte) 128: return WebViewPoint.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); } } @Override protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { if (value instanceof WebViewPoint) { stream.write(128); writeValue(stream, ((WebViewPoint) value).toList()); } else { super.writeValue(stream, value); } } } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface WebViewHostApi { void create(@NonNull Long instanceId, @NonNull Boolean useHybridComposition); void loadData( @NonNull Long instanceId, @NonNull String data, @Nullable String mimeType, @Nullable String encoding); void loadDataWithBaseUrl( @NonNull Long instanceId, @Nullable String baseUrl, @NonNull String data, @Nullable String mimeType, @Nullable String encoding, @Nullable String historyUrl); void loadUrl( @NonNull Long instanceId, @NonNull String url, @NonNull Map headers); void postUrl(@NonNull Long instanceId, @NonNull String url, @NonNull byte[] data); @Nullable String getUrl(@NonNull Long instanceId); @NonNull Boolean canGoBack(@NonNull Long instanceId); @NonNull Boolean canGoForward(@NonNull Long instanceId); void goBack(@NonNull Long instanceId); void goForward(@NonNull Long instanceId); void reload(@NonNull Long instanceId); void clearCache(@NonNull Long instanceId, @NonNull Boolean includeDiskFiles); void evaluateJavascript( @NonNull Long instanceId, @NonNull String javascriptString, Result result); @Nullable String getTitle(@NonNull Long instanceId); void scrollTo(@NonNull Long instanceId, @NonNull Long x, @NonNull Long y); void scrollBy(@NonNull Long instanceId, @NonNull Long x, @NonNull Long y); @NonNull Long getScrollX(@NonNull Long instanceId); @NonNull Long getScrollY(@NonNull Long instanceId); @NonNull WebViewPoint getScrollPosition(@NonNull Long instanceId); void setWebContentsDebuggingEnabled(@NonNull Boolean enabled); void setWebViewClient(@NonNull Long instanceId, @NonNull Long webViewClientInstanceId); void addJavaScriptChannel(@NonNull Long instanceId, @NonNull Long javaScriptChannelInstanceId); void removeJavaScriptChannel( @NonNull Long instanceId, @NonNull Long javaScriptChannelInstanceId); void setDownloadListener(@NonNull Long instanceId, @Nullable Long listenerInstanceId); void setWebChromeClient(@NonNull Long instanceId, @Nullable Long clientInstanceId); void setBackgroundColor(@NonNull Long instanceId, @NonNull Long color); /** The codec used by WebViewHostApi. */ static MessageCodec getCodec() { return WebViewHostApiCodec.INSTANCE; } /** Sets up an instance of `WebViewHostApi` to handle messages through the `binaryMessenger`. */ static void setup(BinaryMessenger binaryMessenger, WebViewHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.WebViewHostApi.create", 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 useHybridCompositionArg = (Boolean) args.get(1); if (useHybridCompositionArg == null) { throw new NullPointerException("useHybridCompositionArg unexpectedly null."); } api.create( (instanceIdArg == null) ? null : instanceIdArg.longValue(), useHybridCompositionArg); 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.WebViewHostApi.loadData", 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."); } String dataArg = (String) args.get(1); if (dataArg == null) { throw new NullPointerException("dataArg unexpectedly null."); } String mimeTypeArg = (String) args.get(2); String encodingArg = (String) args.get(3); api.loadData( (instanceIdArg == null) ? null : instanceIdArg.longValue(), dataArg, mimeTypeArg, encodingArg); 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.WebViewHostApi.loadDataWithBaseUrl", 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."); } String baseUrlArg = (String) args.get(1); String dataArg = (String) args.get(2); if (dataArg == null) { throw new NullPointerException("dataArg unexpectedly null."); } String mimeTypeArg = (String) args.get(3); String encodingArg = (String) args.get(4); String historyUrlArg = (String) args.get(5); api.loadDataWithBaseUrl( (instanceIdArg == null) ? null : instanceIdArg.longValue(), baseUrlArg, dataArg, mimeTypeArg, encodingArg, historyUrlArg); 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.WebViewHostApi.loadUrl", 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."); } String urlArg = (String) args.get(1); if (urlArg == null) { throw new NullPointerException("urlArg unexpectedly null."); } Map headersArg = (Map) args.get(2); if (headersArg == null) { throw new NullPointerException("headersArg unexpectedly null."); } api.loadUrl( (instanceIdArg == null) ? null : instanceIdArg.longValue(), urlArg, headersArg); 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.WebViewHostApi.postUrl", 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."); } String urlArg = (String) args.get(1); if (urlArg == null) { throw new NullPointerException("urlArg unexpectedly null."); } byte[] dataArg = (byte[]) args.get(2); if (dataArg == null) { throw new NullPointerException("dataArg unexpectedly null."); } api.postUrl( (instanceIdArg == null) ? null : instanceIdArg.longValue(), urlArg, dataArg); 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.WebViewHostApi.getUrl", 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."); } String output = api.getUrl((instanceIdArg == null) ? null : instanceIdArg.longValue()); wrapped.add(0, output); } 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.WebViewHostApi.canGoBack", 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 output = api.canGoBack((instanceIdArg == null) ? null : instanceIdArg.longValue()); wrapped.add(0, output); } 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.WebViewHostApi.canGoForward", 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 output = api.canGoForward((instanceIdArg == null) ? null : instanceIdArg.longValue()); wrapped.add(0, output); } 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.WebViewHostApi.goBack", 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."); } api.goBack((instanceIdArg == null) ? null : instanceIdArg.longValue()); 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.WebViewHostApi.goForward", 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."); } api.goForward((instanceIdArg == null) ? null : instanceIdArg.longValue()); 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.WebViewHostApi.reload", 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."); } api.reload((instanceIdArg == null) ? null : instanceIdArg.longValue()); 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.WebViewHostApi.clearCache", 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 includeDiskFilesArg = (Boolean) args.get(1); if (includeDiskFilesArg == null) { throw new NullPointerException("includeDiskFilesArg unexpectedly null."); } api.clearCache( (instanceIdArg == null) ? null : instanceIdArg.longValue(), includeDiskFilesArg); 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.WebViewHostApi.evaluateJavascript", 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."); } String javascriptStringArg = (String) args.get(1); if (javascriptStringArg == null) { throw new NullPointerException("javascriptStringArg unexpectedly null."); } Result resultCallback = new Result() { public void success(String result) { wrapped.add(0, result); reply.reply(wrapped); } public void error(Throwable error) { ArrayList wrappedError = wrapError(error); reply.reply(wrappedError); } }; api.evaluateJavascript( (instanceIdArg == null) ? null : instanceIdArg.longValue(), javascriptStringArg, resultCallback); } catch (Error | RuntimeException exception) { ArrayList wrappedError = wrapError(exception); reply.reply(wrappedError); } }); } else { channel.setMessageHandler(null); } } { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.WebViewHostApi.getTitle", 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."); } String output = api.getTitle((instanceIdArg == null) ? null : instanceIdArg.longValue()); wrapped.add(0, output); } 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.WebViewHostApi.scrollTo", 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."); } Number xArg = (Number) args.get(1); if (xArg == null) { throw new NullPointerException("xArg unexpectedly null."); } Number yArg = (Number) args.get(2); if (yArg == null) { throw new NullPointerException("yArg unexpectedly null."); } api.scrollTo( (instanceIdArg == null) ? null : instanceIdArg.longValue(), (xArg == null) ? null : xArg.longValue(), (yArg == null) ? null : yArg.longValue()); 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.WebViewHostApi.scrollBy", 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."); } Number xArg = (Number) args.get(1); if (xArg == null) { throw new NullPointerException("xArg unexpectedly null."); } Number yArg = (Number) args.get(2); if (yArg == null) { throw new NullPointerException("yArg unexpectedly null."); } api.scrollBy( (instanceIdArg == null) ? null : instanceIdArg.longValue(), (xArg == null) ? null : xArg.longValue(), (yArg == null) ? null : yArg.longValue()); 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.WebViewHostApi.getScrollX", 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."); } Long output = api.getScrollX((instanceIdArg == null) ? null : instanceIdArg.longValue()); wrapped.add(0, output); } 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.WebViewHostApi.getScrollY", 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."); } Long output = api.getScrollY((instanceIdArg == null) ? null : instanceIdArg.longValue()); wrapped.add(0, output); } 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.WebViewHostApi.getScrollPosition", 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."); } WebViewPoint output = api.getScrollPosition( (instanceIdArg == null) ? null : instanceIdArg.longValue()); wrapped.add(0, output); } 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.WebViewHostApi.setWebContentsDebuggingEnabled", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; Boolean enabledArg = (Boolean) args.get(0); if (enabledArg == null) { throw new NullPointerException("enabledArg unexpectedly null."); } api.setWebContentsDebuggingEnabled(enabledArg); 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.WebViewHostApi.setWebViewClient", 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."); } Number webViewClientInstanceIdArg = (Number) args.get(1); if (webViewClientInstanceIdArg == null) { throw new NullPointerException("webViewClientInstanceIdArg unexpectedly null."); } api.setWebViewClient( (instanceIdArg == null) ? null : instanceIdArg.longValue(), (webViewClientInstanceIdArg == null) ? null : webViewClientInstanceIdArg.longValue()); 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.WebViewHostApi.addJavaScriptChannel", 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."); } Number javaScriptChannelInstanceIdArg = (Number) args.get(1); if (javaScriptChannelInstanceIdArg == null) { throw new NullPointerException( "javaScriptChannelInstanceIdArg unexpectedly null."); } api.addJavaScriptChannel( (instanceIdArg == null) ? null : instanceIdArg.longValue(), (javaScriptChannelInstanceIdArg == null) ? null : javaScriptChannelInstanceIdArg.longValue()); 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.WebViewHostApi.removeJavaScriptChannel", 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."); } Number javaScriptChannelInstanceIdArg = (Number) args.get(1); if (javaScriptChannelInstanceIdArg == null) { throw new NullPointerException( "javaScriptChannelInstanceIdArg unexpectedly null."); } api.removeJavaScriptChannel( (instanceIdArg == null) ? null : instanceIdArg.longValue(), (javaScriptChannelInstanceIdArg == null) ? null : javaScriptChannelInstanceIdArg.longValue()); 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.WebViewHostApi.setDownloadListener", 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."); } Number listenerInstanceIdArg = (Number) args.get(1); api.setDownloadListener( (instanceIdArg == null) ? null : instanceIdArg.longValue(), (listenerInstanceIdArg == null) ? null : listenerInstanceIdArg.longValue()); 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.WebViewHostApi.setWebChromeClient", 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."); } Number clientInstanceIdArg = (Number) args.get(1); api.setWebChromeClient( (instanceIdArg == null) ? null : instanceIdArg.longValue(), (clientInstanceIdArg == null) ? null : clientInstanceIdArg.longValue()); 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.WebViewHostApi.setBackgroundColor", 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."); } Number colorArg = (Number) args.get(1); if (colorArg == null) { throw new NullPointerException("colorArg unexpectedly null."); } api.setBackgroundColor( (instanceIdArg == null) ? null : instanceIdArg.longValue(), (colorArg == null) ? null : colorArg.longValue()); wrapped.add(0, null); } catch (Error | RuntimeException exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } } } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface WebSettingsHostApi { void create(@NonNull Long instanceId, @NonNull Long webViewInstanceId); void setDomStorageEnabled(@NonNull Long instanceId, @NonNull Boolean flag); void setJavaScriptCanOpenWindowsAutomatically(@NonNull Long instanceId, @NonNull Boolean flag); void setSupportMultipleWindows(@NonNull Long instanceId, @NonNull Boolean support); void setJavaScriptEnabled(@NonNull Long instanceId, @NonNull Boolean flag); void setUserAgentString(@NonNull Long instanceId, @Nullable String userAgentString); void setMediaPlaybackRequiresUserGesture(@NonNull Long instanceId, @NonNull Boolean require); void setSupportZoom(@NonNull Long instanceId, @NonNull Boolean support); void setLoadWithOverviewMode(@NonNull Long instanceId, @NonNull Boolean overview); void setUseWideViewPort(@NonNull Long instanceId, @NonNull Boolean use); void setDisplayZoomControls(@NonNull Long instanceId, @NonNull Boolean enabled); void setBuiltInZoomControls(@NonNull Long instanceId, @NonNull Boolean enabled); void setAllowFileAccess(@NonNull Long instanceId, @NonNull Boolean enabled); /** The codec used by WebSettingsHostApi. */ static MessageCodec getCodec() { return new StandardMessageCodec(); } /** * Sets up an instance of `WebSettingsHostApi` to handle messages through the `binaryMessenger`. */ static void setup(BinaryMessenger binaryMessenger, WebSettingsHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.WebSettingsHostApi.create", 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."); } Number webViewInstanceIdArg = (Number) args.get(1); if (webViewInstanceIdArg == null) { throw new NullPointerException("webViewInstanceIdArg unexpectedly null."); } api.create( (instanceIdArg == null) ? null : instanceIdArg.longValue(), (webViewInstanceIdArg == null) ? null : webViewInstanceIdArg.longValue()); 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.WebSettingsHostApi.setDomStorageEnabled", 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 flagArg = (Boolean) args.get(1); if (flagArg == null) { throw new NullPointerException("flagArg unexpectedly null."); } api.setDomStorageEnabled( (instanceIdArg == null) ? null : instanceIdArg.longValue(), flagArg); 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.WebSettingsHostApi.setJavaScriptCanOpenWindowsAutomatically", 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 flagArg = (Boolean) args.get(1); if (flagArg == null) { throw new NullPointerException("flagArg unexpectedly null."); } api.setJavaScriptCanOpenWindowsAutomatically( (instanceIdArg == null) ? null : instanceIdArg.longValue(), flagArg); 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.WebSettingsHostApi.setSupportMultipleWindows", 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 supportArg = (Boolean) args.get(1); if (supportArg == null) { throw new NullPointerException("supportArg unexpectedly null."); } api.setSupportMultipleWindows( (instanceIdArg == null) ? null : instanceIdArg.longValue(), supportArg); 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.WebSettingsHostApi.setJavaScriptEnabled", 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 flagArg = (Boolean) args.get(1); if (flagArg == null) { throw new NullPointerException("flagArg unexpectedly null."); } api.setJavaScriptEnabled( (instanceIdArg == null) ? null : instanceIdArg.longValue(), flagArg); 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.WebSettingsHostApi.setUserAgentString", 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."); } String userAgentStringArg = (String) args.get(1); api.setUserAgentString( (instanceIdArg == null) ? null : instanceIdArg.longValue(), userAgentStringArg); 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.WebSettingsHostApi.setMediaPlaybackRequiresUserGesture", 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 requireArg = (Boolean) args.get(1); if (requireArg == null) { throw new NullPointerException("requireArg unexpectedly null."); } api.setMediaPlaybackRequiresUserGesture( (instanceIdArg == null) ? null : instanceIdArg.longValue(), requireArg); 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.WebSettingsHostApi.setSupportZoom", 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 supportArg = (Boolean) args.get(1); if (supportArg == null) { throw new NullPointerException("supportArg unexpectedly null."); } api.setSupportZoom( (instanceIdArg == null) ? null : instanceIdArg.longValue(), supportArg); 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.WebSettingsHostApi.setLoadWithOverviewMode", 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 overviewArg = (Boolean) args.get(1); if (overviewArg == null) { throw new NullPointerException("overviewArg unexpectedly null."); } api.setLoadWithOverviewMode( (instanceIdArg == null) ? null : instanceIdArg.longValue(), overviewArg); 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.WebSettingsHostApi.setUseWideViewPort", 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 useArg = (Boolean) args.get(1); if (useArg == null) { throw new NullPointerException("useArg unexpectedly null."); } api.setUseWideViewPort( (instanceIdArg == null) ? null : instanceIdArg.longValue(), useArg); 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.WebSettingsHostApi.setDisplayZoomControls", 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 enabledArg = (Boolean) args.get(1); if (enabledArg == null) { throw new NullPointerException("enabledArg unexpectedly null."); } api.setDisplayZoomControls( (instanceIdArg == null) ? null : instanceIdArg.longValue(), enabledArg); 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.WebSettingsHostApi.setBuiltInZoomControls", 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 enabledArg = (Boolean) args.get(1); if (enabledArg == null) { throw new NullPointerException("enabledArg unexpectedly null."); } api.setBuiltInZoomControls( (instanceIdArg == null) ? null : instanceIdArg.longValue(), enabledArg); 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.WebSettingsHostApi.setAllowFileAccess", 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 enabledArg = (Boolean) args.get(1); if (enabledArg == null) { throw new NullPointerException("enabledArg unexpectedly null."); } api.setAllowFileAccess( (instanceIdArg == null) ? null : instanceIdArg.longValue(), enabledArg); wrapped.add(0, null); } catch (Error | RuntimeException exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } } } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface JavaScriptChannelHostApi { void create(@NonNull Long instanceId, @NonNull String channelName); /** The codec used by JavaScriptChannelHostApi. */ static MessageCodec getCodec() { return new StandardMessageCodec(); } /** * Sets up an instance of `JavaScriptChannelHostApi` to handle messages through the * `binaryMessenger`. */ static void setup(BinaryMessenger binaryMessenger, JavaScriptChannelHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.JavaScriptChannelHostApi.create", 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."); } String channelNameArg = (String) args.get(1); if (channelNameArg == null) { throw new NullPointerException("channelNameArg unexpectedly null."); } api.create( (instanceIdArg == null) ? null : instanceIdArg.longValue(), channelNameArg); wrapped.add(0, null); } catch (Error | RuntimeException exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } } } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class JavaScriptChannelFlutterApi { private final BinaryMessenger binaryMessenger; public JavaScriptChannelFlutterApi(BinaryMessenger argBinaryMessenger) { this.binaryMessenger = argBinaryMessenger; } public interface Reply { void reply(T reply); } /** The codec used by JavaScriptChannelFlutterApi. */ static MessageCodec getCodec() { return new StandardMessageCodec(); } public void postMessage( @NonNull Long instanceIdArg, @NonNull String messageArg, Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.JavaScriptChannelFlutterApi.postMessage", getCodec()); channel.send( new ArrayList(Arrays.asList(instanceIdArg, messageArg)), channelReply -> { callback.reply(null); }); } } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface WebViewClientHostApi { void create(@NonNull Long instanceId); void setSynchronousReturnValueForShouldOverrideUrlLoading( @NonNull Long instanceId, @NonNull Boolean value); /** The codec used by WebViewClientHostApi. */ static MessageCodec getCodec() { return new StandardMessageCodec(); } /** * Sets up an instance of `WebViewClientHostApi` to handle messages through the * `binaryMessenger`. */ static void setup(BinaryMessenger binaryMessenger, WebViewClientHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.WebViewClientHostApi.create", 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."); } api.create((instanceIdArg == null) ? null : instanceIdArg.longValue()); 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.WebViewClientHostApi.setSynchronousReturnValueForShouldOverrideUrlLoading", 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.setSynchronousReturnValueForShouldOverrideUrlLoading( (instanceIdArg == null) ? null : instanceIdArg.longValue(), valueArg); wrapped.add(0, null); } catch (Error | RuntimeException exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } } } private static class WebViewClientFlutterApiCodec extends StandardMessageCodec { public static final WebViewClientFlutterApiCodec INSTANCE = new WebViewClientFlutterApiCodec(); private WebViewClientFlutterApiCodec() {} @Override protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { switch (type) { case (byte) 128: return WebResourceErrorData.fromList((ArrayList) readValue(buffer)); case (byte) 129: return WebResourceRequestData.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); } } @Override protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { if (value instanceof WebResourceErrorData) { stream.write(128); writeValue(stream, ((WebResourceErrorData) value).toList()); } else if (value instanceof WebResourceRequestData) { stream.write(129); writeValue(stream, ((WebResourceRequestData) value).toList()); } else { super.writeValue(stream, value); } } } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class WebViewClientFlutterApi { private final BinaryMessenger binaryMessenger; public WebViewClientFlutterApi(BinaryMessenger argBinaryMessenger) { this.binaryMessenger = argBinaryMessenger; } public interface Reply { void reply(T reply); } /** The codec used by WebViewClientFlutterApi. */ static MessageCodec getCodec() { return WebViewClientFlutterApiCodec.INSTANCE; } public void onPageStarted( @NonNull Long instanceIdArg, @NonNull Long webViewInstanceIdArg, @NonNull String urlArg, Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.WebViewClientFlutterApi.onPageStarted", getCodec()); channel.send( new ArrayList(Arrays.asList(instanceIdArg, webViewInstanceIdArg, urlArg)), channelReply -> { callback.reply(null); }); } public void onPageFinished( @NonNull Long instanceIdArg, @NonNull Long webViewInstanceIdArg, @NonNull String urlArg, Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.WebViewClientFlutterApi.onPageFinished", getCodec()); channel.send( new ArrayList(Arrays.asList(instanceIdArg, webViewInstanceIdArg, urlArg)), channelReply -> { callback.reply(null); }); } public void onReceivedRequestError( @NonNull Long instanceIdArg, @NonNull Long webViewInstanceIdArg, @NonNull WebResourceRequestData requestArg, @NonNull WebResourceErrorData errorArg, Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedRequestError", getCodec()); channel.send( new ArrayList( Arrays.asList(instanceIdArg, webViewInstanceIdArg, requestArg, errorArg)), channelReply -> { callback.reply(null); }); } public void onReceivedError( @NonNull Long instanceIdArg, @NonNull Long webViewInstanceIdArg, @NonNull Long errorCodeArg, @NonNull String descriptionArg, @NonNull String failingUrlArg, Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedError", getCodec()); channel.send( new ArrayList( Arrays.asList( instanceIdArg, webViewInstanceIdArg, errorCodeArg, descriptionArg, failingUrlArg)), channelReply -> { callback.reply(null); }); } public void requestLoading( @NonNull Long instanceIdArg, @NonNull Long webViewInstanceIdArg, @NonNull WebResourceRequestData requestArg, Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.WebViewClientFlutterApi.requestLoading", getCodec()); channel.send( new ArrayList(Arrays.asList(instanceIdArg, webViewInstanceIdArg, requestArg)), channelReply -> { callback.reply(null); }); } public void urlLoading( @NonNull Long instanceIdArg, @NonNull Long webViewInstanceIdArg, @NonNull String urlArg, Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.WebViewClientFlutterApi.urlLoading", getCodec()); channel.send( new ArrayList(Arrays.asList(instanceIdArg, webViewInstanceIdArg, urlArg)), channelReply -> { callback.reply(null); }); } } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface DownloadListenerHostApi { void create(@NonNull Long instanceId); /** The codec used by DownloadListenerHostApi. */ static MessageCodec getCodec() { return new StandardMessageCodec(); } /** * Sets up an instance of `DownloadListenerHostApi` to handle messages through the * `binaryMessenger`. */ static void setup(BinaryMessenger binaryMessenger, DownloadListenerHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.DownloadListenerHostApi.create", 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."); } api.create((instanceIdArg == null) ? null : instanceIdArg.longValue()); wrapped.add(0, null); } catch (Error | RuntimeException exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } } } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class DownloadListenerFlutterApi { private final BinaryMessenger binaryMessenger; public DownloadListenerFlutterApi(BinaryMessenger argBinaryMessenger) { this.binaryMessenger = argBinaryMessenger; } public interface Reply { void reply(T reply); } /** The codec used by DownloadListenerFlutterApi. */ static MessageCodec getCodec() { return new StandardMessageCodec(); } public void onDownloadStart( @NonNull Long instanceIdArg, @NonNull String urlArg, @NonNull String userAgentArg, @NonNull String contentDispositionArg, @NonNull String mimetypeArg, @NonNull Long contentLengthArg, Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.DownloadListenerFlutterApi.onDownloadStart", getCodec()); channel.send( new ArrayList( Arrays.asList( instanceIdArg, urlArg, userAgentArg, contentDispositionArg, mimetypeArg, contentLengthArg)), channelReply -> { callback.reply(null); }); } } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ 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(); } /** * Sets up an instance of `WebChromeClientHostApi` to handle messages through the * `binaryMessenger`. */ static void setup(BinaryMessenger binaryMessenger, WebChromeClientHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.WebChromeClientHostApi.create", 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."); } api.create((instanceIdArg == null) ? null : instanceIdArg.longValue()); 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) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } } } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface FlutterAssetManagerHostApi { @NonNull List list(@NonNull String path); @NonNull String getAssetFilePathByName(@NonNull String name); /** The codec used by FlutterAssetManagerHostApi. */ static MessageCodec getCodec() { return new StandardMessageCodec(); } /** * Sets up an instance of `FlutterAssetManagerHostApi` to handle messages through the * `binaryMessenger`. */ static void setup(BinaryMessenger binaryMessenger, FlutterAssetManagerHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.FlutterAssetManagerHostApi.list", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; String pathArg = (String) args.get(0); if (pathArg == null) { throw new NullPointerException("pathArg unexpectedly null."); } List output = api.list(pathArg); wrapped.add(0, output); } 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.FlutterAssetManagerHostApi.getAssetFilePathByName", getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList<>(); try { ArrayList args = (ArrayList) message; assert args != null; String nameArg = (String) args.get(0); if (nameArg == null) { throw new NullPointerException("nameArg unexpectedly null."); } String output = api.getAssetFilePathByName(nameArg); wrapped.add(0, output); } catch (Error | RuntimeException exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } } } /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ public static class WebChromeClientFlutterApi { private final BinaryMessenger binaryMessenger; public WebChromeClientFlutterApi(BinaryMessenger argBinaryMessenger) { this.binaryMessenger = argBinaryMessenger; } public interface Reply { void reply(T reply); } /** The codec used by WebChromeClientFlutterApi. */ static MessageCodec getCodec() { return new StandardMessageCodec(); } public void onProgressChanged( @NonNull Long instanceIdArg, @NonNull Long webViewInstanceIdArg, @NonNull Long progressArg, Reply callback) { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.WebChromeClientFlutterApi.onProgressChanged", getCodec()); channel.send( new ArrayList(Arrays.asList(instanceIdArg, webViewInstanceIdArg, progressArg)), channelReply -> { 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 { void create(@NonNull Long instanceId); void deleteAllData(@NonNull Long instanceId); /** The codec used by WebStorageHostApi. */ static MessageCodec getCodec() { return new StandardMessageCodec(); } /** * Sets up an instance of `WebStorageHostApi` to handle messages through the `binaryMessenger`. */ static void setup(BinaryMessenger binaryMessenger, WebStorageHostApi api) { { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, "dev.flutter.pigeon.WebStorageHostApi.create", 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."); } api.create((instanceIdArg == null) ? null : instanceIdArg.longValue()); 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.WebStorageHostApi.deleteAllData", 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."); } api.deleteAllData((instanceIdArg == null) ? null : instanceIdArg.longValue()); wrapped.add(0, null); } catch (Error | RuntimeException exception) { ArrayList wrappedError = wrapError(exception); wrapped = wrappedError; } reply.reply(wrapped); }); } else { channel.setMessageHandler(null); } } } } 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 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 errorList; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/InputAwareWebView.java ================================================ // Copyright 2013 The Flutter 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 android.content.Context.INPUT_METHOD_SERVICE; import android.content.Context; import android.graphics.Rect; import android.os.Build; import android.util.Log; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.webkit.WebView; import android.widget.ListPopupWindow; /** * A WebView subclass that mirrors the same implementation hacks that the system WebView does in * order to correctly create an InputConnection. * *

These hacks are only needed in Android versions below N and exist to create an InputConnection * on the WebView's dedicated input, or IME, thread. The majority of this proxying logic is in * {@link #checkInputConnectionProxy}. * *

See also {@link ThreadedInputConnectionProxyAdapterView}. */ class InputAwareWebView extends WebView { private static final String TAG = "InputAwareWebView"; private View threadedInputConnectionProxyView; private ThreadedInputConnectionProxyAdapterView proxyAdapterView; private View containerView; InputAwareWebView(Context context, View containerView) { super(context); this.containerView = containerView; } void setContainerView(View containerView) { this.containerView = containerView; if (proxyAdapterView == null) { return; } Log.w(TAG, "The containerView has changed while the proxyAdapterView exists."); if (containerView != null) { setInputConnectionTarget(proxyAdapterView); } } /** * Set our proxy adapter view to use its cached input connection instead of creating new ones. * *

This is used to avoid losing our input connection when the virtual display is resized. */ void lockInputConnection() { if (proxyAdapterView == null) { return; } proxyAdapterView.setLocked(true); } /** Sets the proxy adapter view back to its default behavior. */ void unlockInputConnection() { if (proxyAdapterView == null) { return; } proxyAdapterView.setLocked(false); } /** Restore the original InputConnection, if needed. */ void dispose() { resetInputConnection(); } /** * Creates an InputConnection from the IME thread when needed. * *

We only need to create a {@link ThreadedInputConnectionProxyAdapterView} and create an * InputConnectionProxy on the IME thread when WebView is doing the same thing. So we rely on the * system calling this method for WebView's proxy view in order to know when we need to create our * own. * *

This method would normally be called for any View that used the InputMethodManager. We rely * on flutter/engine filtering the calls we receive down to the ones in our hierarchy and the * system WebView in order to know whether or not the system WebView expects an InputConnection on * the IME thread. */ @Override public boolean checkInputConnectionProxy(final View view) { // Check to see if the view param is WebView's ThreadedInputConnectionProxyView. View previousProxy = threadedInputConnectionProxyView; threadedInputConnectionProxyView = view; if (previousProxy == view) { // This isn't a new ThreadedInputConnectionProxyView. Ignore it. return super.checkInputConnectionProxy(view); } if (containerView == null) { Log.e( TAG, "Can't create a proxy view because there's no container view. Text input may not work."); return super.checkInputConnectionProxy(view); } // We've never seen this before, so we make the assumption that this is WebView's // ThreadedInputConnectionProxyView. We are making the assumption that the only view that could // possibly be interacting with the IMM here is WebView's ThreadedInputConnectionProxyView. proxyAdapterView = new ThreadedInputConnectionProxyAdapterView( /*containerView=*/ containerView, /*targetView=*/ view, /*imeHandler=*/ view.getHandler()); setInputConnectionTarget(/*targetView=*/ proxyAdapterView); return super.checkInputConnectionProxy(view); } /** * Ensure that input creation happens back on {@link #containerView}'s thread once this view no * longer has focus. * *

The logic in {@link #checkInputConnectionProxy} forces input creation to happen on Webview's * thread for all connections. We undo it here so users will be able to go back to typing in * Flutter UIs as expected. */ @Override public void clearFocus() { super.clearFocus(); resetInputConnection(); } /** * Ensure that input creation happens back on {@link #containerView}. * *

The logic in {@link #checkInputConnectionProxy} forces input creation to happen on Webview's * thread for all connections. We undo it here so users will be able to go back to typing in * Flutter UIs as expected. */ private void resetInputConnection() { if (proxyAdapterView == null) { // No need to reset the InputConnection to the default thread if we've never changed it. return; } if (containerView == null) { Log.e(TAG, "Can't reset the input connection to the container view because there is none."); return; } setInputConnectionTarget(/*targetView=*/ containerView); } /** * This is the crucial trick that gets the InputConnection creation to happen on the correct * thread pre Android N. * https://cs.chromium.org/chromium/src/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java?l=169&rcl=f0698ee3e4483fad5b0c34159276f71cfaf81f3a * *

{@code targetView} should have a {@link View#getHandler} method with the thread that future * InputConnections should be created on. */ void setInputConnectionTarget(final View targetView) { if (containerView == null) { Log.e( TAG, "Can't set the input connection target because there is no containerView to use as a handler."); return; } targetView.requestFocus(); containerView.post( new Runnable() { @Override public void run() { if (containerView == null) { Log.e( TAG, "Can't set the input connection target because there is no containerView to use as a handler."); return; } InputMethodManager imm = (InputMethodManager) getContext().getSystemService(INPUT_METHOD_SERVICE); // This is a hack to make InputMethodManager believe that the target view now has focus. // As a result, InputMethodManager will think that targetView is focused, and will call // getHandler() of the view when creating input connection. // Step 1: Set targetView as InputMethodManager#mNextServedView. This does not affect // the real window focus. targetView.onWindowFocusChanged(true); // Step 2: Have InputMethodManager focus in on targetView. As a result, IMM will call // onCreateInputConnection() on targetView on the same thread as // targetView.getHandler(). It will also call subsequent InputConnection methods on this // thread. This is the IME thread in cases where targetView is our proxyAdapterView. imm.isActive(containerView); } }); } @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { // This works around a crash when old (<67.0.3367.0) Chromium versions are used. // Prior to Chromium 67.0.3367 the following sequence happens when a select drop down is shown // on tablets: // // - WebView is calling ListPopupWindow#show // - buildDropDown is invoked, which sets mDropDownList to a DropDownListView. // - showAsDropDown is invoked - resulting in mDropDownList being added to the window and is // also synchronously performing the following sequence: // - WebView's focus change listener is loosing focus (as mDropDownList got it) // - WebView is hiding all popups (as it lost focus) // - WebView's SelectPopupDropDown#hide is invoked. // - DropDownPopupWindow#dismiss is invoked setting mDropDownList to null. // - mDropDownList#setSelection is invoked and is throwing a NullPointerException (as we just set mDropDownList to null). // // To workaround this, we drop the problematic focus lost call. // See more details on: https://github.com/flutter/flutter/issues/54164 // // We don't do this after Android P as it shipped with a new enough WebView version, and it's // better to not do this on all future Android versions in case DropDownListView's code changes. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P && isCalledFromListPopupWindowShow() && !focused) { return; } super.onFocusChanged(focused, direction, previouslyFocusedRect); } private boolean isCalledFromListPopupWindowShow() { StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); for (StackTraceElement stackTraceElement : stackTraceElements) { if (stackTraceElement.getClassName().equals(ListPopupWindow.class.getCanonicalName()) && stackTraceElement.getMethodName().equals("show")) { return true; } } return false; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/InstanceManager.java ================================================ // Copyright 2013 The Flutter 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.Handler; import android.os.Looper; import android.util.Log; import androidx.annotation.Nullable; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.WeakHashMap; /** * Maintains instances used to communicate with the corresponding objects in Dart. * *

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

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

Added instances are added as a weak reference and a strong reference. When the strong * reference is removed with `{@link #remove(long)}` and the weak reference is deallocated, the * `finalizationListener` is made with the instance's identifier. However, if the strong reference * is removed and then the identifier is retrieved with the intention to pass the identifier to Dart * (e.g. calling {@link #getIdentifierForStrongReference(Object)}), the strong reference to the * instance is recreated. The strong reference will then need to be removed manually again. */ @SuppressWarnings("unchecked") public class InstanceManager { // Identifiers are locked to a specific range to avoid collisions with objects // created simultaneously from Dart. // Host uses identifiers >= 2^16 and Dart is expected to use values n where, // 0 <= n < 2^16. private 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 { void onFinalize(long identifier); } private final WeakHashMap identifiers = new WeakHashMap<>(); private final HashMap> weakInstances = new HashMap<>(); private final HashMap strongInstances = new HashMap<>(); private final ReferenceQueue referenceQueue = new ReferenceQueue<>(); private final HashMap, Long> weakReferencesToIdentifiers = new HashMap<>(); private final Handler handler = new Handler(Looper.getMainLooper()); private final FinalizationListener finalizationListener; private long nextIdentifier = MIN_HOST_CREATED_IDENTIFIER; private boolean isClosed = false; /** * Instantiate a new manager. * *

When the manager is no longer needed, {@link #close()} must be called. * * @param finalizationListener the listener for garbage collected weak references. * @return a new `InstanceManager`. */ public static InstanceManager open(FinalizationListener finalizationListener) { return new InstanceManager(finalizationListener); } private InstanceManager(FinalizationListener finalizationListener) { this.finalizationListener = finalizationListener; handler.postDelayed( this::releaseAllFinalizedInstances, CLEAR_FINALIZED_WEAK_REFERENCES_INTERVAL); } /** * Removes `identifier` and its associated strongly referenced instance, if present, from the * manager. * * @param identifier the identifier paired to an instance. * @param the expected return type. * @return the removed instance if the manager contains the given identifier, otherwise null if * the manager doesn't contain the value or the manager is closed. */ @Nullable public T remove(long identifier) { if (isClosed()) { Log.w(TAG, CLOSED_WARNING); return null; } return (T) strongInstances.remove(identifier); } /** * Retrieves the identifier paired with an instance. * *

If the manager contains `instance`, as a strong or weak reference, the strong reference to * `instance` will be recreated and will need to be removed again with {@link #remove(long)}. * * @param instance an instance that may be stored in the manager. * @return the identifier associated with `instance` if the manager contains the value, otherwise * null if the manager doesn't contain the value or the manager is closed. */ @Nullable public Long getIdentifierForStrongReference(Object instance) { if (isClosed()) { Log.w(TAG, CLOSED_WARNING); return null; } final Long identifier = identifiers.get(instance); if (identifier != null) { strongInstances.put(identifier, instance); } return identifier; } /** * Adds a new instance that was instantiated from Dart. * *

If an instance or identifier has already been added, it will be replaced by the new values. * The Dart InstanceManager is considered the source of truth and has the capability to overwrite * stored pairs in response to hot restarts. * *

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) { if (isClosed()) { Log.w(TAG, CLOSED_WARNING); return; } addInstance(instance, 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. If the manager is closed, returns -1. */ public long addHostCreatedInstance(Object instance) { if (isClosed()) { Log.w(TAG, CLOSED_WARNING); return -1; } final long identifier = nextIdentifier++; addInstance(instance, identifier); return identifier; } /** * Retrieves the instance associated with identifier. * * @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 if the manager doesn't contain the value or the manager is closed. */ @Nullable public T getInstance(long identifier) { if (isClosed()) { Log.w(TAG, CLOSED_WARNING); return null; } final WeakReference instance = (WeakReference) weakInstances.get(identifier); if (instance != null) { return instance.get(); } return (T) strongInstances.get(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`. If the manager is closed, returns * `false`. */ public boolean containsInstance(Object instance) { if (isClosed()) { Log.w(TAG, CLOSED_WARNING); return false; } return identifiers.containsKey(instance); } /** * Closes the manager and releases resources. * *

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() { WeakReference reference; while ((reference = (WeakReference) referenceQueue.poll()) != null) { final Long identifier = weakReferencesToIdentifiers.remove(reference); if (identifier != null) { weakInstances.remove(identifier); strongInstances.remove(identifier); finalizationListener.onFinalize(identifier); } } handler.postDelayed( this::releaseAllFinalizedInstances, CLEAR_FINALIZED_WEAK_REFERENCES_INTERVAL); } private void addInstance(Object instance, long identifier) { if (identifier < 0) { throw new IllegalArgumentException("Identifier must be >= 0."); } final WeakReference weakReference = new WeakReference<>(instance, referenceQueue); identifiers.put(instance, identifier); weakInstances.put(identifier, weakReference); weakReferencesToIdentifiers.put(weakReference, identifier); strongInstances.put(identifier, instance); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaObjectHostApiImpl.java ================================================ // Copyright 2013 The Flutter 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 androidx.annotation.NonNull; /** * A pigeon Host API implementation that handles creating {@link Object}s and invoking its static * and instance methods. * *

{@link Object} instances created by {@link JavaObjectHostApiImpl} are used to intercommunicate * with a paired Dart object. */ public class JavaObjectHostApiImpl implements GeneratedAndroidWebView.JavaObjectHostApi { private final InstanceManager instanceManager; /** * Constructs a {@link JavaObjectHostApiImpl}. * * @param instanceManager maintains instances stored to communicate with Dart objects */ public JavaObjectHostApiImpl(InstanceManager instanceManager) { this.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); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannel.java ================================================ // Copyright 2013 The Flutter 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.Handler; import android.os.Looper; import android.webkit.JavascriptInterface; import androidx.annotation.NonNull; /** * Added as a JavaScript interface to the WebView for any JavaScript channel that the Dart code sets * up. * *

Exposes a single method named `postMessage` to JavaScript, which sends a message to the Dart * code. */ public class JavaScriptChannel { private final Handler platformThreadHandler; final String javaScriptChannelName; private final JavaScriptChannelFlutterApiImpl flutterApi; /** * Creates a {@link JavaScriptChannel} that passes arguments of callback methods to Dart. * * @param flutterApi the Flutter Api to which JS messages are sent * @param channelName JavaScript channel the message was sent through * @param platformThreadHandler handles making callbacks on the desired thread */ public JavaScriptChannel( @NonNull JavaScriptChannelFlutterApiImpl flutterApi, String channelName, Handler platformThreadHandler) { this.flutterApi = flutterApi; this.javaScriptChannelName = channelName; this.platformThreadHandler = platformThreadHandler; } // Suppressing unused warning as this is invoked from JavaScript. @SuppressWarnings("unused") @JavascriptInterface public void postMessage(final String message) { final Runnable postMessageRunnable = () -> { flutterApi.postMessage(JavaScriptChannel.this, message, reply -> {}); }; if (platformThreadHandler.getLooper() == Looper.myLooper()) { postMessageRunnable.run(); } else { platformThreadHandler.post(postMessageRunnable); } } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelFlutterApiImpl.java ================================================ // Copyright 2013 The Flutter 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 io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.JavaScriptChannelFlutterApi; /** * Flutter Api implementation for {@link JavaScriptChannel}. * *

Passes arguments of callbacks methods from a {@link JavaScriptChannel} to Dart. */ public class JavaScriptChannelFlutterApiImpl extends JavaScriptChannelFlutterApi { 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 JavaScriptChannelFlutterApiImpl( BinaryMessenger binaryMessenger, InstanceManager instanceManager) { super(binaryMessenger); this.instanceManager = instanceManager; } /** Passes arguments from {@link JavaScriptChannel#postMessage} to Dart. */ public void postMessage( JavaScriptChannel javaScriptChannel, String messageArg, Reply callback) { super.postMessage(getIdentifierForJavaScriptChannel(javaScriptChannel), messageArg, callback); } private long getIdentifierForJavaScriptChannel(JavaScriptChannel javaScriptChannel) { final Long identifier = instanceManager.getIdentifierForStrongReference(javaScriptChannel); if (identifier == null) { throw new IllegalStateException("Could not find identifier for JavaScriptChannel."); } return identifier; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelHostApiImpl.java ================================================ // Copyright 2013 The Flutter 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.Handler; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.JavaScriptChannelHostApi; /** * Host api implementation for {@link JavaScriptChannel}. * *

Handles creating {@link JavaScriptChannel}s that intercommunicate with a paired Dart object. */ public class JavaScriptChannelHostApiImpl implements JavaScriptChannelHostApi { private final InstanceManager instanceManager; private final JavaScriptChannelCreator javaScriptChannelCreator; private final JavaScriptChannelFlutterApiImpl flutterApi; private Handler platformThreadHandler; /** Handles creating {@link JavaScriptChannel}s for a {@link JavaScriptChannelHostApiImpl}. */ public static class JavaScriptChannelCreator { /** * Creates a {@link JavaScriptChannel}. * * @param flutterApi handles sending messages to Dart * @param channelName JavaScript channel the message should be sent through * @param platformThreadHandler handles making callbacks on the desired thread * @return the created {@link JavaScriptChannel} */ public JavaScriptChannel createJavaScriptChannel( JavaScriptChannelFlutterApiImpl flutterApi, String channelName, Handler platformThreadHandler) { return new JavaScriptChannel(flutterApi, channelName, platformThreadHandler); } } /** * Creates a host API that handles creating {@link JavaScriptChannel}s. * * @param instanceManager maintains instances stored to communicate with Dart objects * @param javaScriptChannelCreator handles creating {@link JavaScriptChannel}s * @param flutterApi handles sending messages to Dart * @param platformThreadHandler handles making callbacks on the desired thread */ public JavaScriptChannelHostApiImpl( InstanceManager instanceManager, JavaScriptChannelCreator javaScriptChannelCreator, JavaScriptChannelFlutterApiImpl flutterApi, Handler platformThreadHandler) { this.instanceManager = instanceManager; this.javaScriptChannelCreator = javaScriptChannelCreator; this.flutterApi = flutterApi; this.platformThreadHandler = platformThreadHandler; } /** * Sets the platformThreadHandler to make callbacks * * @param platformThreadHandler the new thread handler */ public void setPlatformThreadHandler(Handler platformThreadHandler) { this.platformThreadHandler = platformThreadHandler; } @Override public void create(Long instanceId, String channelName) { final JavaScriptChannel javaScriptChannel = javaScriptChannelCreator.createJavaScriptChannel( flutterApi, channelName, platformThreadHandler); instanceManager.addDartCreatedInstance(javaScriptChannel, instanceId); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/ThreadedInputConnectionProxyAdapterView.java ================================================ // Copyright 2013 The Flutter 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.Handler; import android.os.IBinder; import android.view.View; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; /** * A fake View only exposed to InputMethodManager. * *

This follows a similar flow to Chromium's WebView (see * https://cs.chromium.org/chromium/src/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionProxyView.java). * WebView itself bounces its InputConnection around several different threads. We follow its logic * here to get the same working connection. * *

This exists solely to forward input creation to WebView's ThreadedInputConnectionProxyView on * the IME thread. The way that this is created in {@link * InputAwareWebView#checkInputConnectionProxy} guarantees that we have a handle to * ThreadedInputConnectionProxyView and {@link #onCreateInputConnection} is always called on the IME * thread. We delegate to ThreadedInputConnectionProxyView there to get WebView's input connection. */ final class ThreadedInputConnectionProxyAdapterView extends View { final Handler imeHandler; final IBinder windowToken; final View containerView; final View rootView; final View targetView; private boolean triggerDelayed = true; private boolean isLocked = false; private InputConnection cachedConnection; ThreadedInputConnectionProxyAdapterView(View containerView, View targetView, Handler imeHandler) { super(containerView.getContext()); this.imeHandler = imeHandler; this.containerView = containerView; this.targetView = targetView; windowToken = containerView.getWindowToken(); rootView = containerView.getRootView(); setFocusable(true); setFocusableInTouchMode(true); setVisibility(VISIBLE); } /** Returns whether or not this is currently asynchronously acquiring an input connection. */ boolean isTriggerDelayed() { return triggerDelayed; } /** Sets whether or not this should use its previously cached input connection. */ void setLocked(boolean locked) { isLocked = locked; } /** * This is expected to be called on the IME thread. See the setup required for this in {@link * InputAwareWebView#checkInputConnectionProxy(View)}. * *

Delegates to ThreadedInputConnectionProxyView to get WebView's input connection. */ @Override public InputConnection onCreateInputConnection(final EditorInfo outAttrs) { triggerDelayed = false; InputConnection inputConnection = (isLocked) ? cachedConnection : targetView.onCreateInputConnection(outAttrs); triggerDelayed = true; cachedConnection = inputConnection; return inputConnection; } @Override public boolean checkInputConnectionProxy(View view) { return true; } @Override public boolean hasWindowFocus() { // None of our views here correctly report they have window focus because of how we're embedding // the platform view inside of a virtual display. return true; } @Override public View getRootView() { return rootView; } @Override public boolean onCheckIsTextEditor() { return true; } @Override public boolean isFocused() { return true; } @Override public IBinder getWindowToken() { return windowToken; } @Override public Handler getHandler() { return imeHandler; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java ================================================ // Copyright 2013 The Flutter 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 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}. * *

Passes arguments of callbacks methods from a {@link WebChromeClient} to Dart. */ public class WebChromeClientFlutterApiImpl extends WebChromeClientFlutterApi { private final BinaryMessenger binaryMessenger; 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 WebChromeClientFlutterApiImpl( BinaryMessenger binaryMessenger, InstanceManager instanceManager) { super(binaryMessenger); this.binaryMessenger = binaryMessenger; this.instanceManager = instanceManager; } /** Passes arguments from {@link WebChromeClient#onProgressChanged} to Dart. */ public void onProgressChanged( WebChromeClient webChromeClient, WebView webView, Long progress, Reply callback) { final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView); if (webViewIdentifier == null) { throw new IllegalStateException("Could not find identifier for WebView."); } super.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) { throw new IllegalStateException("Could not find identifier for WebChromeClient."); } return identifier; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java ================================================ // Copyright 2013 The Flutter 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.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; import android.webkit.WebViewClient; import androidx.annotation.NonNull; import androidx.annotation.Nullable; 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}. * *

Handles creating {@link WebChromeClient}s that intercommunicate with a paired Dart object. */ public class WebChromeClientHostApiImpl implements WebChromeClientHostApi { private final InstanceManager instanceManager; private final WebChromeClientCreator webChromeClientCreator; private final WebChromeClientFlutterApiImpl flutterApi; /** * Implementation of {@link WebChromeClient} that passes arguments of callback methods to Dart. */ 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. * * @param flutterApi handles sending messages to Dart */ public WebChromeClientImpl(@NonNull WebChromeClientFlutterApiImpl flutterApi) { this.flutterApi = flutterApi; } @Override 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; } } /** * Implementation of {@link WebChromeClient} that only allows secure urls when opening a new * window. */ public static class SecureWebChromeClient extends WebChromeClient { @Nullable private WebViewClient webViewClient; @Override public boolean onCreateWindow( final WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { return onCreateWindow(view, resultMsg, new WebView(view.getContext())); } /** * Verifies that a url opened by `Window.open` has a secure url. * * @param view the WebView from which the request for a new window originated. * @param resultMsg the message to send when once a new WebView has been created. resultMsg.obj * is a {@link WebView.WebViewTransport} object. This should be used to transport the new * WebView, by calling WebView.WebViewTransport.setWebView(WebView) * @param onCreateWindowWebView the temporary WebView used to verify the url is secure * @return this method should return true if the host application will create a new window, in * which case resultMsg should be sent to its target. Otherwise, this method should return * false. Returning false from this method but also sending resultMsg will result in * undefined behavior */ @VisibleForTesting boolean onCreateWindow( final WebView view, Message resultMsg, @Nullable WebView onCreateWindowWebView) { // WebChromeClient requires a WebViewClient because of a bug fix that makes // calls to WebViewClient.requestLoading/WebViewClient.urlLoading when a new // window is opened. This is to make sure a url opened by `Window.open` has // a secure url. if (webViewClient == null) { return false; } final WebViewClient windowWebViewClient = new WebViewClient() { @RequiresApi(api = Build.VERSION_CODES.N) @Override public boolean shouldOverrideUrlLoading( @NonNull WebView windowWebView, @NonNull WebResourceRequest request) { if (!webViewClient.shouldOverrideUrlLoading(view, request)) { view.loadUrl(request.getUrl().toString()); } return true; } @Override public boolean shouldOverrideUrlLoading(WebView windowWebView, String url) { if (!webViewClient.shouldOverrideUrlLoading(view, url)) { view.loadUrl(url); } return true; } }; if (onCreateWindowWebView == null) { onCreateWindowWebView = new WebView(view.getContext()); } onCreateWindowWebView.setWebViewClient(windowWebViewClient); final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; transport.setWebView(onCreateWindowWebView); resultMsg.sendToTarget(); return true; } /** * Set the {@link WebViewClient} that calls to {@link WebChromeClient#onCreateWindow} are passed * to. * * @param webViewClient the forwarding {@link WebViewClient} */ public void setWebViewClient(@NonNull WebViewClient webViewClient) { this.webViewClient = webViewClient; } } /** Handles creating {@link WebChromeClient}s for a {@link WebChromeClientHostApiImpl}. */ public static class WebChromeClientCreator { /** * Creates a {@link DownloadListenerHostApiImpl.DownloadListenerImpl}. * * @param flutterApi handles sending messages to Dart * @return the created {@link WebChromeClientHostApiImpl.WebChromeClientImpl} */ public WebChromeClientImpl createWebChromeClient(WebChromeClientFlutterApiImpl flutterApi) { return new WebChromeClientImpl(flutterApi); } } /** * Creates a host API that handles creating {@link WebChromeClient}s. * * @param instanceManager maintains instances stored to communicate with Dart objects * @param webChromeClientCreator handles creating {@link WebChromeClient}s * @param flutterApi handles sending messages to Dart */ public WebChromeClientHostApiImpl( InstanceManager instanceManager, WebChromeClientCreator webChromeClientCreator, WebChromeClientFlutterApiImpl flutterApi) { this.instanceManager = instanceManager; this.webChromeClientCreator = webChromeClientCreator; this.flutterApi = flutterApi; } @Override public void create(Long instanceId) { final WebChromeClient webChromeClient = 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); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java ================================================ // Copyright 2013 The Flutter 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.webkit.WebSettings; import android.webkit.WebView; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebSettingsHostApi; /** * Host api implementation for {@link WebSettings}. * *

Handles creating {@link WebSettings}s that intercommunicate with a paired Dart object. */ public class WebSettingsHostApiImpl implements WebSettingsHostApi { private final InstanceManager instanceManager; private final WebSettingsCreator webSettingsCreator; /** Handles creating {@link WebSettings} for a {@link WebSettingsHostApiImpl}. */ public static class WebSettingsCreator { /** * Creates a {@link WebSettings}. * * @param webView the {@link WebView} which the settings affect * @return the created {@link WebSettings} */ public WebSettings createWebSettings(WebView webView) { return webView.getSettings(); } } /** * Creates a host API that handles creating {@link WebSettings} and invoke its methods. * * @param instanceManager maintains instances stored to communicate with Dart objects * @param webSettingsCreator handles creating {@link WebSettings}s */ public WebSettingsHostApiImpl( InstanceManager instanceManager, WebSettingsCreator webSettingsCreator) { this.instanceManager = instanceManager; this.webSettingsCreator = webSettingsCreator; } @Override public void create(Long instanceId, Long webViewInstanceId) { final WebView webView = (WebView) instanceManager.getInstance(webViewInstanceId); instanceManager.addDartCreatedInstance( webSettingsCreator.createWebSettings(webView), instanceId); } @Override public void setDomStorageEnabled(Long instanceId, Boolean flag) { final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId); webSettings.setDomStorageEnabled(flag); } @Override public void setJavaScriptCanOpenWindowsAutomatically(Long instanceId, Boolean flag) { final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId); webSettings.setJavaScriptCanOpenWindowsAutomatically(flag); } @Override public void setSupportMultipleWindows(Long instanceId, Boolean support) { final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId); webSettings.setSupportMultipleWindows(support); } @Override public void setJavaScriptEnabled(Long instanceId, Boolean flag) { final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId); webSettings.setJavaScriptEnabled(flag); } @Override public void setUserAgentString(Long instanceId, String userAgentString) { final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId); webSettings.setUserAgentString(userAgentString); } @Override public void setMediaPlaybackRequiresUserGesture(Long instanceId, Boolean require) { final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId); webSettings.setMediaPlaybackRequiresUserGesture(require); } @Override public void setSupportZoom(Long instanceId, Boolean support) { final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId); webSettings.setSupportZoom(support); } @Override public void setLoadWithOverviewMode(Long instanceId, Boolean overview) { final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId); webSettings.setLoadWithOverviewMode(overview); } @Override public void setUseWideViewPort(Long instanceId, Boolean use) { final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId); webSettings.setUseWideViewPort(use); } @Override public void setDisplayZoomControls(Long instanceId, Boolean enabled) { final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId); webSettings.setDisplayZoomControls(enabled); } @Override public void setBuiltInZoomControls(Long instanceId, Boolean enabled) { final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId); webSettings.setBuiltInZoomControls(enabled); } @Override public void setAllowFileAccess(Long instanceId, Boolean enabled) { final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId); webSettings.setAllowFileAccess(enabled); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebStorageHostApiImpl.java ================================================ // Copyright 2013 The Flutter 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.webkit.WebStorage; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebStorageHostApi; /** * Host api implementation for {@link WebStorage}. * *

Handles creating {@link WebStorage}s that intercommunicate with a paired Dart object. */ public class WebStorageHostApiImpl implements WebStorageHostApi { private final InstanceManager instanceManager; private final WebStorageCreator webStorageCreator; /** Handles creating {@link WebStorage} for a {@link WebStorageHostApiImpl}. */ public static class WebStorageCreator { /** * Creates a {@link WebStorage}. * * @return the created {@link WebStorage}. Defaults to {@link WebStorage#getInstance} */ public WebStorage createWebStorage() { return WebStorage.getInstance(); } } /** * Creates a host API that handles creating {@link WebStorage} and invoke its methods. * * @param instanceManager maintains instances stored to communicate with Dart objects * @param webStorageCreator handles creating {@link WebStorage}s */ public WebStorageHostApiImpl( InstanceManager instanceManager, WebStorageCreator webStorageCreator) { this.instanceManager = instanceManager; this.webStorageCreator = webStorageCreator; } @Override public void create(Long instanceId) { instanceManager.addDartCreatedInstance(webStorageCreator.createWebStorage(), instanceId); } @Override public void deleteAllData(Long instanceId) { final WebStorage webStorage = (WebStorage) instanceManager.getInstance(instanceId); webStorage.deleteAllData(); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java ================================================ // Copyright 2013 The Flutter 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.annotation.SuppressLint; import android.os.Build; import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.annotation.RequiresApi; import androidx.webkit.WebResourceErrorCompat; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewClientFlutterApi; import java.util.HashMap; /** * Flutter Api implementation for {@link WebViewClient}. * *

Passes arguments of callbacks methods from a {@link WebViewClient} to Dart. */ public class WebViewClientFlutterApiImpl extends WebViewClientFlutterApi { private final InstanceManager instanceManager; @RequiresApi(api = Build.VERSION_CODES.M) static GeneratedAndroidWebView.WebResourceErrorData createWebResourceErrorData( WebResourceError error) { return new GeneratedAndroidWebView.WebResourceErrorData.Builder() .setErrorCode((long) error.getErrorCode()) .setDescription(error.getDescription().toString()) .build(); } @SuppressLint("RequiresFeature") static GeneratedAndroidWebView.WebResourceErrorData createWebResourceErrorData( WebResourceErrorCompat error) { return new GeneratedAndroidWebView.WebResourceErrorData.Builder() .setErrorCode((long) error.getErrorCode()) .setDescription(error.getDescription().toString()) .build(); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) static GeneratedAndroidWebView.WebResourceRequestData createWebResourceRequestData( WebResourceRequest request) { final GeneratedAndroidWebView.WebResourceRequestData.Builder requestData = new GeneratedAndroidWebView.WebResourceRequestData.Builder() .setUrl(request.getUrl().toString()) .setIsForMainFrame(request.isForMainFrame()) .setHasGesture(request.hasGesture()) .setMethod(request.getMethod()) .setRequestHeaders( request.getRequestHeaders() != null ? request.getRequestHeaders() : new HashMap<>()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { requestData.setIsRedirect(request.isRedirect()); } return requestData.build(); } /** * 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 WebViewClientFlutterApiImpl( BinaryMessenger binaryMessenger, InstanceManager instanceManager) { super(binaryMessenger); this.instanceManager = instanceManager; } /** Passes arguments from {@link WebViewClient#onPageStarted} to Dart. */ public void onPageStarted( WebViewClient webViewClient, WebView webView, String urlArg, Reply callback) { final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView); if (webViewIdentifier == null) { throw new IllegalStateException("Could not find identifier for WebView."); } onPageStarted(getIdentifierForClient(webViewClient), webViewIdentifier, urlArg, callback); } /** Passes arguments from {@link WebViewClient#onPageFinished} to Dart. */ public void onPageFinished( WebViewClient webViewClient, WebView webView, String urlArg, Reply callback) { final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView); if (webViewIdentifier == null) { throw new IllegalStateException("Could not find identifier for WebView."); } onPageFinished(getIdentifierForClient(webViewClient), webViewIdentifier, urlArg, callback); } /** * Passes arguments from {@link WebViewClient#onReceivedError(WebView, WebResourceRequest, * WebResourceError)} to Dart. */ @RequiresApi(api = Build.VERSION_CODES.M) public void onReceivedRequestError( WebViewClient webViewClient, WebView webView, WebResourceRequest request, WebResourceError error, Reply callback) { final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView); if (webViewIdentifier == null) { throw new IllegalStateException("Could not find identifier for WebView."); } onReceivedRequestError( getIdentifierForClient(webViewClient), webViewIdentifier, createWebResourceRequestData(request), createWebResourceErrorData(error), callback); } /** * Passes arguments from {@link androidx.webkit.WebViewClientCompat#onReceivedError(WebView, * WebResourceRequest, WebResourceError)} to Dart. */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public void onReceivedRequestError( WebViewClient webViewClient, WebView webView, WebResourceRequest request, WebResourceErrorCompat error, Reply callback) { final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView); if (webViewIdentifier == null) { throw new IllegalStateException("Could not find identifier for WebView."); } onReceivedRequestError( getIdentifierForClient(webViewClient), webViewIdentifier, createWebResourceRequestData(request), createWebResourceErrorData(error), callback); } /** * Passes arguments from {@link WebViewClient#onReceivedError(WebView, int, String, String)} to * Dart. */ public void onReceivedError( WebViewClient webViewClient, WebView webView, Long errorCodeArg, String descriptionArg, String failingUrlArg, Reply callback) { final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView); if (webViewIdentifier == null) { throw new IllegalStateException("Could not find identifier for WebView."); } onReceivedError( getIdentifierForClient(webViewClient), webViewIdentifier, errorCodeArg, descriptionArg, failingUrlArg, callback); } /** * Passes arguments from {@link WebViewClient#shouldOverrideUrlLoading(WebView, * WebResourceRequest)} to Dart. */ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public void requestLoading( WebViewClient webViewClient, WebView webView, WebResourceRequest request, Reply callback) { final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView); if (webViewIdentifier == null) { throw new IllegalStateException("Could not find identifier for WebView."); } requestLoading( getIdentifierForClient(webViewClient), webViewIdentifier, createWebResourceRequestData(request), callback); } /** * Passes arguments from {@link WebViewClient#shouldOverrideUrlLoading(WebView, String)} to Dart. */ public void urlLoading( WebViewClient webViewClient, WebView webView, String urlArg, Reply callback) { final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView); if (webViewIdentifier == null) { throw new IllegalStateException("Could not find identifier for WebView."); } urlLoading(getIdentifierForClient(webViewClient), webViewIdentifier, urlArg, callback); } private long getIdentifierForClient(WebViewClient webViewClient) { final Long identifier = instanceManager.getIdentifierForStrongReference(webViewClient); if (identifier == null) { throw new IllegalStateException("Could not find identifier for WebViewClient."); } return identifier; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java ================================================ // Copyright 2013 The Flutter 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.annotation.SuppressLint; import android.annotation.TargetApi; import android.graphics.Bitmap; import android.os.Build; import android.view.KeyEvent; import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.webkit.WebResourceErrorCompat; import androidx.webkit.WebViewClientCompat; import java.util.Objects; /** * Host api implementation for {@link WebViewClient}. * *

Handles creating {@link WebViewClient}s that intercommunicate with a paired Dart object. */ public class WebViewClientHostApiImpl implements GeneratedAndroidWebView.WebViewClientHostApi { private final InstanceManager instanceManager; private final WebViewClientCreator webViewClientCreator; private final WebViewClientFlutterApiImpl flutterApi; /** Implementation of {@link WebViewClient} that passes arguments of callback methods to Dart. */ @RequiresApi(Build.VERSION_CODES.N) public static class WebViewClientImpl extends WebViewClient { private final WebViewClientFlutterApiImpl flutterApi; private boolean returnValueForShouldOverrideUrlLoading = false; /** * Creates a {@link WebViewClient} that passes arguments of callbacks methods to Dart. * * @param flutterApi handles sending messages to Dart */ public WebViewClientImpl(@NonNull WebViewClientFlutterApiImpl flutterApi) { this.flutterApi = flutterApi; } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { flutterApi.onPageStarted(this, view, url, reply -> {}); } @Override public void onPageFinished(WebView view, String url) { flutterApi.onPageFinished(this, view, url, reply -> {}); } @Override public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { flutterApi.onReceivedRequestError(this, view, request, error, reply -> {}); } @Override public void onReceivedError( WebView view, int errorCode, String description, String failingUrl) { flutterApi.onReceivedError( this, view, (long) errorCode, description, failingUrl, reply -> {}); } @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { flutterApi.requestLoading(this, view, request, reply -> {}); return returnValueForShouldOverrideUrlLoading; } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { flutterApi.urlLoading(this, view, url, reply -> {}); return returnValueForShouldOverrideUrlLoading; } @Override public void onUnhandledKeyEvent(WebView view, KeyEvent event) { // Deliberately empty. Occasionally the webview will mark events as having failed to be // handled even though they were handled. We don't want to propagate those as they're not // truly lost. } /** Sets return value for {@link #shouldOverrideUrlLoading}. */ public void setReturnValueForShouldOverrideUrlLoading(boolean value) { returnValueForShouldOverrideUrlLoading = value; } } /** * Implementation of {@link WebViewClientCompat} that passes arguments of callback methods to * Dart. */ public static class WebViewClientCompatImpl extends WebViewClientCompat { private final WebViewClientFlutterApiImpl flutterApi; private boolean returnValueForShouldOverrideUrlLoading = false; public WebViewClientCompatImpl(@NonNull WebViewClientFlutterApiImpl flutterApi) { this.flutterApi = flutterApi; } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { flutterApi.onPageStarted(this, view, url, reply -> {}); } @Override public void onPageFinished(WebView view, String url) { flutterApi.onPageFinished(this, view, url, reply -> {}); } // This method is only called when the WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR feature is // enabled. The deprecated method is called when a device doesn't support this. @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @SuppressLint("RequiresFeature") @Override public void onReceivedError( @NonNull WebView view, @NonNull WebResourceRequest request, @NonNull WebResourceErrorCompat error) { flutterApi.onReceivedRequestError(this, view, request, error, reply -> {}); } @Override public void onReceivedError( WebView view, int errorCode, String description, String failingUrl) { flutterApi.onReceivedError( this, view, (long) errorCode, description, failingUrl, reply -> {}); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public boolean shouldOverrideUrlLoading( @NonNull WebView view, @NonNull WebResourceRequest request) { flutterApi.requestLoading(this, view, request, reply -> {}); return returnValueForShouldOverrideUrlLoading; } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { flutterApi.urlLoading(this, view, url, reply -> {}); return returnValueForShouldOverrideUrlLoading; } @Override public void onUnhandledKeyEvent(WebView view, KeyEvent event) { // Deliberately empty. Occasionally the webview will mark events as having failed to be // handled even though they were handled. We don't want to propagate those as they're not // truly lost. } /** Sets return value for {@link #shouldOverrideUrlLoading}. */ public void setReturnValueForShouldOverrideUrlLoading(boolean value) { returnValueForShouldOverrideUrlLoading = value; } } /** Handles creating {@link WebViewClient}s for a {@link WebViewClientHostApiImpl}. */ public static class WebViewClientCreator { /** * Creates a {@link WebViewClient}. * * @param flutterApi handles sending messages to Dart * @return the created {@link WebViewClient} */ public WebViewClient createWebViewClient(WebViewClientFlutterApiImpl flutterApi) { // WebViewClientCompat is used to get // shouldOverrideUrlLoading(WebView view, WebResourceRequest request) // invoked by the webview on older Android devices, without it pages that use iframes will // be broken when a navigationDelegate is set on Android version earlier than N. // // However, this if statement attempts to avoid using WebViewClientCompat on versions >= N due // to bug https://bugs.chromium.org/p/chromium/issues/detail?id=925887. Also, see // https://github.com/flutter/flutter/issues/29446. if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return new WebViewClientImpl(flutterApi); } else { return new WebViewClientCompatImpl(flutterApi); } } } /** * Creates a host API that handles creating {@link WebViewClient}s. * * @param instanceManager maintains instances stored to communicate with Dart objects * @param webViewClientCreator handles creating {@link WebViewClient}s * @param flutterApi handles sending messages to Dart */ public WebViewClientHostApiImpl( InstanceManager instanceManager, WebViewClientCreator webViewClientCreator, WebViewClientFlutterApiImpl flutterApi) { this.instanceManager = instanceManager; this.webViewClientCreator = webViewClientCreator; this.flutterApi = flutterApi; } @Override public void create(@NonNull Long instanceId) { final WebViewClient webViewClient = webViewClientCreator.createWebViewClient(flutterApi); instanceManager.addDartCreatedInstance(webViewClient, instanceId); } @Override public void setSynchronousReturnValueForShouldOverrideUrlLoading( @NonNull Long instanceId, @NonNull Boolean value) { final WebViewClient webViewClient = Objects.requireNonNull(instanceManager.getInstance(instanceId)); if (webViewClient instanceof WebViewClientCompatImpl) { ((WebViewClientCompatImpl) webViewClient).setReturnValueForShouldOverrideUrlLoading(value); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && webViewClient instanceof WebViewClientImpl) { ((WebViewClientImpl) webViewClient).setReturnValueForShouldOverrideUrlLoading(value); } else { throw new IllegalStateException( "This WebViewClient doesn't support setting the returnValueForShouldOverrideUrlLoading."); } } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApi.java ================================================ // Copyright 2013 The Flutter 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.webkit.WebView; import androidx.annotation.Nullable; import io.flutter.embedding.engine.FlutterEngine; /** * App and package facing native API provided by the `webview_flutter_android` plugin. * *

This class follows the convention of breaking changes of the Dart API, which means that any * changes to the class that are not backwards compatible will only be made with a major version * change of the plugin. * *

Native code other than this external API does not follow breaking change conventions, so app * or plugin clients should not use any other native APIs. */ @SuppressWarnings("unused") public interface WebViewFlutterAndroidExternalApi { /** * Retrieves the {@link WebView} that is associated with `identifier`. * *

See the Dart method `AndroidWebViewController.webViewIdentifier` to get the identifier of an * underlying `WebView`. * * @param engine the execution environment the {@link WebViewFlutterPlugin} should belong to. If * the engine doesn't contain an attached instance of {@link WebViewFlutterPlugin}, this * method returns null. * @param identifier the associated identifier of the `WebView`. * @return the `WebView` associated with `identifier` or null if a `WebView` instance associated * with `identifier` could not be found. */ @Nullable static WebView getWebView(FlutterEngine engine, long identifier) { final WebViewFlutterPlugin webViewPlugin = (WebViewFlutterPlugin) engine.getPlugins().get(WebViewFlutterPlugin.class); if (webViewPlugin != null && webViewPlugin.getInstanceManager() != null) { final Object instance = webViewPlugin.getInstanceManager().getInstance(identifier); if (instance instanceof WebView) { return (WebView) instance; } } return null; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java ================================================ // Copyright 2013 The Flutter 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.content.Context; import android.os.Handler; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; 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.plugin.platform.PlatformViewRegistry; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.CookieManagerHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.DownloadListenerHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.FlutterAssetManagerHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.JavaObjectHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.JavaScriptChannelHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebChromeClientHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebSettingsHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebStorageHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewClientHostApi; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewHostApi; /** * Java platform implementation of the webview_flutter plugin. * *

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

Call {@link #registerWith} to use the stable {@code io.flutter.plugin.common} package instead. */ public class WebViewFlutterPlugin implements FlutterPlugin, ActivityAware { @Nullable private InstanceManager instanceManager; private FlutterPluginBinding pluginBinding; private WebViewHostApiImpl webViewHostApi; private JavaScriptChannelHostApiImpl javaScriptChannelHostApi; /** * Add an instance of this to {@link io.flutter.embedding.engine.plugins.PluginRegistry} to * register it. * *

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

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

Calling this automatically initializes the plugin. However plugins initialized this way * won't react to changes in activity or context, unlike {@link WebViewFlutterPlugin}. */ @SuppressWarnings({"unused", "deprecation"}) public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { new WebViewFlutterPlugin() .setUp( registrar.messenger(), registrar.platformViewRegistry(), registrar.activity(), registrar.view(), new FlutterAssetManager.RegistrarFlutterAssetManager( registrar.context().getAssets(), registrar)); } private void setUp( BinaryMessenger binaryMessenger, PlatformViewRegistry viewRegistry, Context context, View containerView, FlutterAssetManager flutterAssetManager) { instanceManager = InstanceManager.open( identifier -> new GeneratedAndroidWebView.JavaObjectFlutterApi(binaryMessenger) .dispose(identifier, reply -> {})); viewRegistry.registerViewFactory( "plugins.flutter.io/webview", new FlutterWebViewFactory(instanceManager)); webViewHostApi = new WebViewHostApiImpl( instanceManager, binaryMessenger, new WebViewHostApiImpl.WebViewProxy(), context, containerView); javaScriptChannelHostApi = new JavaScriptChannelHostApiImpl( instanceManager, new JavaScriptChannelHostApiImpl.JavaScriptChannelCreator(), new JavaScriptChannelFlutterApiImpl(binaryMessenger, instanceManager), new Handler(context.getMainLooper())); JavaObjectHostApi.setup(binaryMessenger, new JavaObjectHostApiImpl(instanceManager)); WebViewHostApi.setup(binaryMessenger, webViewHostApi); JavaScriptChannelHostApi.setup(binaryMessenger, javaScriptChannelHostApi); WebViewClientHostApi.setup( binaryMessenger, new WebViewClientHostApiImpl( instanceManager, new WebViewClientHostApiImpl.WebViewClientCreator(), new WebViewClientFlutterApiImpl(binaryMessenger, instanceManager))); WebChromeClientHostApi.setup( binaryMessenger, new WebChromeClientHostApiImpl( instanceManager, new WebChromeClientHostApiImpl.WebChromeClientCreator(), new WebChromeClientFlutterApiImpl(binaryMessenger, instanceManager))); DownloadListenerHostApi.setup( binaryMessenger, new DownloadListenerHostApiImpl( instanceManager, new DownloadListenerHostApiImpl.DownloadListenerCreator(), new DownloadListenerFlutterApiImpl(binaryMessenger, instanceManager))); WebSettingsHostApi.setup( binaryMessenger, new WebSettingsHostApiImpl( instanceManager, new WebSettingsHostApiImpl.WebSettingsCreator())); FlutterAssetManagerHostApi.setup( binaryMessenger, new FlutterAssetManagerHostApiImpl(flutterAssetManager)); CookieManagerHostApi.setup(binaryMessenger, new CookieManagerHostApiImpl()); WebStorageHostApi.setup( binaryMessenger, new WebStorageHostApiImpl(instanceManager, new WebStorageHostApiImpl.WebStorageCreator())); } @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { pluginBinding = binding; setUp( binding.getBinaryMessenger(), binding.getPlatformViewRegistry(), binding.getApplicationContext(), null, new FlutterAssetManager.PluginBindingFlutterAssetManager( binding.getApplicationContext().getAssets(), binding.getFlutterAssets())); } @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { if (instanceManager != null) { instanceManager.close(); instanceManager = null; } } @Override public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) { updateContext(activityPluginBinding.getActivity()); } @Override public void onDetachedFromActivityForConfigChanges() { updateContext(pluginBinding.getApplicationContext()); } @Override public void onReattachedToActivityForConfigChanges( @NonNull ActivityPluginBinding activityPluginBinding) { updateContext(activityPluginBinding.getActivity()); } @Override public void onDetachedFromActivity() { updateContext(pluginBinding.getApplicationContext()); } private void updateContext(Context context) { webViewHostApi.setContext(context); javaScriptChannelHostApi.setPlatformThreadHandler(new Handler(context.getMainLooper())); } /** Maintains instances used to communicate with the corresponding objects in Dart. */ @Nullable public InstanceManager getInstanceManager() { return instanceManager; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java ================================================ // Copyright 2013 The Flutter 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.annotation.SuppressLint; import android.content.Context; import android.hardware.display.DisplayManager; import android.view.View; import android.webkit.DownloadListener; import android.webkit.WebChromeClient; import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.platform.PlatformView; import io.flutter.plugins.webviewflutter.GeneratedAndroidWebView.WebViewHostApi; import java.util.Map; import java.util.Objects; /** * Host api implementation for {@link WebView}. * *

Handles creating {@link WebView}s that intercommunicate with a paired Dart object. */ public class WebViewHostApiImpl implements WebViewHostApi { private final InstanceManager instanceManager; private final WebViewProxy webViewProxy; // Only used with WebView using virtual displays. @Nullable private final View containerView; private final BinaryMessenger binaryMessenger; private Context context; /** Handles creating and calling static methods for {@link WebView}s. */ public static class WebViewProxy { /** * Creates a {@link WebViewPlatformView}. * * @param context an Activity Context to access application assets * @param binaryMessenger used to communicate with Dart over asynchronous messages * @param instanceManager mangages instances used to communicate with the corresponding objects * in Dart * @return the created {@link WebViewPlatformView} */ public WebViewPlatformView createWebView( Context context, BinaryMessenger binaryMessenger, InstanceManager instanceManager) { return new WebViewPlatformView(context, binaryMessenger, instanceManager); } /** * Creates a {@link InputAwareWebViewPlatformView}. * * @param context an Activity Context to access application assets * @param containerView parent View of the WebView * @return the created {@link InputAwareWebViewPlatformView} */ public InputAwareWebViewPlatformView createInputAwareWebView( Context context, BinaryMessenger binaryMessenger, InstanceManager instanceManager, @Nullable View containerView) { return new InputAwareWebViewPlatformView( context, binaryMessenger, instanceManager, containerView); } /** * Forwards call to {@link WebView#setWebContentsDebuggingEnabled}. * * @param enabled whether debugging should be enabled */ public void setWebContentsDebuggingEnabled(boolean enabled) { WebView.setWebContentsDebuggingEnabled(enabled); } } /** Implementation of {@link WebView} that can be used as a Flutter {@link PlatformView}s. */ public static class WebViewPlatformView extends WebView implements PlatformView { private WebViewClient currentWebViewClient; private WebChromeClientHostApiImpl.SecureWebChromeClient currentWebChromeClient; /** * Creates a {@link WebViewPlatformView}. * * @param context an Activity Context to access application assets. This value cannot be null. */ public WebViewPlatformView( Context context, BinaryMessenger binaryMessenger, InstanceManager instanceManager) { super(context); currentWebViewClient = new WebViewClient(); currentWebChromeClient = new WebChromeClientHostApiImpl.SecureWebChromeClient(); setWebViewClient(currentWebViewClient); setWebChromeClient(currentWebChromeClient); } @Override public View getView() { return this; } @Override public void dispose() {} @Override public void setWebViewClient(WebViewClient webViewClient) { super.setWebViewClient(webViewClient); currentWebViewClient = webViewClient; currentWebChromeClient.setWebViewClient(webViewClient); } @Override public void setWebChromeClient(WebChromeClient client) { super.setWebChromeClient(client); if (!(client instanceof WebChromeClientHostApiImpl.SecureWebChromeClient)) { throw new AssertionError("Client must be a SecureWebChromeClient."); } currentWebChromeClient = (WebChromeClientHostApiImpl.SecureWebChromeClient) client; currentWebChromeClient.setWebViewClient(currentWebViewClient); } // When running unit tests, the parent `WebView` class is replaced by a stub that returns null // for every method. This is overridden so that this returns the current WebChromeClient during // unit tests. This should only remain overridden as long as `setWebChromeClient` is overridden. @Nullable @Override public WebChromeClient getWebChromeClient() { return currentWebChromeClient; } } /** * Implementation of {@link InputAwareWebView} that can be used as a Flutter {@link * PlatformView}s. */ @SuppressLint("ViewConstructor") public static class InputAwareWebViewPlatformView extends InputAwareWebView implements PlatformView { private WebViewClient currentWebViewClient; private WebChromeClientHostApiImpl.SecureWebChromeClient currentWebChromeClient; /** * Creates a {@link InputAwareWebViewPlatformView}. * * @param context an Activity Context to access application assets. This value cannot be null. */ public InputAwareWebViewPlatformView( Context context, BinaryMessenger binaryMessenger, InstanceManager instanceManager, View containerView) { super(context, containerView); currentWebViewClient = new WebViewClient(); currentWebChromeClient = new WebChromeClientHostApiImpl.SecureWebChromeClient(); setWebViewClient(currentWebViewClient); setWebChromeClient(currentWebChromeClient); } @Override public View getView() { return this; } @Override public void onFlutterViewAttached(@NonNull View flutterView) { setContainerView(flutterView); } @Override public void onFlutterViewDetached() { setContainerView(null); } @Override public void dispose() { super.dispose(); destroy(); } @Override public void onInputConnectionLocked() { lockInputConnection(); } @Override public void onInputConnectionUnlocked() { unlockInputConnection(); } @Override public void setWebViewClient(WebViewClient webViewClient) { super.setWebViewClient(webViewClient); currentWebViewClient = webViewClient; currentWebChromeClient.setWebViewClient(webViewClient); } @Override public void setWebChromeClient(WebChromeClient client) { super.setWebChromeClient(client); if (!(client instanceof WebChromeClientHostApiImpl.SecureWebChromeClient)) { throw new AssertionError("Client must be a SecureWebChromeClient."); } currentWebChromeClient = (WebChromeClientHostApiImpl.SecureWebChromeClient) client; currentWebChromeClient.setWebViewClient(currentWebViewClient); } } /** * Creates a host API that handles creating {@link WebView}s and invoking its methods. * * @param instanceManager maintains instances stored to communicate with Dart objects * @param binaryMessenger used to communicate with Dart over asynchronous messages * @param webViewProxy handles creating {@link WebView}s and calling its static methods * @param context an Activity Context to access application assets. This value cannot be null. * @param containerView parent of the webView */ public WebViewHostApiImpl( InstanceManager instanceManager, BinaryMessenger binaryMessenger, WebViewProxy webViewProxy, Context context, @Nullable View containerView) { this.instanceManager = instanceManager; this.binaryMessenger = binaryMessenger; this.webViewProxy = webViewProxy; this.context = context; this.containerView = containerView; } /** * Sets the context to construct {@link WebView}s. * * @param context the new context. */ public void setContext(Context context) { this.context = context; } @Override public void create(Long instanceId, Boolean useHybridComposition) { DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy(); DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); displayListenerProxy.onPreWebViewInitialization(displayManager); final WebView webView = useHybridComposition ? webViewProxy.createWebView(context, binaryMessenger, instanceManager) : webViewProxy.createInputAwareWebView( context, binaryMessenger, instanceManager, containerView); displayListenerProxy.onPostWebViewInitialization(displayManager); instanceManager.addDartCreatedInstance(webView, instanceId); } @Override public void loadData(Long instanceId, String data, String mimeType, String encoding) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); webView.loadData(data, mimeType, encoding); } @Override public void loadDataWithBaseUrl( Long instanceId, String baseUrl, String data, String mimeType, String encoding, String historyUrl) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); webView.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); } @Override public void loadUrl(Long instanceId, String url, Map headers) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); webView.loadUrl(url, headers); } @Override public void postUrl(Long instanceId, String url, byte[] data) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); webView.postUrl(url, data); } @Override public String getUrl(Long instanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); return webView.getUrl(); } @Override public Boolean canGoBack(Long instanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); return webView.canGoBack(); } @Override public Boolean canGoForward(Long instanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); return webView.canGoForward(); } @Override public void goBack(Long instanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); webView.goBack(); } @Override public void goForward(Long instanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); webView.goForward(); } @Override public void reload(Long instanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); webView.reload(); } @Override public void clearCache(Long instanceId, Boolean includeDiskFiles) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); webView.clearCache(includeDiskFiles); } @Override public void evaluateJavascript( Long instanceId, String javascriptString, GeneratedAndroidWebView.Result result) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); webView.evaluateJavascript(javascriptString, result::success); } @Override public String getTitle(Long instanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); return webView.getTitle(); } @Override public void scrollTo(Long instanceId, Long x, Long y) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); webView.scrollTo(x.intValue(), y.intValue()); } @Override public void scrollBy(Long instanceId, Long x, Long y) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); webView.scrollBy(x.intValue(), y.intValue()); } @Override public Long getScrollX(Long instanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); return (long) webView.getScrollX(); } @Override public Long getScrollY(Long instanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); return (long) webView.getScrollY(); } @NonNull @Override public GeneratedAndroidWebView.WebViewPoint getScrollPosition(@NonNull Long instanceId) { final WebView webView = Objects.requireNonNull(instanceManager.getInstance(instanceId)); return new GeneratedAndroidWebView.WebViewPoint.Builder() .setX((long) webView.getScrollX()) .setY((long) webView.getScrollY()) .build(); } @Override public void setWebContentsDebuggingEnabled(Boolean enabled) { webViewProxy.setWebContentsDebuggingEnabled(enabled); } @Override public void setWebViewClient(Long instanceId, Long webViewClientInstanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); webView.setWebViewClient((WebViewClient) instanceManager.getInstance(webViewClientInstanceId)); } @Override public void addJavaScriptChannel(Long instanceId, Long javaScriptChannelInstanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); final JavaScriptChannel javaScriptChannel = (JavaScriptChannel) instanceManager.getInstance(javaScriptChannelInstanceId); webView.addJavascriptInterface(javaScriptChannel, javaScriptChannel.javaScriptChannelName); } @Override public void removeJavaScriptChannel(Long instanceId, Long javaScriptChannelInstanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); final JavaScriptChannel javaScriptChannel = (JavaScriptChannel) instanceManager.getInstance(javaScriptChannelInstanceId); webView.removeJavascriptInterface(javaScriptChannel.javaScriptChannelName); } @Override public void setDownloadListener(Long instanceId, Long listenerInstanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); webView.setDownloadListener((DownloadListener) instanceManager.getInstance(listenerInstanceId)); } @Override public void setWebChromeClient(Long instanceId, Long clientInstanceId) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); webView.setWebChromeClient((WebChromeClient) instanceManager.getInstance(clientInstanceId)); } @Override public void setBackgroundColor(Long instanceId, Long color) { final WebView webView = (WebView) instanceManager.getInstance(instanceId); webView.setBackgroundColor(color.intValue()); } /** Maintains instances used to communicate with the corresponding WebView Dart object. */ public InstanceManager getInstanceManager() { return instanceManager; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/test/java/android/util/LongSparseArray.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package android.util; import java.util.HashMap; // Creates an implementation of LongSparseArray that can be used with unittests and the JVM. // Typically android.util.LongSparseArray does nothing when not used with an Android environment. public class LongSparseArray { private final HashMap mHashMap; public LongSparseArray() { mHashMap = new HashMap<>(); } public void append(long key, E value) { mHashMap.put(key, value); } public E get(long key) { return mHashMap.get(key); } public void remove(long key) { mHashMap.remove(key); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/CookieManagerHostApiImplTest.java ================================================ // Copyright 2013 The Flutter 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.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.os.Build; import android.webkit.CookieManager; import android.webkit.ValueCallback; import io.flutter.plugins.webviewflutter.utils.TestUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; public class CookieManagerHostApiImplTest { private CookieManager cookieManager; private MockedStatic staticMockCookieManager; @Before public void setup() { staticMockCookieManager = mockStatic(CookieManager.class); cookieManager = mock(CookieManager.class); when(CookieManager.getInstance()).thenReturn(cookieManager); when(cookieManager.hasCookies()).thenReturn(true); doAnswer( answer -> { ((ValueCallback) answer.getArgument(0)).onReceiveValue(true); return null; }) .when(cookieManager) .removeAllCookies(any()); } @After public void tearDown() { staticMockCookieManager.close(); } @Test public void setCookieShouldCallSetCookie() { // Setup CookieManagerHostApiImpl impl = new CookieManagerHostApiImpl(); // Run impl.setCookie("flutter.dev", "foo=bar; path=/"); // Verify verify(cookieManager).setCookie("flutter.dev", "foo=bar; path=/"); } @Test public void clearCookiesShouldCallRemoveAllCookiesOnAndroidLAbove() { // Setup TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.LOLLIPOP); GeneratedAndroidWebView.Result result = mock(GeneratedAndroidWebView.Result.class); CookieManagerHostApiImpl impl = new CookieManagerHostApiImpl(); // Run impl.clearCookies(result); // Verify verify(cookieManager).removeAllCookies(any()); verify(result).success(true); } @Test public void clearCookiesShouldCallRemoveAllCookieBelowAndroidL() { // Setup TestUtils.setFinalStatic(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.KITKAT_WATCH); GeneratedAndroidWebView.Result result = mock(GeneratedAndroidWebView.Result.class); CookieManagerHostApiImpl impl = new CookieManagerHostApiImpl(); // Run impl.clearCookies(result); // Verify verify(cookieManager).removeAllCookie(); verify(result).success(true); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/DownloadListenerTest.java ================================================ // Copyright 2013 The Flutter 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.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import io.flutter.plugins.webviewflutter.DownloadListenerHostApiImpl.DownloadListenerCreator; import io.flutter.plugins.webviewflutter.DownloadListenerHostApiImpl.DownloadListenerImpl; 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 DownloadListenerTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock public DownloadListenerFlutterApiImpl mockFlutterApi; InstanceManager instanceManager; DownloadListenerHostApiImpl hostApiImpl; DownloadListenerImpl downloadListener; @Before public void setUp() { instanceManager = InstanceManager.open(identifier -> {}); final DownloadListenerCreator downloadListenerCreator = new DownloadListenerCreator() { @Override public DownloadListenerImpl createDownloadListener( DownloadListenerFlutterApiImpl flutterApi) { downloadListener = super.createDownloadListener(flutterApi); return downloadListener; } }; hostApiImpl = new DownloadListenerHostApiImpl(instanceManager, downloadListenerCreator, mockFlutterApi); hostApiImpl.create(0L); } @After public void tearDown() { instanceManager.close(); } @Test public void postMessage() { downloadListener.onDownloadStart( "https://www.google.com", "userAgent", "contentDisposition", "mimetype", 54); verify(mockFlutterApi) .onDownloadStart( eq(downloadListener), eq("https://www.google.com"), eq("userAgent"), eq("contentDisposition"), eq("mimetype"), eq(54L), any()); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FileChooserParamsTest.java ================================================ // Copyright 2013 The Flutter 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); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FlutterAssetManagerHostApiImplTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.webviewflutter; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.IOException; import java.util.List; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; public class FlutterAssetManagerHostApiImplTest { @Mock FlutterAssetManager mockFlutterAssetManager; FlutterAssetManagerHostApiImpl testFlutterAssetManagerHostApiImpl; @Before public void setUp() { mockFlutterAssetManager = mock(FlutterAssetManager.class); testFlutterAssetManagerHostApiImpl = new FlutterAssetManagerHostApiImpl(mockFlutterAssetManager); } @Test public void list() { try { when(mockFlutterAssetManager.list("test/path")) .thenReturn(new String[] {"index.html", "styles.css"}); List actualFilePaths = testFlutterAssetManagerHostApiImpl.list("test/path"); verify(mockFlutterAssetManager).list("test/path"); assertArrayEquals(new String[] {"index.html", "styles.css"}, actualFilePaths.toArray()); } catch (IOException ex) { fail(); } } @Test public void list_returns_empty_list_when_no_results() { try { when(mockFlutterAssetManager.list("test/path")).thenReturn(null); List actualFilePaths = testFlutterAssetManagerHostApiImpl.list("test/path"); verify(mockFlutterAssetManager).list("test/path"); assertArrayEquals(new String[] {}, actualFilePaths.toArray()); } catch (IOException ex) { fail(); } } @Test(expected = RuntimeException.class) public void list_should_convert_io_exception_to_runtime_exception() { try { when(mockFlutterAssetManager.list("test/path")).thenThrow(new IOException()); testFlutterAssetManagerHostApiImpl.list("test/path"); } catch (IOException ex) { fail(); } } @Test public void getAssetFilePathByName() { when(mockFlutterAssetManager.getAssetFilePathByName("index.html")) .thenReturn("flutter_assets/index.html"); String filePath = testFlutterAssetManagerHostApiImpl.getAssetFilePathByName("index.html"); verify(mockFlutterAssetManager).getAssetFilePathByName("index.html"); assertEquals("flutter_assets/index.html", filePath); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/InputAwareWebViewTest.java ================================================ // Copyright 2013 The Flutter 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.assertNotNull; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.content.Context; import android.view.View; import org.junit.Test; public class InputAwareWebViewTest { static class TestView extends View { Runnable postAction; public TestView(Context context) { super(context); } @Override public boolean post(Runnable action) { postAction = action; return true; } } @Test public void runnableChecksContainerViewIsNull() { final Context mockContext = mock(Context.class); final TestView containerView = new TestView(mockContext); final InputAwareWebView inputAwareWebView = new InputAwareWebView(mockContext, containerView); final View mockProxyAdapterView = mock(View.class); inputAwareWebView.setInputConnectionTarget(mockProxyAdapterView); inputAwareWebView.setContainerView(null); assertNotNull(containerView.postAction); containerView.postAction.run(); verify(mockProxyAdapterView, never()).onWindowFocusChanged(anyBoolean()); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/InstanceManagerTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.webviewflutter; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import org.junit.Test; public class InstanceManagerTest { @Test public void addDartCreatedInstance() { final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); final Object object = new Object(); instanceManager.addDartCreatedInstance(object, 0); assertEquals(object, instanceManager.getInstance(0)); assertEquals((Long) 0L, instanceManager.getIdentifierForStrongReference(object)); assertTrue(instanceManager.containsInstance(object)); instanceManager.close(); } @Test public void addHostCreatedInstance() { final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); final Object object = new Object(); long identifier = instanceManager.addHostCreatedInstance(object); assertNotNull(instanceManager.getInstance(identifier)); assertEquals(object, instanceManager.getInstance(identifier)); assertTrue(instanceManager.containsInstance(object)); instanceManager.close(); } @Test public void remove() { final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); Object object = new Object(); instanceManager.addDartCreatedInstance(object, 0); assertEquals(object, instanceManager.remove(0)); // To allow for object to be garbage collected. //noinspection UnusedAssignment object = null; Runtime.getRuntime().gc(); assertNull(instanceManager.getInstance(0)); 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)); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaObjectHostApiTest.java ================================================ // Copyright 2013 The Flutter 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.assertNull; import org.junit.Test; public class JavaObjectHostApiTest { @Test public void dispose() { final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); final JavaObjectHostApiImpl hostApi = new JavaObjectHostApiImpl(instanceManager); Object object = new Object(); instanceManager.addDartCreatedInstance(object, 0); // To free object for garbage collection. //noinspection UnusedAssignment object = null; hostApi.dispose(0L); Runtime.getRuntime().gc(); assertNull(instanceManager.getInstance(0)); instanceManager.close(); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaScriptChannelTest.java ================================================ // Copyright 2013 The Flutter 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.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import android.os.Handler; import io.flutter.plugins.webviewflutter.JavaScriptChannelHostApiImpl.JavaScriptChannelCreator; 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 JavaScriptChannelTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock public JavaScriptChannelFlutterApiImpl mockFlutterApi; InstanceManager instanceManager; JavaScriptChannelHostApiImpl hostApiImpl; JavaScriptChannel javaScriptChannel; @Before public void setUp() { instanceManager = InstanceManager.open(identifier -> {}); final JavaScriptChannelCreator javaScriptChannelCreator = new JavaScriptChannelCreator() { @Override public JavaScriptChannel createJavaScriptChannel( JavaScriptChannelFlutterApiImpl javaScriptChannelFlutterApi, String channelName, Handler platformThreadHandler) { javaScriptChannel = super.createJavaScriptChannel( javaScriptChannelFlutterApi, channelName, platformThreadHandler); return javaScriptChannel; } }; hostApiImpl = new JavaScriptChannelHostApiImpl( instanceManager, javaScriptChannelCreator, mockFlutterApi, new Handler()); hostApiImpl.create(0L, "aChannelName"); } @After public void tearDown() { instanceManager.close(); } @Test public void postMessage() { javaScriptChannel.postMessage("A message post."); verify(mockFlutterApi).postMessage(eq(javaScriptChannel), eq("A message post."), any()); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/PluginBindingFlutterAssetManagerTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.webviewflutter; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.res.AssetManager; import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterAssets; import io.flutter.plugins.webviewflutter.FlutterAssetManager.PluginBindingFlutterAssetManager; import java.io.IOException; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; public class PluginBindingFlutterAssetManagerTest { @Mock AssetManager mockAssetManager; @Mock FlutterAssets mockFlutterAssets; PluginBindingFlutterAssetManager tesPluginBindingFlutterAssetManager; @Before public void setUp() { mockAssetManager = mock(AssetManager.class); mockFlutterAssets = mock(FlutterAssets.class); tesPluginBindingFlutterAssetManager = new PluginBindingFlutterAssetManager(mockAssetManager, mockFlutterAssets); } @Test public void list() { try { when(mockAssetManager.list("test/path")) .thenReturn(new String[] {"index.html", "styles.css"}); String[] actualFilePaths = tesPluginBindingFlutterAssetManager.list("test/path"); verify(mockAssetManager).list("test/path"); assertArrayEquals(new String[] {"index.html", "styles.css"}, actualFilePaths); } catch (IOException ex) { fail(); } } @Test public void registrar_getAssetFilePathByName() { tesPluginBindingFlutterAssetManager.getAssetFilePathByName("sample_movie.mp4"); verify(mockFlutterAssets).getAssetFilePathByName("sample_movie.mp4"); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/RegistrarFlutterAssetManagerTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.webviewflutter; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.res.AssetManager; import io.flutter.plugin.common.PluginRegistry.Registrar; import io.flutter.plugins.webviewflutter.FlutterAssetManager.RegistrarFlutterAssetManager; import java.io.IOException; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @SuppressWarnings("deprecation") public class RegistrarFlutterAssetManagerTest { @Mock AssetManager mockAssetManager; @Mock Registrar mockRegistrar; RegistrarFlutterAssetManager testRegistrarFlutterAssetManager; @Before public void setUp() { mockAssetManager = mock(AssetManager.class); mockRegistrar = mock(Registrar.class); testRegistrarFlutterAssetManager = new RegistrarFlutterAssetManager(mockAssetManager, mockRegistrar); } @Test public void list() { try { when(mockAssetManager.list("test/path")) .thenReturn(new String[] {"index.html", "styles.css"}); String[] actualFilePaths = testRegistrarFlutterAssetManager.list("test/path"); verify(mockAssetManager).list("test/path"); assertArrayEquals(new String[] {"index.html", "styles.css"}, actualFilePaths); } catch (IOException ex) { fail(); } } @Test public void registrar_getAssetFilePathByName() { testRegistrarFlutterAssetManager.getAssetFilePathByName("sample_movie.mp4"); verify(mockRegistrar).lookupKeyForAsset("sample_movie.mp4"); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java ================================================ // Copyright 2013 The Flutter 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.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.net.Uri; import android.os.Message; import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebView.WebViewTransport; import android.webkit.WebViewClient; import io.flutter.plugins.webviewflutter.WebChromeClientHostApiImpl.WebChromeClientCreator; import io.flutter.plugins.webviewflutter.WebChromeClientHostApiImpl.WebChromeClientImpl; 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 WebChromeClientTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock public WebChromeClientFlutterApiImpl mockFlutterApi; @Mock public WebView mockWebView; @Mock public WebViewClient mockWebViewClient; InstanceManager instanceManager; WebChromeClientHostApiImpl hostApiImpl; WebChromeClientImpl webChromeClient; @Before public void setUp() { instanceManager = InstanceManager.open(identifier -> {}); instanceManager.addDartCreatedInstance(mockWebView, 0L); final WebChromeClientCreator webChromeClientCreator = new WebChromeClientCreator() { @Override public WebChromeClientImpl createWebChromeClient( WebChromeClientFlutterApiImpl flutterApi) { webChromeClient = super.createWebChromeClient(flutterApi); return webChromeClient; } }; hostApiImpl = new WebChromeClientHostApiImpl(instanceManager, webChromeClientCreator, mockFlutterApi); hostApiImpl.create(2L); } @After public void tearDown() { instanceManager.close(); } @Test public void onProgressChanged() { webChromeClient.onProgressChanged(mockWebView, 23); verify(mockFlutterApi).onProgressChanged(eq(webChromeClient), eq(mockWebView), eq(23L), any()); } @Test public void onCreateWindow() { final WebView mockOnCreateWindowWebView = mock(WebView.class); // Create a fake message to transport requests to onCreateWindowWebView. final Message message = new Message(); message.obj = mock(WebViewTransport.class); webChromeClient.setWebViewClient(mockWebViewClient); assertTrue(webChromeClient.onCreateWindow(mockWebView, message, mockOnCreateWindowWebView)); /// Capture the WebViewClient used with onCreateWindow WebView. final ArgumentCaptor webViewClientCaptor = ArgumentCaptor.forClass(WebViewClient.class); verify(mockOnCreateWindowWebView).setWebViewClient(webViewClientCaptor.capture()); final WebViewClient onCreateWindowWebViewClient = webViewClientCaptor.getValue(); assertNotNull(onCreateWindowWebViewClient); /// Create a WebResourceRequest with a Uri. final WebResourceRequest mockRequest = mock(WebResourceRequest.class); when(mockRequest.getUrl()).thenReturn(mock(Uri.class)); when(mockRequest.getUrl().toString()).thenReturn("https://www.google.com"); // Test when the forwarding WebViewClient is overriding all url loading. when(mockWebViewClient.shouldOverrideUrlLoading(any(), any(WebResourceRequest.class))) .thenReturn(true); assertTrue( onCreateWindowWebViewClient.shouldOverrideUrlLoading( mockOnCreateWindowWebView, mockRequest)); verify(mockWebView, never()).loadUrl(any()); // Test when the forwarding WebViewClient is NOT overriding all url loading. when(mockWebViewClient.shouldOverrideUrlLoading(any(), any(WebResourceRequest.class))) .thenReturn(false); assertTrue( onCreateWindowWebViewClient.shouldOverrideUrlLoading( mockOnCreateWindowWebView, mockRequest)); verify(mockWebView).loadUrl("https://www.google.com"); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsTest.java ================================================ // Copyright 2013 The Flutter 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.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.webkit.WebSettings; import io.flutter.plugins.webviewflutter.WebSettingsHostApiImpl.WebSettingsCreator; 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 WebSettingsTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock public WebSettings mockWebSettings; @Mock WebSettingsCreator mockWebSettingsCreator; InstanceManager testInstanceManager; WebSettingsHostApiImpl testHostApiImpl; @Before public void setUp() { testInstanceManager = InstanceManager.open(identifier -> {}); when(mockWebSettingsCreator.createWebSettings(any())).thenReturn(mockWebSettings); testHostApiImpl = new WebSettingsHostApiImpl(testInstanceManager, mockWebSettingsCreator); testHostApiImpl.create(0L, 0L); } @After public void tearDown() { testInstanceManager.close(); } @Test public void setDomStorageEnabled() { testHostApiImpl.setDomStorageEnabled(0L, true); verify(mockWebSettings).setDomStorageEnabled(true); } @Test public void setJavaScriptCanOpenWindowsAutomatically() { testHostApiImpl.setJavaScriptCanOpenWindowsAutomatically(0L, false); verify(mockWebSettings).setJavaScriptCanOpenWindowsAutomatically(false); } @Test public void setSupportMultipleWindows() { testHostApiImpl.setSupportMultipleWindows(0L, true); verify(mockWebSettings).setSupportMultipleWindows(true); } @Test public void setJavaScriptEnabled() { testHostApiImpl.setJavaScriptEnabled(0L, false); verify(mockWebSettings).setJavaScriptEnabled(false); } @Test public void setUserAgentString() { testHostApiImpl.setUserAgentString(0L, "hello"); verify(mockWebSettings).setUserAgentString("hello"); } @Test public void setMediaPlaybackRequiresUserGesture() { testHostApiImpl.setMediaPlaybackRequiresUserGesture(0L, false); verify(mockWebSettings).setMediaPlaybackRequiresUserGesture(false); } @Test public void setSupportZoom() { testHostApiImpl.setSupportZoom(0L, true); verify(mockWebSettings).setSupportZoom(true); } @Test public void setLoadWithOverviewMode() { testHostApiImpl.setLoadWithOverviewMode(0L, false); verify(mockWebSettings).setLoadWithOverviewMode(false); } @Test public void setUseWideViewPort() { testHostApiImpl.setUseWideViewPort(0L, true); verify(mockWebSettings).setUseWideViewPort(true); } @Test public void setDisplayZoomControls() { testHostApiImpl.setDisplayZoomControls(0L, false); verify(mockWebSettings).setDisplayZoomControls(false); } @Test public void setBuiltInZoomControls() { testHostApiImpl.setBuiltInZoomControls(0L, true); verify(mockWebSettings).setBuiltInZoomControls(true); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebStorageHostApiImplTest.java ================================================ // Copyright 2013 The Flutter 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.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.webkit.WebStorage; 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 WebStorageHostApiImplTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock public WebStorage mockWebStorage; @Mock WebStorageHostApiImpl.WebStorageCreator mockWebStorageCreator; InstanceManager testInstanceManager; WebStorageHostApiImpl testHostApiImpl; @Before public void setUp() { testInstanceManager = InstanceManager.open(identifier -> {}); when(mockWebStorageCreator.createWebStorage()).thenReturn(mockWebStorage); testHostApiImpl = new WebStorageHostApiImpl(testInstanceManager, mockWebStorageCreator); testHostApiImpl.create(0L); } @After public void tearDown() { testInstanceManager.close(); } @Test public void deleteAllData() { testHostApiImpl.deleteAllData(0L); verify(mockWebStorage).deleteAllData(); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java ================================================ // Copyright 2013 The Flutter 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.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.net.Uri; import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebViewClient; import io.flutter.plugins.webviewflutter.WebViewClientHostApiImpl.WebViewClientCompatImpl; import io.flutter.plugins.webviewflutter.WebViewClientHostApiImpl.WebViewClientCreator; import java.util.HashMap; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; public class WebViewClientTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock public WebViewClientFlutterApiImpl mockFlutterApi; @Mock public WebView mockWebView; @Mock public WebViewClientCompatImpl mockWebViewClient; InstanceManager instanceManager; WebViewClientHostApiImpl hostApiImpl; WebViewClientCompatImpl webViewClient; @Before public void setUp() { instanceManager = InstanceManager.open(identifier -> {}); instanceManager.addDartCreatedInstance(mockWebView, 0L); final WebViewClientCreator webViewClientCreator = new WebViewClientCreator() { @Override public WebViewClient createWebViewClient(WebViewClientFlutterApiImpl flutterApi) { webViewClient = (WebViewClientCompatImpl) super.createWebViewClient(flutterApi); return webViewClient; } }; hostApiImpl = new WebViewClientHostApiImpl(instanceManager, webViewClientCreator, mockFlutterApi); hostApiImpl.create(1L); } @After public void tearDown() { instanceManager.close(); } @Test public void onPageStarted() { webViewClient.onPageStarted(mockWebView, "https://www.google.com", null); verify(mockFlutterApi) .onPageStarted(eq(webViewClient), eq(mockWebView), eq("https://www.google.com"), any()); } @Test public void onReceivedError() { webViewClient.onReceivedError(mockWebView, 32, "description", "https://www.google.com"); verify(mockFlutterApi) .onReceivedError( eq(webViewClient), eq(mockWebView), eq(32L), eq("description"), eq("https://www.google.com"), any()); } @Test public void urlLoading() { webViewClient.shouldOverrideUrlLoading(mockWebView, "https://www.google.com"); verify(mockFlutterApi) .urlLoading(eq(webViewClient), eq(mockWebView), eq("https://www.google.com"), any()); } @Test public void convertWebResourceRequestWithNullHeaders() { final Uri mockUri = mock(Uri.class); when(mockUri.toString()).thenReturn(""); final WebResourceRequest mockRequest = mock(WebResourceRequest.class); when(mockRequest.getMethod()).thenReturn("method"); when(mockRequest.getUrl()).thenReturn(mockUri); when(mockRequest.isForMainFrame()).thenReturn(true); when(mockRequest.getRequestHeaders()).thenReturn(null); final GeneratedAndroidWebView.WebResourceRequestData data = WebViewClientFlutterApiImpl.createWebResourceRequestData(mockRequest); assertEquals(data.getRequestHeaders(), new HashMap()); } @Test public void setReturnValueForShouldOverrideUrlLoading() { final WebViewClientHostApiImpl webViewClientHostApi = new WebViewClientHostApiImpl( instanceManager, new WebViewClientCreator() { @Override public WebViewClient createWebViewClient(WebViewClientFlutterApiImpl flutterApi) { return mockWebViewClient; } }, mockFlutterApi); instanceManager.addDartCreatedInstance(mockWebViewClient, 0); webViewClientHostApi.setSynchronousReturnValueForShouldOverrideUrlLoading(0L, false); verify(mockWebViewClient).setReturnValueForShouldOverrideUrlLoading(false); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewFlutterAndroidExternalApiTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.webviewflutter; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.Context; import android.webkit.WebView; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.PluginRegistry; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.platform.PlatformViewRegistry; import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; public class WebViewFlutterAndroidExternalApiTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock Context mockContext; @Mock BinaryMessenger mockBinaryMessenger; @Mock PlatformViewRegistry mockViewRegistry; @Mock FlutterPlugin.FlutterPluginBinding mockPluginBinding; @Test public void getWebView() { final WebViewFlutterPlugin webViewFlutterPlugin = new WebViewFlutterPlugin(); when(mockPluginBinding.getApplicationContext()).thenReturn(mockContext); when(mockPluginBinding.getPlatformViewRegistry()).thenReturn(mockViewRegistry); when(mockPluginBinding.getBinaryMessenger()).thenReturn(mockBinaryMessenger); webViewFlutterPlugin.onAttachedToEngine(mockPluginBinding); final InstanceManager instanceManager = webViewFlutterPlugin.getInstanceManager(); assertNotNull(instanceManager); final WebView mockWebView = mock(WebView.class); instanceManager.addDartCreatedInstance(mockWebView, 0); final PluginRegistry mockPluginRegistry = mock(PluginRegistry.class); when(mockPluginRegistry.get(WebViewFlutterPlugin.class)).thenReturn(webViewFlutterPlugin); final FlutterEngine mockFlutterEngine = mock(FlutterEngine.class); when(mockFlutterEngine.getPlugins()).thenReturn(mockPluginRegistry); assertEquals(WebViewFlutterAndroidExternalApi.getWebView(mockFlutterEngine, 0), mockWebView); webViewFlutterPlugin.onDetachedFromEngine(mockPluginBinding); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.plugins.webviewflutter; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.webkit.DownloadListener; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebViewClient; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugins.webviewflutter.WebViewHostApiImpl.WebViewPlatformView; import java.util.HashMap; 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 WebViewTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock public WebViewPlatformView mockWebView; @Mock WebViewHostApiImpl.WebViewProxy mockWebViewProxy; @Mock Context mockContext; @Mock BinaryMessenger mockBinaryMessenger; InstanceManager testInstanceManager; WebViewHostApiImpl testHostApiImpl; @Before public void setUp() { testInstanceManager = InstanceManager.open(identifier -> {}); when(mockWebViewProxy.createWebView(mockContext, mockBinaryMessenger, testInstanceManager)) .thenReturn(mockWebView); testHostApiImpl = new WebViewHostApiImpl( testInstanceManager, mockBinaryMessenger, mockWebViewProxy, mockContext, null); testHostApiImpl.create(0L, true); } @After public void tearDown() { testInstanceManager.close(); } @Test public void loadData() { testHostApiImpl.loadData( 0L, "VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", "text/plain", "base64"); verify(mockWebView) .loadData("VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", "text/plain", "base64"); } @Test public void loadDataWithNullValues() { testHostApiImpl.loadData(0L, "VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", null, null); verify(mockWebView).loadData("VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", null, null); } @Test public void loadDataWithBaseUrl() { testHostApiImpl.loadDataWithBaseUrl( 0L, "https://flutter.dev", "VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", "text/plain", "base64", "about:blank"); verify(mockWebView) .loadDataWithBaseURL( "https://flutter.dev", "VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", "text/plain", "base64", "about:blank"); } @Test public void loadDataWithBaseUrlAndNullValues() { testHostApiImpl.loadDataWithBaseUrl( 0L, null, "VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", null, null, null); verify(mockWebView) .loadDataWithBaseURL(null, "VGhpcyBkYXRhIGlzIGJhc2U2NCBlbmNvZGVkLg==", null, null, null); } @Test public void loadUrl() { testHostApiImpl.loadUrl(0L, "https://www.google.com", new HashMap<>()); verify(mockWebView).loadUrl("https://www.google.com", new HashMap<>()); } @Test public void postUrl() { testHostApiImpl.postUrl(0L, "https://www.google.com", new byte[] {0x01, 0x02}); verify(mockWebView).postUrl("https://www.google.com", new byte[] {0x01, 0x02}); } @Test public void getUrl() { when(mockWebView.getUrl()).thenReturn("https://www.google.com"); assertEquals(testHostApiImpl.getUrl(0L), "https://www.google.com"); } @Test public void canGoBack() { when(mockWebView.canGoBack()).thenReturn(true); assertEquals(testHostApiImpl.canGoBack(0L), true); } @Test public void canGoForward() { when(mockWebView.canGoForward()).thenReturn(false); assertEquals(testHostApiImpl.canGoForward(0L), false); } @Test public void goBack() { testHostApiImpl.goBack(0L); verify(mockWebView).goBack(); } @Test public void goForward() { testHostApiImpl.goForward(0L); verify(mockWebView).goForward(); } @Test public void reload() { testHostApiImpl.reload(0L); verify(mockWebView).reload(); } @Test public void clearCache() { testHostApiImpl.clearCache(0L, false); verify(mockWebView).clearCache(false); } @Test public void evaluateJavaScript() { final String[] successValue = new String[1]; testHostApiImpl.evaluateJavascript( 0L, "2 + 2", new GeneratedAndroidWebView.Result() { @Override public void success(String result) { successValue[0] = result; } @Override public void error(Throwable error) {} }); @SuppressWarnings("unchecked") final ArgumentCaptor> callbackCaptor = ArgumentCaptor.forClass(ValueCallback.class); verify(mockWebView).evaluateJavascript(eq("2 + 2"), callbackCaptor.capture()); callbackCaptor.getValue().onReceiveValue("da result"); assertEquals(successValue[0], "da result"); } @Test public void getTitle() { when(mockWebView.getTitle()).thenReturn("My title"); assertEquals(testHostApiImpl.getTitle(0L), "My title"); } @Test public void scrollTo() { testHostApiImpl.scrollTo(0L, 12L, 13L); verify(mockWebView).scrollTo(12, 13); } @Test public void scrollBy() { testHostApiImpl.scrollBy(0L, 15L, 23L); verify(mockWebView).scrollBy(15, 23); } @Test public void getScrollX() { when(mockWebView.getScrollX()).thenReturn(55); assertEquals((long) testHostApiImpl.getScrollX(0L), 55); } @Test public void getScrollY() { when(mockWebView.getScrollY()).thenReturn(23); assertEquals((long) testHostApiImpl.getScrollY(0L), 23); } @Test public void getScrollPosition() { when(mockWebView.getScrollX()).thenReturn(1); when(mockWebView.getScrollY()).thenReturn(2); final GeneratedAndroidWebView.WebViewPoint position = testHostApiImpl.getScrollPosition(0L); assertEquals((long) position.getX(), 1L); assertEquals((long) position.getY(), 2L); } @Test public void setWebViewClient() { final WebViewClient mockWebViewClient = mock(WebViewClient.class); testInstanceManager.addDartCreatedInstance(mockWebViewClient, 1L); testHostApiImpl.setWebViewClient(0L, 1L); verify(mockWebView).setWebViewClient(mockWebViewClient); } @Test public void addJavaScriptChannel() { final JavaScriptChannel javaScriptChannel = new JavaScriptChannel(mock(JavaScriptChannelFlutterApiImpl.class), "aName", null); testInstanceManager.addDartCreatedInstance(javaScriptChannel, 1L); testHostApiImpl.addJavaScriptChannel(0L, 1L); verify(mockWebView).addJavascriptInterface(javaScriptChannel, "aName"); } @Test public void removeJavaScriptChannel() { final JavaScriptChannel javaScriptChannel = new JavaScriptChannel(mock(JavaScriptChannelFlutterApiImpl.class), "aName", null); testInstanceManager.addDartCreatedInstance(javaScriptChannel, 1L); testHostApiImpl.removeJavaScriptChannel(0L, 1L); verify(mockWebView).removeJavascriptInterface("aName"); } @Test public void setDownloadListener() { final DownloadListener mockDownloadListener = mock(DownloadListener.class); testInstanceManager.addDartCreatedInstance(mockDownloadListener, 1L); testHostApiImpl.setDownloadListener(0L, 1L); verify(mockWebView).setDownloadListener(mockDownloadListener); } @Test public void setWebChromeClient() { final WebChromeClient mockWebChromeClient = mock(WebChromeClient.class); testInstanceManager.addDartCreatedInstance(mockWebChromeClient, 1L); testHostApiImpl.setWebChromeClient(0L, 1L); verify(mockWebView).setWebChromeClient(mockWebChromeClient); } @Test public void defaultWebChromeClientIsSecureWebChromeClient() { final WebViewPlatformView webView = new WebViewPlatformView(mockContext, null, null); assertTrue( webView.getWebChromeClient() instanceof WebChromeClientHostApiImpl.SecureWebChromeClient); assertFalse( webView.getWebChromeClient() instanceof WebChromeClientHostApiImpl.WebChromeClientImpl); } @Test public void defaultWebChromeClientDoesNotAttemptToCommunicateWithDart() { final WebViewPlatformView webView = new WebViewPlatformView(mockContext, null, null); // 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]); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/utils/TestUtils.java ================================================ // Copyright 2013 The Flutter 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.utils; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import org.junit.Assert; public class TestUtils { public static void setFinalStatic(Class classToModify, String fieldName, Object newValue) { try { Field field = classToModify.getField(fieldName); field.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.set(null, newValue); } catch (Exception e) { Assert.fail("Unable to mock static field: " + fieldName); } } public static void setPrivateField(T instance, String fieldName, Object newValue) { try { Field field = instance.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(instance, newValue); } catch (Exception e) { Assert.fail("Unable to mock private field: " + fieldName); } } public static Object getPrivateField(T instance, String fieldName) { try { Field field = instance.getClass().getDeclaredField(fieldName); field.setAccessible(true); return field.get(instance); } catch (Exception e) { Assert.fail("Unable to mock private field: " + fieldName); return null; } } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/.metadata ================================================ # 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: 1e5cb2d87f8542f9fbbd0f22d528823274be0acb channel: master ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/README.md ================================================ # 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. ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 32 lintOptions { disable 'InvalidPackage' } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "io.flutter.plugins.webviewflutterandroidexample" minSdkVersion 21 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' api 'androidx.test:core:1.4.0' } ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/android/app/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java ================================================ // Copyright 2013 The Flutter 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; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface DartIntegrationTest {} ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/BackgroundColorTest.java ================================================ // Copyright 2013 The Flutter 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.webviewflutterexample; import static androidx.test.espresso.flutter.EspressoFlutter.onFlutterWidget; import static androidx.test.espresso.flutter.action.FlutterActions.click; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withText; import static androidx.test.espresso.flutter.matcher.FlutterMatchers.withValueKey; import static org.junit.Assert.assertEquals; import android.graphics.Bitmap; import android.graphics.Color; import androidx.test.core.app.ActivityScenario; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.screenshot.ScreenCapture; import androidx.test.runner.screenshot.Screenshot; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class BackgroundColorTest { @Rule public ActivityTestRule myActivityTestRule = new ActivityTestRule<>(DriverExtensionActivity.class, true, false); @Before public void setUp() { ActivityScenario.launch(DriverExtensionActivity.class); } @Ignore("Doesn't run in Firebase Test Lab: https://github.com/flutter/flutter/issues/94748") @Test public void backgroundColor() { onFlutterWidget(withValueKey("ShowPopupMenu")).perform(click()); onFlutterWidget(withValueKey("ShowTransparentBackgroundExample")).perform(click()); onFlutterWidget(withText("Transparent background test")); final ScreenCapture screenCapture = Screenshot.capture(); final Bitmap screenBitmap = screenCapture.getBitmap(); final int centerLeftColor = screenBitmap.getPixel(10, (int) Math.floor(screenBitmap.getHeight() / 2.0)); final int centerColor = screenBitmap.getPixel( (int) Math.floor(screenBitmap.getWidth() / 2.0), (int) Math.floor(screenBitmap.getHeight() / 2.0)); // Flutter Colors.green color : 0xFF4CAF50 // https://github.com/flutter/flutter/blob/f4abaa0735eba4dfd8f33f73363911d63931fe03/packages/flutter/lib/src/material/colors.dart#L1208 // The background color of the webview is : rgba(0, 0, 0, 0.5) // The expected color is : rgba(38, 87, 40, 1) -> 0xFF265728 assertEquals(0xFF265728, centerLeftColor); assertEquals(Color.RED, centerColor); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/MainActivityTest.java ================================================ // Copyright 2013 The Flutter 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.webviewflutterexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class MainActivityTest { @Rule public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); } ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/WebViewTest.java ================================================ // Copyright 2013 The Flutter 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.webviewflutterexample; import static org.junit.Assert.assertTrue; import androidx.test.core.app.ActivityScenario; import io.flutter.plugins.webviewflutter.WebViewFlutterPlugin; import org.junit.Test; public class WebViewTest { @Test public void webViewPluginIsAdded() { final ActivityScenario scenario = ActivityScenario.launch(WebViewTestActivity.class); scenario.onActivity( activity -> { assertTrue(activity.engine.getPlugins().has(WebViewFlutterPlugin.class)); }); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/DriverExtensionActivity.java ================================================ // Copyright 2013 The Flutter 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.webviewflutterexample; import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity; public class DriverExtensionActivity extends FlutterActivity { @Override @NonNull public String getDartEntrypointFunctionName() { return "appMain"; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/WebViewTestActivity.java ================================================ // Copyright 2013 The Flutter 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.webviewflutterexample; import androidx.annotation.NonNull; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; // Extends FlutterActivity to make the FlutterEngine accessible for testing. public class WebViewTestActivity extends FlutterActivity { public FlutterEngine engine; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); engine = flutterEngine; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/android/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.1.2' } } allprojects { repositories { google() mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/assets/www/index.html ================================================ Load file or HTML string example

Local demo page

This is an example page used to demonstrate how to load a local file or HTML string using the Flutter webview plugin.

================================================ FILE: packages/webview_flutter/webview_flutter_android/example/assets/www/styles/style.css ================================================ h1 { color: blue; } ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/integration_test/legacy/webview_flutter_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This test is run using `flutter drive` by the CI (see /script/tool/README.md // in this repository for details on driving that tooling manually), but can // also be run using `flutter test` directly during development. import 'dart:async'; import 'dart:convert'; import 'dart:io'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/foundation.dart'; 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/instance_manager.dart'; import 'package:webview_flutter_android/src/weak_reference_utils.dart'; import 'package:webview_flutter_android/src/webview_flutter_android_legacy.dart'; import 'package:webview_flutter_android_example/legacy/navigation_decision.dart'; import 'package:webview_flutter_android_example/legacy/navigation_request.dart'; import 'package:webview_flutter_android_example/legacy/web_view.dart'; import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; Future main() async { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); final HttpServer server = await HttpServer.bind(InternetAddress.anyIPv4, 0); server.forEach((HttpRequest request) { if (request.uri.path == '/hello.txt') { request.response.writeln('Hello, world.'); } else if (request.uri.path == '/secondary.txt') { request.response.writeln('How are you today?'); } else if (request.uri.path == '/headers') { request.response.writeln('${request.headers}'); } else if (request.uri.path == '/favicon.ico') { request.response.statusCode = HttpStatus.notFound; } else { fail('unexpected request: ${request.method} ${request.uri}'); } request.response.close(); }); final String prefixUrl = 'http://${server.address.address}:${server.port}'; final String primaryUrl = '$prefixUrl/hello.txt'; final String secondaryUrl = '$prefixUrl/secondary.txt'; final String headersUrl = '$prefixUrl/headers'; testWidgets('initialUrl', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageFinishedCompleter = Completer(); await tester.pumpWidget( MaterialApp( home: Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, onPageFinished: pageFinishedCompleter.complete, ), ), ), ); final WebViewController controller = await controllerCompleter.future; await pageFinishedCompleter.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); testWidgets('loadUrl', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final StreamController pageLoads = StreamController(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, onPageFinished: (String url) { pageLoads.add(url); }, ), ), ); final WebViewController controller = await controllerCompleter.future; await controller.loadUrl(secondaryUrl); await expectLater( pageLoads.stream.firstWhere((String url) => url == secondaryUrl), completion(secondaryUrl), ); }); testWidgets( 'withWeakRefenceTo allows encapsulating class to be garbage collected', (WidgetTester tester) async { final Completer gcCompleter = Completer(); final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: gcCompleter.complete, ); ClassWithCallbackClass? instance = ClassWithCallbackClass(); instanceManager.addHostCreatedInstance(instance.callbackClass, 0); instance = null; // Force garbage collection. await IntegrationTestWidgetsFlutterBinding.instance .watchPerformance(() async { await tester.pumpAndSettle(); }); final int gcIdentifier = await gcCompleter.future; expect(gcIdentifier, 0); }, timeout: const Timeout(Duration(seconds: 10))); testWidgets('evaluateJavascript', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, ), ), ); final WebViewController controller = await controllerCompleter.future; final String result = await controller.evaluateJavascript('1 + 1'); expect(result, equals('2')); }); testWidgets('loadUrl with headers', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final StreamController pageStarts = StreamController(); final StreamController pageLoads = StreamController(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageStarted: (String url) { pageStarts.add(url); }, onPageFinished: (String url) { pageLoads.add(url); }, ), ), ); final WebViewController controller = await controllerCompleter.future; final Map headers = { 'test_header': 'flutter_test_header' }; await controller.loadUrl(headersUrl, headers: headers); final String? currentUrl = await controller.currentUrl(); expect(currentUrl, headersUrl); await pageStarts.stream.firstWhere((String url) => url == currentUrl); await pageLoads.stream.firstWhere((String url) => url == currentUrl); final String content = await controller .runJavascriptReturningResult('document.documentElement.innerText'); expect(content.contains('flutter_test_header'), isTrue); }); testWidgets('JavascriptChannel', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageStarted = Completer(); final Completer pageLoaded = Completer(); final Completer channelCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), // This is the data URL for: '' initialUrl: 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, javascriptChannels: { JavascriptChannel( name: 'Echo', onMessageReceived: (JavascriptMessage message) { channelCompleter.complete(message.message); }, ), }, onPageStarted: (String url) { pageStarted.complete(null); }, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageStarted.future; await pageLoaded.future; expect(channelCompleter.isCompleted, isFalse); await controller.runJavascript('Echo.postMessage("hello");'); await expectLater(channelCompleter.future, completion('hello')); }); testWidgets('resize webview', (WidgetTester tester) async { final Completer initialResizeCompleter = Completer(); final Completer buttonTapResizeCompleter = Completer(); final Completer onPageFinished = Completer(); bool resizeButtonTapped = false; await tester.pumpWidget(ResizableWebView( onResize: (_) { if (resizeButtonTapped) { buttonTapResizeCompleter.complete(); } else { initialResizeCompleter.complete(); } }, onPageFinished: () => onPageFinished.complete(), )); await onPageFinished.future; // Wait for a potential call to resize after page is loaded. await initialResizeCompleter.future.timeout( const Duration(seconds: 3), onTimeout: () => null, ); resizeButtonTapped = true; await tester.tap(find.byKey(const ValueKey('resizeButton'))); await tester.pumpAndSettle(); expect(buttonTapResizeCompleter.future, completes); }); testWidgets('set custom userAgent', (WidgetTester tester) async { final Completer controllerCompleter1 = Completer(); final GlobalKey globalKey = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: globalKey, initialUrl: 'about:blank', javascriptMode: JavascriptMode.unrestricted, userAgent: 'Custom_User_Agent1', onWebViewCreated: (WebViewController controller) { controllerCompleter1.complete(controller); }, ), ), ); final WebViewController controller1 = await controllerCompleter1.future; final String customUserAgent1 = await _getUserAgent(controller1); expect(customUserAgent1, 'Custom_User_Agent1'); // rebuild the WebView with a different user agent. await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: globalKey, initialUrl: 'about:blank', javascriptMode: JavascriptMode.unrestricted, userAgent: 'Custom_User_Agent2', ), ), ); final String customUserAgent2 = await _getUserAgent(controller1); expect(customUserAgent2, 'Custom_User_Agent2'); }); testWidgets('use default platform userAgent after webView is rebuilt', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final GlobalKey globalKey = GlobalKey(); // Build the webView with no user agent to get the default platform user agent. await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: globalKey, initialUrl: primaryUrl, javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, ), ), ); final WebViewController controller = await controllerCompleter.future; final String defaultPlatformUserAgent = await _getUserAgent(controller); // rebuild the WebView with a custom user agent. await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: globalKey, initialUrl: 'about:blank', javascriptMode: JavascriptMode.unrestricted, userAgent: 'Custom_User_Agent', ), ), ); final String customUserAgent = await _getUserAgent(controller); expect(customUserAgent, 'Custom_User_Agent'); // rebuilds the WebView with no user agent. await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: globalKey, initialUrl: 'about:blank', javascriptMode: JavascriptMode.unrestricted, ), ), ); final String customUserAgent2 = await _getUserAgent(controller); expect(customUserAgent2, defaultPlatformUserAgent); }); group('Video playback policy', () { late String videoTestBase64; setUpAll(() async { final ByteData videoData = await rootBundle.load('assets/sample_video.mp4'); final String base64VideoData = base64Encode(Uint8List.view(videoData.buffer)); final String videoTest = ''' Video auto play '''; videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest)); }); testWidgets('Auto media playback', (WidgetTester tester) async { Completer controllerCompleter = Completer(); Completer pageLoaded = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (String url) { pageLoaded.complete(null); }, initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, ), ), ); WebViewController controller = await controllerCompleter.future; await pageLoaded.future; String isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(false)); controllerCompleter = Completer(); pageLoaded = Completer(); // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); controller = await controllerCompleter.future; await pageLoaded.future; isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(true)); }); testWidgets('Changes to initialMediaPlaybackPolicy are ignored', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); Completer pageLoaded = Completer(); final GlobalKey key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: key, initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (String url) { pageLoaded.complete(null); }, initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; String isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(false)); pageLoaded = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: key, initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); await controller.reload(); await pageLoaded.future; isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(false)); }); testWidgets('Video plays inline when allowsInlineMediaPlayback is true', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageLoaded = Completer(); final Completer videoPlaying = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, javascriptChannels: { JavascriptChannel( name: 'VideoTestTime', onMessageReceived: (JavascriptMessage message) { final double currentTime = double.parse(message.message); // Let it play for at least 1 second to make sure the related video's properties are set. if (currentTime > 1 && !videoPlaying.isCompleted) { videoPlaying.complete(null); } }, ), }, onPageFinished: (String url) { pageLoaded.complete(null); }, initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, allowsInlineMediaPlayback: true, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; // Pump once to trigger the video play. await tester.pump(); // Makes sure we get the correct event that indicates the video is actually playing. await videoPlaying.future; final String fullScreen = await controller.runJavascriptReturningResult('isFullScreen();'); expect(fullScreen, _webviewBool(false)); }); }); group('Audio playback policy', () { late String audioTestBase64; setUpAll(() async { final ByteData audioData = await rootBundle.load('assets/sample_audio.ogg'); final String base64AudioData = base64Encode(Uint8List.view(audioData.buffer)); final String audioTest = ''' Audio auto play '''; audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest)); }); testWidgets('Auto media playback', (WidgetTester tester) async { Completer controllerCompleter = Completer(); Completer pageStarted = Completer(); Completer pageLoaded = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageStarted: (String url) { pageStarted.complete(null); }, onPageFinished: (String url) { pageLoaded.complete(null); }, initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, ), ), ); WebViewController controller = await controllerCompleter.future; await pageStarted.future; await pageLoaded.future; String isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(false)); controllerCompleter = Completer(); pageStarted = Completer(); pageLoaded = Completer(); // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageStarted: (String url) { pageStarted.complete(null); }, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); controller = await controllerCompleter.future; await pageStarted.future; await pageLoaded.future; isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(true)); }); testWidgets('Changes to initialMediaPlaybackPolicy are ignored', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); Completer pageStarted = Completer(); Completer pageLoaded = Completer(); final GlobalKey key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: key, initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageStarted: (String url) { pageStarted.complete(null); }, onPageFinished: (String url) { pageLoaded.complete(null); }, initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageStarted.future; await pageLoaded.future; String isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(false)); pageStarted = Completer(); pageLoaded = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: key, initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageStarted: (String url) { pageStarted.complete(null); }, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); await controller.reload(); await pageStarted.future; await pageLoaded.future; isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(false)); }); }); testWidgets('getTitle', (WidgetTester tester) async { const String getTitleTest = ''' Some title '''; final String getTitleTestBase64 = base64Encode(const Utf8Encoder().convert(getTitleTest)); final Completer pageStarted = Completer(); final Completer pageLoaded = Completer(); final Completer controllerCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( initialUrl: 'data:text/html;charset=utf-8;base64,$getTitleTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, onPageStarted: (String url) { pageStarted.complete(null); }, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageStarted.future; await pageLoaded.future; final String? title = await controller.getTitle(); expect(title, 'Some title'); }); group('Programmatic Scroll', () { testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { const String scrollTestPage = '''
'''; final String scrollTestPageBase64 = base64Encode(const Utf8Encoder().convert(scrollTestPage)); final Completer pageLoaded = Completer(); final Completer controllerCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( initialUrl: 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; await tester.pumpAndSettle(const Duration(seconds: 3)); int scrollPosX = await controller.getScrollX(); int scrollPosY = await controller.getScrollY(); // Check scrollTo() const int X_SCROLL = 123; const int Y_SCROLL = 321; // Get the initial position; this ensures that scrollTo is actually // changing something, but also gives the native view's scroll position // time to settle. expect(scrollPosX, isNot(X_SCROLL)); expect(scrollPosX, isNot(Y_SCROLL)); await controller.scrollTo(X_SCROLL, Y_SCROLL); scrollPosX = await controller.getScrollX(); scrollPosY = await controller.getScrollY(); expect(scrollPosX, X_SCROLL); expect(scrollPosY, Y_SCROLL); // Check scrollBy() (on top of scrollTo()) await controller.scrollBy(X_SCROLL, Y_SCROLL); scrollPosX = await controller.getScrollX(); scrollPosY = await controller.getScrollY(); expect(scrollPosX, X_SCROLL * 2); expect(scrollPosY, Y_SCROLL * 2); }); }); group('SurfaceAndroidWebView', () { setUpAll(() { WebView.platform = SurfaceAndroidWebView(); }); tearDownAll(() { WebView.platform = AndroidWebView(); }); testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { const String scrollTestPage = '''
'''; final String scrollTestPageBase64 = base64Encode(const Utf8Encoder().convert(scrollTestPage)); final Completer pageLoaded = Completer(); final Completer controllerCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( initialUrl: 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; await tester.pumpAndSettle(const Duration(seconds: 3)); // Check scrollTo() const int X_SCROLL = 123; const int Y_SCROLL = 321; await controller.scrollTo(X_SCROLL, Y_SCROLL); int scrollPosX = await controller.getScrollX(); int scrollPosY = await controller.getScrollY(); expect(X_SCROLL, scrollPosX); expect(Y_SCROLL, scrollPosY); // Check scrollBy() (on top of scrollTo()) await controller.scrollBy(X_SCROLL, Y_SCROLL); scrollPosX = await controller.getScrollX(); scrollPosY = await controller.getScrollY(); expect(X_SCROLL * 2, scrollPosX); expect(Y_SCROLL * 2, scrollPosY); }); testWidgets('inputs are scrolled into view when focused', (WidgetTester tester) async { const String scrollTestPage = '''
'''; final String scrollTestPageBase64 = base64Encode(const Utf8Encoder().convert(scrollTestPage)); final Completer pageLoaded = Completer(); final Completer controllerCompleter = Completer(); await tester.runAsync(() async { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: SizedBox( width: 200, height: 200, child: WebView( initialUrl: 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, onPageFinished: (String url) { pageLoaded.complete(null); }, javascriptMode: JavascriptMode.unrestricted, ), ), ), ); await Future.delayed(const Duration(milliseconds: 20)); await tester.pump(); }); final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; final String viewportRectJSON = await _runJavaScriptReturningResult( controller, 'JSON.stringify(viewport.getBoundingClientRect())'); 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 = await _runJavaScriptReturningResult( controller, 'JSON.stringify(inputEl.getBoundingClientRect())'); final Map initialInputClientRectRelativeToViewport = jsonDecode(initialInputClientRectJSON) as Map; expect( getDomRectComponent( initialInputClientRectRelativeToViewport, 'bottom') <= getDomRectComponent(viewportRectRelativeToViewport, 'bottom'), isFalse); await controller.runJavascript('inputEl.focus()'); // Check that focusing the input brought it into view. final String lastInputClientRectJSON = await _runJavaScriptReturningResult( controller, 'JSON.stringify(inputEl.getBoundingClientRect())'); final Map lastInputClientRectRelativeToViewport = jsonDecode(lastInputClientRectJSON) as Map; expect( getDomRectComponent(lastInputClientRectRelativeToViewport, 'top') >= getDomRectComponent(viewportRectRelativeToViewport, 'top'), isTrue); expect( getDomRectComponent( lastInputClientRectRelativeToViewport, 'bottom') <= getDomRectComponent(viewportRectRelativeToViewport, 'bottom'), isTrue); expect( getDomRectComponent(lastInputClientRectRelativeToViewport, 'left') >= getDomRectComponent(viewportRectRelativeToViewport, 'left'), isTrue); expect( getDomRectComponent(lastInputClientRectRelativeToViewport, 'right') <= getDomRectComponent(viewportRectRelativeToViewport, 'right'), isTrue); }); }); group('NavigationDelegate', () { const String blankPage = ''; final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' '${base64Encode(const Utf8Encoder().convert(blankPage))}'; testWidgets('can allow requests', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final StreamController pageLoads = StreamController.broadcast(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: blankPageEncoded, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, navigationDelegate: (NavigationRequest request) { return (request.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; }, onPageFinished: (String url) => pageLoads.add(url), ), ), ); await pageLoads.stream.first; // Wait for initial page load. final WebViewController controller = await controllerCompleter.future; await controller.runJavascript('location.href = "$secondaryUrl"'); await pageLoads.stream.first; // Wait for the next page load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); testWidgets('onWebResourceError', (WidgetTester tester) async { final Completer errorCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'https://www.notawebsite..com', onWebResourceError: (WebResourceError error) { errorCompleter.complete(error); }, ), ), ); final WebResourceError error = await errorCompleter.future; expect(error, isNotNull); expect(error.errorType, isNotNull); expect( error.failingUrl?.startsWith('https://www.notawebsite..com'), isTrue); }); testWidgets('onWebResourceError is not called with valid url', (WidgetTester tester) async { final Completer errorCompleter = Completer(); final Completer pageFinishCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', onWebResourceError: (WebResourceError error) { errorCompleter.complete(error); }, onPageFinished: (_) => pageFinishCompleter.complete(), ), ), ); expect(errorCompleter.future, doesNotComplete); await pageFinishCompleter.future; }); testWidgets( 'onWebResourceError only called for main frame', (WidgetTester tester) async { const String iframeTest = ''' WebResourceError test '''; final String iframeTestBase64 = base64Encode(const Utf8Encoder().convert(iframeTest)); final Completer errorCompleter = Completer(); final Completer pageFinishCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'data:text/html;charset=utf-8;base64,$iframeTestBase64', onWebResourceError: (WebResourceError error) { errorCompleter.complete(error); }, onPageFinished: (_) => pageFinishCompleter.complete(), ), ), ); expect(errorCompleter.future, doesNotComplete); await pageFinishCompleter.future; }, ); testWidgets('can block requests', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final StreamController pageLoads = StreamController.broadcast(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: blankPageEncoded, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, navigationDelegate: (NavigationRequest request) { return (request.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; }, onPageFinished: (String url) => pageLoads.add(url), ), ), ); await pageLoads.stream.first; // Wait for initial page load. final WebViewController controller = await controllerCompleter.future; 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 .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 Completer controllerCompleter = Completer(); final StreamController pageLoads = StreamController.broadcast(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: blankPageEncoded, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, navigationDelegate: (NavigationRequest request) async { NavigationDecision decision = NavigationDecision.prevent; decision = await Future.delayed( const Duration(milliseconds: 10), () => NavigationDecision.navigate); return decision; }, onPageFinished: (String url) => pageLoads.add(url), ), ), ); await pageLoads.stream.first; // Wait for initial page load. final WebViewController controller = await controllerCompleter.future; await controller.runJavascript('location.href = "$secondaryUrl"'); await pageLoads.stream.first; // Wait for second page to load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); }); testWidgets('launches with gestureNavigationEnabled on iOS', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: SizedBox( width: 400, height: 300, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, gestureNavigationEnabled: true, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, ), ), ), ); final WebViewController controller = await controllerCompleter.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); testWidgets('target _blank opens in same window', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageLoaded = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); final WebViewController controller = await controllerCompleter.future; await controller.runJavascript('window.open("$primaryUrl", "_blank")'); await pageLoaded.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); testWidgets( 'can open new window and go back', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); Completer pageLoaded = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (String url) { pageLoaded.complete(); }, initialUrl: primaryUrl, ), ), ); final WebViewController controller = await controllerCompleter.future; expect(controller.currentUrl(), completion(primaryUrl)); await pageLoaded.future; pageLoaded = Completer(); await controller.runJavascript('window.open("$secondaryUrl")'); await pageLoaded.future; pageLoaded = Completer(); expect(controller.currentUrl(), completion(secondaryUrl)); expect(controller.canGoBack(), completion(true)); await controller.goBack(); await pageLoaded.future; await expectLater(controller.currentUrl(), completion(primaryUrl)); }, ); testWidgets( 'JavaScript does not run in parent window', (WidgetTester tester) async { const String iframe = ''' '''; final String iframeTestBase64 = base64Encode(const Utf8Encoder().convert(iframe)); final String openWindowTest = ''' XSS test '''; final String openWindowTestBase64 = base64Encode(const Utf8Encoder().convert(openWindowTest)); final Completer controllerCompleter = Completer(); final Completer pageLoadCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, initialUrl: 'data:text/html;charset=utf-8;base64,$openWindowTestBase64', onPageFinished: (String url) { pageLoadCompleter.complete(); }, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageLoadCompleter.future; final String iframeLoaded = await controller.runJavascriptReturningResult('iframeLoaded'); expect(iframeLoaded, 'true'); final String elementText = await controller.runJavascriptReturningResult( 'document.querySelector("p") && document.querySelector("p").textContent', ); expect(elementText, 'null'); }, ); testWidgets( 'clearCache should clear local storage', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); Completer pageLoadCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (_) => pageLoadCompleter.complete(), onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, ), ), ); await pageLoadCompleter.future; pageLoadCompleter = Completer(); final WebViewController controller = await controllerCompleter.future; await controller.runJavascript('localStorage.setItem("myCat", "Tom");'); final String myCatItem = await controller.runJavascriptReturningResult( 'localStorage.getItem("myCat");', ); expect(myCatItem, '"Tom"'); await controller.clearCache(); await pageLoadCompleter.future; final String nullItem = await controller.runJavascriptReturningResult( 'localStorage.getItem("myCat");', ); expect(nullItem, 'null'); }, ); } // JavaScript booleans evaluate to different string values on Android and iOS. // This utility method returns the string boolean value of the current platform. String _webviewBool(bool value) { if (defaultTargetPlatform == TargetPlatform.iOS) { return value ? '1' : '0'; } return value ? 'true' : 'false'; } /// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests. Future _getUserAgent(WebViewController controller) async { return _runJavaScriptReturningResult(controller, 'navigator.userAgent;'); } Future _runJavaScriptReturningResult( WebViewController controller, String js, ) async { return jsonDecode(await controller.runJavascriptReturningResult(js)) as String; } class ResizableWebView extends StatefulWidget { const ResizableWebView({ Key? key, required this.onResize, required this.onPageFinished, }) : super(key: key); final JavascriptMessageHandler onResize; final VoidCallback onPageFinished; @override State createState() => ResizableWebViewState(); } class ResizableWebViewState extends State { double webViewWidth = 200; double webViewHeight = 200; static const String resizePage = ''' Resize test '''; @override Widget build(BuildContext context) { final String resizeTestBase64 = base64Encode(const Utf8Encoder().convert(resizePage)); return Directionality( textDirection: TextDirection.ltr, child: Column( children: [ SizedBox( width: webViewWidth, height: webViewHeight, child: WebView( initialUrl: 'data:text/html;charset=utf-8;base64,$resizeTestBase64', javascriptChannels: { JavascriptChannel( name: 'Resize', onMessageReceived: widget.onResize, ), }, onPageFinished: (_) => widget.onPageFinished(), javascriptMode: JavascriptMode.unrestricted, ), ), TextButton( key: const Key('resizeButton'), onPressed: () { setState(() { webViewWidth += 100.0; webViewHeight += 100.0; }); }, child: const Text('ResizeButton'), ), ], ), ); } } class CopyableObjectWithCallback with Copyable { CopyableObjectWithCallback(this.callback); final VoidCallback callback; @override CopyableObjectWithCallback copy() { return CopyableObjectWithCallback(callback); } } class ClassWithCallbackClass { ClassWithCallbackClass() { callbackClass = CopyableObjectWithCallback( withWeakReferenceTo( this, (WeakReference weakReference) { return () { // Weak reference to `this` in callback. // ignore: unnecessary_statements weakReference; }; }, ), ); } late final CopyableObjectWithCallback callbackClass; } ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This test is run using `flutter drive` by the CI (see /script/tool/README.md // in this repository for details on driving that tooling manually), but can // also be run using `flutter test` directly during development. import 'dart:async'; import 'dart:convert'; import 'dart:io'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; 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'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; Future main() async { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); final HttpServer server = await HttpServer.bind(InternetAddress.anyIPv4, 0); server.forEach((HttpRequest request) { if (request.uri.path == '/hello.txt') { request.response.writeln('Hello, world.'); } else if (request.uri.path == '/secondary.txt') { request.response.writeln('How are you today?'); } else if (request.uri.path == '/headers') { request.response.writeln('${request.headers}'); } else if (request.uri.path == '/favicon.ico') { request.response.statusCode = HttpStatus.notFound; } else { fail('unexpected request: ${request.method} ${request.uri}'); } request.response.close(); }); final String prefixUrl = 'http://${server.address.address}:${server.port}'; final String primaryUrl = '$prefixUrl/hello.txt'; final String secondaryUrl = '$prefixUrl/secondary.txt'; final String headersUrl = '$prefixUrl/headers'; testWidgets('loadRequest', (WidgetTester tester) async { final Completer pageFinished = Completer(); final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..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( 'withWeakRefenceTo allows encapsulating class to be garbage collected', (WidgetTester tester) async { final Completer gcCompleter = Completer(); final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: gcCompleter.complete, ); ClassWithCallbackClass? instance = ClassWithCallbackClass(); instanceManager.addHostCreatedInstance(instance.callbackClass, 0); instance = null; // Force garbage collection. await IntegrationTestWidgetsFlutterBinding.instance .watchPerformance(() async { await tester.pumpAndSettle(); }); final int gcIdentifier = await gcCompleter.future; 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(); final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..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'), completion(2), ); }); testWidgets('loadRequest with headers', (WidgetTester tester) async { final Map headers = { 'test_header': 'flutter_test_header' }; final StreamController pageLoads = StreamController(); final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), )..setOnPageFinished((String url) => pageLoads.add(url)), ) ..loadRequest( LoadRequestParams( uri: Uri.parse(headersUrl), headers: headers, ), ); 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( 'document.documentElement.innerText', ) as String; expect(content.contains('flutter_test_header'), isTrue); }); testWidgets('JavascriptChannel', (WidgetTester tester) async { final Completer pageFinished = Completer(); final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageFinished.complete()), ); final Completer channelCompleter = Completer(); await controller.addJavaScriptChannel( JavaScriptChannelParams( name: 'Echo', onMessageReceived: (JavaScriptMessage message) { channelCompleter.complete(message.message); }, ), ); await controller.loadHtmlString( '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");'); await expectLater(channelCompleter.future, completion('hello')); }); testWidgets('resize webview', (WidgetTester tester) async { final Completer initialResizeCompleter = Completer(); final Completer buttonTapResizeCompleter = Completer(); final Completer onPageFinished = Completer(); bool resizeButtonTapped = false; await tester.pumpWidget(ResizableWebView( onResize: () { if (resizeButtonTapped) { buttonTapResizeCompleter.complete(); } else { initialResizeCompleter.complete(); } }, onPageFinished: () => onPageFinished.complete(), )); await onPageFinished.future; // Wait for a potential call to resize after page is loaded. await initialResizeCompleter.future.timeout( const Duration(seconds: 3), onTimeout: () => null, ); resizeButtonTapped = true; await tester.tap(find.byKey(const ValueKey('resizeButton'))); await tester.pumpAndSettle(); await expectLater(buttonTapResizeCompleter.future, completes); }); testWidgets('set custom userAgent', (WidgetTester tester) async { final Completer pageFinished = Completer(); final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageFinished.complete()), ) ..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 pageFinished.future; final String customUserAgent = await _getUserAgent(controller); expect(customUserAgent, 'Custom_User_Agent1'); }); group('Video playback policy', () { late String videoTestBase64; setUpAll(() async { final ByteData videoData = await rootBundle.load('assets/sample_video.mp4'); final String base64VideoData = base64Encode(Uint8List.view(videoData.buffer)); final String videoTest = ''' Video auto play '''; videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest)); }); testWidgets('Auto media playback', (WidgetTester tester) async { Completer pageLoaded = Completer(); PlatformWebViewController controller = AndroidWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( AndroidNavigationDelegate( const PlatformNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageLoaded.complete()), ) ..setMediaPlaybackRequiresUserGesture(false) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,$videoTestBase64', ), ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }, )); await pageLoaded.future; bool isPaused = await controller.runJavaScriptReturningResult('isPaused();') as bool; expect(isPaused, false); pageLoaded = Completer(); controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageLoaded.complete()), ) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,$videoTestBase64', ), ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }, )); await pageLoaded.future; isPaused = await controller.runJavaScriptReturningResult('isPaused();') as bool; expect(isPaused, true); }); testWidgets('Video plays inline', (WidgetTester tester) async { final Completer pageLoaded = Completer(); final Completer videoPlaying = Completer(); final PlatformWebViewController controller = AndroidWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( AndroidNavigationDelegate( const PlatformNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageLoaded.complete()), ) ..addJavaScriptChannel( JavaScriptChannelParams( name: 'VideoTestTime', onMessageReceived: (JavaScriptMessage message) { final double currentTime = double.parse(message.message); // Let it play for at least 1 second to make sure the related video's properties are set. if (currentTime > 1 && !videoPlaying.isCompleted) { videoPlaying.complete(null); } }, ), ) ..setMediaPlaybackRequiresUserGesture(false) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,$videoTestBase64', ), ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }, )); await pageLoaded.future; // Makes sure we get the correct event that indicates the video is actually playing. await videoPlaying.future; final bool fullScreen = await controller .runJavaScriptReturningResult('isFullScreen();') as bool; expect(fullScreen, false); }); }); group('Audio playback policy', () { late String audioTestBase64; setUpAll(() async { final ByteData audioData = await rootBundle.load('assets/sample_audio.ogg'); final String base64AudioData = base64Encode(Uint8List.view(audioData.buffer)); final String audioTest = ''' Audio auto play '''; audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest)); }); testWidgets('Auto media playback', (WidgetTester tester) async { Completer pageLoaded = Completer(); PlatformWebViewController controller = AndroidWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( AndroidNavigationDelegate( const PlatformNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageLoaded.complete()), ) ..setMediaPlaybackRequiresUserGesture(false) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,$audioTestBase64', ), ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }, )); await pageLoaded.future; bool isPaused = await controller.runJavaScriptReturningResult('isPaused();') as bool; expect(isPaused, false); pageLoaded = Completer(); controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageLoaded.complete()), ) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,$audioTestBase64', ), ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }, )); await pageLoaded.future; isPaused = await controller.runJavaScriptReturningResult('isPaused();') as bool; expect(isPaused, true); }); }); testWidgets('getTitle', (WidgetTester tester) async { const String getTitleTest = ''' Some title '''; final String getTitleTestBase64 = base64Encode(const Utf8Encoder().convert(getTitleTest)); final Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageLoaded.complete()), ) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,$getTitleTestBase64', ), ), ); 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 // code has the title when the page load completes. Execute some JavaScript // before checking the title to ensure that the page has been fully parsed // and processed. await controller.runJavaScript('1;'); final String? title = await controller.getTitle(); expect(title, 'Some title'); }); group('Programmatic Scroll', () { testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { const String scrollTestPage = '''
'''; final String scrollTestPageBase64 = base64Encode(const Utf8Encoder().convert(scrollTestPage)); final Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageLoaded.complete()), ) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', ), ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }, )); await pageLoaded.future; await tester.pumpAndSettle(const Duration(seconds: 3)); Offset scrollPos = await controller.getScrollPosition(); // Check scrollTo() const int X_SCROLL = 123; const int Y_SCROLL = 321; // Get the initial position; this ensures that scrollTo is actually // changing something, but also gives the native view's scroll position // time to settle. expect(scrollPos.dx, isNot(X_SCROLL)); expect(scrollPos.dy, isNot(Y_SCROLL)); await controller.scrollTo(X_SCROLL, Y_SCROLL); scrollPos = await controller.getScrollPosition(); expect(scrollPos.dx, X_SCROLL); expect(scrollPos.dy, Y_SCROLL); // Check scrollBy() (on top of scrollTo()) await controller.scrollBy(X_SCROLL, Y_SCROLL); scrollPos = await controller.getScrollPosition(); expect(scrollPos.dx, X_SCROLL * 2); expect(scrollPos.dy, Y_SCROLL * 2); }); }); group('NavigationDelegate', () { const String blankPage = ''; final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' '${base64Encode(const Utf8Encoder().convert(blankPage))}'; testWidgets('can allow requests', (WidgetTester tester) async { Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ) ..setOnPageFinished((_) => pageLoaded.complete()) ..setOnNavigationRequest((NavigationRequest navigationRequest) { return (navigationRequest.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; }), ) ..loadRequest( LoadRequestParams(uri: Uri.parse(blankPageEncoded)), ); 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; // Wait for the next page load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); testWidgets('onWebResourceError', (WidgetTester tester) async { final Completer errorCompleter = Completer(); final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), )..setOnWebResourceError((WebResourceError error) { errorCompleter.complete(error); }), ) ..loadRequest( 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); expect(error.errorType, isNotNull); expect( (error as AndroidWebResourceError) .failingUrl ?.startsWith('https://www.notawebsite..com'), isTrue, ); }); testWidgets('onWebResourceError is not called with valid url', (WidgetTester tester) async { final Completer errorCompleter = Completer(); final Completer pageFinishCompleter = Completer(); final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ) ..setOnPageFinished((_) => pageFinishCompleter.complete()) ..setOnWebResourceError((WebResourceError error) { errorCompleter.complete(error); }), ) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', ), ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }, )); expect(errorCompleter.future, doesNotComplete); await pageFinishCompleter.future; }); testWidgets('can block requests', (WidgetTester tester) async { Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ) ..setOnPageFinished((_) => pageLoaded.complete()) ..setOnNavigationRequest((NavigationRequest navigationRequest) { return (navigationRequest.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; }), ) ..loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded))); 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 pageLoaded.future .timeout(const Duration(milliseconds: 500), onTimeout: () => false); final String? currentUrl = await controller.currentUrl(); expect(currentUrl, isNot(contains('youtube.com'))); }); testWidgets('supports asynchronous decisions', (WidgetTester tester) async { Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ) ..setOnPageFinished((_) => pageLoaded.complete()) ..setOnNavigationRequest( (NavigationRequest navigationRequest) async { NavigationDecision decision = NavigationDecision.prevent; decision = await Future.delayed( const Duration(milliseconds: 10), () => NavigationDecision.navigate); return decision; }), ) ..loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded))); 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; // Wait for second page to load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); }); testWidgets('target _blank opens in same window', (WidgetTester tester) async { final Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate(PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageLoaded.complete())); await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }, )); await controller.runJavaScript('window.open("$primaryUrl", "_blank")'); await pageLoaded.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); testWidgets( 'can open new window and go back', (WidgetTester tester) async { Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..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); }, )); expect(controller.currentUrl(), completion(primaryUrl)); await pageLoaded.future; pageLoaded = Completer(); await controller.runJavaScript('window.open("$secondaryUrl")'); await pageLoaded.future; pageLoaded = Completer(); expect(controller.currentUrl(), completion(secondaryUrl)); expect(controller.canGoBack(), completion(true)); await controller.goBack(); await pageLoaded.future; await expectLater(controller.currentUrl(), completion(primaryUrl)); }, ); testWidgets( 'JavaScript does not run in parent window', (WidgetTester tester) async { const String iframe = ''' '''; final String iframeTestBase64 = base64Encode(const Utf8Encoder().convert(iframe)); final String openWindowTest = ''' XSS test '''; final String openWindowTestBase64 = base64Encode(const Utf8Encoder().convert(openWindowTest)); final Completer pageLoadCompleter = Completer(); final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate(PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageLoadCompleter.complete())) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,$openWindowTestBase64', ), ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }, )); await pageLoadCompleter.future; final bool iframeLoaded = await controller.runJavaScriptReturningResult('iframeLoaded') as bool; expect(iframeLoaded, true); final String elementText = await controller.runJavaScriptReturningResult( 'document.querySelector("p") && document.querySelector("p").textContent', ) as String; 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. Future _getUserAgent(PlatformWebViewController controller) async { return _runJavaScriptReturningResult(controller, 'navigator.userAgent;'); } Future _runJavaScriptReturningResult( PlatformWebViewController controller, String js, ) async { return jsonDecode(await controller.runJavaScriptReturningResult(js) as String) as String; } class ResizableWebView extends StatefulWidget { const ResizableWebView({ Key? key, required this.onResize, required this.onPageFinished, }) : super(key: key); final VoidCallback onResize; final VoidCallback onPageFinished; @override State createState() => ResizableWebViewState(); } class ResizableWebViewState extends State { late final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), )..setOnPageFinished((_) => widget.onPageFinished()), ) ..addJavaScriptChannel( JavaScriptChannelParams( name: 'Resize', onMessageReceived: (_) { widget.onResize(); }, ), ) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,${base64Encode(const Utf8Encoder().convert(resizePage))}', ), ), ); double webViewWidth = 200; double webViewHeight = 200; static const String resizePage = ''' Resize test '''; @override Widget build(BuildContext context) { return Directionality( textDirection: TextDirection.ltr, child: Column( children: [ SizedBox( width: webViewWidth, height: webViewHeight, child: PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context), ), TextButton( key: const Key('resizeButton'), onPressed: () { setState(() { webViewWidth += 100.0; webViewHeight += 100.0; }); }, child: const Text('ResizeButton'), ), ], ), ); } } class CopyableObjectWithCallback with Copyable { CopyableObjectWithCallback(this.callback); final VoidCallback callback; @override CopyableObjectWithCallback copy() { return CopyableObjectWithCallback(callback); } } class ClassWithCallbackClass { ClassWithCallbackClass() { callbackClass = CopyableObjectWithCallback( withWeakReferenceTo( this, (WeakReference weakReference) { return () { // Weak reference to `this` in callback. // ignore: unnecessary_statements weakReference; }; }, ), ); } late final CopyableObjectWithCallback callbackClass; } ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/lib/legacy/navigation_decision.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// A decision on how to handle a navigation request. enum NavigationDecision { /// Prevent the navigation from taking place. prevent, /// Allow the navigation to take place. navigate, } ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/lib/legacy/navigation_request.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Information about a navigation action that is about to be executed. class NavigationRequest { NavigationRequest._({required this.url, required this.isForMainFrame}); /// The URL that will be loaded if the navigation is executed. final String url; /// Whether the navigation request is to be loaded as the main frame. final bool isForMainFrame; @override String toString() { return '$NavigationRequest(url: $url, isForMainFrame: $isForMainFrame)'; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/lib/legacy/web_view.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; // ignore: implementation_imports import 'package:webview_flutter_android/src/webview_flutter_android_legacy.dart'; // ignore: implementation_imports import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; import 'navigation_decision.dart'; import 'navigation_request.dart'; /// Optional callback invoked when a web view is first created. [controller] is /// the [WebViewController] for the created web view. typedef WebViewCreatedCallback = void Function(WebViewController controller); /// Decides how to handle a specific navigation request. /// /// The returned [NavigationDecision] determines how the navigation described by /// `navigation` should be handled. /// /// See also: [WebView.navigationDelegate]. typedef NavigationDelegate = FutureOr Function( NavigationRequest navigation); /// Signature for when a [WebView] has started loading a page. typedef PageStartedCallback = void Function(String url); /// Signature for when a [WebView] has finished loading a page. typedef PageFinishedCallback = void Function(String url); /// Signature for when a [WebView] is loading a page. typedef PageLoadingCallback = void Function(int progress); /// Signature for when a [WebView] has failed to load a resource. typedef WebResourceErrorCallback = void Function(WebResourceError error); /// A web view widget for showing html content. /// /// The [WebView] widget wraps around the [AndroidWebView] or /// [SurfaceAndroidWebView] classes and acts like a facade which makes it easier /// to inject a [AndroidWebView] or [SurfaceAndroidWebView] control into the /// widget tree. /// /// The [WebView] widget is controlled using the [WebViewController] which is /// provided through the `onWebViewCreated` callback. /// /// In this example project it's main purpose is to facilitate integration /// testing of the `webview_flutter_android` package. class WebView extends StatefulWidget { /// Creates a new web view. /// /// The web view can be controlled using a `WebViewController` that is passed to the /// `onWebViewCreated` callback once the web view is created. /// /// The `javascriptMode` and `autoMediaPlaybackPolicy` parameters must not be null. const WebView({ Key? key, this.onWebViewCreated, this.initialUrl, this.initialCookies = const [], this.javascriptMode = JavascriptMode.disabled, this.javascriptChannels, this.navigationDelegate, this.gestureRecognizers, this.onPageStarted, this.onPageFinished, this.onProgress, this.onWebResourceError, this.debuggingEnabled = false, this.gestureNavigationEnabled = false, this.userAgent, this.zoomEnabled = true, this.initialMediaPlaybackPolicy = AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, this.allowsInlineMediaPlayback = false, this.backgroundColor, }) : assert(javascriptMode != null), assert(initialMediaPlaybackPolicy != null), assert(allowsInlineMediaPlayback != null), super(key: key); /// The WebView platform that's used by this WebView. /// /// The default value is [AndroidWebView]. static WebViewPlatform platform = AndroidWebView(); /// If not null invoked once the web view is created. final WebViewCreatedCallback? onWebViewCreated; /// Which gestures should be consumed by the web view. /// /// It is possible for other gesture recognizers to be competing with the web view on pointer /// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle /// vertical drags. The web view will claim gestures that are recognized by any of the /// recognizers on this list. /// /// When this set is empty or null, the web view will only handle pointer events for gestures that /// were not claimed by any other gesture recognizer. final Set>? gestureRecognizers; /// The initial URL to load. final String? initialUrl; /// The initial cookies to set. final List initialCookies; /// Whether JavaScript execution is enabled. final JavascriptMode javascriptMode; /// The set of [JavascriptChannel]s available to JavaScript code running in the web view. /// /// For each [JavascriptChannel] in the set, a channel object is made available for the /// JavaScript code in a window property named [JavascriptChannel.name]. /// The JavaScript code can then call `postMessage` on that object to send a message that will be /// passed to [JavascriptChannel.onMessageReceived]. /// /// For example for the following [JavascriptChannel]: /// /// ```dart /// JavascriptChannel(name: 'Print', onMessageReceived: (JavascriptMessage message) { print(message.message); }); /// ``` /// /// JavaScript code can call: /// /// ```javascript /// Print.postMessage('Hello'); /// ``` /// /// To asynchronously invoke the message handler which will print the message to standard output. /// /// Adding a new JavaScript channel only takes affect after the next page is loaded. /// /// Set values must not be null. A [JavascriptChannel.name] cannot be the same for multiple /// channels in the list. /// /// A null value is equivalent to an empty set. final Set? javascriptChannels; /// A delegate function that decides how to handle navigation actions. /// /// When a navigation is initiated by the WebView (e.g when a user clicks a link) /// this delegate is called and has to decide how to proceed with the navigation. /// /// See [NavigationDecision] for possible decisions the delegate can take. /// /// When null all navigation actions are allowed. /// /// Caveats on Android: /// /// * Navigation actions targeted to the main frame can be intercepted, /// navigation actions targeted to subframes are allowed regardless of the value /// returned by this delegate. /// * Setting a navigationDelegate makes the WebView treat all navigations as if they were /// triggered by a user gesture, this disables some of Chromium's security mechanisms. /// A navigationDelegate should only be set when loading trusted content. /// * On Android WebView versions earlier than 67(most devices running at least Android L+ should have /// a later version): /// * When a navigationDelegate is set pages with frames are not properly handled by the /// webview, and frames will be opened in the main frame. /// * When a navigationDelegate is set HTTP requests do not include the HTTP referer header. final NavigationDelegate? navigationDelegate; /// Controls whether inline playback of HTML5 videos is allowed on iOS. /// /// This field is ignored on Android because Android allows it by default. /// /// By default `allowsInlineMediaPlayback` is false. final bool allowsInlineMediaPlayback; /// Invoked when a page starts loading. final PageStartedCallback? onPageStarted; /// Invoked when a page has finished loading. /// /// This is invoked only for the main frame. /// /// When [onPageFinished] is invoked on Android, the page being rendered may /// not be updated yet. /// /// When invoked on iOS or Android, any JavaScript code that is embedded /// directly in the HTML has been loaded and code injected with /// [WebViewController.evaluateJavascript] can assume this. final PageFinishedCallback? onPageFinished; /// Invoked when a page is loading. final PageLoadingCallback? onProgress; /// Invoked when a web resource has failed to load. /// /// This callback is only called for the main page. final WebResourceErrorCallback? onWebResourceError; /// Controls whether WebView debugging is enabled. /// /// Setting this to true enables [WebView debugging on Android](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/). /// /// WebView debugging is enabled by default in dev builds on iOS. /// /// To debug WebViews on iOS: /// - Enable developer options (Open Safari, go to Preferences -> Advanced and make sure "Show Develop Menu in Menubar" is on.) /// - From the Menu-bar (of Safari) select Develop -> iPhone Simulator -> /// /// By default `debuggingEnabled` is false. final bool debuggingEnabled; /// A Boolean value indicating whether horizontal swipe gestures will trigger back-forward list navigations. /// /// This only works on iOS. /// /// By default `gestureNavigationEnabled` is false. final bool gestureNavigationEnabled; /// A Boolean value indicating whether the WebView should support zooming using its on-screen zoom controls and gestures. /// /// By default 'zoomEnabled' is true final bool zoomEnabled; /// The value used for the HTTP User-Agent: request header. /// /// When null the platform's webview default is used for the User-Agent header. /// /// When the [WebView] is rebuilt with a different `userAgent`, the page reloads and the request uses the new User Agent. /// /// When [WebViewController.goBack] is called after changing `userAgent` the previous `userAgent` value is used until the page is reloaded. /// /// This field is ignored on iOS versions prior to 9 as the platform does not support a custom /// user agent. /// /// By default `userAgent` is null. final String? userAgent; /// Which restrictions apply on automatic media playback. /// /// This initial value is applied to the platform's webview upon creation. Any following /// changes to this parameter are ignored (as long as the state of the [WebView] is preserved). /// /// The default policy is [AutoMediaPlaybackPolicy.require_user_action_for_all_media_types]. final AutoMediaPlaybackPolicy initialMediaPlaybackPolicy; /// The background color of the [WebView]. /// /// When `null` the platform's webview default background color is used. By /// default [backgroundColor] is `null`. final Color? backgroundColor; @override State createState() => _WebViewState(); } class _WebViewState extends State { final Completer _controller = Completer(); late final JavascriptChannelRegistry _javascriptChannelRegistry; late final _PlatformCallbacksHandler _platformCallbacksHandler; @override void initState() { super.initState(); _platformCallbacksHandler = _PlatformCallbacksHandler(widget); _javascriptChannelRegistry = JavascriptChannelRegistry(widget.javascriptChannels); } @override void didUpdateWidget(WebView oldWidget) { super.didUpdateWidget(oldWidget); _controller.future.then((WebViewController controller) { controller.updateWidget(widget); }); } @override Widget build(BuildContext context) { return WebView.platform.build( context: context, onWebViewPlatformCreated: (WebViewPlatformController? webViewPlatformController) { final WebViewController controller = WebViewController( widget, webViewPlatformController!, _javascriptChannelRegistry, ); _controller.complete(controller); if (widget.onWebViewCreated != null) { widget.onWebViewCreated!(controller); } }, webViewPlatformCallbacksHandler: _platformCallbacksHandler, creationParams: CreationParams( initialUrl: widget.initialUrl, webSettings: _webSettingsFromWidget(widget), javascriptChannelNames: _javascriptChannelRegistry.channels.keys.toSet(), autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, userAgent: widget.userAgent, backgroundColor: widget.backgroundColor, cookies: widget.initialCookies, ), javascriptChannelRegistry: _javascriptChannelRegistry, ); } } class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { _PlatformCallbacksHandler(this._webView); final WebView _webView; @override FutureOr onNavigationRequest({ required String url, required bool isForMainFrame, }) async { if (url.startsWith('https://www.youtube.com/')) { debugPrint('blocking navigation to $url'); return false; } debugPrint('allowing navigation to $url'); return true; } @override void onPageStarted(String url) { if (_webView.onPageStarted != null) { _webView.onPageStarted!(url); } } @override void onPageFinished(String url) { if (_webView.onPageFinished != null) { _webView.onPageFinished!(url); } } @override void onProgress(int progress) { if (_webView.onProgress != null) { _webView.onProgress!(progress); } } @override void onWebResourceError(WebResourceError error) { if (_webView.onWebResourceError != null) { _webView.onWebResourceError!(error); } } } /// Controls a [WebView]. /// /// A [WebViewController] instance can be obtained by setting the [WebView.onWebViewCreated] /// callback for a [WebView] widget. class WebViewController { /// Creates a [WebViewController] which can be used to control the provided /// [WebView] widget. WebViewController( this._widget, this._webViewPlatformController, this._javascriptChannelRegistry, ) : assert(_webViewPlatformController != null) { _settings = _webSettingsFromWidget(_widget); } final JavascriptChannelRegistry _javascriptChannelRegistry; final WebViewPlatformController _webViewPlatformController; late WebSettings _settings; WebView _widget; /// Loads the file located on the specified [absoluteFilePath]. /// /// The [absoluteFilePath] parameter should contain the absolute path to the /// file as it is stored on the device. For example: /// `/Users/username/Documents/www/index.html`. /// /// Throws an ArgumentError if the [absoluteFilePath] does not exist. Future loadFile(String absoluteFilePath) { return _webViewPlatformController.loadFile(absoluteFilePath); } /// Loads the Flutter asset specified in the pubspec.yaml file. /// /// Throws an ArgumentError if [key] is not part of the specified assets /// in the pubspec.yaml file. Future loadFlutterAsset(String key) { return _webViewPlatformController.loadFlutterAsset(key); } /// Loads the supplied HTML string. /// /// The [baseUrl] parameter is used when resolving relative URLs within the /// HTML string. Future loadHtmlString(String html, {String? baseUrl}) { return _webViewPlatformController.loadHtmlString( html, baseUrl: baseUrl, ); } /// Loads the specified URL. /// /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will /// be added as key value pairs of HTTP headers for the request. /// /// `url` must not be null. /// /// Throws an ArgumentError if `url` is not a valid URL string. Future loadUrl( String url, { Map? headers, }) async { assert(url != null); _validateUrlString(url); return _webViewPlatformController.loadUrl(url, headers); } /// Loads a page by making the specified request. Future loadRequest(WebViewRequest request) async { return _webViewPlatformController.loadRequest(request); } /// Accessor to the current URL that the WebView is displaying. /// /// If [WebView.initialUrl] was never specified, returns `null`. /// Note that this operation is asynchronous, and it is possible that the /// current URL changes again by the time this function returns (in other /// words, by the time this future completes, the WebView may be displaying a /// different URL). Future currentUrl() { return _webViewPlatformController.currentUrl(); } /// Checks whether there's a back history item. /// /// Note that this operation is asynchronous, and it is possible that the "canGoBack" state has /// changed by the time the future completed. Future canGoBack() { return _webViewPlatformController.canGoBack(); } /// Checks whether there's a forward history item. /// /// Note that this operation is asynchronous, and it is possible that the "canGoForward" state has /// changed by the time the future completed. Future canGoForward() { return _webViewPlatformController.canGoForward(); } /// Goes back in the history of this WebView. /// /// If there is no back history item this is a no-op. Future goBack() { return _webViewPlatformController.goBack(); } /// Goes forward in the history of this WebView. /// /// If there is no forward history item this is a no-op. Future goForward() { return _webViewPlatformController.goForward(); } /// Reloads the current URL. Future reload() { return _webViewPlatformController.reload(); } /// Clears all caches used by the [WebView]. /// /// The following caches are cleared: /// 1. Browser HTTP Cache. /// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches. /// These are not yet supported in iOS WkWebView. Service workers tend to use this cache. /// 3. Application cache. /// 4. Local Storage. /// /// Note: Calling this method also triggers a reload. Future clearCache() async { await _webViewPlatformController.clearCache(); return reload(); } /// Update the widget managed by the [WebViewController]. Future updateWidget(WebView widget) async { _widget = widget; await _updateSettings(_webSettingsFromWidget(widget)); await _updateJavascriptChannels( _javascriptChannelRegistry.channels.values.toSet()); } Future _updateSettings(WebSettings newSettings) { final WebSettings update = _clearUnchangedWebSettings(_settings, newSettings); _settings = newSettings; return _webViewPlatformController.updateSettings(update); } Future _updateJavascriptChannels( Set? newChannels) async { final Set currentChannels = _javascriptChannelRegistry.channels.keys.toSet(); final Set newChannelNames = _extractChannelNames(newChannels); final Set channelsToAdd = newChannelNames.difference(currentChannels); final Set channelsToRemove = currentChannels.difference(newChannelNames); if (channelsToRemove.isNotEmpty) { await _webViewPlatformController .removeJavascriptChannels(channelsToRemove); } if (channelsToAdd.isNotEmpty) { await _webViewPlatformController.addJavascriptChannels(channelsToAdd); } _javascriptChannelRegistry.updateJavascriptChannelsFromSet(newChannels); } @visibleForTesting // ignore: public_member_api_docs Future evaluateJavascript(String javascriptString) { if (_settings.javascriptMode == JavascriptMode.disabled) { return Future.error(FlutterError( 'JavaScript mode must be enabled/unrestricted when calling evaluateJavascript.')); } return _webViewPlatformController.evaluateJavascript(javascriptString); } /// Runs the given JavaScript in the context of the current page. /// If you are looking for the result, use [runJavascriptReturningResult] instead. /// The Future completes with an error if a JavaScript error occurred. /// /// When running JavaScript in a [WebView], it is best practice to wait for // the [WebView.onPageFinished] callback. This guarantees all the JavaScript // embedded in the main frame HTML has been loaded. Future runJavascript(String javaScriptString) { if (_settings.javascriptMode == JavascriptMode.disabled) { return Future.error(FlutterError( 'Javascript mode must be enabled/unrestricted when calling runJavascript.')); } return _webViewPlatformController.runJavascript(javaScriptString); } /// Runs the given JavaScript in the context of the current page, and returns the result. /// /// Returns the evaluation result as a JSON formatted string. /// The Future completes with an error if a JavaScript error occurred. /// /// When evaluating JavaScript in a [WebView], it is best practice to wait for /// the [WebView.onPageFinished] callback. This guarantees all the JavaScript /// embedded in the main frame HTML has been loaded. Future runJavascriptReturningResult(String javaScriptString) { if (_settings.javascriptMode == JavascriptMode.disabled) { return Future.error(FlutterError( 'Javascript mode must be enabled/unrestricted when calling runJavascriptReturningResult.')); } return _webViewPlatformController .runJavascriptReturningResult(javaScriptString); } /// Returns the title of the currently loaded page. Future getTitle() { return _webViewPlatformController.getTitle(); } /// Sets the WebView's content scroll position. /// /// The parameters `x` and `y` specify the scroll position in WebView pixels. Future scrollTo(int x, int y) { return _webViewPlatformController.scrollTo(x, y); } /// Move the scrolled position of this view. /// /// The parameters `x` and `y` specify the amount of WebView pixels to scroll by horizontally and vertically respectively. Future scrollBy(int x, int y) { return _webViewPlatformController.scrollBy(x, y); } /// Return the horizontal scroll position, in WebView pixels, of this view. /// /// Scroll position is measured from left. Future getScrollX() { return _webViewPlatformController.getScrollX(); } /// Return the vertical scroll position, in WebView pixels, of this view. /// /// Scroll position is measured from top. Future getScrollY() { return _webViewPlatformController.getScrollY(); } // This method assumes that no fields in `currentValue` are null. WebSettings _clearUnchangedWebSettings( WebSettings currentValue, WebSettings newValue) { assert(currentValue.javascriptMode != null); assert(currentValue.hasNavigationDelegate != null); assert(currentValue.hasProgressTracking != null); assert(currentValue.debuggingEnabled != null); assert(currentValue.userAgent != null); assert(newValue.javascriptMode != null); assert(newValue.hasNavigationDelegate != null); assert(newValue.debuggingEnabled != null); assert(newValue.userAgent != null); assert(newValue.zoomEnabled != null); JavascriptMode? javascriptMode; bool? hasNavigationDelegate; bool? hasProgressTracking; bool? debuggingEnabled; WebSetting userAgent = const WebSetting.absent(); bool? zoomEnabled; if (currentValue.javascriptMode != newValue.javascriptMode) { javascriptMode = newValue.javascriptMode; } if (currentValue.hasNavigationDelegate != newValue.hasNavigationDelegate) { hasNavigationDelegate = newValue.hasNavigationDelegate; } if (currentValue.hasProgressTracking != newValue.hasProgressTracking) { hasProgressTracking = newValue.hasProgressTracking; } if (currentValue.debuggingEnabled != newValue.debuggingEnabled) { debuggingEnabled = newValue.debuggingEnabled; } if (currentValue.userAgent != newValue.userAgent) { userAgent = newValue.userAgent; } if (currentValue.zoomEnabled != newValue.zoomEnabled) { zoomEnabled = newValue.zoomEnabled; } return WebSettings( javascriptMode: javascriptMode, hasNavigationDelegate: hasNavigationDelegate, hasProgressTracking: hasProgressTracking, debuggingEnabled: debuggingEnabled, userAgent: userAgent, zoomEnabled: zoomEnabled, ); } Set _extractChannelNames(Set? channels) { final Set channelNames = channels == null ? {} : channels.map((JavascriptChannel channel) => channel.name).toSet(); return channelNames; } // Throws an ArgumentError if `url` is not a valid URL string. void _validateUrlString(String url) { try { final Uri uri = Uri.parse(url); if (uri.scheme.isEmpty) { throw ArgumentError('Missing scheme in URL string: "$url"'); } } on FormatException catch (e) { throw ArgumentError(e); } } } WebSettings _webSettingsFromWidget(WebView widget) { return WebSettings( javascriptMode: widget.javascriptMode, hasNavigationDelegate: widget.navigationDelegate != null, hasProgressTracking: widget.onProgress != null, debuggingEnabled: widget.debuggingEnabled, gestureNavigationEnabled: widget.gestureNavigationEnabled, allowsInlineMediaPlayback: widget.allowsInlineMediaPlayback, userAgent: WebSetting.of(widget.userAgent), zoomEnabled: widget.zoomEnabled, ); } /// App-facing cookie manager that exposes the correct platform implementation. class WebViewCookieManager extends WebViewCookieManagerPlatform { WebViewCookieManager._(); /// Returns an instance of the cookie manager for the current platform. static WebViewCookieManagerPlatform get instance { if (WebViewCookieManagerPlatform.instance == null) { if (Platform.isAndroid) { WebViewCookieManagerPlatform.instance = WebViewAndroidCookieManager(); } else { throw AssertionError( 'This platform is currently unsupported for webview_flutter_android.'); } } return WebViewCookieManagerPlatform.instance!; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; void main() { runApp(const MaterialApp(home: WebViewExample())); } const String kNavigationExamplePage = ''' Navigation Delegate Example

The navigation delegate is set to block navigation to the youtube website.

'''; const String kLocalExamplePage = ''' Load file or HTML string example

Local demo page

This is an example page used to demonstrate how to load a local file or HTML string using the Flutter webview plugin.

'''; const String kTransparentBackgroundPage = ''' Transparent background test

Transparent background test

'''; class WebViewExample extends StatefulWidget { const WebViewExample({Key? key, this.cookieManager}) : super(key: key); final PlatformWebViewCookieManager? cookieManager; @override State createState() => _WebViewExampleState(); } class _WebViewExampleState extends State { late final PlatformWebViewController _controller; @override void initState() { super.initState(); _controller = PlatformWebViewController( AndroidWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setBackgroundColor(const Color(0x80000000)) ..setPlatformNavigationDelegate( PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ) ..setOnProgress((int progress) { debugPrint('WebView is loading (progress : $progress%)'); }) ..setOnPageStarted((String url) { debugPrint('Page started loading: $url'); }) ..setOnPageFinished((String url) { debugPrint('Page finished loading: $url'); }) ..setOnWebResourceError((WebResourceError error) { debugPrint(''' Page resource error: code: ${error.errorCode} description: ${error.description} errorType: ${error.errorType} isForMainFrame: ${error.isForMainFrame} '''); }) ..setOnNavigationRequest((NavigationRequest request) { if (request.url.startsWith('https://www.youtube.com/')) { debugPrint('blocking navigation to ${request.url}'); return NavigationDecision.prevent; } debugPrint('allowing navigation to ${request.url}'); return NavigationDecision.navigate; }), ) ..addJavaScriptChannel(JavaScriptChannelParams( name: 'Toaster', onMessageReceived: (JavaScriptMessage message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message.message)), ); }, )) ..loadRequest(LoadRequestParams( uri: Uri.parse('https://flutter.dev'), )); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFF4CAF50), appBar: AppBar( title: const Text('Flutter WebView example'), // This drop down menu demonstrates that Flutter widgets can be shown over the web view. actions: [ NavigationControls(webViewController: _controller), SampleMenu( webViewController: _controller, cookieManager: widget.cookieManager, ), ], ), body: PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: _controller), ).build(context), floatingActionButton: favoriteButton(), ); } Widget favoriteButton() { return FloatingActionButton( onPressed: () async { final String? url = await _controller.currentUrl(); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Favorited $url')), ); } }, child: const Icon(Icons.favorite), ); } } enum MenuOptions { showUserAgent, listCookies, clearCookies, addToCache, listCache, clearCache, navigationDelegate, doPostRequest, loadLocalFile, loadFlutterAsset, loadHtmlString, transparentBackground, setCookie, } class SampleMenu extends StatelessWidget { SampleMenu({ Key? key, required this.webViewController, PlatformWebViewCookieManager? cookieManager, }) : cookieManager = cookieManager ?? PlatformWebViewCookieManager( const PlatformWebViewCookieManagerCreationParams(), ), super(key: key); final PlatformWebViewController webViewController; late final PlatformWebViewCookieManager cookieManager; @override Widget build(BuildContext context) { return PopupMenuButton( key: const ValueKey('ShowPopupMenu'), onSelected: (MenuOptions value) { switch (value) { case MenuOptions.showUserAgent: _onShowUserAgent(); break; case MenuOptions.listCookies: _onListCookies(context); break; case MenuOptions.clearCookies: _onClearCookies(context); break; case MenuOptions.addToCache: _onAddToCache(context); break; case MenuOptions.listCache: _onListCache(); break; case MenuOptions.clearCache: _onClearCache(context); break; case MenuOptions.navigationDelegate: _onNavigationDelegateExample(); break; case MenuOptions.doPostRequest: _onDoPostRequest(); break; case MenuOptions.loadLocalFile: _onLoadLocalFileExample(); break; case MenuOptions.loadFlutterAsset: _onLoadFlutterAssetExample(); break; case MenuOptions.loadHtmlString: _onLoadHtmlStringExample(); break; case MenuOptions.transparentBackground: _onTransparentBackground(); break; case MenuOptions.setCookie: _onSetCookie(); break; } }, itemBuilder: (BuildContext context) => >[ const PopupMenuItem( value: MenuOptions.showUserAgent, child: Text('Show user agent'), ), const PopupMenuItem( value: MenuOptions.listCookies, child: Text('List cookies'), ), const PopupMenuItem( value: MenuOptions.clearCookies, child: Text('Clear cookies'), ), const PopupMenuItem( value: MenuOptions.addToCache, child: Text('Add to cache'), ), const PopupMenuItem( value: MenuOptions.listCache, child: Text('List cache'), ), const PopupMenuItem( value: MenuOptions.clearCache, child: Text('Clear cache'), ), const PopupMenuItem( value: MenuOptions.navigationDelegate, child: Text('Navigation Delegate example'), ), const PopupMenuItem( value: MenuOptions.doPostRequest, child: Text('Post Request'), ), const PopupMenuItem( value: MenuOptions.loadHtmlString, child: Text('Load HTML string'), ), const PopupMenuItem( value: MenuOptions.loadLocalFile, child: Text('Load local file'), ), const PopupMenuItem( value: MenuOptions.loadFlutterAsset, child: Text('Load Flutter Asset'), ), const PopupMenuItem( value: MenuOptions.setCookie, child: Text('Set cookie'), ), const PopupMenuItem( key: ValueKey('ShowTransparentBackgroundExample'), value: MenuOptions.transparentBackground, child: Text('Transparent background example'), ), ], ); } Future _onShowUserAgent() { // Send a message with the user agent string to the Toaster JavaScript channel we registered // with the WebView. return webViewController.runJavaScript( 'Toaster.postMessage("User Agent: " + navigator.userAgent);', ); } Future _onListCookies(BuildContext context) async { final String cookies = await webViewController .runJavaScriptReturningResult('document.cookie') as String; 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";', ); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Added a test entry to cache.'), )); } } Future _onListCache() { return webViewController.runJavaScript('caches.keys()' // ignore: missing_whitespace_between_adjacent_strings '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))' '.then((caches) => Toaster.postMessage(caches))'); } Future _onClearCache(BuildContext context) async { await webViewController.clearCache(); await webViewController.clearLocalStorage(); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Cache cleared.'), )); } } Future _onClearCookies(BuildContext context) async { final bool hadCookies = await cookieManager.clearCookies(); String message = 'There were cookies. Now, they are gone!'; if (!hadCookies) { message = 'There are no cookies.'; } if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(message), )); } } Future _onNavigationDelegateExample() { final String contentBase64 = base64Encode( const Utf8Encoder().convert(kNavigationExamplePage), ); return webViewController.loadRequest( LoadRequestParams( uri: Uri.parse('data:text/html;base64,$contentBase64'), ), ); } Future _onSetCookie() async { await cookieManager.setCookie( const WebViewCookie( name: 'foo', value: 'bar', domain: 'httpbin.org', path: '/anything', ), ); await webViewController.loadRequest(LoadRequestParams( uri: Uri.parse('https://httpbin.org/anything'), )); } Future _onDoPostRequest() { return webViewController.loadRequest(LoadRequestParams( uri: Uri.parse('https://httpbin.org/post'), method: LoadRequestMethod.post, headers: const { 'foo': 'bar', 'Content-Type': 'text/plain', }, body: Uint8List.fromList('Test Body'.codeUnits), )); } Future _onLoadLocalFileExample() async { final String pathToIndex = await _prepareLocalFile(); await webViewController.loadFile(pathToIndex); } Future _onLoadFlutterAssetExample() { return webViewController.loadFlutterAsset('assets/www/index.html'); } Future _onLoadHtmlStringExample() { return webViewController.loadHtmlString(kLocalExamplePage); } Future _onTransparentBackground() { return webViewController.loadHtmlString(kTransparentBackgroundPage); } Widget _getCookieList(String cookies) { if (cookies == null || cookies == '""') { return Container(); } final List cookieList = cookies.split(';'); final Iterable cookieWidgets = cookieList.map((String cookie) => Text(cookie)); return Column( mainAxisAlignment: MainAxisAlignment.end, mainAxisSize: MainAxisSize.min, children: cookieWidgets.toList(), ); } static Future _prepareLocalFile() async { final String tmpDir = (await getTemporaryDirectory()).path; final File indexFile = File( {tmpDir, 'www', 'index.html'}.join(Platform.pathSeparator)); await indexFile.create(recursive: true); await indexFile.writeAsString(kLocalExamplePage); return indexFile.path; } } class NavigationControls extends StatelessWidget { const NavigationControls({Key? key, required this.webViewController}) : super(key: key); final PlatformWebViewController webViewController; @override Widget build(BuildContext context) { return Row( children: [ IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () async { if (await webViewController.canGoBack()) { await webViewController.goBack(); } else { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('No back history item')), ); } } }, ), IconButton( icon: const Icon(Icons.arrow_forward_ios), onPressed: () async { if (await webViewController.canGoForward()) { await webViewController.goForward(); } else { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('No forward history item')), ); } } }, ), IconButton( icon: const Icon(Icons.replay), onPressed: () => webViewController.reload(), ), ], ); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/pubspec.yaml ================================================ name: webview_flutter_android_example description: Demonstrates how to use the webview_flutter_android plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter flutter_driver: sdk: flutter path_provider: ^2.0.6 webview_flutter_android: # When depending on this package from a real application you should use: # webview_flutter: ^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: ../ webview_flutter_platform_interface: ^2.0.0 dev_dependencies: espresso: ^0.2.0 flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true assets: - assets/sample_audio.ogg - assets/sample_video.mp4 - assets/www/index.html - assets/www/styles/style.css ================================================ FILE: packages/webview_flutter/webview_flutter_android/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart ================================================ // Copyright 2013 The Flutter 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 'android_webview.dart' as android_webview; /// Handles constructing objects and calling static methods for the Android /// WebView native library. /// /// This class provides dependency injection for the implementations of the /// platform interface classes. Improving the ease of unit testing and/or /// overriding the underlying Android WebView classes. /// /// By default each function calls the default constructor of the WebView class /// it intends to return. class AndroidWebViewProxy { /// Constructs a [AndroidWebViewProxy]. const AndroidWebViewProxy({ this.createAndroidWebView = android_webview.WebView.new, this.createAndroidWebChromeClient = android_webview.WebChromeClient.new, this.createAndroidWebViewClient = android_webview.WebViewClient.new, this.createFlutterAssetManager = android_webview.FlutterAssetManager.new, this.createJavaScriptChannel = android_webview.JavaScriptChannel.new, this.createDownloadListener = android_webview.DownloadListener.new, }); /// Constructs a [android_webview.WebView]. /// /// Due to changes in Flutter 3.0 the [useHybridComposition] doesn't have /// any effect and should not be exposed publicly. More info here: /// https://github.com/flutter/flutter/issues/108106 final android_webview.WebView Function({ required bool useHybridComposition, }) createAndroidWebView; /// Constructs a [android_webview.WebChromeClient]. 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]. final android_webview.WebViewClient Function({ void Function(android_webview.WebView webView, String url)? onPageStarted, void Function(android_webview.WebView webView, String url)? onPageFinished, void Function( android_webview.WebView webView, android_webview.WebResourceRequest request, android_webview.WebResourceError error, )? onReceivedRequestError, @Deprecated('Only called on Android version < 23.') void Function( android_webview.WebView webView, int errorCode, String description, String failingUrl, )? onReceivedError, void Function( android_webview.WebView webView, android_webview.WebResourceRequest request, )? requestLoading, void Function(android_webview.WebView webView, String url)? urlLoading, }) createAndroidWebViewClient; /// Constructs a [android_webview.FlutterAssetManager]. final android_webview.FlutterAssetManager Function() createFlutterAssetManager; /// Constructs a [android_webview.JavaScriptChannel]. final android_webview.JavaScriptChannel Function( String channelName, { required void Function(String) postMessage, }) createJavaScriptChannel; /// Constructs a [android_webview.DownloadListener]. final android_webview.DownloadListener Function({ required void Function( String url, String userAgent, String contentDisposition, String mimetype, int contentLength, ) onDownloadStart, }) createDownloadListener; /// Enables debugging of web contents (HTML / CSS / JavaScript) loaded into any WebViews of this application. /// /// This flag can be enabled in order to facilitate debugging of web layouts /// and JavaScript code running inside WebViews. Please refer to /// [android_webview.WebView] documentation for the debugging guide. The /// default is false. /// /// See [android_webview.WebView].setWebContentsDebuggingEnabled. Future setWebContentsDebuggingEnabled(bool enabled) { return android_webview.WebView.setWebContentsDebuggingEnabled(enabled); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(bparrishMines): Replace unused callback methods in constructors with // variables once automatic garbage collection is fully implemented. See // https://github.com/flutter/flutter/issues/107199. // ignore_for_file: avoid_unused_constructor_parameters // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart' show BinaryMessenger; import 'package:flutter/widgets.dart' show AndroidViewSurface; 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. class JavaObject with Copyable { /// Constructs a [JavaObject] without creating the associated Java object. /// /// This should only be used by subclasses created by this library or to /// create copies. JavaObject.detached({ BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, }) : _api = JavaObjectHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ); /// Global instance of [InstanceManager]. static final InstanceManager globalInstanceManager = InstanceManager( onWeakReferenceRemoved: (int identifier) { JavaObjectHostApiImpl().dispose(identifier); }, ); /// Pigeon Host Api implementation for [JavaObject]. final JavaObjectHostApiImpl _api; /// Release the reference to a native Java instance. static void dispose(JavaObject instance) { instance._api.instanceManager.removeWeakReference(instance); } @override JavaObject copy() { return JavaObject.detached(); } } /// An Android View that displays web pages. /// /// **Basic usage** /// In most cases, we recommend using a standard web browser, like Chrome, to /// deliver content to the user. To learn more about web browsers, read the /// guide on invoking a browser with /// [url_launcher](https://pub.dev/packages/url_launcher). /// /// WebView objects allow you to display web content as part of your widget /// layout, but lack some of the features of fully-developed browsers. A WebView /// is useful when you need increased control over the UI and advanced /// configuration options that will allow you to embed web pages in a /// specially-designed environment for your app. /// /// To learn more about WebView and alternatives for serving web content, read /// the documentation on /// [Web-based content](https://developer.android.com/guide/webapps). /// /// When a [WebView] is no longer needed [release] must be called. class WebView extends JavaObject { /// Constructs a new WebView. /// /// Due to changes in Flutter 3.0 the [useHybridComposition] doesn't have /// any effect and should not be exposed publicly. More info here: /// https://github.com/flutter/flutter/issues/108106 WebView({this.useHybridComposition = false}) : super.detached() { api.createFromInstance(this); } /// Constructs a [WebView] without creating the associated Java object. /// /// This should only be used by subclasses created by this library or to /// create copies. WebView.detached({this.useHybridComposition = false}) : super.detached(); /// Pigeon Host Api implementation for [WebView]. @visibleForTesting static WebViewHostApiImpl api = WebViewHostApiImpl(); /// Whether the [WebView] will be rendered with an [AndroidViewSurface]. /// /// This implementation uses hybrid composition to render the WebView Widget. /// This comes at the cost of some performance on Android versions below 10. /// See /// https://flutter.dev/docs/development/platform-integration/platform-views#performance /// for more information. /// /// Defaults to false. final bool useHybridComposition; /// The [WebSettings] object used to control the settings for this WebView. late final WebSettings settings = WebSettings(this); /// Enables debugging of web contents (HTML / CSS / JavaScript) loaded into any WebViews of this application. /// /// This flag can be enabled in order to facilitate debugging of web layouts /// and JavaScript code running inside WebViews. Please refer to [WebView] /// documentation for the debugging guide. The default is false. static Future setWebContentsDebuggingEnabled(bool enabled) { return api.setWebContentsDebuggingEnabled(enabled); } /// Loads the given data into this WebView using a 'data' scheme URL. /// /// Note that JavaScript's same origin policy means that script running in a /// page loaded using this method will be unable to access content loaded /// using any scheme other than 'data', including 'http(s)'. To avoid this /// restriction, use [loadDataWithBaseURL()] with an appropriate base URL. /// /// The [encoding] parameter specifies whether the data is base64 or URL /// encoded. If the data is base64 encoded, the value of the encoding /// parameter must be `'base64'`. HTML can be encoded with /// `base64.encode(bytes)` like so: /// ```dart /// import 'dart:convert'; /// /// final unencodedHtml = ''' /// '%28' is the code for '(' /// '''; /// final encodedHtml = base64.encode(utf8.encode(unencodedHtml)); /// print(encodedHtml); /// ``` /// /// The [mimeType] parameter specifies the format of the data. If WebView /// can't handle the specified MIME type, it will download the data. If /// `null`, defaults to 'text/html'. Future loadData({ required String data, String? mimeType, String? encoding, }) { return api.loadDataFromInstance( this, data, mimeType, encoding, ); } /// Loads the given data into this WebView. /// /// The [baseUrl] is used as base URL for the content. It is used both to /// resolve relative URLs and when applying JavaScript's same origin policy. /// /// The [historyUrl] is used for the history entry. /// /// The [mimeType] parameter specifies the format of the data. If WebView /// can't handle the specified MIME type, it will download the data. If /// `null`, defaults to 'text/html'. /// /// Note that content specified in this way can access local device files (via /// 'file' scheme URLs) only if baseUrl specifies a scheme other than 'http', /// 'https', 'ftp', 'ftps', 'about' or 'javascript'. /// /// If the base URL uses the data scheme, this method is equivalent to calling /// [loadData] and the [historyUrl] is ignored, and the data will be treated /// as part of a data: URL, including the requirement that the content be /// URL-encoded or base64 encoded. If the base URL uses any other scheme, then /// the data will be loaded into the WebView as a plain string (i.e. not part /// of a data URL) and any URL-encoded entities in the string will not be /// decoded. /// /// Note that the [baseUrl] is sent in the 'Referer' HTTP header when /// requesting subresources (images, etc.) of the page loaded using this /// method. /// /// If a valid HTTP or HTTPS base URL is not specified in [baseUrl], then /// content loaded using this method will have a `window.origin` value of /// `"null"`. This must not be considered to be a trusted origin by the /// application or by any JavaScript code running inside the WebView (for /// example, event sources in DOM event handlers or web messages), because /// malicious content can also create frames with a null origin. If you need /// to identify the main frame's origin in a trustworthy way, you should use a /// valid HTTP or HTTPS base URL to set the origin. Future loadDataWithBaseUrl({ String? baseUrl, required String data, String? mimeType, String? encoding, String? historyUrl, }) { return api.loadDataWithBaseUrlFromInstance( this, baseUrl, data, mimeType, encoding, historyUrl, ); } /// Loads the given URL with additional HTTP headers, specified as a map from name to value. /// /// Note that if this map contains any of the headers that are set by default /// by this WebView, such as those controlling caching, accept types or the /// User-Agent, their values may be overridden by this WebView's defaults. /// /// Also see compatibility note on [evaluateJavascript]. Future loadUrl(String url, Map headers) { return api.loadUrlFromInstance(this, url, headers); } /// Loads the URL with postData using "POST" method into this WebView. /// /// If url is not a network URL, it will be loaded with [loadUrl] instead, ignoring the postData param. Future postUrl(String url, Uint8List data) { return api.postUrlFromInstance(this, url, data); } /// Gets the URL for the current page. /// /// This is not always the same as the URL passed to /// [WebViewClient.onPageStarted] because although the load for that URL has /// begun, the current page may not have changed. /// /// Returns null if no page has been loaded. Future getUrl() { return api.getUrlFromInstance(this); } /// Whether this WebView has a back history item. Future canGoBack() { return api.canGoBackFromInstance(this); } /// Whether this WebView has a forward history item. Future canGoForward() { return api.canGoForwardFromInstance(this); } /// Goes back in the history of this WebView. Future goBack() { return api.goBackFromInstance(this); } /// Goes forward in the history of this WebView. Future goForward() { return api.goForwardFromInstance(this); } /// Reloads the current URL. Future reload() { return api.reloadFromInstance(this); } /// Clears the resource cache. /// /// Note that the cache is per-application, so this will clear the cache for /// all WebViews used. Future clearCache(bool includeDiskFiles) { return api.clearCacheFromInstance(this, includeDiskFiles); } // TODO(bparrishMines): Update documentation once addJavascriptInterface is added. /// Asynchronously evaluates JavaScript in the context of the currently displayed page. /// /// If non-null, the returned value will be any result returned from that /// execution. /// /// Compatibility note. Applications targeting Android versions N or later, /// JavaScript state from an empty WebView is no longer persisted across /// navigations like [loadUrl]. For example, global variables and functions /// defined before calling [loadUrl]) will not exist in the loaded page. Future evaluateJavascript(String javascriptString) { return api.evaluateJavascriptFromInstance( this, javascriptString, ); } // TODO(bparrishMines): Update documentation when WebViewClient.onReceivedTitle is added. /// Gets the title for the current page. /// /// Returns null if no page has been loaded. Future getTitle() { return api.getTitleFromInstance(this); } // TODO(bparrishMines): Update documentation when onScrollChanged is added. /// Set the scrolled position of your view. Future scrollTo(int x, int y) { return api.scrollToFromInstance(this, x, y); } // TODO(bparrishMines): Update documentation when onScrollChanged is added. /// Move the scrolled position of your view. Future scrollBy(int x, int y) { return api.scrollByFromInstance(this, x, y); } /// Return the scrolled left position of this view. /// /// This is the left edge of the displayed part of your view. You do not /// need to draw any pixels farther left, since those are outside of the frame /// of your view on screen. Future getScrollX() { return api.getScrollXFromInstance(this); } /// Return the scrolled top position of this view. /// /// This is the top edge of the displayed part of your view. You do not need /// to draw any pixels above it, since those are outside of the frame of your /// view on screen. Future getScrollY() { return api.getScrollYFromInstance(this); } /// Returns the X and Y scroll position of this view. Future getScrollPosition() { return api.getScrollPositionFromInstance(this); } /// Sets the [WebViewClient] that will receive various notifications and requests. /// /// This will replace the current handler. Future setWebViewClient(WebViewClient webViewClient) { return api.setWebViewClientFromInstance(this, webViewClient); } /// Injects the supplied [JavascriptChannel] into this WebView. /// /// The object is injected into all frames of the web page, including all the /// iframes, using the supplied name. This allows the object's methods to /// be accessed from JavaScript. /// /// Note that injected objects will not appear in JavaScript until the page is /// next (re)loaded. JavaScript should be enabled before injecting the object. /// For example: /// /// ```dart /// webview.settings.setJavaScriptEnabled(true); /// webView.addJavascriptChannel(JavScriptChannel("injectedObject")); /// webView.loadUrl("about:blank", {}); /// webView.loadUrl("javascript:injectedObject.postMessage("Hello, World!")", {}); /// ``` /// /// **Important** /// * Because the object is exposed to all the frames, any frame could obtain /// the object name and call methods on it. There is no way to tell the /// calling frame's origin from the app side, so the app must not assume that /// the caller is trustworthy unless the app can guarantee that no third party /// content is ever loaded into the WebView even inside an iframe. Future addJavaScriptChannel(JavaScriptChannel javaScriptChannel) { JavaScriptChannel.api.createFromInstance(javaScriptChannel); return api.addJavaScriptChannelFromInstance(this, javaScriptChannel); } /// Removes a previously injected [JavaScriptChannel] from this WebView. /// /// Note that the removal will not be reflected in JavaScript until the page /// is next (re)loaded. See [addJavaScriptChannel]. Future removeJavaScriptChannel(JavaScriptChannel javaScriptChannel) { JavaScriptChannel.api.createFromInstance(javaScriptChannel); return api.removeJavaScriptChannelFromInstance(this, javaScriptChannel); } /// Registers the interface to be used when content can not be handled by the rendering engine, and should be downloaded instead. /// /// This will replace the current handler. Future setDownloadListener(DownloadListener? listener) { return api.setDownloadListenerFromInstance(this, listener); } /// Sets the chrome handler. /// /// This is an implementation of [WebChromeClient] for use in handling /// JavaScript dialogs, favicons, titles, and the progress. This will replace /// the current handler. Future setWebChromeClient(WebChromeClient? client) { return api.setWebChromeClientFromInstance(this, client); } /// Sets the background color of this WebView. Future setBackgroundColor(Color color) { return api.setBackgroundColorFromInstance(this, color.value); } @override WebView copy() { return WebView.detached(useHybridComposition: useHybridComposition); } } /// Manages cookies globally for all webviews. class CookieManager { CookieManager._(); static CookieManager? _instance; /// Gets the globally set CookieManager instance. static CookieManager get instance => _instance ??= CookieManager._(); /// Setter for the singleton value, for testing purposes only. @visibleForTesting static set instance(CookieManager value) => _instance = value; /// Pigeon Host Api implementation for [CookieManager]. @visibleForTesting static CookieManagerHostApi api = CookieManagerHostApi(); /// Sets a single cookie (key-value pair) for the given URL. Any existing /// cookie with the same host, path and name will be replaced with the new /// cookie. The cookie being set will be ignored if it is expired. To set /// multiple cookies, your application should invoke this method multiple /// times. /// /// The value parameter must follow the format of the Set-Cookie HTTP /// response header defined by RFC6265bis. This is a key-value pair of the /// form "key=value", optionally followed by a list of cookie attributes /// delimited with semicolons (ex. "key=value; Max-Age=123"). Please consult /// the RFC specification for a list of valid attributes. /// /// Note: if specifying a value containing the "Secure" attribute, url must /// use the "https://" scheme. /// /// Params: /// url – the URL for which the cookie is to be set /// value – the cookie as a string, using the format of the 'Set-Cookie' HTTP response header Future setCookie(String url, String value) => api.setCookie(url, value); /// Removes all cookies. /// /// The returned future resolves to true if any cookies were removed. Future clearCookies() => api.clearCookies(); } /// Manages settings state for a [WebView]. /// /// When a WebView is first created, it obtains a set of default settings. These /// default settings will be returned from any getter call. A WebSettings object /// obtained from [WebView.settings] is tied to the life of the WebView. If a /// WebView has been destroyed, any method call on [WebSettings] will throw an /// Exception. class WebSettings extends JavaObject { /// Constructs a [WebSettings]. /// /// This constructor is only used for testing. An instance should be obtained /// with [WebView.settings]. @visibleForTesting WebSettings(WebView webView) : super.detached() { api.createFromInstance(this, webView); } /// Constructs a [WebSettings] without creating the associated Java object. /// /// This should only be used by subclasses created by this library or to /// create copies. WebSettings.detached() : super.detached(); /// Pigeon Host Api implementation for [WebSettings]. @visibleForTesting static WebSettingsHostApiImpl api = WebSettingsHostApiImpl(); /// Sets whether the DOM storage API is enabled. /// /// The default value is false. Future setDomStorageEnabled(bool flag) { return api.setDomStorageEnabledFromInstance(this, flag); } /// Tells JavaScript to open windows automatically. /// /// This applies to the JavaScript function `window.open()`. The default is /// false. Future setJavaScriptCanOpenWindowsAutomatically(bool flag) { return api.setJavaScriptCanOpenWindowsAutomaticallyFromInstance( this, flag, ); } // TODO(bparrishMines): Update documentation when WebChromeClient.onCreateWindow is added. /// Sets whether the WebView should supports multiple windows. /// /// The default is false. Future setSupportMultipleWindows(bool support) { return api.setSupportMultipleWindowsFromInstance(this, support); } /// Tells the WebView to enable JavaScript execution. /// /// The default is false. Future setJavaScriptEnabled(bool flag) { return api.setJavaScriptEnabledFromInstance(this, flag); } /// Sets the WebView's user-agent string. /// /// If the string is empty, the system default value will be used. Note that /// starting from KITKAT Android version, changing the user-agent while /// loading a web page causes WebView to initiate loading once again. Future setUserAgentString(String? userAgentString) { return api.setUserAgentStringFromInstance(this, userAgentString); } /// Sets whether the WebView requires a user gesture to play media. /// /// The default is true. Future setMediaPlaybackRequiresUserGesture(bool require) { return api.setMediaPlaybackRequiresUserGestureFromInstance(this, require); } // TODO(bparrishMines): Update documentation when WebView.zoomIn and WebView.zoomOut are added. /// Sets whether the WebView should support zooming using its on-screen zoom controls and gestures. /// /// The particular zoom mechanisms that should be used can be set with /// [setBuiltInZoomControls]. /// /// The default is true. Future setSupportZoom(bool support) { return api.setSupportZoomFromInstance(this, support); } /// Sets whether the WebView loads pages in overview mode, that is, zooms out the content to fit on screen by width. /// /// This setting is taken into account when the content width is greater than /// the width of the WebView control, for example, when [setUseWideViewPort] /// is enabled. /// /// The default is false. Future setLoadWithOverviewMode(bool overview) { return api.setLoadWithOverviewModeFromInstance(this, overview); } /// Sets whether the WebView should enable support for the "viewport" HTML meta tag or should use a wide viewport. /// /// When the value of the setting is false, the layout width is always set to /// the width of the WebView control in device-independent (CSS) pixels. When /// the value is true and the page contains the viewport meta tag, the value /// of the width specified in the tag is used. If the page does not contain /// the tag or does not provide a width, then a wide viewport will be used. Future setUseWideViewPort(bool use) { return api.setUseWideViewPortFromInstance(this, use); } // TODO(bparrishMines): Update documentation when ZoomButtonsController is added. /// Sets whether the WebView should display on-screen zoom controls when using the built-in zoom mechanisms. /// /// See [setBuiltInZoomControls]. The default is true. However, on-screen zoom /// controls are deprecated in Android so it's recommended to set this to /// false. Future setDisplayZoomControls(bool enabled) { return api.setDisplayZoomControlsFromInstance(this, enabled); } // TODO(bparrishMines): Update documentation when ZoomButtonsController is added. /// Sets whether the WebView should use its built-in zoom mechanisms. /// /// The built-in zoom mechanisms comprise on-screen zoom controls, which are /// displayed over the WebView's content, and the use of a pinch gesture to /// control zooming. Whether or not these on-screen controls are displayed can /// be set with [setDisplayZoomControls]. The default is false. /// /// The built-in mechanisms are the only currently supported zoom mechanisms, /// so it is recommended that this setting is always enabled. However, /// on-screen zoom controls are deprecated in Android so it's recommended to /// disable [setDisplayZoomControls]. Future setBuiltInZoomControls(bool enabled) { return api.setBuiltInZoomControlsFromInstance(this, enabled); } /// Enables or disables file access within WebView. /// /// This enables or disables file system access only. Assets and resources are /// still accessible using file:///android_asset and file:///android_res. The /// default value is true for apps targeting Build.VERSION_CODES.Q and below, /// and false when targeting Build.VERSION_CODES.R and above. Future setAllowFileAccess(bool enabled) { return api.setAllowFileAccessFromInstance(this, enabled); } @override WebSettings copy() { return WebSettings.detached(); } } /// Exposes a channel to receive calls from javaScript. /// /// See [WebView.addJavaScriptChannel]. class JavaScriptChannel extends JavaObject { /// Constructs a [JavaScriptChannel]. JavaScriptChannel( this.channelName, { required this.postMessage, }) : super.detached() { AndroidWebViewFlutterApis.instance.ensureSetUp(); api.createFromInstance(this); } /// Constructs a [JavaScriptChannel] without creating the associated Java /// object. /// /// This should only be used by subclasses created by this library or to /// create copies. JavaScriptChannel.detached( this.channelName, { required this.postMessage, }) : super.detached(); /// Pigeon Host Api implementation for [JavaScriptChannel]. @visibleForTesting static JavaScriptChannelHostApiImpl api = JavaScriptChannelHostApiImpl(); /// Used to identify this object to receive messages from javaScript. final String channelName; /// Callback method when javaScript calls `postMessage` on the object instance passed. final void Function(String message) postMessage; @override JavaScriptChannel copy() { return JavaScriptChannel.detached(channelName, postMessage: postMessage); } } /// Receive various notifications and requests for [WebView]. class WebViewClient extends JavaObject { /// Constructs a [WebViewClient]. WebViewClient({ this.onPageStarted, this.onPageFinished, this.onReceivedRequestError, @Deprecated('Only called on Android version < 23.') this.onReceivedError, this.requestLoading, this.urlLoading, }) : super.detached() { AndroidWebViewFlutterApis.instance.ensureSetUp(); api.createFromInstance(this); } /// Constructs a [WebViewClient] without creating the associated Java object. /// /// This should only be used by subclasses created by this library or to /// create copies. WebViewClient.detached({ this.onPageStarted, this.onPageFinished, this.onReceivedRequestError, @Deprecated('Only called on Android version < 23.') this.onReceivedError, this.requestLoading, this.urlLoading, }) : super.detached(); /// User authentication failed on server. /// /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_AUTHENTICATION static const int errorAuthentication = -4; /// Malformed URL. /// /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_BAD_URL static const int errorBadUrl = -12; /// Failed to connect to the server. /// /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_CONNECT static const int errorConnect = -6; /// Failed to perform SSL handshake. /// /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_FAILED_SSL_HANDSHAKE static const int errorFailedSslHandshake = -11; /// Generic file error. /// /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_FILE static const int errorFile = -13; /// File not found. /// /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_FILE_NOT_FOUND static const int errorFileNotFound = -14; /// Server or proxy hostname lookup failed. /// /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_HOST_LOOKUP static const int errorHostLookup = -2; /// Failed to read or write to the server. /// /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_IO static const int errorIO = -7; /// User authentication failed on proxy. /// /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_PROXY_AUTHENTICATION static const int errorProxyAuthentication = -5; /// Too many redirects. /// /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_REDIRECT_LOOP static const int errorRedirectLoop = -9; /// Connection timed out. /// /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_TIMEOUT static const int errorTimeout = -8; /// Too many requests during this load. /// /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_TOO_MANY_REQUESTS static const int errorTooManyRequests = -15; /// Generic error. /// /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_UNKNOWN static const int errorUnknown = -1; /// Resource load was canceled by Safe Browsing. /// /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_UNSAFE_RESOURCE static const int errorUnsafeResource = -16; /// Unsupported authentication scheme (not basic or digest). /// /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_UNSUPPORTED_AUTH_SCHEME static const int errorUnsupportedAuthScheme = -3; /// Unsupported URI scheme. /// /// See https://developer.android.com/reference/android/webkit/WebViewClient#ERROR_UNSUPPORTED_SCHEME static const int errorUnsupportedScheme = -10; /// Pigeon Host Api implementation for [WebViewClient]. @visibleForTesting static WebViewClientHostApiImpl api = WebViewClientHostApiImpl(); /// Notify the host application that a page has started loading. /// /// This method is called once for each main frame load so a page with iframes /// or framesets will call onPageStarted one time for the main frame. This /// also means that [onPageStarted] will not be called when the contents of an /// embedded frame changes, i.e. clicking a link whose target is an iframe, it /// will also not be called for fragment navigations (navigations to /// #fragment_id). final void Function(WebView webView, String url)? onPageStarted; // TODO(bparrishMines): Update documentation when WebView.postVisualStateCallback is added. /// Notify the host application that a page has finished loading. /// /// This method is called only for main frame. Receiving an [onPageFinished] /// callback does not guarantee that the next frame drawn by WebView will /// reflect the state of the DOM at this point. final void Function(WebView webView, String url)? onPageFinished; /// Report web resource loading error to the host application. /// /// These errors usually indicate inability to connect to the server. Note /// that unlike the deprecated version of the callback, the new version will /// be called for any resource (iframe, image, etc.), not just for the main /// page. Thus, it is recommended to perform minimum required work in this /// callback. final void Function( WebView webView, WebResourceRequest request, WebResourceError error, )? onReceivedRequestError; /// Report an error to the host application. /// /// These errors are unrecoverable (i.e. the main resource is unavailable). /// The errorCode parameter corresponds to one of the error* constants. @Deprecated('Only called on Android version < 23.') final void Function( WebView webView, int errorCode, String description, String failingUrl, )? onReceivedError; /// When the current [WebView] wants to load a URL. /// /// The value set by [setSynchronousReturnValueForShouldOverrideUrlLoading] /// indicates whether the [WebView] loaded the request. final void Function(WebView webView, WebResourceRequest request)? requestLoading; /// When the current [WebView] wants to load a URL. /// /// The value set by [setSynchronousReturnValueForShouldOverrideUrlLoading] /// indicates whether the [WebView] loaded the URL. final void Function(WebView webView, String url)? urlLoading; /// Sets the required synchronous return value for the Java method, /// `WebViewClient.shouldOverrideUrlLoading(...)`. /// /// The Java method, `WebViewClient.shouldOverrideUrlLoading(...)`, requires /// a boolean to be returned and this method sets the returned value for all /// calls to the Java method. /// /// Setting this to true causes the current [WebView] to abort loading any URL /// received by [requestLoading] or [urlLoading], while setting this to false /// causes the [WebView] to continue loading a URL as usual. /// /// Defaults to false. Future setSynchronousReturnValueForShouldOverrideUrlLoading( bool value, ) { return api.setShouldOverrideUrlLoadingReturnValueFromInstance(this, value); } @override WebViewClient copy() { return WebViewClient.detached( onPageStarted: onPageStarted, onPageFinished: onPageFinished, onReceivedRequestError: onReceivedRequestError, onReceivedError: onReceivedError, requestLoading: requestLoading, urlLoading: urlLoading, ); } } /// The interface to be used when content can not be handled by the rendering /// engine for [WebView], and should be downloaded instead. class DownloadListener extends JavaObject { /// Constructs a [DownloadListener]. DownloadListener({required this.onDownloadStart}) : super.detached() { AndroidWebViewFlutterApis.instance.ensureSetUp(); api.createFromInstance(this); } /// Constructs a [DownloadListener] without creating the associated Java /// object. /// /// This should only be used by subclasses created by this library or to /// create copies. DownloadListener.detached({required this.onDownloadStart}) : super.detached(); /// Pigeon Host Api implementation for [DownloadListener]. @visibleForTesting static DownloadListenerHostApiImpl api = DownloadListenerHostApiImpl(); /// Notify the host application that a file should be downloaded. final void Function( String url, String userAgent, String contentDisposition, String mimetype, int contentLength, ) onDownloadStart; @override DownloadListener copy() { return DownloadListener.detached(onDownloadStart: onDownloadStart); } } /// Handles JavaScript dialogs, favicons, titles, and the progress for [WebView]. class WebChromeClient extends JavaObject { /// Constructs a [WebChromeClient]. WebChromeClient({this.onProgressChanged, this.onShowFileChooser}) : super.detached() { AndroidWebViewFlutterApis.instance.ensureSetUp(); api.createFromInstance(this); } /// Constructs a [WebChromeClient] without creating the associated Java /// object. /// /// This should only be used by subclasses created by this library or to /// create copies. WebChromeClient.detached({ this.onProgressChanged, this.onShowFileChooser, }) : super.detached(); /// Pigeon Host Api implementation for [WebChromeClient]. @visibleForTesting static WebChromeClientHostApiImpl api = WebChromeClientHostApiImpl(); /// 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, 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, ); } } /// Encompasses parameters to the [WebViewClient.requestLoading] method. class WebResourceRequest { /// Constructs a [WebResourceRequest]. WebResourceRequest({ required this.url, required this.isForMainFrame, required this.isRedirect, required this.hasGesture, required this.method, required this.requestHeaders, }); /// The URL for which the resource request was made. final String url; /// Whether the request was made in order to fetch the main frame's document. final bool isForMainFrame; /// Whether the request was a result of a server-side redirect. /// /// Only supported on Android version >= 24. final bool? isRedirect; /// Whether a gesture (such as a click) was associated with the request. final bool hasGesture; /// The method associated with the request, for example "GET". final String method; /// The headers associated with the request. final Map requestHeaders; } /// Encapsulates information about errors occurred during loading of web resources. /// /// See [WebViewClient.onReceivedRequestError]. class WebResourceError { /// Constructs a [WebResourceError]. WebResourceError({ required this.errorCode, required this.description, }); /// The integer code of the error (e.g. [WebViewClient.errorAuthentication]. final int errorCode; /// Describes the error. final String description; } /// Manages Flutter assets that are part of Android's app bundle. class FlutterAssetManager { /// Constructs the [FlutterAssetManager]. const FlutterAssetManager(); /// Pigeon Host Api implementation for [FlutterAssetManager]. @visibleForTesting static FlutterAssetManagerHostApi api = FlutterAssetManagerHostApi(); /// Lists all assets at the given path. /// /// The assets are returned as a `List`. The `List` only /// contains files which are direct childs Future> list(String path) => api.list(path); /// Gets the relative file path to the Flutter asset with the given name. Future getAssetFilePathByName(String name) => api.getAssetFilePathByName(name); } /// Manages the JavaScript storage APIs provided by the [WebView]. /// /// Wraps [WebStorage](https://developer.android.com/reference/android/webkit/WebStorage). class WebStorage extends JavaObject { /// Constructs a [WebStorage]. /// /// This constructor is only used for testing. An instance should be obtained /// with [WebStorage.instance]. @visibleForTesting WebStorage() : super.detached() { AndroidWebViewFlutterApis.instance.ensureSetUp(); api.createFromInstance(this); } /// Constructs a [WebStorage] without creating the associated Java object. /// /// This should only be used by subclasses created by this library or to /// create copies. WebStorage.detached() : super.detached(); /// Pigeon Host Api implementation for [WebStorage]. @visibleForTesting static WebStorageHostApiImpl api = WebStorageHostApiImpl(); /// The singleton instance of this class. static WebStorage instance = WebStorage(); /// Clears all storage currently being used by the JavaScript storage APIs. Future deleteAllData() { return api.deleteAllDataFromInstance(this); } @override WebStorage copy() { return WebStorage.detached(); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart ================================================ // Copyright 2013 The Flutter 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'; /// 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, required this.isForMainFrame, this.isRedirect, required this.hasGesture, required this.method, required this.requestHeaders, }); String url; bool isForMainFrame; bool? isRedirect; bool hasGesture; String method; Map requestHeaders; Object encode() { return [ url, isForMainFrame, isRedirect, hasGesture, method, requestHeaders, ]; } static WebResourceRequestData decode(Object result) { result as List; return WebResourceRequestData( 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(), ); } } class WebResourceErrorData { WebResourceErrorData({ required this.errorCode, required this.description, }); int errorCode; String description; Object encode() { return [ errorCode, description, ]; } static WebResourceErrorData decode(Object result) { result as List; return WebResourceErrorData( errorCode: result[0]! as int, description: result[1]! as String, ); } } class WebViewPoint { WebViewPoint({ required this.x, required this.y, }); int x; int y; Object encode() { return [ x, y, ]; } static WebViewPoint decode(Object result) { result as List; return WebViewPoint( x: result[0]! as int, y: result[1]! as int, ); } } /// Handles methods calls to the native Java Object class. /// /// Also handles calls to remove the reference to an instance with `dispose`. /// /// See https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html. class JavaObjectHostApi { /// Constructor for [JavaObjectHostApi]. 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. JavaObjectHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = StandardMessageCodec(); Future dispose(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.JavaObjectHostApi.dispose', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } } /// Handles callbacks methods for the native Java Object class. /// /// See https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html. abstract class JavaObjectFlutterApi { static const MessageCodec codec = StandardMessageCodec(); void dispose(int identifier); static void setup(JavaObjectFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.JavaObjectFlutterApi.dispose', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.JavaObjectFlutterApi.dispose 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.JavaObjectFlutterApi.dispose was null, expected non-null int.'); api.dispose(arg_identifier!); return; }); } } } } class CookieManagerHostApi { /// Constructor for [CookieManagerHostApi]. 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. CookieManagerHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = StandardMessageCodec(); Future clearCookies() async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.CookieManagerHostApi.clearCookies', 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?)!; } } Future setCookie(String arg_url, String arg_value) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.CookieManagerHostApi.setCookie', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } } class _WebViewHostApiCodec extends StandardMessageCodec { const _WebViewHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is WebViewPoint) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return WebViewPoint.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } class WebViewHostApi { /// Constructor for [WebViewHostApi]. 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. WebViewHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = _WebViewHostApiCodec(); Future create(int arg_instanceId, bool arg_useHybridComposition) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.create', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_instanceId, arg_useHybridComposition]) 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 loadData(int arg_instanceId, String arg_data, String? arg_mimeType, String? arg_encoding) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.loadData', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send( [arg_instanceId, arg_data, arg_mimeType, arg_encoding]) 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 loadDataWithBaseUrl( int arg_instanceId, String? arg_baseUrl, String arg_data, String? arg_mimeType, String? arg_encoding, String? arg_historyUrl) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.loadDataWithBaseUrl', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([ arg_instanceId, arg_baseUrl, arg_data, arg_mimeType, arg_encoding, arg_historyUrl ]) 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 loadUrl(int arg_instanceId, String arg_url, Map arg_headers) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.loadUrl', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_instanceId, arg_url, arg_headers]) 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 postUrl( int arg_instanceId, String arg_url, Uint8List arg_data) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.postUrl', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future getUrl(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getUrl', codec, binaryMessenger: _binaryMessenger); 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 (replyList[0] as String?); } } Future canGoBack(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.canGoBack', codec, binaryMessenger: _binaryMessenger); 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 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 canGoForward(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.canGoForward', codec, binaryMessenger: _binaryMessenger); 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 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 goBack(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.goBack', codec, binaryMessenger: _binaryMessenger); 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 goForward(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.goForward', codec, binaryMessenger: _binaryMessenger); 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 reload(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.reload', codec, binaryMessenger: _binaryMessenger); 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 clearCache(int arg_instanceId, bool arg_includeDiskFiles) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.clearCache', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_instanceId, arg_includeDiskFiles]) 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 evaluateJavascript( int arg_instanceId, String arg_javascriptString) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.evaluateJavascript', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_instanceId, arg_javascriptString]) 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?); } } Future getTitle(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getTitle', codec, binaryMessenger: _binaryMessenger); 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 (replyList[0] as String?); } } Future scrollTo(int arg_instanceId, int arg_x, int arg_y) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.scrollTo', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future scrollBy(int arg_instanceId, int arg_x, int arg_y) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.scrollBy', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future getScrollX(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getScrollX', codec, binaryMessenger: _binaryMessenger); 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 if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { return (replyList[0] as int?)!; } } Future getScrollY(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getScrollY', codec, binaryMessenger: _binaryMessenger); 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 if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { return (replyList[0] as int?)!; } } Future getScrollPosition(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getScrollPosition', codec, binaryMessenger: _binaryMessenger); 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 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 WebViewPoint?)!; } } Future setWebContentsDebuggingEnabled(bool arg_enabled) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.setWebContentsDebuggingEnabled', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future setWebViewClient( int arg_instanceId, int arg_webViewClientInstanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.setWebViewClient', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_instanceId, arg_webViewClientInstanceId]) 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 addJavaScriptChannel( int arg_instanceId, int arg_javaScriptChannelInstanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.addJavaScriptChannel', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_instanceId, arg_javaScriptChannelInstanceId]) 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 removeJavaScriptChannel( int arg_instanceId, int arg_javaScriptChannelInstanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.removeJavaScriptChannel', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_instanceId, arg_javaScriptChannelInstanceId]) 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 setDownloadListener( int arg_instanceId, int? arg_listenerInstanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.setDownloadListener', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_instanceId, arg_listenerInstanceId]) 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 setWebChromeClient( int arg_instanceId, int? arg_clientInstanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.setWebChromeClient', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_instanceId, arg_clientInstanceId]) 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 setBackgroundColor(int arg_instanceId, int arg_color) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.setBackgroundColor', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } } class WebSettingsHostApi { /// Constructor for [WebSettingsHostApi]. 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. WebSettingsHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = StandardMessageCodec(); Future create(int arg_instanceId, int arg_webViewInstanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.create', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_instanceId, arg_webViewInstanceId]) 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 setDomStorageEnabled(int arg_instanceId, bool arg_flag) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setDomStorageEnabled', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future setJavaScriptCanOpenWindowsAutomatically( int arg_instanceId, bool arg_flag) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptCanOpenWindowsAutomatically', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future setSupportMultipleWindows( int arg_instanceId, bool arg_support) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setSupportMultipleWindows', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future setJavaScriptEnabled(int arg_instanceId, bool arg_flag) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptEnabled', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future setUserAgentString( int arg_instanceId, String? arg_userAgentString) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setUserAgentString', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future setMediaPlaybackRequiresUserGesture( int arg_instanceId, bool arg_require) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setMediaPlaybackRequiresUserGesture', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future setSupportZoom(int arg_instanceId, bool arg_support) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setSupportZoom', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future setLoadWithOverviewMode( int arg_instanceId, bool arg_overview) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setLoadWithOverviewMode', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future setUseWideViewPort(int arg_instanceId, bool arg_use) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setUseWideViewPort', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future setDisplayZoomControls( int arg_instanceId, bool arg_enabled) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setDisplayZoomControls', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future setBuiltInZoomControls( int arg_instanceId, bool arg_enabled) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setBuiltInZoomControls', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future setAllowFileAccess(int arg_instanceId, bool arg_enabled) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } } class JavaScriptChannelHostApi { /// Constructor for [JavaScriptChannelHostApi]. 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. JavaScriptChannelHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = StandardMessageCodec(); Future create(int arg_instanceId, String arg_channelName) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.JavaScriptChannelHostApi.create', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } } abstract class JavaScriptChannelFlutterApi { static const MessageCodec codec = StandardMessageCodec(); void postMessage(int instanceId, String message); static void setup(JavaScriptChannelFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.JavaScriptChannelFlutterApi.postMessage', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.JavaScriptChannelFlutterApi.postMessage 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.JavaScriptChannelFlutterApi.postMessage was null, expected non-null int.'); final String? arg_message = (args[1] as String?); assert(arg_message != null, 'Argument for dev.flutter.pigeon.JavaScriptChannelFlutterApi.postMessage was null, expected non-null String.'); api.postMessage(arg_instanceId!, arg_message!); return; }); } } } } class WebViewClientHostApi { /// Constructor for [WebViewClientHostApi]. 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. WebViewClientHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = StandardMessageCodec(); Future create(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewClientHostApi.create', codec, binaryMessenger: _binaryMessenger); 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 setSynchronousReturnValueForShouldOverrideUrlLoading( int arg_instanceId, bool arg_value) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewClientHostApi.setSynchronousReturnValueForShouldOverrideUrlLoading', 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } } class _WebViewClientFlutterApiCodec extends StandardMessageCodec { const _WebViewClientFlutterApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is WebResourceErrorData) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is WebResourceRequestData) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return WebResourceErrorData.decode(readValue(buffer)!); case 129: return WebResourceRequestData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } 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}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewClientFlutterApi.onPageStarted', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onPageStarted 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.WebViewClientFlutterApi.onPageStarted was null, expected non-null int.'); final int? arg_webViewInstanceId = (args[1] as int?); assert(arg_webViewInstanceId != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onPageStarted was null, expected non-null int.'); final String? arg_url = (args[2] as String?); assert(arg_url != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onPageStarted was null, expected non-null String.'); api.onPageStarted(arg_instanceId!, arg_webViewInstanceId!, arg_url!); return; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewClientFlutterApi.onPageFinished', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onPageFinished 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.WebViewClientFlutterApi.onPageFinished was null, expected non-null int.'); final int? arg_webViewInstanceId = (args[1] as int?); assert(arg_webViewInstanceId != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onPageFinished was null, expected non-null int.'); final String? arg_url = (args[2] as String?); assert(arg_url != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onPageFinished was null, expected non-null String.'); api.onPageFinished(arg_instanceId!, arg_webViewInstanceId!, arg_url!); return; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedRequestError', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedRequestError 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.WebViewClientFlutterApi.onReceivedRequestError was null, expected non-null int.'); final int? arg_webViewInstanceId = (args[1] as int?); assert(arg_webViewInstanceId != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedRequestError was null, expected non-null int.'); final WebResourceRequestData? arg_request = (args[2] as WebResourceRequestData?); assert(arg_request != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedRequestError was null, expected non-null WebResourceRequestData.'); final WebResourceErrorData? arg_error = (args[3] as WebResourceErrorData?); assert(arg_error != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedRequestError was null, expected non-null WebResourceErrorData.'); api.onReceivedRequestError(arg_instanceId!, arg_webViewInstanceId!, arg_request!, arg_error!); return; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedError', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedError 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.WebViewClientFlutterApi.onReceivedError was null, expected non-null int.'); final int? arg_webViewInstanceId = (args[1] as int?); assert(arg_webViewInstanceId != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedError was null, expected non-null int.'); final int? arg_errorCode = (args[2] as int?); assert(arg_errorCode != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedError was null, expected non-null int.'); final String? arg_description = (args[3] as String?); assert(arg_description != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedError was null, expected non-null String.'); final String? arg_failingUrl = (args[4] as String?); assert(arg_failingUrl != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedError was null, expected non-null String.'); api.onReceivedError(arg_instanceId!, arg_webViewInstanceId!, arg_errorCode!, arg_description!, arg_failingUrl!); return; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewClientFlutterApi.requestLoading', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.requestLoading 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.WebViewClientFlutterApi.requestLoading was null, expected non-null int.'); final int? arg_webViewInstanceId = (args[1] as int?); assert(arg_webViewInstanceId != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.requestLoading was null, expected non-null int.'); final WebResourceRequestData? arg_request = (args[2] as WebResourceRequestData?); assert(arg_request != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.requestLoading was null, expected non-null WebResourceRequestData.'); api.requestLoading( arg_instanceId!, arg_webViewInstanceId!, arg_request!); return; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewClientFlutterApi.urlLoading', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.urlLoading 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.WebViewClientFlutterApi.urlLoading was null, expected non-null int.'); final int? arg_webViewInstanceId = (args[1] as int?); assert(arg_webViewInstanceId != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.urlLoading was null, expected non-null int.'); final String? arg_url = (args[2] as String?); assert(arg_url != null, 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.urlLoading was null, expected non-null String.'); api.urlLoading(arg_instanceId!, arg_webViewInstanceId!, arg_url!); return; }); } } } } class DownloadListenerHostApi { /// Constructor for [DownloadListenerHostApi]. 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. DownloadListenerHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = StandardMessageCodec(); Future create(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.DownloadListenerHostApi.create', codec, binaryMessenger: _binaryMessenger); 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; } } } abstract class DownloadListenerFlutterApi { static const MessageCodec codec = StandardMessageCodec(); void onDownloadStart(int instanceId, String url, String userAgent, String contentDisposition, String mimetype, int contentLength); static void setup(DownloadListenerFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.DownloadListenerFlutterApi.onDownloadStart', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.DownloadListenerFlutterApi.onDownloadStart 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.DownloadListenerFlutterApi.onDownloadStart was null, expected non-null int.'); final String? arg_url = (args[1] as String?); assert(arg_url != null, 'Argument for dev.flutter.pigeon.DownloadListenerFlutterApi.onDownloadStart was null, expected non-null String.'); final String? arg_userAgent = (args[2] as String?); assert(arg_userAgent != null, 'Argument for dev.flutter.pigeon.DownloadListenerFlutterApi.onDownloadStart was null, expected non-null String.'); final String? arg_contentDisposition = (args[3] as String?); assert(arg_contentDisposition != null, 'Argument for dev.flutter.pigeon.DownloadListenerFlutterApi.onDownloadStart was null, expected non-null String.'); final String? arg_mimetype = (args[4] as String?); assert(arg_mimetype != null, 'Argument for dev.flutter.pigeon.DownloadListenerFlutterApi.onDownloadStart was null, expected non-null String.'); final int? arg_contentLength = (args[5] as int?); assert(arg_contentLength != null, 'Argument for dev.flutter.pigeon.DownloadListenerFlutterApi.onDownloadStart was null, expected non-null int.'); api.onDownloadStart(arg_instanceId!, arg_url!, arg_userAgent!, arg_contentDisposition!, arg_mimetype!, arg_contentLength!); return; }); } } } } class WebChromeClientHostApi { /// Constructor for [WebChromeClientHostApi]. 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. WebChromeClientHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = StandardMessageCodec(); Future create(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebChromeClientHostApi.create', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } } class FlutterAssetManagerHostApi { /// Constructor for [FlutterAssetManagerHostApi]. 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. FlutterAssetManagerHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = StandardMessageCodec(); Future> list(String arg_path) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.FlutterAssetManagerHostApi.list', codec, binaryMessenger: _binaryMessenger); 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 (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(); } } Future getAssetFilePathByName(String arg_name) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName', codec, binaryMessenger: _binaryMessenger); 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 (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { return (replyList[0] as String?)!; } } } abstract class 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}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebChromeClientFlutterApi.onProgressChanged', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebChromeClientFlutterApi.onProgressChanged 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.onProgressChanged was null, expected non-null int.'); final int? arg_webViewInstanceId = (args[1] as int?); assert(arg_webViewInstanceId != null, 'Argument for dev.flutter.pigeon.WebChromeClientFlutterApi.onProgressChanged was null, expected non-null int.'); final int? arg_progress = (args[2] as int?); assert(arg_progress != null, 'Argument for dev.flutter.pigeon.WebChromeClientFlutterApi.onProgressChanged was null, expected non-null int.'); api.onProgressChanged( arg_instanceId!, arg_webViewInstanceId!, arg_progress!); return; }); } } { 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; }); } } } } class WebStorageHostApi { /// Constructor for [WebStorageHostApi]. 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. WebStorageHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = StandardMessageCodec(); Future create(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebStorageHostApi.create', codec, binaryMessenger: _binaryMessenger); 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 deleteAllData(int arg_instanceId) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebStorageHostApi.deleteAllData', codec, binaryMessenger: _binaryMessenger); 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; } } } 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; }); } } } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#106316) // ignore: unnecessary_import import 'dart:typed_data'; import 'dart:ui'; import 'package:flutter/services.dart' show BinaryMessenger; import 'android_webview.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( url: data.url, isForMainFrame: data.isForMainFrame, isRedirect: data.isRedirect, hasGesture: data.hasGesture, method: data.method, requestHeaders: data.requestHeaders.cast(), ); } /// Converts [WebResourceErrorData] to [WebResourceError]. WebResourceError _toWebResourceError(WebResourceErrorData data) { return WebResourceError( errorCode: data.errorCode, description: data.description, ); } /// Handles initialization of Flutter APIs for Android WebView. class AndroidWebViewFlutterApis { /// Creates a [AndroidWebViewFlutterApis]. AndroidWebViewFlutterApis({ JavaObjectFlutterApiImpl? javaObjectFlutterApi, DownloadListenerFlutterApiImpl? downloadListenerFlutterApi, WebViewClientFlutterApiImpl? webViewClientFlutterApi, WebChromeClientFlutterApiImpl? webChromeClientFlutterApi, JavaScriptChannelFlutterApiImpl? javaScriptChannelFlutterApi, FileChooserParamsFlutterApiImpl? fileChooserParamsFlutterApi, }) { this.javaObjectFlutterApi = javaObjectFlutterApi ?? JavaObjectFlutterApiImpl(); this.downloadListenerFlutterApi = downloadListenerFlutterApi ?? DownloadListenerFlutterApiImpl(); this.webViewClientFlutterApi = webViewClientFlutterApi ?? WebViewClientFlutterApiImpl(); this.webChromeClientFlutterApi = webChromeClientFlutterApi ?? WebChromeClientFlutterApiImpl(); this.javaScriptChannelFlutterApi = javaScriptChannelFlutterApi ?? JavaScriptChannelFlutterApiImpl(); this.fileChooserParamsFlutterApi = fileChooserParamsFlutterApi ?? FileChooserParamsFlutterApiImpl(); } static bool _haveBeenSetUp = false; /// Mutable instance containing all Flutter Apis for Android WebView. /// /// This should only be changed for testing purposes. static AndroidWebViewFlutterApis instance = AndroidWebViewFlutterApis(); /// Handles callbacks methods for the native Java Object class. late final JavaObjectFlutterApi javaObjectFlutterApi; /// Flutter Api for [DownloadListener]. late final DownloadListenerFlutterApiImpl downloadListenerFlutterApi; /// Flutter Api for [WebViewClient]. late final WebViewClientFlutterApiImpl webViewClientFlutterApi; /// Flutter Api for [WebChromeClient]. late final WebChromeClientFlutterApiImpl webChromeClientFlutterApi; /// 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) { JavaObjectFlutterApi.setup(javaObjectFlutterApi); DownloadListenerFlutterApi.setup(downloadListenerFlutterApi); WebViewClientFlutterApi.setup(webViewClientFlutterApi); WebChromeClientFlutterApi.setup(webChromeClientFlutterApi); JavaScriptChannelFlutterApi.setup(javaScriptChannelFlutterApi); FileChooserParamsFlutterApi.setup(fileChooserParamsFlutterApi); _haveBeenSetUp = true; } } } /// Handles methods calls to the native Java Object class. class JavaObjectHostApiImpl extends JavaObjectHostApi { /// Constructs a [JavaObjectHostApiImpl]. JavaObjectHostApiImpl({ this.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager, 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; /// Maintains instances stored to communicate with native language objects. final InstanceManager instanceManager; } /// Handles callbacks methods for the native Java Object class. class JavaObjectFlutterApiImpl implements JavaObjectFlutterApi { /// Constructs a [JavaObjectFlutterApiImpl]. JavaObjectFlutterApiImpl({InstanceManager? instanceManager}) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with native language objects. final InstanceManager instanceManager; @override void dispose(int identifier) { instanceManager.remove(identifier); } } /// Host api implementation for [WebView]. class WebViewHostApiImpl extends WebViewHostApi { /// Constructs a [WebViewHostApiImpl]. WebViewHostApiImpl({ super.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; /// Helper method to convert instances ids to objects. Future createFromInstance(WebView instance) { return create( instanceManager.addDartCreatedInstance(instance), instance.useHybridComposition, ); } /// Helper method to convert the instances ids to objects. Future loadDataFromInstance( WebView instance, String data, String? mimeType, String? encoding, ) { return loadData( instanceManager.getIdentifier(instance)!, data, mimeType, encoding, ); } /// Helper method to convert instances ids to objects. Future loadDataWithBaseUrlFromInstance( WebView instance, String? baseUrl, String data, String? mimeType, String? encoding, String? historyUrl, ) { return loadDataWithBaseUrl( instanceManager.getIdentifier(instance)!, baseUrl, data, mimeType, encoding, historyUrl, ); } /// Helper method to convert instances ids to objects. Future loadUrlFromInstance( WebView instance, String url, Map headers, ) { return loadUrl(instanceManager.getIdentifier(instance)!, url, headers); } /// Helper method to convert instances ids to objects. Future postUrlFromInstance( WebView instance, String url, Uint8List data, ) { return postUrl(instanceManager.getIdentifier(instance)!, url, data); } /// Helper method to convert instances ids to objects. Future getUrlFromInstance(WebView instance) { return getUrl(instanceManager.getIdentifier(instance)!); } /// Helper method to convert instances ids to objects. Future canGoBackFromInstance(WebView instance) { return canGoBack(instanceManager.getIdentifier(instance)!); } /// Helper method to convert instances ids to objects. Future canGoForwardFromInstance(WebView instance) { return canGoForward(instanceManager.getIdentifier(instance)!); } /// Helper method to convert instances ids to objects. Future goBackFromInstance(WebView instance) { return goBack(instanceManager.getIdentifier(instance)!); } /// Helper method to convert instances ids to objects. Future goForwardFromInstance(WebView instance) { return goForward(instanceManager.getIdentifier(instance)!); } /// Helper method to convert instances ids to objects. Future reloadFromInstance(WebView instance) { return reload(instanceManager.getIdentifier(instance)!); } /// Helper method to convert instances ids to objects. Future clearCacheFromInstance(WebView instance, bool includeDiskFiles) { return clearCache( instanceManager.getIdentifier(instance)!, includeDiskFiles, ); } /// Helper method to convert instances ids to objects. Future evaluateJavascriptFromInstance( WebView instance, String javascriptString, ) { return evaluateJavascript( instanceManager.getIdentifier(instance)!, javascriptString, ); } /// Helper method to convert instances ids to objects. Future getTitleFromInstance(WebView instance) { return getTitle(instanceManager.getIdentifier(instance)!); } /// Helper method to convert instances ids to objects. Future scrollToFromInstance(WebView instance, int x, int y) { return scrollTo(instanceManager.getIdentifier(instance)!, x, y); } /// Helper method to convert instances ids to objects. Future scrollByFromInstance(WebView instance, int x, int y) { return scrollBy(instanceManager.getIdentifier(instance)!, x, y); } /// Helper method to convert instances ids to objects. Future getScrollXFromInstance(WebView instance) { return getScrollX(instanceManager.getIdentifier(instance)!); } /// Helper method to convert instances ids to objects. Future getScrollYFromInstance(WebView instance) { return getScrollY(instanceManager.getIdentifier(instance)!); } /// Helper method to convert instances ids to objects. Future getScrollPositionFromInstance(WebView instance) async { final WebViewPoint position = await getScrollPosition(instanceManager.getIdentifier(instance)!); return Offset(position.x.toDouble(), position.y.toDouble()); } /// Helper method to convert instances ids to objects. Future setWebViewClientFromInstance( WebView instance, WebViewClient webViewClient, ) { return setWebViewClient( instanceManager.getIdentifier(instance)!, instanceManager.getIdentifier(webViewClient)!, ); } /// Helper method to convert instances ids to objects. Future addJavaScriptChannelFromInstance( WebView instance, JavaScriptChannel javaScriptChannel, ) { return addJavaScriptChannel( instanceManager.getIdentifier(instance)!, instanceManager.getIdentifier(javaScriptChannel)!, ); } /// Helper method to convert instances ids to objects. Future removeJavaScriptChannelFromInstance( WebView instance, JavaScriptChannel javaScriptChannel, ) { return removeJavaScriptChannel( instanceManager.getIdentifier(instance)!, instanceManager.getIdentifier(javaScriptChannel)!, ); } /// Helper method to convert instances ids to objects. Future setDownloadListenerFromInstance( WebView instance, DownloadListener? listener, ) { return setDownloadListener( instanceManager.getIdentifier(instance)!, listener != null ? instanceManager.getIdentifier(listener) : null, ); } /// Helper method to convert instances ids to objects. Future setWebChromeClientFromInstance( WebView instance, WebChromeClient? client, ) { return setWebChromeClient( instanceManager.getIdentifier(instance)!, client != null ? instanceManager.getIdentifier(client) : null, ); } /// Helper method to convert instances ids to objects. Future setBackgroundColorFromInstance(WebView instance, int color) { return setBackgroundColor(instanceManager.getIdentifier(instance)!, color); } } /// Host api implementation for [WebSettings]. class WebSettingsHostApiImpl extends WebSettingsHostApi { /// Constructs a [WebSettingsHostApiImpl]. WebSettingsHostApiImpl({ super.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; /// Helper method to convert instances ids to objects. Future createFromInstance(WebSettings instance, WebView webView) { return create( instanceManager.addDartCreatedInstance(instance), instanceManager.getIdentifier(webView)!, ); } /// Helper method to convert instances ids to objects. Future setDomStorageEnabledFromInstance( WebSettings instance, bool flag, ) { return setDomStorageEnabled(instanceManager.getIdentifier(instance)!, flag); } /// Helper method to convert instances ids to objects. Future setJavaScriptCanOpenWindowsAutomaticallyFromInstance( WebSettings instance, bool flag, ) { return setJavaScriptCanOpenWindowsAutomatically( instanceManager.getIdentifier(instance)!, flag, ); } /// Helper method to convert instances ids to objects. Future setSupportMultipleWindowsFromInstance( WebSettings instance, bool support, ) { return setSupportMultipleWindows( instanceManager.getIdentifier(instance)!, support); } /// Helper method to convert instances ids to objects. Future setJavaScriptEnabledFromInstance( WebSettings instance, bool flag, ) { return setJavaScriptEnabled( instanceManager.getIdentifier(instance)!, flag, ); } /// Helper method to convert instances ids to objects. Future setUserAgentStringFromInstance( WebSettings instance, String? userAgentString, ) { return setUserAgentString( instanceManager.getIdentifier(instance)!, userAgentString, ); } /// Helper method to convert instances ids to objects. Future setMediaPlaybackRequiresUserGestureFromInstance( WebSettings instance, bool require, ) { return setMediaPlaybackRequiresUserGesture( instanceManager.getIdentifier(instance)!, require, ); } /// Helper method to convert instances ids to objects. Future setSupportZoomFromInstance( WebSettings instance, bool support, ) { return setSupportZoom(instanceManager.getIdentifier(instance)!, support); } /// Helper method to convert instances ids to objects. Future setLoadWithOverviewModeFromInstance( WebSettings instance, bool overview, ) { return setLoadWithOverviewMode( instanceManager.getIdentifier(instance)!, overview, ); } /// Helper method to convert instances ids to objects. Future setUseWideViewPortFromInstance( WebSettings instance, bool use, ) { return setUseWideViewPort(instanceManager.getIdentifier(instance)!, use); } /// Helper method to convert instances ids to objects. Future setDisplayZoomControlsFromInstance( WebSettings instance, bool enabled, ) { return setDisplayZoomControls( instanceManager.getIdentifier(instance)!, enabled, ); } /// Helper method to convert instances ids to objects. Future setBuiltInZoomControlsFromInstance( WebSettings instance, bool enabled, ) { return setBuiltInZoomControls( instanceManager.getIdentifier(instance)!, enabled, ); } /// Helper method to convert instances ids to objects. Future setAllowFileAccessFromInstance( WebSettings instance, bool enabled, ) { return setAllowFileAccess( instanceManager.getIdentifier(instance)!, enabled, ); } } /// Host api implementation for [JavaScriptChannel]. class JavaScriptChannelHostApiImpl extends JavaScriptChannelHostApi { /// Constructs a [JavaScriptChannelHostApiImpl]. JavaScriptChannelHostApiImpl({ super.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; /// Helper method to convert instances ids to objects. Future createFromInstance(JavaScriptChannel instance) async { if (instanceManager.getIdentifier(instance) == null) { final int identifier = instanceManager.addDartCreatedInstance(instance); await create( identifier, instance.channelName, ); } } } /// Flutter api implementation for [JavaScriptChannel]. class JavaScriptChannelFlutterApiImpl extends JavaScriptChannelFlutterApi { /// Constructs a [JavaScriptChannelFlutterApiImpl]. JavaScriptChannelFlutterApiImpl({InstanceManager? instanceManager}) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; @override void postMessage(int instanceId, String message) { final JavaScriptChannel? instance = instanceManager .getInstanceWithWeakReference(instanceId) as JavaScriptChannel?; assert( instance != null, 'InstanceManager does not contain an JavaScriptChannel with instanceId: $instanceId', ); instance!.postMessage(message); } } /// Host api implementation for [WebViewClient]. class WebViewClientHostApiImpl extends WebViewClientHostApi { /// Constructs a [WebViewClientHostApiImpl]. WebViewClientHostApiImpl({ super.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; /// Helper method to convert instances ids to objects. Future createFromInstance(WebViewClient instance) async { if (instanceManager.getIdentifier(instance) == null) { final int identifier = instanceManager.addDartCreatedInstance(instance); return create(identifier); } } /// Helper method to convert instances ids to objects. Future setShouldOverrideUrlLoadingReturnValueFromInstance( WebViewClient instance, bool value, ) { return setSynchronousReturnValueForShouldOverrideUrlLoading( instanceManager.getIdentifier(instance)!, value, ); } } /// Flutter api implementation for [WebViewClient]. class WebViewClientFlutterApiImpl extends WebViewClientFlutterApi { /// Constructs a [WebViewClientFlutterApiImpl]. WebViewClientFlutterApiImpl({InstanceManager? instanceManager}) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; @override void onPageFinished(int instanceId, int webViewInstanceId, String url) { final WebViewClient? instance = instanceManager .getInstanceWithWeakReference(instanceId) as WebViewClient?; final WebView? webViewInstance = instanceManager .getInstanceWithWeakReference(webViewInstanceId) as WebView?; assert( instance != null, 'InstanceManager does not contain an WebViewClient with instanceId: $instanceId', ); assert( webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); if (instance!.onPageFinished != null) { instance.onPageFinished!(webViewInstance!, url); } } @override void onPageStarted(int instanceId, int webViewInstanceId, String url) { final WebViewClient? instance = instanceManager .getInstanceWithWeakReference(instanceId) as WebViewClient?; final WebView? webViewInstance = instanceManager .getInstanceWithWeakReference(webViewInstanceId) as WebView?; assert( instance != null, 'InstanceManager does not contain an WebViewClient with instanceId: $instanceId', ); assert( webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); if (instance!.onPageStarted != null) { instance.onPageStarted!(webViewInstance!, url); } } @override void onReceivedError( int instanceId, int webViewInstanceId, int errorCode, String description, String failingUrl, ) { final WebViewClient? instance = instanceManager .getInstanceWithWeakReference(instanceId) as WebViewClient?; final WebView? webViewInstance = instanceManager .getInstanceWithWeakReference(webViewInstanceId) as WebView?; assert( instance != null, 'InstanceManager does not contain an WebViewClient with instanceId: $instanceId', ); assert( webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); // ignore: deprecated_member_use_from_same_package if (instance!.onReceivedError != null) { instance.onReceivedError!( webViewInstance!, errorCode, description, failingUrl, ); } } @override void onReceivedRequestError( int instanceId, int webViewInstanceId, WebResourceRequestData request, WebResourceErrorData error, ) { final WebViewClient? instance = instanceManager .getInstanceWithWeakReference(instanceId) as WebViewClient?; final WebView? webViewInstance = instanceManager .getInstanceWithWeakReference(webViewInstanceId) as WebView?; assert( instance != null, 'InstanceManager does not contain an WebViewClient with instanceId: $instanceId', ); assert( webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); if (instance!.onReceivedRequestError != null) { instance.onReceivedRequestError!( webViewInstance!, _toWebResourceRequest(request), _toWebResourceError(error), ); } } @override void requestLoading( int instanceId, int webViewInstanceId, WebResourceRequestData request, ) { final WebViewClient? instance = instanceManager .getInstanceWithWeakReference(instanceId) as WebViewClient?; final WebView? webViewInstance = instanceManager .getInstanceWithWeakReference(webViewInstanceId) as WebView?; assert( instance != null, 'InstanceManager does not contain an WebViewClient with instanceId: $instanceId', ); assert( webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); if (instance!.requestLoading != null) { instance.requestLoading!( webViewInstance!, _toWebResourceRequest(request), ); } } @override void urlLoading( int instanceId, int webViewInstanceId, String url, ) { final WebViewClient? instance = instanceManager .getInstanceWithWeakReference(instanceId) as WebViewClient?; final WebView? webViewInstance = instanceManager .getInstanceWithWeakReference(webViewInstanceId) as WebView?; assert( instance != null, 'InstanceManager does not contain an WebViewClient with instanceId: $instanceId', ); assert( webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); if (instance!.urlLoading != null) { instance.urlLoading!(webViewInstance!, url); } } } /// Host api implementation for [DownloadListener]. class DownloadListenerHostApiImpl extends DownloadListenerHostApi { /// Constructs a [DownloadListenerHostApiImpl]. DownloadListenerHostApiImpl({ super.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; /// Helper method to convert instances ids to objects. Future createFromInstance(DownloadListener instance) async { if (instanceManager.getIdentifier(instance) == null) { final int identifier = instanceManager.addDartCreatedInstance(instance); return create(identifier); } } } /// Flutter api implementation for [DownloadListener]. class DownloadListenerFlutterApiImpl extends DownloadListenerFlutterApi { /// Constructs a [DownloadListenerFlutterApiImpl]. DownloadListenerFlutterApiImpl({InstanceManager? instanceManager}) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; @override void onDownloadStart( int instanceId, String url, String userAgent, String contentDisposition, String mimetype, int contentLength, ) { final DownloadListener? instance = instanceManager .getInstanceWithWeakReference(instanceId) as DownloadListener?; assert( instance != null, 'InstanceManager does not contain an DownloadListener with instanceId: $instanceId', ); instance!.onDownloadStart( url, userAgent, contentDisposition, mimetype, contentLength, ); } } /// Host api implementation for [DownloadListener]. class WebChromeClientHostApiImpl extends WebChromeClientHostApi { /// Constructs a [WebChromeClientHostApiImpl]. WebChromeClientHostApiImpl({ super.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; /// Helper method to convert instances ids to objects. Future createFromInstance(WebChromeClient instance) async { if (instanceManager.getIdentifier(instance) == null) { final int identifier = instanceManager.addDartCreatedInstance(instance); 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]. class WebChromeClientFlutterApiImpl extends WebChromeClientFlutterApi { /// Constructs a [DownloadListenerFlutterApiImpl]. WebChromeClientFlutterApiImpl({InstanceManager? instanceManager}) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; @override void onProgressChanged(int instanceId, int webViewInstanceId, int progress) { final WebChromeClient? instance = instanceManager .getInstanceWithWeakReference(instanceId) as WebChromeClient?; final WebView? webViewInstance = instanceManager .getInstanceWithWeakReference(webViewInstanceId) as WebView?; assert( instance != null, 'InstanceManager does not contain an WebChromeClient with instanceId: $instanceId', ); assert( webViewInstance != null, 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', ); if (instance!.onProgressChanged != null) { 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]. class WebStorageHostApiImpl extends WebStorageHostApi { /// Constructs a [WebStorageHostApiImpl]. WebStorageHostApiImpl({ super.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager; /// Maintains instances stored to communicate with java objects. final InstanceManager instanceManager; /// Helper method to convert instances ids to objects. Future createFromInstance(WebStorage instance) async { if (instanceManager.getIdentifier(instance) == null) { final int identifier = instanceManager.addDartCreatedInstance(instance); return create(identifier); } } /// Helper method to convert instances ids to objects. Future deleteAllDataFromInstance(WebStorage instance) { 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, ); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(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'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'android_proxy.dart'; import 'android_webview.dart' as android_webview; import 'instance_manager.dart'; import 'platform_views_service_proxy.dart'; import 'weak_reference_utils.dart'; /// Object specifying creation parameters for creating a [AndroidWebViewController]. /// /// When adding additional fields make sure they can be null or have a default /// value to avoid breaking changes. See [PlatformWebViewControllerCreationParams] for /// more information. @immutable class AndroidWebViewControllerCreationParams extends PlatformWebViewControllerCreationParams { /// Creates a new [AndroidWebViewControllerCreationParams] instance. AndroidWebViewControllerCreationParams({ @visibleForTesting this.androidWebViewProxy = const AndroidWebViewProxy(), @visibleForTesting android_webview.WebStorage? androidWebStorage, }) : androidWebStorage = androidWebStorage ?? android_webview.WebStorage.instance, super(); /// Creates a [AndroidWebViewControllerCreationParams] instance based on [PlatformWebViewControllerCreationParams]. factory AndroidWebViewControllerCreationParams.fromPlatformWebViewControllerCreationParams( // Recommended placeholder to prevent being broken by platform interface. // ignore: avoid_unused_constructor_parameters PlatformWebViewControllerCreationParams params, { @visibleForTesting AndroidWebViewProxy androidWebViewProxy = const AndroidWebViewProxy(), @visibleForTesting android_webview.WebStorage? androidWebStorage, }) { return AndroidWebViewControllerCreationParams( androidWebViewProxy: androidWebViewProxy, androidWebStorage: androidWebStorage ?? android_webview.WebStorage.instance, ); } /// Handles constructing objects and calling static methods for the Android WebView /// native library. @visibleForTesting final AndroidWebViewProxy androidWebViewProxy; /// Manages the JavaScript storage APIs provided by the [android_webview.WebView]. @visibleForTesting final android_webview.WebStorage androidWebStorage; } /// Implementation of the [PlatformWebViewController] with the Android WebView API. class AndroidWebViewController extends PlatformWebViewController { /// Creates a new [AndroidWebViewCookieManager]. AndroidWebViewController(PlatformWebViewControllerCreationParams params) : super.implementation(params is AndroidWebViewControllerCreationParams ? params : AndroidWebViewControllerCreationParams .fromPlatformWebViewControllerCreationParams(params)) { _webView.settings.setDomStorageEnabled(true); _webView.settings.setJavaScriptCanOpenWindowsAutomatically(true); _webView.settings.setSupportMultipleWindows(true); _webView.settings.setLoadWithOverviewMode(true); _webView.settings.setUseWideViewPort(true); _webView.settings.setDisplayZoomControls(false); _webView.settings.setBuiltInZoomControls(true); _webView.setWebChromeClient(_webChromeClient); } AndroidWebViewControllerCreationParams get _androidWebViewParams => params as AndroidWebViewControllerCreationParams; /// The native [android_webview.WebView] being controlled. late final android_webview.WebView _webView = _androidWebViewParams.androidWebViewProxy.createAndroidWebView( // Due to changes in Flutter 3.0 the `useHybridComposition` doesn't have // any effect and is purposefully not exposed publicly by the // [AndroidWebViewController]. More info here: // https://github.com/flutter/flutter/issues/108106 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(); final Map _javaScriptChannelParams = {}; AndroidNavigationDelegate? _currentNavigationDelegate; Future> Function(FileSelectorParams)? _onShowFileSelectorCallback; /// Whether to enable the platform's webview content debugging tools. /// /// Defaults to false. static Future enableDebugging( bool enabled, { @visibleForTesting AndroidWebViewProxy webViewProxy = const AndroidWebViewProxy(), }) { return webViewProxy.setWebContentsDebuggingEnabled(enabled); } /// Identifier used to retrieve the underlying native `WKWebView`. /// /// This is typically used by other plugins to retrieve the native `WebView` /// from an `InstanceManager`. /// /// See Java method `WebViewFlutterPlugin.getWebView`. int get webViewIdentifier => // ignore: invalid_use_of_visible_for_testing_member android_webview.WebView.api.instanceManager.getIdentifier(_webView)!; @override Future loadFile( String absoluteFilePath, ) { final String url = absoluteFilePath.startsWith('file://') ? absoluteFilePath : Uri.file(absoluteFilePath).toString(); _webView.settings.setAllowFileAccess(true); return _webView.loadUrl(url, {}); } @override Future loadFlutterAsset( String key, ) async { final String assetFilePath = await _flutterAssetManager.getAssetFilePathByName(key); final List pathElements = assetFilePath.split('/'); final String fileName = pathElements.removeLast(); final List paths = await _flutterAssetManager.list(pathElements.join('/')); if (!paths.contains(fileName)) { throw ArgumentError( 'Asset for key "$key" not found.', 'key', ); } return _webView.loadUrl( Uri.file('/android_asset/$assetFilePath').toString(), {}, ); } @override Future loadHtmlString( String html, { String? baseUrl, }) { return _webView.loadDataWithBaseUrl( baseUrl: baseUrl, data: html, mimeType: 'text/html', ); } @override Future loadRequest( LoadRequestParams params, ) { if (!params.uri.hasScheme) { throw ArgumentError('WebViewRequest#uri is required to have a scheme.'); } switch (params.method) { case LoadRequestMethod.get: return _webView.loadUrl(params.uri.toString(), params.headers); case LoadRequestMethod.post: return _webView.postUrl( params.uri.toString(), params.body ?? Uint8List(0)); } // 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 Future currentUrl() => _webView.getUrl(); @override Future canGoBack() => _webView.canGoBack(); @override Future canGoForward() => _webView.canGoForward(); @override Future goBack() => _webView.goBack(); @override Future goForward() => _webView.goForward(); @override Future reload() => _webView.reload(); @override Future clearCache() => _webView.clearCache(true); @override Future clearLocalStorage() => _androidWebViewParams.androidWebStorage.deleteAllData(); @override Future setPlatformNavigationDelegate( covariant AndroidNavigationDelegate handler) async { _currentNavigationDelegate = handler; handler.setOnLoadRequest(loadRequest); _webView.setWebViewClient(handler.androidWebViewClient); _webView.setDownloadListener(handler.androidDownloadListener); } @override Future runJavaScript(String javaScript) { return _webView.evaluateJavascript(javaScript); } @override Future runJavaScriptReturningResult(String javaScript) async { final String? result = await _webView.evaluateJavascript(javaScript); if (result == null) { return ''; } else if (result == 'true') { return true; } else if (result == 'false') { return false; } return num.tryParse(result) ?? result; } @override Future addJavaScriptChannel( JavaScriptChannelParams javaScriptChannelParams, ) { final AndroidJavaScriptChannelParams androidJavaScriptParams = javaScriptChannelParams is AndroidJavaScriptChannelParams ? javaScriptChannelParams : AndroidJavaScriptChannelParams.fromJavaScriptChannelParams( javaScriptChannelParams); // When JavaScript channel with the same name exists make sure to remove it // before registering the new channel. if (_javaScriptChannelParams.containsKey(androidJavaScriptParams.name)) { _webView .removeJavaScriptChannel(androidJavaScriptParams._javaScriptChannel); } _javaScriptChannelParams[androidJavaScriptParams.name] = androidJavaScriptParams; return _webView .addJavaScriptChannel(androidJavaScriptParams._javaScriptChannel); } @override Future removeJavaScriptChannel(String javaScriptChannelName) async { final AndroidJavaScriptChannelParams? javaScriptChannelParams = _javaScriptChannelParams[javaScriptChannelName]; if (javaScriptChannelParams == null) { return; } _javaScriptChannelParams.remove(javaScriptChannelName); return _webView .removeJavaScriptChannel(javaScriptChannelParams._javaScriptChannel); } @override Future getTitle() => _webView.getTitle(); @override Future scrollTo(int x, int y) => _webView.scrollTo(x, y); @override Future scrollBy(int x, int y) => _webView.scrollBy(x, y); @override Future getScrollPosition() { return _webView.getScrollPosition(); } @override Future enableZoom(bool enabled) => _webView.settings.setSupportZoom(enabled); @override Future setBackgroundColor(Color color) => _webView.setBackgroundColor(color); @override Future setJavaScriptMode(JavaScriptMode javaScriptMode) => _webView.settings .setJavaScriptEnabled(javaScriptMode == JavaScriptMode.unrestricted); @override Future setUserAgent(String? userAgent) => _webView.settings.setUserAgentString(userAgent); /// Sets the restrictions that apply on automatic media playback. 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. /// /// See [AndroidWebViewController.addJavaScriptChannel]. @immutable class AndroidJavaScriptChannelParams extends JavaScriptChannelParams { /// Constructs a [AndroidJavaScriptChannelParams]. AndroidJavaScriptChannelParams({ required super.name, required super.onMessageReceived, @visibleForTesting AndroidWebViewProxy webViewProxy = const AndroidWebViewProxy(), }) : assert(name.isNotEmpty), _javaScriptChannel = webViewProxy.createJavaScriptChannel( name, postMessage: withWeakReferenceTo( onMessageReceived, (WeakReference weakReference) { return ( String message, ) { if (weakReference.target != null) { weakReference.target!( JavaScriptMessage(message: message), ); } }; }, ), ); /// Constructs a [AndroidJavaScriptChannelParams] using a /// [JavaScriptChannelParams]. AndroidJavaScriptChannelParams.fromJavaScriptChannelParams( JavaScriptChannelParams params, { @visibleForTesting AndroidWebViewProxy webViewProxy = const AndroidWebViewProxy(), }) : this( name: params.name, onMessageReceived: params.onMessageReceived, webViewProxy: webViewProxy, ); final android_webview.JavaScriptChannel _javaScriptChannel; } /// Object specifying creation parameters for creating a [AndroidWebViewWidget]. /// /// When adding additional fields make sure they can be null or have a default /// value to avoid breaking changes. See [PlatformWebViewWidgetCreationParams] for /// more information. @immutable class AndroidWebViewWidgetCreationParams extends PlatformWebViewWidgetCreationParams { /// Creates [AndroidWebWidgetCreationParams]. AndroidWebViewWidgetCreationParams({ super.key, required super.controller, super.layoutDirection, super.gestureRecognizers, this.displayWithHybridComposition = false, @visibleForTesting InstanceManager? instanceManager, @visibleForTesting this.platformViewsServiceProxy = const PlatformViewsServiceProxy(), }) : instanceManager = instanceManager ?? android_webview.JavaObject.globalInstanceManager; /// Constructs a [WebKitWebViewWidgetCreationParams] using a /// [PlatformWebViewWidgetCreationParams]. AndroidWebViewWidgetCreationParams.fromPlatformWebViewWidgetCreationParams( PlatformWebViewWidgetCreationParams params, { bool displayWithHybridComposition = false, @visibleForTesting InstanceManager? instanceManager, @visibleForTesting PlatformViewsServiceProxy platformViewsServiceProxy = const PlatformViewsServiceProxy(), }) : this( key: params.key, controller: params.controller, layoutDirection: params.layoutDirection, gestureRecognizers: params.gestureRecognizers, displayWithHybridComposition: displayWithHybridComposition, instanceManager: instanceManager, platformViewsServiceProxy: platformViewsServiceProxy, ); /// Maintains instances used to communicate with the native objects they /// represent. /// /// This field is exposed for testing purposes only and should not be used /// outside of tests. @visibleForTesting final InstanceManager instanceManager; /// Proxy that provides access to the platform views service. /// /// This service allows creating and controlling platform-specific views. @visibleForTesting final PlatformViewsServiceProxy platformViewsServiceProxy; /// Whether the [WebView] will be displayed using the Hybrid Composition /// PlatformView implementation. /// /// For most use cases, this flag should be set to false. Hybrid Composition /// can have performance costs but doesn't have the limitation of rendering to /// an Android SurfaceTexture. See /// * https://flutter.dev/docs/development/platform-integration/platform-views#performance /// * https://github.com/flutter/flutter/issues/104889 /// * https://github.com/flutter/flutter/issues/116954 /// /// Defaults to false. final bool displayWithHybridComposition; } /// An implementation of [PlatformWebViewWidget] with the Android WebView API. class AndroidWebViewWidget extends PlatformWebViewWidget { /// Constructs a [WebKitWebViewWidget]. AndroidWebViewWidget(PlatformWebViewWidgetCreationParams params) : super.implementation( params is AndroidWebViewWidgetCreationParams ? params : AndroidWebViewWidgetCreationParams .fromPlatformWebViewWidgetCreationParams(params), ); AndroidWebViewWidgetCreationParams get _androidParams => params as AndroidWebViewWidgetCreationParams; @override Widget build(BuildContext context) { return PlatformViewLink( key: _androidParams.key, viewType: 'plugins.flutter.io/webview', surfaceFactory: ( BuildContext context, PlatformViewController controller, ) { return AndroidViewSurface( controller: controller as AndroidViewController, gestureRecognizers: _androidParams.gestureRecognizers, hitTestBehavior: PlatformViewHitTestBehavior.opaque, ); }, onCreatePlatformView: (PlatformViewCreationParams params) { return _initAndroidView( params, displayWithHybridComposition: _androidParams.displayWithHybridComposition, ) ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) ..create(); }, ); } AndroidViewController _initAndroidView( PlatformViewCreationParams params, { required bool displayWithHybridComposition, }) { if (displayWithHybridComposition) { return _androidParams.platformViewsServiceProxy.initExpensiveAndroidView( id: params.id, viewType: 'plugins.flutter.io/webview', layoutDirection: _androidParams.layoutDirection, creationParams: _androidParams.instanceManager.getIdentifier( (_androidParams.controller as AndroidWebViewController)._webView), creationParamsCodec: const StandardMessageCodec(), ); } else { return _androidParams.platformViewsServiceProxy.initSurfaceAndroidView( id: params.id, viewType: 'plugins.flutter.io/webview', layoutDirection: _androidParams.layoutDirection, creationParams: _androidParams.instanceManager.getIdentifier( (_androidParams.controller as AndroidWebViewController)._webView), creationParamsCodec: const StandardMessageCodec(), ); } } } /// 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; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/lib/src/android_webview_cookie_manager.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'android_webview.dart'; /// Object specifying creation parameters for creating a [AndroidWebViewCookieManager]. /// /// When adding additional fields make sure they can be null or have a default /// value to avoid breaking changes. See [PlatformWebViewCookieManagerCreationParams] for /// more information. @immutable class AndroidWebViewCookieManagerCreationParams extends PlatformWebViewCookieManagerCreationParams { /// Creates a new [AndroidWebViewCookieManagerCreationParams] instance. const AndroidWebViewCookieManagerCreationParams._( // This parameter prevents breaking changes later. // ignore: avoid_unused_constructor_parameters PlatformWebViewCookieManagerCreationParams params, ) : super(); /// Creates a [AndroidWebViewCookieManagerCreationParams] instance based on [PlatformWebViewCookieManagerCreationParams]. factory AndroidWebViewCookieManagerCreationParams.fromPlatformWebViewCookieManagerCreationParams( PlatformWebViewCookieManagerCreationParams params) { return AndroidWebViewCookieManagerCreationParams._(params); } } /// Handles all cookie operations for the Android platform. class AndroidWebViewCookieManager extends PlatformWebViewCookieManager { /// Creates a new [AndroidWebViewCookieManager]. AndroidWebViewCookieManager( PlatformWebViewCookieManagerCreationParams params, { CookieManager? cookieManager, }) : _cookieManager = cookieManager ?? CookieManager.instance, super.implementation( params is AndroidWebViewCookieManagerCreationParams ? params : AndroidWebViewCookieManagerCreationParams .fromPlatformWebViewCookieManagerCreationParams(params), ); final CookieManager _cookieManager; @override Future clearCookies() { return _cookieManager.clearCookies(); } @override Future setCookie(WebViewCookie cookie) { if (!_isValidPath(cookie.path)) { throw ArgumentError( 'The path property for the provided cookie was not given a legal value.'); } return _cookieManager.setCookie( cookie.domain, '${Uri.encodeComponent(cookie.name)}=${Uri.encodeComponent(cookie.value)}; path=${cookie.path}', ); } bool _isValidPath(String path) { // Permitted ranges based on RFC6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 for (final int char in path.codeUnits) { if ((char < 0x20 || char > 0x3A) && (char < 0x3C || char > 0x7E)) { return false; } } return true; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/lib/src/android_webview_platform.dart ================================================ // Copyright 2013 The Flutter 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:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'android_webview_controller.dart'; import 'android_webview_cookie_manager.dart'; /// Implementation of [WebViewPlatform] using the WebKit API. class AndroidWebViewPlatform extends WebViewPlatform { /// Registers this class as the default instance of [WebViewPlatform]. static void registerWith() { WebViewPlatform.instance = AndroidWebViewPlatform(); } @override AndroidWebViewController createPlatformWebViewController( PlatformWebViewControllerCreationParams params, ) { return AndroidWebViewController(params); } @override AndroidNavigationDelegate createPlatformNavigationDelegate( PlatformNavigationDelegateCreationParams params, ) { return AndroidNavigationDelegate(params); } @override AndroidWebViewWidget createPlatformWebViewWidget( PlatformWebViewWidgetCreationParams params, ) { return AndroidWebViewWidget(params); } @override AndroidWebViewCookieManager createPlatformCookieManager( PlatformWebViewCookieManagerCreationParams params, ) { return AndroidWebViewCookieManager(params); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/lib/src/instance_manager.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; /// An immutable object that can provide functional copies of itself. /// /// All implementers are expected to be immutable as defined by the annotation. // TODO(bparrishMines): Uncomment annotation once // https://github.com/flutter/plugins/pull/5831 lands or when making a breaking // change for https://github.com/flutter/flutter/issues/107199. // @immutable mixin Copyable { /// Instantiates and returns a functionally identical object to oneself. /// /// Outside of tests, this method should only ever be called by /// [InstanceManager]. /// /// Subclasses should always override their parent's implementation of this /// method. @protected Copyable copy(); } /// Maintains instances used to communicate with the native objects they /// represent. /// /// Added instances are stored as weak references and their copies are stored /// as strong references to maintain access to their variables and callback /// methods. Both are stored with the same identifier. /// /// When a weak referenced instance becomes inaccessible, /// [onWeakReferenceRemoved] is called with its associated identifier. /// /// If an instance is retrieved and has the possibility to be used, /// (e.g. calling [getInstanceWithWeakReference]) a copy of the strong reference /// is added as a weak reference with the same identifier. This prevents a /// scenario where the weak referenced instance was released and then later /// returned by the host platform. class InstanceManager { /// Constructs an [InstanceManager]. InstanceManager({required void Function(int) onWeakReferenceRemoved}) { this.onWeakReferenceRemoved = (int identifier) { _weakInstances.remove(identifier); onWeakReferenceRemoved(identifier); }; _finalizer = Finalizer(this.onWeakReferenceRemoved); } // Identifiers are locked to a specific range to avoid collisions with objects // created simultaneously by the host platform. // Host uses identifiers >= 2^16 and Dart is expected to use values n where, // 0 <= n < 2^16. static const int _maxDartCreatedIdentifier = 65536; // Expando is used because it doesn't prevent its keys from becoming // inaccessible. This allows the manager to efficiently retrieve an identifier // of an instance without holding a strong reference to that instance. // // It also doesn't use `==` to search for identifiers, which would lead to an // infinite loop when comparing an object to its copy. (i.e. which was caused // by calling instanceManager.getIdentifier() inside of `==` while this was a // HashMap). final Expando _identifiers = Expando(); final Map> _weakInstances = >{}; final Map _strongInstances = {}; late final Finalizer _finalizer; int _nextIdentifier = 0; /// Called when a weak referenced instance is removed by [removeWeakReference] /// or becomes inaccessible. late final void Function(int) onWeakReferenceRemoved; /// Adds a new instance that was instantiated by Dart. /// /// In other words, Dart wants to add a new instance that will represent /// an object that will be instantiated on the host platform. /// /// Throws assertion error if the instance has already been added. /// /// Returns the randomly generated id of the [instance] added. int addDartCreatedInstance(Copyable instance) { assert(getIdentifier(instance) == null); final int identifier = _nextUniqueIdentifier(); _addInstanceWithIdentifier(instance, identifier); return identifier; } /// Removes the instance, if present, and call [onWeakReferenceRemoved] with /// its identifier. /// /// Returns the identifier associated with the removed instance. Otherwise, /// `null` if the instance was not found in this manager. /// /// This does not remove the the strong referenced instance associated with /// [instance]. This can be done with [remove]. int? removeWeakReference(Copyable instance) { final int? identifier = getIdentifier(instance); if (identifier == null) { return null; } _identifiers[instance] = null; _finalizer.detach(instance); onWeakReferenceRemoved(identifier); return identifier; } /// Removes [identifier] and its associated strongly referenced instance, if /// present, from the manager. /// /// Returns the strong referenced instance associated with [identifier] before /// it was removed. Returns `null` if [identifier] was not associated with /// any strong reference. /// /// This does not remove the the weak referenced instance associtated with /// [identifier]. This can be done with [removeWeakReference]. T? remove(int identifier) { return _strongInstances.remove(identifier) as T?; } /// Retrieves the instance associated with identifier. /// /// The value returned is chosen from the following order: /// /// 1. A weakly referenced instance associated with identifier. /// 2. If the only instance associated with identifier is a strongly /// referenced instance, a copy of the instance is added as a weak reference /// with the same identifier. Returning the newly created copy. /// 3. If no instance is associated with identifier, returns null. /// /// This method also expects the host `InstanceManager` to have a strong /// reference to the instance the identifier is associated with. T? getInstanceWithWeakReference(int identifier) { final Copyable? weakInstance = _weakInstances[identifier]?.target; if (weakInstance == null) { final Copyable? strongInstance = _strongInstances[identifier]; if (strongInstance != null) { final Copyable copy = strongInstance.copy(); _identifiers[copy] = identifier; _weakInstances[identifier] = WeakReference(copy); _finalizer.attach(copy, identifier, detach: copy); return copy as T; } return strongInstance as T?; } return weakInstance as T; } /// Retrieves the identifier associated with instance. int? getIdentifier(Copyable instance) { return _identifiers[instance]; } /// Adds a new instance that was instantiated by the host platform. /// /// In other words, the host platform wants to add a new instance that /// represents an object on the host platform. Stored with [identifier]. /// /// Throws assertion error if the instance or its identifier has already been /// added. /// /// Returns unique identifier of the [instance] added. void addHostCreatedInstance(Copyable instance, int identifier) { assert(!containsIdentifier(identifier)); assert(getIdentifier(instance) == null); assert(identifier >= 0); _addInstanceWithIdentifier(instance, identifier); } void _addInstanceWithIdentifier(Copyable instance, int identifier) { _identifiers[instance] = identifier; _weakInstances[identifier] = WeakReference(instance); _finalizer.attach(instance, identifier, detach: instance); final Copyable copy = instance.copy(); _identifiers[copy] = identifier; _strongInstances[identifier] = copy; } /// Whether this manager contains the given [identifier]. bool containsIdentifier(int identifier) { return _weakInstances.containsKey(identifier) || _strongInstances.containsKey(identifier); } int _nextUniqueIdentifier() { late int identifier; do { identifier = _nextIdentifier; _nextIdentifier = (_nextIdentifier + 1) % _maxDartCreatedIdentifier; } while (containsIdentifier(identifier)); return identifier; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android.dart ================================================ // Copyright 2013 The Flutter 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:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; // ignore: implementation_imports import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; import '../android_webview.dart'; import 'webview_android_widget.dart'; /// Builds an Android webview. /// /// This is used as the default implementation for [WebView.platform] on Android. It uses /// an [AndroidView] to embed the webview in the widget hierarchy, and uses a method channel to /// communicate with the platform code. class AndroidWebView implements WebViewPlatform { @override Widget build({ required BuildContext context, required CreationParams creationParams, required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, required JavascriptChannelRegistry javascriptChannelRegistry, WebViewPlatformCreatedCallback? onWebViewPlatformCreated, Set>? gestureRecognizers, }) { return WebViewAndroidWidget( useHybridComposition: false, creationParams: creationParams, callbacksHandler: webViewPlatformCallbacksHandler, javascriptChannelRegistry: javascriptChannelRegistry, onBuildWidget: (WebViewAndroidPlatformController controller) { return GestureDetector( // We prevent text selection by intercepting the long press event. // This is a temporary stop gap due to issues with text selection on Android: // https://github.com/flutter/flutter/issues/24585 - the text selection // dialog is not responding to touch events. // https://github.com/flutter/flutter/issues/24584 - the text selection // handles are not showing. // TODO(amirh): remove this when the issues above are fixed. onLongPress: () {}, excludeFromSemantics: true, child: AndroidView( viewType: 'plugins.flutter.io/webview', onPlatformViewCreated: (int id) { if (onWebViewPlatformCreated != null) { onWebViewPlatformCreated(controller); } }, gestureRecognizers: gestureRecognizers, layoutDirection: Directionality.maybeOf(context) ?? TextDirection.rtl, creationParams: JavaObject.globalInstanceManager .getIdentifier(controller.webView), creationParamsCodec: const StandardMessageCodec(), ), ); }, ); } @override Future clearCookies() { if (WebViewCookieManagerPlatform.instance == null) { throw Exception( 'Could not clear cookies as no implementation for WebViewCookieManagerPlatform has been registered.'); } return WebViewCookieManagerPlatform.instance!.clearCookies(); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_cookie_manager.dart ================================================ // Copyright 2013 The Flutter 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: implementation_imports import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; import '../android_webview.dart' as android_webview; /// Handles all cookie operations for the current platform. class WebViewAndroidCookieManager extends WebViewCookieManagerPlatform { @override Future clearCookies() => android_webview.CookieManager.instance.clearCookies(); @override Future setCookie(WebViewCookie cookie) { if (!_isValidPath(cookie.path)) { throw ArgumentError( 'The path property for the provided cookie was not given a legal value.'); } return android_webview.CookieManager.instance.setCookie( cookie.domain, '${Uri.encodeComponent(cookie.name)}=${Uri.encodeComponent(cookie.value)}; path=${cookie.path}', ); } bool _isValidPath(String path) { // Permitted ranges based on RFC6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 for (final int char in path.codeUnits) { if ((char < 0x20 || char > 0x3A) && (char < 0x3C || char > 0x7E)) { return false; } } return true; } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_android_widget.dart ================================================ // Copyright 2013 The Flutter 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 'dart:typed_data'; import 'package:flutter/widgets.dart'; // ignore: implementation_imports import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; import '../android_webview.dart' as android_webview; import '../weak_reference_utils.dart'; import 'webview_android_cookie_manager.dart'; /// Creates a [Widget] with a [android_webview.WebView]. class WebViewAndroidWidget extends StatefulWidget { /// Constructs a [WebViewAndroidWidget]. const WebViewAndroidWidget({ super.key, required this.creationParams, required this.useHybridComposition, required this.callbacksHandler, required this.javascriptChannelRegistry, required this.onBuildWidget, @visibleForTesting this.webViewProxy = const WebViewProxy(), @visibleForTesting this.flutterAssetManager = const android_webview.FlutterAssetManager(), @visibleForTesting this.webStorage, }); /// Initial parameters used to setup the WebView. final CreationParams creationParams; /// Whether the [android_webview.WebView] will be rendered with an [AndroidViewSurface]. /// /// This implementation uses hybrid composition to render the /// [WebViewAndroidWidget]. This comes at the cost of some performance on /// Android versions below 10. See /// https://flutter.dev/docs/development/platform-integration/platform-views#performance /// for more information. /// /// Defaults to false. final bool useHybridComposition; /// Handles callbacks that are made by [android_webview.WebViewClient], [android_webview.DownloadListener], and [android_webview.WebChromeClient]. final WebViewPlatformCallbacksHandler callbacksHandler; /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. final JavascriptChannelRegistry javascriptChannelRegistry; /// Handles constructing [android_webview.WebView]s and calling static methods. /// /// This should only be changed for testing purposes. final WebViewProxy webViewProxy; /// Manages access to Flutter assets that are part of the Android App bundle. /// /// This should only be changed for testing purposes. final android_webview.FlutterAssetManager flutterAssetManager; /// Callback to build a widget once [android_webview.WebView] has been initialized. final Widget Function(WebViewAndroidPlatformController controller) onBuildWidget; /// Manages the JavaScript storage APIs. final android_webview.WebStorage? webStorage; @override State createState() => _WebViewAndroidWidgetState(); } class _WebViewAndroidWidgetState extends State { late final WebViewAndroidPlatformController controller; @override void initState() { super.initState(); controller = WebViewAndroidPlatformController( useHybridComposition: widget.useHybridComposition, creationParams: widget.creationParams, callbacksHandler: widget.callbacksHandler, javascriptChannelRegistry: widget.javascriptChannelRegistry, webViewProxy: widget.webViewProxy, flutterAssetManager: widget.flutterAssetManager, webStorage: widget.webStorage, ); } @override Widget build(BuildContext context) { return widget.onBuildWidget(controller); } } /// Implementation of [WebViewPlatformController] with the Android WebView api. class WebViewAndroidPlatformController extends WebViewPlatformController { /// Construct a [WebViewAndroidPlatformController]. WebViewAndroidPlatformController({ required bool useHybridComposition, required CreationParams creationParams, required this.callbacksHandler, required this.javascriptChannelRegistry, @visibleForTesting this.webViewProxy = const WebViewProxy(), @visibleForTesting this.flutterAssetManager = const android_webview.FlutterAssetManager(), @visibleForTesting android_webview.WebStorage? webStorage, }) : webStorage = webStorage ?? android_webview.WebStorage.instance, assert(creationParams.webSettings?.hasNavigationDelegate != null), super(callbacksHandler) { webView = webViewProxy.createWebView( useHybridComposition: useHybridComposition, ); webView.settings.setDomStorageEnabled(true); webView.settings.setJavaScriptCanOpenWindowsAutomatically(true); webView.settings.setSupportMultipleWindows(true); webView.settings.setLoadWithOverviewMode(true); webView.settings.setUseWideViewPort(true); webView.settings.setDisplayZoomControls(false); webView.settings.setBuiltInZoomControls(true); _setCreationParams(creationParams); webView.setDownloadListener(downloadListener); webView.setWebChromeClient(webChromeClient); webView.setWebViewClient(webViewClient); final String? initialUrl = creationParams.initialUrl; if (initialUrl != null) { loadUrl(initialUrl, {}); } } final Map _javaScriptChannels = {}; late final android_webview.WebViewClient _webViewClient = withWeakReferenceTo( this, (WeakReference weakReference) { return webViewProxy.createWebViewClient( onPageStarted: (_, String url) { weakReference.target?.callbacksHandler.onPageStarted(url); }, onPageFinished: (_, String url) { weakReference.target?.callbacksHandler.onPageFinished(url); }, onReceivedError: ( _, int errorCode, String description, String failingUrl, ) { weakReference.target?.callbacksHandler .onWebResourceError(WebResourceError( errorCode: errorCode, description: description, failingUrl: failingUrl, errorType: _errorCodeToErrorType(errorCode), )); }, onReceivedRequestError: ( _, android_webview.WebResourceRequest request, android_webview.WebResourceError error, ) { if (request.isForMainFrame) { weakReference.target?.callbacksHandler .onWebResourceError(WebResourceError( errorCode: error.errorCode, description: error.description, failingUrl: request.url, errorType: _errorCodeToErrorType(error.errorCode), )); } }, urlLoading: (_, String url) { weakReference.target?._handleNavigationRequest( url: url, isForMainFrame: true, ); }, requestLoading: (_, android_webview.WebResourceRequest request) { weakReference.target?._handleNavigationRequest( url: request.url, isForMainFrame: request.isForMainFrame, ); }, ); }); bool _hasNavigationDelegate = false; bool _hasProgressTracking = false; /// Represents the WebView maintained by platform code. late final android_webview.WebView webView; /// Handles callbacks that are made by [android_webview.WebViewClient], [android_webview.DownloadListener], and [android_webview.WebChromeClient]. final WebViewPlatformCallbacksHandler callbacksHandler; /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. final JavascriptChannelRegistry javascriptChannelRegistry; /// Handles constructing [android_webview.WebView]s and calling static methods. /// /// This should only be changed for testing purposes. final WebViewProxy webViewProxy; /// Manages access to Flutter assets that are part of the Android App bundle. /// /// This should only be changed for testing purposes. final android_webview.FlutterAssetManager flutterAssetManager; /// Receives callbacks when content should be downloaded instead. @visibleForTesting late final android_webview.DownloadListener downloadListener = android_webview.DownloadListener( onDownloadStart: withWeakReferenceTo( this, (WeakReference weakReference) { return ( String url, String userAgent, String contentDisposition, String mimetype, int contentLength, ) { weakReference.target?._handleNavigationRequest( url: url, isForMainFrame: true, ); }; }, ), ); /// Handles JavaScript dialogs, favicons, titles, new windows, and the progress for [android_webview.WebView]. @visibleForTesting late final android_webview.WebChromeClient webChromeClient = android_webview.WebChromeClient( onProgressChanged: withWeakReferenceTo( this, (WeakReference weakReference) { return (_, int progress) { final WebViewAndroidPlatformController? controller = weakReference.target; if (controller != null && controller._hasProgressTracking) { controller.callbacksHandler.onProgress(progress); } }; }, )); /// Manages the JavaScript storage APIs. final android_webview.WebStorage webStorage; /// Receive various notifications and requests for [android_webview.WebView]. @visibleForTesting android_webview.WebViewClient get webViewClient => _webViewClient; @override Future loadHtmlString(String html, {String? baseUrl}) { return webView.loadDataWithBaseUrl( baseUrl: baseUrl, data: html, mimeType: 'text/html', ); } @override Future loadFile(String absoluteFilePath) { final String url = absoluteFilePath.startsWith('file://') ? absoluteFilePath : 'file://$absoluteFilePath'; webView.settings.setAllowFileAccess(true); return webView.loadUrl(url, {}); } @override Future loadFlutterAsset(String key) async { final String assetFilePath = await flutterAssetManager.getAssetFilePathByName(key); final List pathElements = assetFilePath.split('/'); final String fileName = pathElements.removeLast(); final List paths = await flutterAssetManager.list(pathElements.join('/')); if (!paths.contains(fileName)) { throw ArgumentError( 'Asset for key "$key" not found.', 'key', ); } return webView.loadUrl( 'file:///android_asset/$assetFilePath', {}, ); } @override Future loadUrl( String url, Map? headers, ) { return webView.loadUrl(url, headers ?? {}); } /// When making a POST request, headers are ignored. As a workaround, make /// the request manually and load the response data using [loadHTMLString]. @override Future loadRequest( WebViewRequest request, ) async { if (!request.uri.hasScheme) { throw ArgumentError('WebViewRequest#uri is required to have a scheme.'); } switch (request.method) { case WebViewRequestMethod.get: return webView.loadUrl(request.uri.toString(), request.headers); case WebViewRequestMethod.post: return webView.postUrl( request.uri.toString(), request.body ?? Uint8List(0)); } // 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 Future currentUrl() => webView.getUrl(); @override Future canGoBack() => webView.canGoBack(); @override Future canGoForward() => webView.canGoForward(); @override Future goBack() => webView.goBack(); @override Future goForward() => webView.goForward(); @override Future reload() => webView.reload(); @override Future clearCache() { webView.clearCache(true); return webStorage.deleteAllData(); } @override Future updateSettings(WebSettings setting) async { _hasProgressTracking = setting.hasProgressTracking ?? _hasProgressTracking; await Future.wait(>[ _setUserAgent(setting.userAgent), if (setting.hasNavigationDelegate != null) _setHasNavigationDelegate(setting.hasNavigationDelegate!), if (setting.javascriptMode != null) _setJavaScriptMode(setting.javascriptMode!), if (setting.debuggingEnabled != null) _setDebuggingEnabled(setting.debuggingEnabled!), if (setting.zoomEnabled != null) _setZoomEnabled(setting.zoomEnabled!), ]); } @override Future evaluateJavascript(String javascript) async { return runJavascriptReturningResult(javascript); } @override Future runJavascript(String javascript) async { await webView.evaluateJavascript(javascript); } @override Future runJavascriptReturningResult(String javascript) async { return await webView.evaluateJavascript(javascript) ?? ''; } @override Future addJavascriptChannels(Set javascriptChannelNames) { return Future.wait( javascriptChannelNames.where( (String channelName) { return !_javaScriptChannels.containsKey(channelName); }, ).map>( (String channelName) { final WebViewAndroidJavaScriptChannel javaScriptChannel = WebViewAndroidJavaScriptChannel( channelName, javascriptChannelRegistry); _javaScriptChannels[channelName] = javaScriptChannel; return webView.addJavaScriptChannel(javaScriptChannel); }, ), ); } @override Future removeJavascriptChannels( Set javascriptChannelNames, ) { return Future.wait( javascriptChannelNames.where( (String channelName) { return _javaScriptChannels.containsKey(channelName); }, ).map>( (String channelName) { final WebViewAndroidJavaScriptChannel javaScriptChannel = _javaScriptChannels[channelName]!; _javaScriptChannels.remove(channelName); return webView.removeJavaScriptChannel(javaScriptChannel); }, ), ); } @override Future getTitle() => webView.getTitle(); @override Future scrollTo(int x, int y) => webView.scrollTo(x, y); @override Future scrollBy(int x, int y) => webView.scrollBy(x, y); @override Future getScrollX() => webView.getScrollX(); @override Future getScrollY() => webView.getScrollY(); void _setCreationParams(CreationParams creationParams) { final WebSettings? webSettings = creationParams.webSettings; if (webSettings != null) { updateSettings(webSettings); } final String? userAgent = creationParams.userAgent; if (userAgent != null) { webView.settings.setUserAgentString(userAgent); } webView.settings.setMediaPlaybackRequiresUserGesture( creationParams.autoMediaPlaybackPolicy != AutoMediaPlaybackPolicy.always_allow, ); final Color? backgroundColor = creationParams.backgroundColor; if (backgroundColor != null) { webView.setBackgroundColor(backgroundColor); } addJavascriptChannels(creationParams.javascriptChannelNames); // TODO(BeMacized): Remove once platform implementations // are able to register themselves (Flutter >=2.8), // https://github.com/flutter/flutter/issues/94224 WebViewCookieManagerPlatform.instance ??= WebViewAndroidCookieManager(); creationParams.cookies .forEach(WebViewCookieManagerPlatform.instance!.setCookie); } Future _setHasNavigationDelegate(bool hasNavigationDelegate) { _hasNavigationDelegate = hasNavigationDelegate; return _webViewClient.setSynchronousReturnValueForShouldOverrideUrlLoading( hasNavigationDelegate, ); } Future _setJavaScriptMode(JavascriptMode mode) { switch (mode) { case JavascriptMode.disabled: return webView.settings.setJavaScriptEnabled(false); case JavascriptMode.unrestricted: return webView.settings.setJavaScriptEnabled(true); } } Future _setDebuggingEnabled(bool debuggingEnabled) { return webViewProxy.setWebContentsDebuggingEnabled(debuggingEnabled); } Future _setUserAgent(WebSetting userAgent) { if (userAgent.isPresent) { // If the string is empty, the system default value will be used. return webView.settings.setUserAgentString(userAgent.value ?? ''); } return Future.value(); } Future _setZoomEnabled(bool zoomEnabled) { return webView.settings.setSupportZoom(zoomEnabled); } 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', ); } void _handleNavigationRequest({ required String url, required bool isForMainFrame, }) { if (!_hasNavigationDelegate) { return; } final FutureOr returnValue = callbacksHandler.onNavigationRequest( url: url, isForMainFrame: isForMainFrame, ); if (returnValue is bool && returnValue) { loadUrl(url, {}); } else if (returnValue is Future) { returnValue.then((bool shouldLoadUrl) { if (shouldLoadUrl) { loadUrl(url, {}); } }); } } } /// Exposes a channel to receive calls from javaScript. class WebViewAndroidJavaScriptChannel extends android_webview.JavaScriptChannel { /// Creates a [WebViewAndroidJavaScriptChannel]. WebViewAndroidJavaScriptChannel( super.channelName, this.javascriptChannelRegistry, ) : super( postMessage: withWeakReferenceTo( javascriptChannelRegistry, (WeakReference weakReference) { return (String message) { weakReference.target?.onJavascriptChannelMessage( channelName, message, ); }; }, ), ); /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. final JavascriptChannelRegistry javascriptChannelRegistry; } /// Handles constructing [android_webview.WebView]s and calling static methods. /// /// This should only be used for testing purposes. @visibleForTesting class WebViewProxy { /// Creates a [WebViewProxy]. const WebViewProxy(); /// Constructs a [android_webview.WebView]. android_webview.WebView createWebView({required bool useHybridComposition}) { return android_webview.WebView(useHybridComposition: useHybridComposition); } /// Constructs a [android_webview.WebViewClient]. android_webview.WebViewClient createWebViewClient({ void Function(android_webview.WebView webView, String url)? onPageStarted, void Function(android_webview.WebView webView, String url)? onPageFinished, void Function( android_webview.WebView webView, android_webview.WebResourceRequest request, android_webview.WebResourceError error, )? onReceivedRequestError, void Function( android_webview.WebView webView, int errorCode, String description, String failingUrl, )? onReceivedError, void Function(android_webview.WebView webView, android_webview.WebResourceRequest request)? requestLoading, void Function(android_webview.WebView webView, String url)? urlLoading, }) { return android_webview.WebViewClient( onPageStarted: onPageStarted, onPageFinished: onPageFinished, onReceivedRequestError: onReceivedRequestError, onReceivedError: onReceivedError, requestLoading: requestLoading, urlLoading: urlLoading, ); } /// Enables debugging of web contents (HTML / CSS / JavaScript) loaded into any WebViews of this application. /// /// This flag can be enabled in order to facilitate debugging of web layouts /// and JavaScript code running inside WebViews. Please refer to /// [android_webview.WebView] documentation for the debugging guide. The /// default is false. /// /// See [android_webview.WebView].setWebContentsDebuggingEnabled. Future setWebContentsDebuggingEnabled(bool enabled) { return android_webview.WebView.setWebContentsDebuggingEnabled(enabled); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/lib/src/legacy/webview_surface_android.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; // ignore: implementation_imports import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; import '../android_webview.dart'; import 'webview_android.dart'; import 'webview_android_widget.dart'; /// Android [WebViewPlatform] that uses [AndroidViewSurface] to build the /// [WebView] widget. /// /// To use this, set [WebView.platform] to an instance of this class. /// /// This implementation uses [AndroidViewSurface] to render the [WebView] on /// Android. It solves multiple issues related to accessibility and interaction /// with the [WebView] at the cost of some performance on Android versions below /// 10. /// /// To support transparent backgrounds on all Android devices, this /// implementation uses hybrid composition when the opacity of /// `CreationParams.backgroundColor` is less than 1.0. See /// https://github.com/flutter/flutter/wiki/Hybrid-Composition for more /// information. class SurfaceAndroidWebView extends AndroidWebView { @override Widget build({ required BuildContext context, required CreationParams creationParams, required JavascriptChannelRegistry javascriptChannelRegistry, WebViewPlatformCreatedCallback? onWebViewPlatformCreated, Set>? gestureRecognizers, required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, }) { return WebViewAndroidWidget( useHybridComposition: true, creationParams: creationParams, callbacksHandler: webViewPlatformCallbacksHandler, javascriptChannelRegistry: javascriptChannelRegistry, onBuildWidget: (WebViewAndroidPlatformController controller) { return PlatformViewLink( viewType: 'plugins.flutter.io/webview', surfaceFactory: ( BuildContext context, PlatformViewController controller, ) { return AndroidViewSurface( controller: controller as AndroidViewController, gestureRecognizers: gestureRecognizers ?? const >{}, hitTestBehavior: PlatformViewHitTestBehavior.opaque, ); }, onCreatePlatformView: (PlatformViewCreationParams params) { final Color? backgroundColor = creationParams.backgroundColor; return _createViewController( // On some Android devices, transparent backgrounds can cause // rendering issues on the non hybrid composition // AndroidViewSurface. This switches the WebView to Hybrid // Composition when the background color is not 100% opaque. hybridComposition: backgroundColor != null && backgroundColor.opacity < 1.0, id: params.id, viewType: 'plugins.flutter.io/webview', // WebView content is not affected by the Android view's layout direction, // we explicitly set it here so that the widget doesn't require an ambient // directionality. layoutDirection: Directionality.maybeOf(context) ?? TextDirection.ltr, webViewIdentifier: JavaObject.globalInstanceManager .getIdentifier(controller.webView)!, ) ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated) ..addOnPlatformViewCreatedListener((int id) { if (onWebViewPlatformCreated != null) { onWebViewPlatformCreated(controller); } }) ..create(); }, ); }, ); } AndroidViewController _createViewController({ required bool hybridComposition, required int id, required String viewType, required TextDirection layoutDirection, required int webViewIdentifier, }) { if (hybridComposition) { return PlatformViewsService.initExpensiveAndroidView( id: id, viewType: viewType, layoutDirection: layoutDirection, creationParams: webViewIdentifier, creationParamsCodec: const StandardMessageCodec(), ); } return PlatformViewsService.initSurfaceAndroidView( id: id, viewType: viewType, layoutDirection: layoutDirection, creationParams: webViewIdentifier, creationParamsCodec: const StandardMessageCodec(), ); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/lib/src/platform_views_service_proxy.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; /// Proxy that provides access to the platform views service. /// /// This service allows creating and controlling platform-specific views. @immutable class PlatformViewsServiceProxy { /// Constructs a [PlatformViewsServiceProxy]. const PlatformViewsServiceProxy(); /// Proxy method for [PlatformViewsService.initExpensiveAndroidView]. ExpensiveAndroidViewController initExpensiveAndroidView({ required int id, required String viewType, required TextDirection layoutDirection, dynamic creationParams, MessageCodec? creationParamsCodec, VoidCallback? onFocus, }) { return PlatformViewsService.initExpensiveAndroidView( id: id, viewType: viewType, layoutDirection: layoutDirection, creationParams: creationParams, creationParamsCodec: creationParamsCodec, onFocus: onFocus, ); } /// Proxy method for [PlatformViewsService.initSurfaceAndroidView]. SurfaceAndroidViewController initSurfaceAndroidView({ required int id, required String viewType, required TextDirection layoutDirection, dynamic creationParams, MessageCodec? creationParamsCodec, VoidCallback? onFocus, }) { return PlatformViewsService.initSurfaceAndroidView( id: id, viewType: viewType, layoutDirection: layoutDirection, creationParams: creationParams, creationParamsCodec: creationParamsCodec, onFocus: onFocus, ); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/lib/src/weak_reference_utils.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Helper method for creating callbacks methods with a weak reference. /// /// Example: /// ``` /// final JavascriptChannelRegistry javascriptChannelRegistry = ... /// /// final WKScriptMessageHandler handler = WKScriptMessageHandler( /// didReceiveScriptMessage: withWeakRefenceTo( /// javascriptChannelRegistry, /// (WeakReference weakReference) { /// return ( /// WKUserContentController userContentController, /// WKScriptMessage message, /// ) { /// weakReference.target?.onJavascriptChannelMessage( /// message.name, /// message.body!.toString(), /// ); /// }; /// }, /// ), /// ); /// ``` S withWeakReferenceTo( T reference, S Function(WeakReference weakReference) onCreate, ) { final WeakReference weakReference = WeakReference(reference); return onCreate(weakReference); } ================================================ FILE: packages/webview_flutter/webview_flutter_android/lib/src/webview_flutter_android_legacy.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'legacy/webview_android.dart'; export 'legacy/webview_android_cookie_manager.dart'; export 'legacy/webview_surface_android.dart'; ================================================ FILE: packages/webview_flutter/webview_flutter_android/lib/webview_flutter_android.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. library webview_flutter_android; export 'src/android_webview_controller.dart'; export 'src/android_webview_cookie_manager.dart'; export 'src/android_webview_platform.dart'; ================================================ FILE: packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart ================================================ // Copyright 2013 The Flutter 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/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', 'found in the LICENSE file.', ]), javaOut: 'android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java', javaOptions: JavaOptions( package: 'io.flutter.plugins.webviewflutter', className: 'GeneratedAndroidWebView', copyrightHeader: [ 'Copyright 2013 The Flutter Authors. All rights reserved.', 'Use of this source code is governed by a BSD-style license that can be', 'found in the LICENSE file.', ], ), ), ) /// 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, this.isForMainFrame, this.isRedirect, this.hasGesture, this.method, this.requestHeaders, ); String url; bool isForMainFrame; bool? isRedirect; bool hasGesture; String method; Map requestHeaders; } class WebResourceErrorData { WebResourceErrorData(this.errorCode, this.description); int errorCode; String description; } class WebViewPoint { WebViewPoint(this.x, this.y); int x; int y; } /// Handles methods calls to the native Java Object class. /// /// Also handles calls to remove the reference to an instance with `dispose`. /// /// See https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html. @HostApi(dartHostTestHandler: 'TestJavaObjectHostApi') abstract class JavaObjectHostApi { void dispose(int identifier); } /// Handles callbacks methods for the native Java Object class. /// /// See https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html. @FlutterApi() abstract class JavaObjectFlutterApi { void dispose(int identifier); } @HostApi() abstract class CookieManagerHostApi { @async bool clearCookies(); void setCookie(String url, String value); } @HostApi(dartHostTestHandler: 'TestWebViewHostApi') abstract class WebViewHostApi { 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); @async String? 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); } @HostApi(dartHostTestHandler: 'TestWebSettingsHostApi') abstract class WebSettingsHostApi { 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); } @HostApi(dartHostTestHandler: 'TestJavaScriptChannelHostApi') abstract class JavaScriptChannelHostApi { void create(int instanceId, String channelName); } @FlutterApi() abstract class JavaScriptChannelFlutterApi { void postMessage(int instanceId, String message); } @HostApi(dartHostTestHandler: 'TestWebViewClientHostApi') abstract class WebViewClientHostApi { void create(int instanceId); void setSynchronousReturnValueForShouldOverrideUrlLoading( int instanceId, bool value, ); } @FlutterApi() abstract class WebViewClientFlutterApi { 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); } @HostApi(dartHostTestHandler: 'TestDownloadListenerHostApi') abstract class DownloadListenerHostApi { void create(int instanceId); } @FlutterApi() abstract class DownloadListenerFlutterApi { void onDownloadStart( int instanceId, String url, String userAgent, String contentDisposition, String mimetype, int contentLength, ); } @HostApi(dartHostTestHandler: 'TestWebChromeClientHostApi') abstract class WebChromeClientHostApi { void create(int instanceId); void setSynchronousReturnValueForOnShowFileChooser( int instanceId, bool value, ); } @HostApi(dartHostTestHandler: 'TestAssetManagerHostApi') abstract class FlutterAssetManagerHostApi { List list(String path); String getAssetFilePathByName(String name); } @FlutterApi() abstract class WebChromeClientFlutterApi { void onProgressChanged(int instanceId, int webViewInstanceId, int progress); @async List onShowFileChooser( int instanceId, int webViewInstanceId, int paramsInstanceId, ); } @HostApi(dartHostTestHandler: 'TestWebStorageHostApi') abstract class WebStorageHostApi { void create(int instanceId); 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, ); } ================================================ FILE: packages/webview_flutter/webview_flutter_android/pubspec.yaml ================================================ 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.3.0 environment: sdk: ">=2.17.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: webview_flutter platforms: android: package: io.flutter.plugins.webviewflutter pluginClass: WebViewFlutterPlugin dartPluginClass: AndroidWebViewPlatform dependencies: flutter: sdk: flutter webview_flutter_platform_interface: ^2.0.0 dev_dependencies: build_runner: ^2.1.4 flutter_driver: sdk: flutter flutter_test: sdk: flutter mockito: ^5.3.2 pigeon: ^4.2.14 ================================================ FILE: packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart ================================================ // Copyright 2013 The Flutter 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_test/flutter_test.dart'; import 'package:webview_flutter_android/src/android_proxy.dart'; import 'package:webview_flutter_android/src/android_webview.dart' as android_webview; import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; void main() { group('AndroidNavigationDelegate', () { test('onPageFinished', () { final AndroidNavigationDelegate androidNavigationDelegate = AndroidNavigationDelegate(_buildCreationParams()); late final String callbackUrl; androidNavigationDelegate .setOnPageFinished((String url) => callbackUrl = url); CapturingWebViewClient.lastCreatedDelegate.onPageFinished!( android_webview.WebView.detached(), 'https://www.google.com', ); expect(callbackUrl, 'https://www.google.com'); }); test('onPageStarted', () { final AndroidNavigationDelegate androidNavigationDelegate = AndroidNavigationDelegate(_buildCreationParams()); late final String callbackUrl; androidNavigationDelegate .setOnPageStarted((String url) => callbackUrl = url); CapturingWebViewClient.lastCreatedDelegate.onPageStarted!( android_webview.WebView.detached(), 'https://www.google.com', ); expect(callbackUrl, 'https://www.google.com'); }); test('onWebResourceError from onReceivedRequestError', () { final AndroidNavigationDelegate androidNavigationDelegate = AndroidNavigationDelegate(_buildCreationParams()); late final WebResourceError callbackError; androidNavigationDelegate.setOnWebResourceError( (WebResourceError error) => callbackError = error); CapturingWebViewClient.lastCreatedDelegate.onReceivedRequestError!( android_webview.WebView.detached(), android_webview.WebResourceRequest( url: 'https://www.google.com', isForMainFrame: false, isRedirect: true, hasGesture: true, method: 'GET', requestHeaders: {'X-Mock': 'mocking'}, ), android_webview.WebResourceError( errorCode: android_webview.WebViewClient.errorFileNotFound, description: 'Page not found.', ), ); expect(callbackError.errorCode, android_webview.WebViewClient.errorFileNotFound); expect(callbackError.description, 'Page not found.'); expect(callbackError.errorType, WebResourceErrorType.fileNotFound); expect(callbackError.isForMainFrame, false); }); test('onWebResourceError from onRequestError', () { final AndroidNavigationDelegate androidNavigationDelegate = AndroidNavigationDelegate(_buildCreationParams()); late final WebResourceError callbackError; androidNavigationDelegate.setOnWebResourceError( (WebResourceError error) => callbackError = error); CapturingWebViewClient.lastCreatedDelegate.onReceivedError!( android_webview.WebView.detached(), android_webview.WebViewClient.errorFileNotFound, 'Page not found.', 'https://www.google.com', ); expect(callbackError.errorCode, android_webview.WebViewClient.errorFileNotFound); expect(callbackError.description, 'Page not found.'); expect(callbackError.errorType, WebResourceErrorType.fileNotFound); expect(callbackError.isForMainFrame, true); }); test( 'onNavigationRequest from requestLoading should not be called when loadUrlCallback is not specified', () { final AndroidNavigationDelegate androidNavigationDelegate = AndroidNavigationDelegate(_buildCreationParams()); NavigationRequest? callbackNavigationRequest; androidNavigationDelegate .setOnNavigationRequest((NavigationRequest navigationRequest) { callbackNavigationRequest = navigationRequest; return NavigationDecision.prevent; }); CapturingWebViewClient.lastCreatedDelegate.requestLoading!( android_webview.WebView.detached(), android_webview.WebResourceRequest( url: 'https://www.google.com', isForMainFrame: true, isRedirect: true, hasGesture: true, method: 'GET', requestHeaders: {'X-Mock': 'mocking'}, ), ); expect(callbackNavigationRequest, isNull); }); test( 'onLoadRequest from requestLoading should not be called when navigationRequestCallback is not specified', () { final Completer completer = Completer(); final AndroidNavigationDelegate androidNavigationDelegate = AndroidNavigationDelegate(_buildCreationParams()); androidNavigationDelegate.setOnLoadRequest((_) { completer.complete(); return completer.future; }); CapturingWebViewClient.lastCreatedDelegate.requestLoading!( android_webview.WebView.detached(), android_webview.WebResourceRequest( url: 'https://www.google.com', isForMainFrame: true, isRedirect: true, hasGesture: true, method: 'GET', requestHeaders: {'X-Mock': 'mocking'}, ), ); expect(completer.isCompleted, false); }); test( 'onLoadRequest from requestLoading should not be called when onNavigationRequestCallback returns NavigationDecision.prevent', () { final Completer completer = Completer(); final AndroidNavigationDelegate androidNavigationDelegate = AndroidNavigationDelegate(_buildCreationParams()); androidNavigationDelegate.setOnLoadRequest((_) { completer.complete(); return completer.future; }); late final NavigationRequest callbackNavigationRequest; androidNavigationDelegate .setOnNavigationRequest((NavigationRequest navigationRequest) { callbackNavigationRequest = navigationRequest; return NavigationDecision.prevent; }); CapturingWebViewClient.lastCreatedDelegate.requestLoading!( android_webview.WebView.detached(), android_webview.WebResourceRequest( url: 'https://www.google.com', isForMainFrame: true, isRedirect: true, hasGesture: true, method: 'GET', requestHeaders: {'X-Mock': 'mocking'}, ), ); expect(callbackNavigationRequest.isMainFrame, true); expect(callbackNavigationRequest.url, 'https://www.google.com'); expect(completer.isCompleted, false); }); test( 'onLoadRequest from requestLoading should complete when onNavigationRequestCallback returns NavigationDecision.navigate', () { final Completer completer = Completer(); late final LoadRequestParams loadRequestParams; final AndroidNavigationDelegate androidNavigationDelegate = AndroidNavigationDelegate(_buildCreationParams()); androidNavigationDelegate.setOnLoadRequest((LoadRequestParams params) { loadRequestParams = params; completer.complete(); return completer.future; }); late final NavigationRequest callbackNavigationRequest; androidNavigationDelegate .setOnNavigationRequest((NavigationRequest navigationRequest) { callbackNavigationRequest = navigationRequest; return NavigationDecision.navigate; }); CapturingWebViewClient.lastCreatedDelegate.requestLoading!( android_webview.WebView.detached(), android_webview.WebResourceRequest( url: 'https://www.google.com', isForMainFrame: true, isRedirect: true, hasGesture: true, method: 'GET', requestHeaders: {'X-Mock': 'mocking'}, ), ); expect(loadRequestParams.uri.toString(), 'https://www.google.com'); expect(loadRequestParams.headers, {'X-Mock': 'mocking'}); expect(callbackNavigationRequest.isMainFrame, true); expect(callbackNavigationRequest.url, 'https://www.google.com'); expect(completer.isCompleted, true); }); test( 'onNavigationRequest from urlLoading should not be called when loadUrlCallback is not specified', () { final AndroidNavigationDelegate androidNavigationDelegate = AndroidNavigationDelegate(_buildCreationParams()); NavigationRequest? callbackNavigationRequest; androidNavigationDelegate .setOnNavigationRequest((NavigationRequest navigationRequest) { callbackNavigationRequest = navigationRequest; return NavigationDecision.prevent; }); CapturingWebViewClient.lastCreatedDelegate.urlLoading!( android_webview.WebView.detached(), 'https://www.google.com', ); expect(callbackNavigationRequest, isNull); }); test( 'onLoadRequest from urlLoading should not be called when navigationRequestCallback is not specified', () { final Completer completer = Completer(); final AndroidNavigationDelegate androidNavigationDelegate = AndroidNavigationDelegate(_buildCreationParams()); androidNavigationDelegate.setOnLoadRequest((_) { completer.complete(); return completer.future; }); CapturingWebViewClient.lastCreatedDelegate.urlLoading!( android_webview.WebView.detached(), 'https://www.google.com', ); expect(completer.isCompleted, false); }); test( 'onLoadRequest from urlLoading should not be called when onNavigationRequestCallback returns NavigationDecision.prevent', () { final Completer completer = Completer(); final AndroidNavigationDelegate androidNavigationDelegate = AndroidNavigationDelegate(_buildCreationParams()); androidNavigationDelegate.setOnLoadRequest((_) { completer.complete(); return completer.future; }); late final NavigationRequest callbackNavigationRequest; androidNavigationDelegate .setOnNavigationRequest((NavigationRequest navigationRequest) { callbackNavigationRequest = navigationRequest; return NavigationDecision.prevent; }); CapturingWebViewClient.lastCreatedDelegate.urlLoading!( android_webview.WebView.detached(), 'https://www.google.com', ); expect(callbackNavigationRequest.isMainFrame, true); expect(callbackNavigationRequest.url, 'https://www.google.com'); expect(completer.isCompleted, false); }); test( 'onLoadRequest from urlLoading should complete when onNavigationRequestCallback returns NavigationDecision.navigate', () { final Completer completer = Completer(); late final LoadRequestParams loadRequestParams; final AndroidNavigationDelegate androidNavigationDelegate = AndroidNavigationDelegate(_buildCreationParams()); androidNavigationDelegate.setOnLoadRequest((LoadRequestParams params) { loadRequestParams = params; completer.complete(); return completer.future; }); late final NavigationRequest callbackNavigationRequest; androidNavigationDelegate .setOnNavigationRequest((NavigationRequest navigationRequest) { callbackNavigationRequest = navigationRequest; return NavigationDecision.navigate; }); CapturingWebViewClient.lastCreatedDelegate.urlLoading!( android_webview.WebView.detached(), 'https://www.google.com', ); expect(loadRequestParams.uri.toString(), 'https://www.google.com'); expect(loadRequestParams.headers, {}); expect(callbackNavigationRequest.isMainFrame, true); expect(callbackNavigationRequest.url, 'https://www.google.com'); expect(completer.isCompleted, true); }); test('setOnNavigationRequest should override URL loading', () { final AndroidNavigationDelegate androidNavigationDelegate = AndroidNavigationDelegate(_buildCreationParams()); androidNavigationDelegate.setOnNavigationRequest( (NavigationRequest request) => NavigationDecision.navigate, ); expect( CapturingWebViewClient.lastCreatedDelegate .synchronousReturnValueForShouldOverrideUrlLoading, isTrue); }); test( 'onLoadRequest from onDownloadStart should not be called when navigationRequestCallback is not specified', () { final Completer completer = Completer(); final AndroidNavigationDelegate androidNavigationDelegate = AndroidNavigationDelegate(_buildCreationParams()); androidNavigationDelegate.setOnLoadRequest((_) { completer.complete(); return completer.future; }); CapturingDownloadListener.lastCreatedListener.onDownloadStart( '', '', '', '', 0, ); expect(completer.isCompleted, false); }); test( 'onLoadRequest from onDownloadStart should not be called when onNavigationRequestCallback returns NavigationDecision.prevent', () { final Completer completer = Completer(); final AndroidNavigationDelegate androidNavigationDelegate = AndroidNavigationDelegate(_buildCreationParams()); androidNavigationDelegate.setOnLoadRequest((_) { completer.complete(); return completer.future; }); late final NavigationRequest callbackNavigationRequest; androidNavigationDelegate .setOnNavigationRequest((NavigationRequest navigationRequest) { callbackNavigationRequest = navigationRequest; return NavigationDecision.prevent; }); CapturingDownloadListener.lastCreatedListener.onDownloadStart( 'https://www.google.com', '', '', '', 0, ); expect(callbackNavigationRequest.isMainFrame, true); expect(callbackNavigationRequest.url, 'https://www.google.com'); expect(completer.isCompleted, false); }); test( 'onLoadRequest from onDownloadStart should complete when onNavigationRequestCallback returns NavigationDecision.navigate', () { final Completer completer = Completer(); late final LoadRequestParams loadRequestParams; final AndroidNavigationDelegate androidNavigationDelegate = AndroidNavigationDelegate(_buildCreationParams()); androidNavigationDelegate.setOnLoadRequest((LoadRequestParams params) { loadRequestParams = params; completer.complete(); return completer.future; }); late final NavigationRequest callbackNavigationRequest; androidNavigationDelegate .setOnNavigationRequest((NavigationRequest navigationRequest) { callbackNavigationRequest = navigationRequest; return NavigationDecision.navigate; }); CapturingDownloadListener.lastCreatedListener.onDownloadStart( 'https://www.google.com', '', '', '', 0, ); expect(loadRequestParams.uri.toString(), 'https://www.google.com'); expect(loadRequestParams.headers, {}); expect(callbackNavigationRequest.isMainFrame, true); expect(callbackNavigationRequest.url, 'https://www.google.com'); expect(completer.isCompleted, true); }); }); } AndroidNavigationDelegateCreationParams _buildCreationParams() { return AndroidNavigationDelegateCreationParams .fromPlatformNavigationDelegateCreationParams( const PlatformNavigationDelegateCreationParams(), androidWebViewProxy: const AndroidWebViewProxy( createAndroidWebChromeClient: CapturingWebChromeClient.new, createAndroidWebViewClient: CapturingWebViewClient.new, createDownloadListener: CapturingDownloadListener.new, ), ); } // Records the last created instance of itself. class CapturingWebViewClient extends android_webview.WebViewClient { CapturingWebViewClient({ super.onPageFinished, super.onPageStarted, super.onReceivedError, super.onReceivedRequestError, super.requestLoading, super.urlLoading, }) : super.detached() { lastCreatedDelegate = this; } static CapturingWebViewClient lastCreatedDelegate = CapturingWebViewClient(); bool synchronousReturnValueForShouldOverrideUrlLoading = false; @override Future setSynchronousReturnValueForShouldOverrideUrlLoading( bool value) async { synchronousReturnValueForShouldOverrideUrlLoading = value; } } // Records the last created instance of itself. class CapturingWebChromeClient extends android_webview.WebChromeClient { CapturingWebChromeClient({ super.onProgressChanged, super.onShowFileChooser, }) : super.detached() { lastCreatedDelegate = this; } static CapturingWebChromeClient lastCreatedDelegate = CapturingWebChromeClient(); } // Records the last created instance of itself. class CapturingDownloadListener extends android_webview.DownloadListener { CapturingDownloadListener({ required super.onDownloadStart, }) : super.detached() { lastCreatedListener = this; } static CapturingDownloadListener lastCreatedListener = CapturingDownloadListener(onDownloadStart: (_, __, ___, ____, _____) {}); } ================================================ FILE: packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_android/src/android_proxy.dart'; import 'package:webview_flutter_android/src/android_webview.dart' as android_webview; 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/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(>[ MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), ]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); AndroidWebViewController createControllerWithMocks({ android_webview.FlutterAssetManager? mockFlutterAssetManager, android_webview.JavaScriptChannel? mockJavaScriptChannel, 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, android_webview.WebSettings? mockSettings, }) { final android_webview.WebView nonNullMockWebView = mockWebView ?? MockWebView(); final AndroidWebViewControllerCreationParams creationParams = AndroidWebViewControllerCreationParams( androidWebStorage: mockWebStorage ?? MockWebStorage(), androidWebViewProxy: AndroidWebViewProxy( 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: ({ void Function(android_webview.WebView webView, String url)? onPageFinished, void Function(android_webview.WebView webView, String url)? onPageStarted, @Deprecated('Only called on Android version < 23.') void Function( android_webview.WebView webView, int errorCode, String description, String failingUrl, )? onReceivedError, void Function( android_webview.WebView webView, android_webview.WebResourceRequest request, android_webview.WebResourceError error, )? onReceivedRequestError, void Function( android_webview.WebView webView, android_webview.WebResourceRequest request, )? requestLoading, void Function(android_webview.WebView webView, String url)? urlLoading, }) => mockWebViewClient ?? MockWebViewClient(), createFlutterAssetManager: () => mockFlutterAssetManager ?? MockFlutterAssetManager(), createJavaScriptChannel: ( String channelName, { required void Function(String) postMessage, }) => mockJavaScriptChannel ?? MockJavaScriptChannel(), )); when(nonNullMockWebView.settings) .thenReturn(mockSettings ?? MockWebSettings()); return AndroidWebViewController(creationParams); } group('AndroidWebViewController', () { AndroidJavaScriptChannelParams createAndroidJavaScriptChannelParamsWithMocks({ String? name, MockJavaScriptChannel? mockJavaScriptChannel, }) { return AndroidJavaScriptChannelParams( name: name ?? 'test', onMessageReceived: (JavaScriptMessage message) {}, webViewProxy: AndroidWebViewProxy( createJavaScriptChannel: ( String channelName, { required void Function(String) postMessage, }) => mockJavaScriptChannel ?? MockJavaScriptChannel(), )); } test('loadFile without file prefix', () async { final MockWebView mockWebView = MockWebView(); final MockWebSettings mockWebSettings = MockWebSettings(); createControllerWithMocks( mockWebView: mockWebView, mockSettings: mockWebSettings, ); verify(mockWebSettings.setBuiltInZoomControls(true)).called(1); verify(mockWebSettings.setDisplayZoomControls(false)).called(1); verify(mockWebSettings.setDomStorageEnabled(true)).called(1); verify(mockWebSettings.setJavaScriptCanOpenWindowsAutomatically(true)) .called(1); verify(mockWebSettings.setLoadWithOverviewMode(true)).called(1); verify(mockWebSettings.setSupportMultipleWindows(true)).called(1); verify(mockWebSettings.setUseWideViewPort(true)).called(1); }); test('loadFile without file prefix', () async { final MockWebView mockWebView = MockWebView(); final MockWebSettings mockWebSettings = MockWebSettings(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, mockSettings: mockWebSettings, ); await controller.loadFile('/path/to/file.html'); verify(mockWebSettings.setAllowFileAccess(true)).called(1); verify(mockWebView.loadUrl( 'file:///path/to/file.html', {}, )).called(1); }); test('loadFile without file prefix and characters to be escaped', () async { final MockWebView mockWebView = MockWebView(); final MockWebSettings mockWebSettings = MockWebSettings(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, mockSettings: mockWebSettings, ); await controller.loadFile('/path/to/?_<_>_.html'); verify(mockWebSettings.setAllowFileAccess(true)).called(1); verify(mockWebView.loadUrl( 'file:///path/to/%3F_%3C_%3E_.html', {}, )).called(1); }); test('loadFile with file prefix', () async { final MockWebView mockWebView = MockWebView(); final MockWebSettings mockWebSettings = MockWebSettings(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); when(mockWebView.settings).thenReturn(mockWebSettings); await controller.loadFile('file:///path/to/file.html'); verify(mockWebSettings.setAllowFileAccess(true)).called(1); verify(mockWebView.loadUrl( 'file:///path/to/file.html', {}, )).called(1); }); test('loadFlutterAsset when asset does not exists', () async { final MockWebView mockWebView = MockWebView(); final MockFlutterAssetManager mockAssetManager = MockFlutterAssetManager(); final AndroidWebViewController controller = createControllerWithMocks( mockFlutterAssetManager: mockAssetManager, mockWebView: mockWebView, ); when(mockAssetManager.getAssetFilePathByName('mock_key')) .thenAnswer((_) => Future.value('')); when(mockAssetManager.list('')) .thenAnswer((_) => Future>.value([])); try { await controller.loadFlutterAsset('mock_key'); fail('Expected an `ArgumentError`.'); } on ArgumentError catch (e) { expect(e.message, 'Asset for key "mock_key" not found.'); expect(e.name, 'key'); } on Error { fail('Expect an `ArgumentError`.'); } verify(mockAssetManager.getAssetFilePathByName('mock_key')).called(1); verify(mockAssetManager.list('')).called(1); verifyNever(mockWebView.loadUrl(any, any)); }); test('loadFlutterAsset when asset does exists', () async { final MockWebView mockWebView = MockWebView(); final MockFlutterAssetManager mockAssetManager = MockFlutterAssetManager(); final AndroidWebViewController controller = createControllerWithMocks( mockFlutterAssetManager: mockAssetManager, mockWebView: mockWebView, ); when(mockAssetManager.getAssetFilePathByName('mock_key')) .thenAnswer((_) => Future.value('www/mock_file.html')); when(mockAssetManager.list('www')).thenAnswer( (_) => Future>.value(['mock_file.html'])); await controller.loadFlutterAsset('mock_key'); verify(mockAssetManager.getAssetFilePathByName('mock_key')).called(1); verify(mockAssetManager.list('www')).called(1); verify(mockWebView.loadUrl( 'file:///android_asset/www/mock_file.html', {})); }); test( 'loadFlutterAsset when asset name contains characters that should be escaped', () async { final MockWebView mockWebView = MockWebView(); final MockFlutterAssetManager mockAssetManager = MockFlutterAssetManager(); final AndroidWebViewController controller = createControllerWithMocks( mockFlutterAssetManager: mockAssetManager, mockWebView: mockWebView, ); when(mockAssetManager.getAssetFilePathByName('mock_key')) .thenAnswer((_) => Future.value('www/?_<_>_.html')); when(mockAssetManager.list('www')).thenAnswer( (_) => Future>.value(['?_<_>_.html'])); await controller.loadFlutterAsset('mock_key'); verify(mockAssetManager.getAssetFilePathByName('mock_key')).called(1); verify(mockAssetManager.list('www')).called(1); verify(mockWebView.loadUrl( 'file:///android_asset/www/%3F_%3C_%3E_.html', {})); }); test('loadHtmlString without baseUrl', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); await controller.loadHtmlString('

Hello Test!

'); verify(mockWebView.loadDataWithBaseUrl( data: '

Hello Test!

', mimeType: 'text/html', )).called(1); }); test('loadHtmlString with baseUrl', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); await controller.loadHtmlString('

Hello Test!

', baseUrl: 'https://flutter.dev'); verify(mockWebView.loadDataWithBaseUrl( data: '

Hello Test!

', baseUrl: 'https://flutter.dev', mimeType: 'text/html', )).called(1); }); test('loadRequest without URI scheme', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); final LoadRequestParams requestParams = LoadRequestParams( uri: Uri.parse('flutter.dev'), ); try { await controller.loadRequest(requestParams); fail('Expect an `ArgumentError`.'); } on ArgumentError catch (e) { expect(e.message, 'WebViewRequest#uri is required to have a scheme.'); } on Error { fail('Expect a `ArgumentError`.'); } verifyNever(mockWebView.loadUrl(any, any)); verifyNever(mockWebView.postUrl(any, any)); }); test('loadRequest using the GET method', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); final LoadRequestParams requestParams = LoadRequestParams( uri: Uri.parse('https://flutter.dev'), headers: const {'X-Test': 'Testing'}, ); await controller.loadRequest(requestParams); verify(mockWebView.loadUrl( 'https://flutter.dev', {'X-Test': 'Testing'}, )); verifyNever(mockWebView.postUrl(any, any)); }); test('loadRequest using the POST method without body', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); final LoadRequestParams requestParams = LoadRequestParams( uri: Uri.parse('https://flutter.dev'), method: LoadRequestMethod.post, headers: const {'X-Test': 'Testing'}, ); await controller.loadRequest(requestParams); verify(mockWebView.postUrl( 'https://flutter.dev', Uint8List(0), )); verifyNever(mockWebView.loadUrl(any, any)); }); test('loadRequest using the POST method with body', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); final LoadRequestParams requestParams = LoadRequestParams( uri: Uri.parse('https://flutter.dev'), method: LoadRequestMethod.post, headers: const {'X-Test': 'Testing'}, body: Uint8List.fromList('{"message": "Hello World!"}'.codeUnits), ); await controller.loadRequest(requestParams); verify(mockWebView.postUrl( 'https://flutter.dev', Uint8List.fromList('{"message": "Hello World!"}'.codeUnits), )); verifyNever(mockWebView.loadUrl(any, any)); }); test('currentUrl', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); await controller.currentUrl(); verify(mockWebView.getUrl()).called(1); }); test('canGoBack', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); await controller.canGoBack(); verify(mockWebView.canGoBack()).called(1); }); test('canGoForward', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); await controller.canGoForward(); verify(mockWebView.canGoForward()).called(1); }); test('goBack', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); await controller.goBack(); verify(mockWebView.goBack()).called(1); }); test('goForward', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); await controller.goForward(); verify(mockWebView.goForward()).called(1); }); test('reload', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); await controller.reload(); verify(mockWebView.reload()).called(1); }); test('clearCache', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); await controller.clearCache(); verify(mockWebView.clearCache(true)).called(1); }); test('clearLocalStorage', () async { final MockWebStorage mockWebStorage = MockWebStorage(); final AndroidWebViewController controller = createControllerWithMocks( mockWebStorage: mockWebStorage, ); await controller.clearLocalStorage(); verify(mockWebStorage.deleteAllData()).called(1); }); test('setPlatformNavigationDelegate', () async { final MockAndroidNavigationDelegate mockNavigationDelegate = MockAndroidNavigationDelegate(); final MockWebView mockWebView = MockWebView(); final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); final MockWebViewClient mockWebViewClient = MockWebViewClient(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); when(mockNavigationDelegate.androidWebChromeClient) .thenReturn(mockWebChromeClient); when(mockNavigationDelegate.androidWebViewClient) .thenReturn(mockWebViewClient); await controller.setPlatformNavigationDelegate(mockNavigationDelegate); 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 { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); await controller.runJavaScript('alert("This is a test.");'); verify(mockWebView.evaluateJavascript('alert("This is a test.");')) .called(1); }); test('runJavaScriptReturningResult with return value', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); when(mockWebView.evaluateJavascript('return "Hello" + " World!";')) .thenAnswer((_) => Future.value('Hello World!')); final String message = await controller.runJavaScriptReturningResult( 'return "Hello" + " World!";') as String; expect(message, 'Hello World!'); }); test('runJavaScriptReturningResult returning null', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); when(mockWebView.evaluateJavascript('alert("This is a test.");')) .thenAnswer((_) => Future.value()); final String message = await controller .runJavaScriptReturningResult('alert("This is a test.");') as String; expect(message, ''); }); test('runJavaScriptReturningResult parses num', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); when(mockWebView.evaluateJavascript('alert("This is a test.");')) .thenAnswer((_) => Future.value('3.14')); final num message = await controller .runJavaScriptReturningResult('alert("This is a test.");') as num; expect(message, 3.14); }); test('runJavaScriptReturningResult parses true', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); when(mockWebView.evaluateJavascript('alert("This is a test.");')) .thenAnswer((_) => Future.value('true')); final bool message = await controller .runJavaScriptReturningResult('alert("This is a test.");') as bool; expect(message, true); }); test('runJavaScriptReturningResult parses false', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); when(mockWebView.evaluateJavascript('alert("This is a test.");')) .thenAnswer((_) => Future.value('false')); final bool message = await controller .runJavaScriptReturningResult('alert("This is a test.");') as bool; expect(message, false); }); test('addJavaScriptChannel', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); final AndroidJavaScriptChannelParams paramsWithMock = createAndroidJavaScriptChannelParamsWithMocks(name: 'test'); await controller.addJavaScriptChannel(paramsWithMock); verify(mockWebView.addJavaScriptChannel( argThat(isA()))) .called(1); }); test( 'addJavaScriptChannel add channel with same name should remove existing channel', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); final AndroidJavaScriptChannelParams paramsWithMock = createAndroidJavaScriptChannelParamsWithMocks(name: 'test'); await controller.addJavaScriptChannel(paramsWithMock); verify(mockWebView.addJavaScriptChannel( argThat(isA()))) .called(1); await controller.addJavaScriptChannel(paramsWithMock); verifyInOrder([ mockWebView.removeJavaScriptChannel( argThat(isA())), mockWebView.addJavaScriptChannel( argThat(isA())), ]); }); test('removeJavaScriptChannel when channel is not registered', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); await controller.removeJavaScriptChannel('test'); verifyNever(mockWebView.removeJavaScriptChannel(any)); }); test('removeJavaScriptChannel when channel exists', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); final AndroidJavaScriptChannelParams paramsWithMock = createAndroidJavaScriptChannelParamsWithMocks(name: 'test'); // Make sure channel exists before removing it. await controller.addJavaScriptChannel(paramsWithMock); verify(mockWebView.addJavaScriptChannel( argThat(isA()))) .called(1); await controller.removeJavaScriptChannel('test'); verify(mockWebView.removeJavaScriptChannel( argThat(isA()))) .called(1); }); test('getTitle', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); await controller.getTitle(); verify(mockWebView.getTitle()).called(1); }); test('scrollTo', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); await controller.scrollTo(4, 2); verify(mockWebView.scrollTo(4, 2)).called(1); }); test('scrollBy', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); await controller.scrollBy(4, 2); verify(mockWebView.scrollBy(4, 2)).called(1); }); test('getScrollPosition', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); when(mockWebView.getScrollPosition()) .thenAnswer((_) => Future.value(const Offset(4, 2))); final Offset position = await controller.getScrollPosition(); verify(mockWebView.getScrollPosition()).called(1); expect(position.dx, 4); expect(position.dy, 2); }); test('enableDebugging', () async { final MockAndroidWebViewProxy mockProxy = MockAndroidWebViewProxy(); await AndroidWebViewController.enableDebugging( true, webViewProxy: mockProxy, ); verify(mockProxy.setWebContentsDebuggingEnabled(true)).called(1); }); test('enableZoom', () async { final MockWebView mockWebView = MockWebView(); final MockWebSettings mockSettings = MockWebSettings(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, mockSettings: mockSettings, ); clearInteractions(mockWebView); await controller.enableZoom(true); verify(mockWebView.settings).called(1); verify(mockSettings.setSupportZoom(true)).called(1); }); test('setBackgroundColor', () async { final MockWebView mockWebView = MockWebView(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); await controller.setBackgroundColor(Colors.blue); verify(mockWebView.setBackgroundColor(Colors.blue)).called(1); }); test('setJavaScriptMode', () async { final MockWebView mockWebView = MockWebView(); final MockWebSettings mockSettings = MockWebSettings(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, mockSettings: mockSettings, ); clearInteractions(mockWebView); await controller.setJavaScriptMode(JavaScriptMode.disabled); verify(mockWebView.settings).called(1); verify(mockSettings.setJavaScriptEnabled(false)).called(1); }); test('setUserAgent', () async { final MockWebView mockWebView = MockWebView(); final MockWebSettings mockSettings = MockWebSettings(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, mockSettings: mockSettings, ); clearInteractions(mockWebView); await controller.setUserAgent('Test Framework'); verify(mockWebView.settings).called(1); verify(mockSettings.setUserAgentString('Test Framework')).called(1); }); }); test('setMediaPlaybackRequiresUserGesture', () async { final MockWebView mockWebView = MockWebView(); final MockWebSettings mockSettings = MockWebSettings(); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, mockSettings: mockSettings, ); await controller.setMediaPlaybackRequiresUserGesture(true); verify(mockSettings.setMediaPlaybackRequiresUserGesture(true)).called(1); }); test('webViewIdentifier', () { final MockWebView mockWebView = MockWebView(); final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); instanceManager.addHostCreatedInstance(mockWebView, 0); android_webview.WebView.api = WebViewHostApiImpl( instanceManager: instanceManager, ); final AndroidWebViewController controller = createControllerWithMocks( mockWebView: mockWebView, ); expect( controller.webViewIdentifier, 0, ); android_webview.WebView.api = WebViewHostApiImpl(); }); group('AndroidWebViewWidget', () { testWidgets('Builds Android view using supplied parameters', (WidgetTester tester) async { final AndroidWebViewController controller = createControllerWithMocks(); final AndroidWebViewWidget webViewWidget = AndroidWebViewWidget( AndroidWebViewWidgetCreationParams( key: const Key('test_web_view'), controller: controller, ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) => webViewWidget.build(context), )); expect(find.byType(PlatformViewLink), findsOneWidget); expect(find.byKey(const Key('test_web_view')), findsOneWidget); }); testWidgets('displayWithHybridComposition is false', (WidgetTester tester) async { final AndroidWebViewController controller = createControllerWithMocks(); final MockPlatformViewsServiceProxy mockPlatformViewsService = MockPlatformViewsServiceProxy(); when( mockPlatformViewsService.initSurfaceAndroidView( id: anyNamed('id'), viewType: anyNamed('viewType'), layoutDirection: anyNamed('layoutDirection'), creationParams: anyNamed('creationParams'), creationParamsCodec: anyNamed('creationParamsCodec'), onFocus: anyNamed('onFocus'), ), ).thenReturn(MockSurfaceAndroidViewController()); final AndroidWebViewWidget webViewWidget = AndroidWebViewWidget( AndroidWebViewWidgetCreationParams( key: const Key('test_web_view'), controller: controller, platformViewsServiceProxy: mockPlatformViewsService, ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) => webViewWidget.build(context), )); await tester.pumpAndSettle(); verify( mockPlatformViewsService.initSurfaceAndroidView( id: anyNamed('id'), viewType: anyNamed('viewType'), layoutDirection: anyNamed('layoutDirection'), creationParams: anyNamed('creationParams'), creationParamsCodec: anyNamed('creationParamsCodec'), onFocus: anyNamed('onFocus'), ), ); }); testWidgets('displayWithHybridComposition is true', (WidgetTester tester) async { final AndroidWebViewController controller = createControllerWithMocks(); final MockPlatformViewsServiceProxy mockPlatformViewsService = MockPlatformViewsServiceProxy(); when( mockPlatformViewsService.initExpensiveAndroidView( id: anyNamed('id'), viewType: anyNamed('viewType'), layoutDirection: anyNamed('layoutDirection'), creationParams: anyNamed('creationParams'), creationParamsCodec: anyNamed('creationParamsCodec'), onFocus: anyNamed('onFocus'), ), ).thenReturn(MockExpensiveAndroidViewController()); final AndroidWebViewWidget webViewWidget = AndroidWebViewWidget( AndroidWebViewWidgetCreationParams( key: const Key('test_web_view'), controller: controller, platformViewsServiceProxy: mockPlatformViewsService, displayWithHybridComposition: true, ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) => webViewWidget.build(context), )); await tester.pumpAndSettle(); verify( mockPlatformViewsService.initExpensiveAndroidView( id: anyNamed('id'), viewType: anyNamed('viewType'), layoutDirection: anyNamed('layoutDirection'), creationParams: anyNamed('creationParams'), creationParamsCodec: anyNamed('creationParamsCodec'), onFocus: anyNamed('onFocus'), ), ); }); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_android/test/android_webview_controller_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i9; import 'dart:typed_data' as _i14; import 'dart:ui' as _i4; 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_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 _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; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart' as _i3; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class class _FakeWebChromeClient_0 extends _i1.SmartFake implements _i2.WebChromeClient { _FakeWebChromeClient_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWebViewClient_1 extends _i1.SmartFake implements _i2.WebViewClient { _FakeWebViewClient_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeDownloadListener_2 extends _i1.SmartFake implements _i2.DownloadListener { _FakeDownloadListener_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePlatformNavigationDelegateCreationParams_3 extends _i1.SmartFake implements _i3.PlatformNavigationDelegateCreationParams { _FakePlatformNavigationDelegateCreationParams_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePlatformWebViewControllerCreationParams_4 extends _i1.SmartFake implements _i3.PlatformWebViewControllerCreationParams { _FakePlatformWebViewControllerCreationParams_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeObject_5 extends _i1.SmartFake implements Object { _FakeObject_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeOffset_6 extends _i1.SmartFake implements _i4.Offset { _FakeOffset_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWebView_7 extends _i1.SmartFake implements _i2.WebView { _FakeWebView_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeFlutterAssetManager_8 extends _i1.SmartFake implements _i2.FlutterAssetManager { _FakeFlutterAssetManager_8( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeJavaScriptChannel_9 extends _i1.SmartFake implements _i2.JavaScriptChannel { _FakeJavaScriptChannel_9( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeInstanceManager_10 extends _i1.SmartFake implements _i5.InstanceManager { _FakeInstanceManager_10( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePlatformViewsServiceProxy_11 extends _i1.SmartFake implements _i6.PlatformViewsServiceProxy { _FakePlatformViewsServiceProxy_11( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePlatformWebViewController_12 extends _i1.SmartFake implements _i3.PlatformWebViewController { _FakePlatformWebViewController_12( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeSize_13 extends _i1.SmartFake implements _i4.Size { _FakeSize_13( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeExpensiveAndroidViewController_14 extends _i1.SmartFake implements _i7.ExpensiveAndroidViewController { _FakeExpensiveAndroidViewController_14( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeSurfaceAndroidViewController_15 extends _i1.SmartFake implements _i7.SurfaceAndroidViewController { _FakeSurfaceAndroidViewController_15( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWebSettings_16 extends _i1.SmartFake implements _i2.WebSettings { _FakeWebSettings_16( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWebStorage_17 extends _i1.SmartFake implements _i2.WebStorage { _FakeWebStorage_17( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [AndroidNavigationDelegate]. /// /// See the documentation for Mockito's code generation for more information. class MockAndroidNavigationDelegate extends _i1.Mock implements _i8.AndroidNavigationDelegate { @override _i2.WebChromeClient get androidWebChromeClient => (super.noSuchMethod( Invocation.getter(#androidWebChromeClient), returnValue: _FakeWebChromeClient_0( this, Invocation.getter(#androidWebChromeClient), ), returnValueForMissingStub: _FakeWebChromeClient_0( this, Invocation.getter(#androidWebChromeClient), ), ) as _i2.WebChromeClient); @override _i2.WebViewClient get androidWebViewClient => (super.noSuchMethod( Invocation.getter(#androidWebViewClient), returnValue: _FakeWebViewClient_1( this, Invocation.getter(#androidWebViewClient), ), returnValueForMissingStub: _FakeWebViewClient_1( this, Invocation.getter(#androidWebViewClient), ), ) as _i2.WebViewClient); @override _i2.DownloadListener get androidDownloadListener => (super.noSuchMethod( Invocation.getter(#androidDownloadListener), returnValue: _FakeDownloadListener_2( this, Invocation.getter(#androidDownloadListener), ), returnValueForMissingStub: _FakeDownloadListener_2( this, Invocation.getter(#androidDownloadListener), ), ) as _i2.DownloadListener); @override _i3.PlatformNavigationDelegateCreationParams get params => (super.noSuchMethod( Invocation.getter(#params), returnValue: _FakePlatformNavigationDelegateCreationParams_3( this, Invocation.getter(#params), ), returnValueForMissingStub: _FakePlatformNavigationDelegateCreationParams_3( this, Invocation.getter(#params), ), ) as _i3.PlatformNavigationDelegateCreationParams); @override _i9.Future setOnLoadRequest(_i8.LoadRequestCallback? onLoadRequest) => (super.noSuchMethod( Invocation.method( #setOnLoadRequest, [onLoadRequest], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setOnNavigationRequest( _i3.NavigationRequestCallback? onNavigationRequest) => (super.noSuchMethod( Invocation.method( #setOnNavigationRequest, [onNavigationRequest], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setOnPageStarted(_i3.PageEventCallback? onPageStarted) => (super.noSuchMethod( Invocation.method( #setOnPageStarted, [onPageStarted], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setOnPageFinished(_i3.PageEventCallback? onPageFinished) => (super.noSuchMethod( Invocation.method( #setOnPageFinished, [onPageFinished], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setOnProgress(_i3.ProgressCallback? onProgress) => (super.noSuchMethod( Invocation.method( #setOnProgress, [onProgress], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setOnWebResourceError( _i3.WebResourceErrorCallback? onWebResourceError) => (super.noSuchMethod( Invocation.method( #setOnWebResourceError, [onWebResourceError], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); } /// A class which mocks [AndroidWebViewController]. /// /// See the documentation for Mockito's code generation for more information. class MockAndroidWebViewController extends _i1.Mock implements _i8.AndroidWebViewController { @override _i3.PlatformWebViewControllerCreationParams get params => (super.noSuchMethod( Invocation.getter(#params), returnValue: _FakePlatformWebViewControllerCreationParams_4( this, Invocation.getter(#params), ), returnValueForMissingStub: _FakePlatformWebViewControllerCreationParams_4( this, Invocation.getter(#params), ), ) as _i3.PlatformWebViewControllerCreationParams); @override _i9.Future loadFile(String? absoluteFilePath) => (super.noSuchMethod( Invocation.method( #loadFile, [absoluteFilePath], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future loadFlutterAsset(String? key) => (super.noSuchMethod( Invocation.method( #loadFlutterAsset, [key], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future loadHtmlString( String? html, { String? baseUrl, }) => (super.noSuchMethod( Invocation.method( #loadHtmlString, [html], {#baseUrl: baseUrl}, ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future loadRequest(_i3.LoadRequestParams? params) => (super.noSuchMethod( Invocation.method( #loadRequest, [params], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future currentUrl() => (super.noSuchMethod( Invocation.method( #currentUrl, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future canGoBack() => (super.noSuchMethod( Invocation.method( #canGoBack, [], ), returnValue: _i9.Future.value(false), returnValueForMissingStub: _i9.Future.value(false), ) as _i9.Future); @override _i9.Future canGoForward() => (super.noSuchMethod( Invocation.method( #canGoForward, [], ), returnValue: _i9.Future.value(false), returnValueForMissingStub: _i9.Future.value(false), ) as _i9.Future); @override _i9.Future goBack() => (super.noSuchMethod( Invocation.method( #goBack, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future goForward() => (super.noSuchMethod( Invocation.method( #goForward, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future reload() => (super.noSuchMethod( Invocation.method( #reload, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future clearCache() => (super.noSuchMethod( Invocation.method( #clearCache, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future clearLocalStorage() => (super.noSuchMethod( Invocation.method( #clearLocalStorage, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setPlatformNavigationDelegate( _i3.PlatformNavigationDelegate? handler) => (super.noSuchMethod( Invocation.method( #setPlatformNavigationDelegate, [handler], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future runJavaScript(String? javaScript) => (super.noSuchMethod( Invocation.method( #runJavaScript, [javaScript], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future runJavaScriptReturningResult(String? javaScript) => (super.noSuchMethod( Invocation.method( #runJavaScriptReturningResult, [javaScript], ), returnValue: _i9.Future.value(_FakeObject_5( this, Invocation.method( #runJavaScriptReturningResult, [javaScript], ), )), returnValueForMissingStub: _i9.Future.value(_FakeObject_5( this, Invocation.method( #runJavaScriptReturningResult, [javaScript], ), )), ) as _i9.Future); @override _i9.Future addJavaScriptChannel( _i3.JavaScriptChannelParams? javaScriptChannelParams) => (super.noSuchMethod( Invocation.method( #addJavaScriptChannel, [javaScriptChannelParams], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future removeJavaScriptChannel(String? javaScriptChannelName) => (super.noSuchMethod( Invocation.method( #removeJavaScriptChannel, [javaScriptChannelName], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future getTitle() => (super.noSuchMethod( Invocation.method( #getTitle, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future scrollTo( int? x, int? y, ) => (super.noSuchMethod( Invocation.method( #scrollTo, [ x, y, ], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future scrollBy( int? x, int? y, ) => (super.noSuchMethod( Invocation.method( #scrollBy, [ x, y, ], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future<_i4.Offset> getScrollPosition() => (super.noSuchMethod( Invocation.method( #getScrollPosition, [], ), returnValue: _i9.Future<_i4.Offset>.value(_FakeOffset_6( this, Invocation.method( #getScrollPosition, [], ), )), returnValueForMissingStub: _i9.Future<_i4.Offset>.value(_FakeOffset_6( this, Invocation.method( #getScrollPosition, [], ), )), ) as _i9.Future<_i4.Offset>); @override _i9.Future enableZoom(bool? enabled) => (super.noSuchMethod( Invocation.method( #enableZoom, [enabled], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setBackgroundColor(_i4.Color? color) => (super.noSuchMethod( Invocation.method( #setBackgroundColor, [color], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setJavaScriptMode(_i3.JavaScriptMode? javaScriptMode) => (super.noSuchMethod( Invocation.method( #setJavaScriptMode, [javaScriptMode], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setUserAgent(String? userAgent) => (super.noSuchMethod( Invocation.method( #setUserAgent, [userAgent], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setMediaPlaybackRequiresUserGesture(bool? require) => (super.noSuchMethod( Invocation.method( #setMediaPlaybackRequiresUserGesture, [require], ), 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 _i10.AndroidWebViewProxy { @override _i2.WebView Function({required bool useHybridComposition}) get createAndroidWebView => (super.noSuchMethod( Invocation.getter(#createAndroidWebView), returnValue: ({required bool useHybridComposition}) => _FakeWebView_7( this, Invocation.getter(#createAndroidWebView), ), returnValueForMissingStub: ({required bool useHybridComposition}) => _FakeWebView_7( this, Invocation.getter(#createAndroidWebView), ), ) as _i2.WebView Function({required bool useHybridComposition})); @override _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, _i9.Future> Function( _i2.WebView, _i2.FileChooserParams, )? onShowFileChooser, }) => _FakeWebChromeClient_0( this, Invocation.getter(#createAndroidWebChromeClient), ), 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, _i9.Future> Function( _i2.WebView, _i2.FileChooserParams, )? onShowFileChooser, })); @override _i2.WebViewClient Function({ void Function( _i2.WebView, String, )? onPageFinished, void Function( _i2.WebView, String, )? onPageStarted, void Function( _i2.WebView, int, String, String, )? onReceivedError, void Function( _i2.WebView, _i2.WebResourceRequest, _i2.WebResourceError, )? onReceivedRequestError, void Function( _i2.WebView, _i2.WebResourceRequest, )? requestLoading, void Function( _i2.WebView, String, )? urlLoading, }) get createAndroidWebViewClient => (super.noSuchMethod( Invocation.getter(#createAndroidWebViewClient), returnValue: ({ void Function( _i2.WebView, String, )? onPageFinished, void Function( _i2.WebView, String, )? onPageStarted, void Function( _i2.WebView, int, String, String, )? onReceivedError, void Function( _i2.WebView, _i2.WebResourceRequest, _i2.WebResourceError, )? onReceivedRequestError, void Function( _i2.WebView, _i2.WebResourceRequest, )? requestLoading, void Function( _i2.WebView, String, )? urlLoading, }) => _FakeWebViewClient_1( this, Invocation.getter(#createAndroidWebViewClient), ), returnValueForMissingStub: ({ void Function( _i2.WebView, String, )? onPageFinished, void Function( _i2.WebView, String, )? onPageStarted, void Function( _i2.WebView, int, String, String, )? onReceivedError, void Function( _i2.WebView, _i2.WebResourceRequest, _i2.WebResourceError, )? onReceivedRequestError, void Function( _i2.WebView, _i2.WebResourceRequest, )? requestLoading, void Function( _i2.WebView, String, )? urlLoading, }) => _FakeWebViewClient_1( this, Invocation.getter(#createAndroidWebViewClient), ), ) as _i2.WebViewClient Function({ void Function( _i2.WebView, String, )? onPageFinished, void Function( _i2.WebView, String, )? onPageStarted, void Function( _i2.WebView, int, String, String, )? onReceivedError, void Function( _i2.WebView, _i2.WebResourceRequest, _i2.WebResourceError, )? onReceivedRequestError, void Function( _i2.WebView, _i2.WebResourceRequest, )? requestLoading, void Function( _i2.WebView, String, )? urlLoading, })); @override _i2.FlutterAssetManager Function() get createFlutterAssetManager => (super.noSuchMethod( Invocation.getter(#createFlutterAssetManager), returnValue: () => _FakeFlutterAssetManager_8( this, Invocation.getter(#createFlutterAssetManager), ), returnValueForMissingStub: () => _FakeFlutterAssetManager_8( this, Invocation.getter(#createFlutterAssetManager), ), ) as _i2.FlutterAssetManager Function()); @override _i2.JavaScriptChannel Function( String, { required void Function(String) postMessage, }) get createJavaScriptChannel => (super.noSuchMethod( Invocation.getter(#createJavaScriptChannel), returnValue: ( String channelName, { required void Function(String) postMessage, }) => _FakeJavaScriptChannel_9( this, Invocation.getter(#createJavaScriptChannel), ), returnValueForMissingStub: ( String channelName, { required void Function(String) postMessage, }) => _FakeJavaScriptChannel_9( this, Invocation.getter(#createJavaScriptChannel), ), ) as _i2.JavaScriptChannel Function( String, { required void Function(String) postMessage, })); @override _i2.DownloadListener Function( {required void Function( String, String, String, String, int, ) onDownloadStart}) get createDownloadListener => (super.noSuchMethod( Invocation.getter(#createDownloadListener), returnValue: ( {required void Function( String, String, String, String, int, ) onDownloadStart}) => _FakeDownloadListener_2( this, Invocation.getter(#createDownloadListener), ), returnValueForMissingStub: ( {required void Function( String, String, String, String, int, ) onDownloadStart}) => _FakeDownloadListener_2( this, Invocation.getter(#createDownloadListener), ), ) as _i2.DownloadListener Function( {required void Function( String, String, String, String, int, ) onDownloadStart})); @override _i9.Future setWebContentsDebuggingEnabled(bool? enabled) => (super.noSuchMethod( Invocation.method( #setWebContentsDebuggingEnabled, [enabled], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); } /// A class which mocks [AndroidWebViewWidgetCreationParams]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockAndroidWebViewWidgetCreationParams extends _i1.Mock implements _i8.AndroidWebViewWidgetCreationParams { @override _i5.InstanceManager get instanceManager => (super.noSuchMethod( Invocation.getter(#instanceManager), returnValue: _FakeInstanceManager_10( this, Invocation.getter(#instanceManager), ), returnValueForMissingStub: _FakeInstanceManager_10( this, Invocation.getter(#instanceManager), ), ) as _i5.InstanceManager); @override _i6.PlatformViewsServiceProxy get platformViewsServiceProxy => (super.noSuchMethod( Invocation.getter(#platformViewsServiceProxy), returnValue: _FakePlatformViewsServiceProxy_11( this, Invocation.getter(#platformViewsServiceProxy), ), returnValueForMissingStub: _FakePlatformViewsServiceProxy_11( this, Invocation.getter(#platformViewsServiceProxy), ), ) as _i6.PlatformViewsServiceProxy); @override bool get displayWithHybridComposition => (super.noSuchMethod( Invocation.getter(#displayWithHybridComposition), returnValue: false, returnValueForMissingStub: false, ) as bool); @override _i3.PlatformWebViewController get controller => (super.noSuchMethod( Invocation.getter(#controller), returnValue: _FakePlatformWebViewController_12( this, Invocation.getter(#controller), ), returnValueForMissingStub: _FakePlatformWebViewController_12( this, Invocation.getter(#controller), ), ) as _i3.PlatformWebViewController); @override _i4.TextDirection get layoutDirection => (super.noSuchMethod( Invocation.getter(#layoutDirection), returnValue: _i4.TextDirection.rtl, returnValueForMissingStub: _i4.TextDirection.rtl, ) as _i4.TextDirection); @override Set<_i11.Factory<_i12.OneSequenceGestureRecognizer>> get gestureRecognizers => (super.noSuchMethod( Invocation.getter(#gestureRecognizers), returnValue: <_i11.Factory<_i12.OneSequenceGestureRecognizer>>{}, returnValueForMissingStub: < _i11.Factory<_i12.OneSequenceGestureRecognizer>>{}, ) as Set<_i11.Factory<_i12.OneSequenceGestureRecognizer>>); } /// A class which mocks [ExpensiveAndroidViewController]. /// /// See the documentation for Mockito's code generation for more information. class MockExpensiveAndroidViewController extends _i1.Mock implements _i7.ExpensiveAndroidViewController { @override bool get requiresViewComposition => (super.noSuchMethod( Invocation.getter(#requiresViewComposition), returnValue: false, returnValueForMissingStub: false, ) as bool); @override int get viewId => (super.noSuchMethod( Invocation.getter(#viewId), returnValue: 0, returnValueForMissingStub: 0, ) as int); @override bool get awaitingCreation => (super.noSuchMethod( Invocation.getter(#awaitingCreation), returnValue: false, returnValueForMissingStub: false, ) as bool); @override _i7.PointTransformer get pointTransformer => (super.noSuchMethod( Invocation.getter(#pointTransformer), returnValue: (_i4.Offset position) => _FakeOffset_6( this, Invocation.getter(#pointTransformer), ), returnValueForMissingStub: (_i4.Offset position) => _FakeOffset_6( this, Invocation.getter(#pointTransformer), ), ) as _i7.PointTransformer); @override set pointTransformer(_i7.PointTransformer? transformer) => super.noSuchMethod( Invocation.setter( #pointTransformer, transformer, ), returnValueForMissingStub: null, ); @override bool get isCreated => (super.noSuchMethod( Invocation.getter(#isCreated), returnValue: false, returnValueForMissingStub: false, ) as bool); @override List<_i7.PlatformViewCreatedCallback> get createdCallbacks => (super.noSuchMethod( Invocation.getter(#createdCallbacks), returnValue: <_i7.PlatformViewCreatedCallback>[], returnValueForMissingStub: <_i7.PlatformViewCreatedCallback>[], ) as List<_i7.PlatformViewCreatedCallback>); @override _i9.Future setOffset(_i4.Offset? off) => (super.noSuchMethod( Invocation.method( #setOffset, [off], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future create({ _i4.Size? size, _i4.Offset? position, }) => (super.noSuchMethod( Invocation.method( #create, [], { #size: size, #position: position, }, ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future<_i4.Size> setSize(_i4.Size? size) => (super.noSuchMethod( Invocation.method( #setSize, [size], ), returnValue: _i9.Future<_i4.Size>.value(_FakeSize_13( this, Invocation.method( #setSize, [size], ), )), returnValueForMissingStub: _i9.Future<_i4.Size>.value(_FakeSize_13( this, Invocation.method( #setSize, [size], ), )), ) as _i9.Future<_i4.Size>); @override _i9.Future sendMotionEvent(_i7.AndroidMotionEvent? event) => (super.noSuchMethod( Invocation.method( #sendMotionEvent, [event], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override void addOnPlatformViewCreatedListener( _i7.PlatformViewCreatedCallback? listener) => super.noSuchMethod( Invocation.method( #addOnPlatformViewCreatedListener, [listener], ), returnValueForMissingStub: null, ); @override void removeOnPlatformViewCreatedListener( _i7.PlatformViewCreatedCallback? listener) => super.noSuchMethod( Invocation.method( #removeOnPlatformViewCreatedListener, [listener], ), returnValueForMissingStub: null, ); @override _i9.Future setLayoutDirection(_i4.TextDirection? layoutDirection) => (super.noSuchMethod( Invocation.method( #setLayoutDirection, [layoutDirection], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future dispatchPointerEvent(_i13.PointerEvent? event) => (super.noSuchMethod( Invocation.method( #dispatchPointerEvent, [event], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future clearFocus() => (super.noSuchMethod( Invocation.method( #clearFocus, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future dispose() => (super.noSuchMethod( Invocation.method( #dispose, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); } /// A class which mocks [FlutterAssetManager]. /// /// See the documentation for Mockito's code generation for more information. class MockFlutterAssetManager extends _i1.Mock implements _i2.FlutterAssetManager { @override _i9.Future> list(String? path) => (super.noSuchMethod( Invocation.method( #list, [path], ), returnValue: _i9.Future>.value([]), returnValueForMissingStub: _i9.Future>.value([]), ) as _i9.Future>); @override _i9.Future getAssetFilePathByName(String? name) => (super.noSuchMethod( Invocation.method( #getAssetFilePathByName, [name], ), returnValue: _i9.Future.value(''), returnValueForMissingStub: _i9.Future.value(''), ) as _i9.Future); } /// A class which mocks [JavaScriptChannel]. /// /// See the documentation for Mockito's code generation for more information. class MockJavaScriptChannel extends _i1.Mock implements _i2.JavaScriptChannel { @override String get channelName => (super.noSuchMethod( Invocation.getter(#channelName), returnValue: '', returnValueForMissingStub: '', ) as String); @override void Function(String) get postMessage => (super.noSuchMethod( Invocation.getter(#postMessage), returnValue: (String message) {}, returnValueForMissingStub: (String message) {}, ) as void Function(String)); @override _i2.JavaScriptChannel copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeJavaScriptChannel_9( this, Invocation.method( #copy, [], ), ), returnValueForMissingStub: _FakeJavaScriptChannel_9( this, Invocation.method( #copy, [], ), ), ) as _i2.JavaScriptChannel); } /// A class which mocks [PlatformViewsServiceProxy]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockPlatformViewsServiceProxy extends _i1.Mock implements _i6.PlatformViewsServiceProxy { @override _i7.ExpensiveAndroidViewController initExpensiveAndroidView({ required int? id, required String? viewType, required _i4.TextDirection? layoutDirection, dynamic creationParams, _i7.MessageCodec? creationParamsCodec, _i4.VoidCallback? onFocus, }) => (super.noSuchMethod( Invocation.method( #initExpensiveAndroidView, [], { #id: id, #viewType: viewType, #layoutDirection: layoutDirection, #creationParams: creationParams, #creationParamsCodec: creationParamsCodec, #onFocus: onFocus, }, ), returnValue: _FakeExpensiveAndroidViewController_14( this, Invocation.method( #initExpensiveAndroidView, [], { #id: id, #viewType: viewType, #layoutDirection: layoutDirection, #creationParams: creationParams, #creationParamsCodec: creationParamsCodec, #onFocus: onFocus, }, ), ), returnValueForMissingStub: _FakeExpensiveAndroidViewController_14( this, Invocation.method( #initExpensiveAndroidView, [], { #id: id, #viewType: viewType, #layoutDirection: layoutDirection, #creationParams: creationParams, #creationParamsCodec: creationParamsCodec, #onFocus: onFocus, }, ), ), ) as _i7.ExpensiveAndroidViewController); @override _i7.SurfaceAndroidViewController initSurfaceAndroidView({ required int? id, required String? viewType, required _i4.TextDirection? layoutDirection, dynamic creationParams, _i7.MessageCodec? creationParamsCodec, _i4.VoidCallback? onFocus, }) => (super.noSuchMethod( Invocation.method( #initSurfaceAndroidView, [], { #id: id, #viewType: viewType, #layoutDirection: layoutDirection, #creationParams: creationParams, #creationParamsCodec: creationParamsCodec, #onFocus: onFocus, }, ), returnValue: _FakeSurfaceAndroidViewController_15( this, Invocation.method( #initSurfaceAndroidView, [], { #id: id, #viewType: viewType, #layoutDirection: layoutDirection, #creationParams: creationParams, #creationParamsCodec: creationParamsCodec, #onFocus: onFocus, }, ), ), returnValueForMissingStub: _FakeSurfaceAndroidViewController_15( this, Invocation.method( #initSurfaceAndroidView, [], { #id: id, #viewType: viewType, #layoutDirection: layoutDirection, #creationParams: creationParams, #creationParamsCodec: creationParamsCodec, #onFocus: onFocus, }, ), ), ) as _i7.SurfaceAndroidViewController); } /// A class which mocks [SurfaceAndroidViewController]. /// /// See the documentation for Mockito's code generation for more information. class MockSurfaceAndroidViewController extends _i1.Mock implements _i7.SurfaceAndroidViewController { @override bool get requiresViewComposition => (super.noSuchMethod( Invocation.getter(#requiresViewComposition), returnValue: false, returnValueForMissingStub: false, ) as bool); @override int get viewId => (super.noSuchMethod( Invocation.getter(#viewId), returnValue: 0, returnValueForMissingStub: 0, ) as int); @override bool get awaitingCreation => (super.noSuchMethod( Invocation.getter(#awaitingCreation), returnValue: false, returnValueForMissingStub: false, ) as bool); @override _i7.PointTransformer get pointTransformer => (super.noSuchMethod( Invocation.getter(#pointTransformer), returnValue: (_i4.Offset position) => _FakeOffset_6( this, Invocation.getter(#pointTransformer), ), returnValueForMissingStub: (_i4.Offset position) => _FakeOffset_6( this, Invocation.getter(#pointTransformer), ), ) as _i7.PointTransformer); @override set pointTransformer(_i7.PointTransformer? transformer) => super.noSuchMethod( Invocation.setter( #pointTransformer, transformer, ), returnValueForMissingStub: null, ); @override bool get isCreated => (super.noSuchMethod( Invocation.getter(#isCreated), returnValue: false, returnValueForMissingStub: false, ) as bool); @override List<_i7.PlatformViewCreatedCallback> get createdCallbacks => (super.noSuchMethod( Invocation.getter(#createdCallbacks), returnValue: <_i7.PlatformViewCreatedCallback>[], returnValueForMissingStub: <_i7.PlatformViewCreatedCallback>[], ) as List<_i7.PlatformViewCreatedCallback>); @override _i9.Future setOffset(_i4.Offset? off) => (super.noSuchMethod( Invocation.method( #setOffset, [off], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future create({ _i4.Size? size, _i4.Offset? position, }) => (super.noSuchMethod( Invocation.method( #create, [], { #size: size, #position: position, }, ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future<_i4.Size> setSize(_i4.Size? size) => (super.noSuchMethod( Invocation.method( #setSize, [size], ), returnValue: _i9.Future<_i4.Size>.value(_FakeSize_13( this, Invocation.method( #setSize, [size], ), )), returnValueForMissingStub: _i9.Future<_i4.Size>.value(_FakeSize_13( this, Invocation.method( #setSize, [size], ), )), ) as _i9.Future<_i4.Size>); @override _i9.Future sendMotionEvent(_i7.AndroidMotionEvent? event) => (super.noSuchMethod( Invocation.method( #sendMotionEvent, [event], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override void addOnPlatformViewCreatedListener( _i7.PlatformViewCreatedCallback? listener) => super.noSuchMethod( Invocation.method( #addOnPlatformViewCreatedListener, [listener], ), returnValueForMissingStub: null, ); @override void removeOnPlatformViewCreatedListener( _i7.PlatformViewCreatedCallback? listener) => super.noSuchMethod( Invocation.method( #removeOnPlatformViewCreatedListener, [listener], ), returnValueForMissingStub: null, ); @override _i9.Future setLayoutDirection(_i4.TextDirection? layoutDirection) => (super.noSuchMethod( Invocation.method( #setLayoutDirection, [layoutDirection], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future dispatchPointerEvent(_i13.PointerEvent? event) => (super.noSuchMethod( Invocation.method( #dispatchPointerEvent, [event], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future clearFocus() => (super.noSuchMethod( Invocation.method( #clearFocus, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future dispose() => (super.noSuchMethod( Invocation.method( #dispose, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); } /// A class which mocks [WebChromeClient]. /// /// 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, [], ), returnValue: _FakeWebChromeClient_0( this, Invocation.method( #copy, [], ), ), returnValueForMissingStub: _FakeWebChromeClient_0( this, Invocation.method( #copy, [], ), ), ) as _i2.WebChromeClient); } /// A class which mocks [WebSettings]. /// /// See the documentation for Mockito's code generation for more information. class MockWebSettings extends _i1.Mock implements _i2.WebSettings { @override _i9.Future setDomStorageEnabled(bool? flag) => (super.noSuchMethod( Invocation.method( #setDomStorageEnabled, [flag], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setJavaScriptCanOpenWindowsAutomatically(bool? flag) => (super.noSuchMethod( Invocation.method( #setJavaScriptCanOpenWindowsAutomatically, [flag], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setSupportMultipleWindows(bool? support) => (super.noSuchMethod( Invocation.method( #setSupportMultipleWindows, [support], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setJavaScriptEnabled(bool? flag) => (super.noSuchMethod( Invocation.method( #setJavaScriptEnabled, [flag], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setUserAgentString(String? userAgentString) => (super.noSuchMethod( Invocation.method( #setUserAgentString, [userAgentString], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setMediaPlaybackRequiresUserGesture(bool? require) => (super.noSuchMethod( Invocation.method( #setMediaPlaybackRequiresUserGesture, [require], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setSupportZoom(bool? support) => (super.noSuchMethod( Invocation.method( #setSupportZoom, [support], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setLoadWithOverviewMode(bool? overview) => (super.noSuchMethod( Invocation.method( #setLoadWithOverviewMode, [overview], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setUseWideViewPort(bool? use) => (super.noSuchMethod( Invocation.method( #setUseWideViewPort, [use], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setDisplayZoomControls(bool? enabled) => (super.noSuchMethod( Invocation.method( #setDisplayZoomControls, [enabled], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setBuiltInZoomControls(bool? enabled) => (super.noSuchMethod( Invocation.method( #setBuiltInZoomControls, [enabled], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setAllowFileAccess(bool? enabled) => (super.noSuchMethod( Invocation.method( #setAllowFileAccess, [enabled], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i2.WebSettings copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWebSettings_16( this, Invocation.method( #copy, [], ), ), returnValueForMissingStub: _FakeWebSettings_16( this, Invocation.method( #copy, [], ), ), ) as _i2.WebSettings); } /// A class which mocks [WebView]. /// /// See the documentation for Mockito's code generation for more information. class MockWebView extends _i1.Mock implements _i2.WebView { @override bool get useHybridComposition => (super.noSuchMethod( Invocation.getter(#useHybridComposition), returnValue: false, returnValueForMissingStub: false, ) as bool); @override _i2.WebSettings get settings => (super.noSuchMethod( Invocation.getter(#settings), returnValue: _FakeWebSettings_16( this, Invocation.getter(#settings), ), returnValueForMissingStub: _FakeWebSettings_16( this, Invocation.getter(#settings), ), ) as _i2.WebSettings); @override _i9.Future loadData({ required String? data, String? mimeType, String? encoding, }) => (super.noSuchMethod( Invocation.method( #loadData, [], { #data: data, #mimeType: mimeType, #encoding: encoding, }, ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future loadDataWithBaseUrl({ String? baseUrl, required String? data, String? mimeType, String? encoding, String? historyUrl, }) => (super.noSuchMethod( Invocation.method( #loadDataWithBaseUrl, [], { #baseUrl: baseUrl, #data: data, #mimeType: mimeType, #encoding: encoding, #historyUrl: historyUrl, }, ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future loadUrl( String? url, Map? headers, ) => (super.noSuchMethod( Invocation.method( #loadUrl, [ url, headers, ], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future postUrl( String? url, _i14.Uint8List? data, ) => (super.noSuchMethod( Invocation.method( #postUrl, [ url, data, ], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future getUrl() => (super.noSuchMethod( Invocation.method( #getUrl, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future canGoBack() => (super.noSuchMethod( Invocation.method( #canGoBack, [], ), returnValue: _i9.Future.value(false), returnValueForMissingStub: _i9.Future.value(false), ) as _i9.Future); @override _i9.Future canGoForward() => (super.noSuchMethod( Invocation.method( #canGoForward, [], ), returnValue: _i9.Future.value(false), returnValueForMissingStub: _i9.Future.value(false), ) as _i9.Future); @override _i9.Future goBack() => (super.noSuchMethod( Invocation.method( #goBack, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future goForward() => (super.noSuchMethod( Invocation.method( #goForward, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future reload() => (super.noSuchMethod( Invocation.method( #reload, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future clearCache(bool? includeDiskFiles) => (super.noSuchMethod( Invocation.method( #clearCache, [includeDiskFiles], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future evaluateJavascript(String? javascriptString) => (super.noSuchMethod( Invocation.method( #evaluateJavascript, [javascriptString], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future getTitle() => (super.noSuchMethod( Invocation.method( #getTitle, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future scrollTo( int? x, int? y, ) => (super.noSuchMethod( Invocation.method( #scrollTo, [ x, y, ], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future scrollBy( int? x, int? y, ) => (super.noSuchMethod( Invocation.method( #scrollBy, [ x, y, ], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future getScrollX() => (super.noSuchMethod( Invocation.method( #getScrollX, [], ), returnValue: _i9.Future.value(0), returnValueForMissingStub: _i9.Future.value(0), ) as _i9.Future); @override _i9.Future getScrollY() => (super.noSuchMethod( Invocation.method( #getScrollY, [], ), returnValue: _i9.Future.value(0), returnValueForMissingStub: _i9.Future.value(0), ) as _i9.Future); @override _i9.Future<_i4.Offset> getScrollPosition() => (super.noSuchMethod( Invocation.method( #getScrollPosition, [], ), returnValue: _i9.Future<_i4.Offset>.value(_FakeOffset_6( this, Invocation.method( #getScrollPosition, [], ), )), returnValueForMissingStub: _i9.Future<_i4.Offset>.value(_FakeOffset_6( this, Invocation.method( #getScrollPosition, [], ), )), ) as _i9.Future<_i4.Offset>); @override _i9.Future setWebViewClient(_i2.WebViewClient? webViewClient) => (super.noSuchMethod( Invocation.method( #setWebViewClient, [webViewClient], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future addJavaScriptChannel( _i2.JavaScriptChannel? javaScriptChannel) => (super.noSuchMethod( Invocation.method( #addJavaScriptChannel, [javaScriptChannel], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future removeJavaScriptChannel( _i2.JavaScriptChannel? javaScriptChannel) => (super.noSuchMethod( Invocation.method( #removeJavaScriptChannel, [javaScriptChannel], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setDownloadListener(_i2.DownloadListener? listener) => (super.noSuchMethod( Invocation.method( #setDownloadListener, [listener], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setWebChromeClient(_i2.WebChromeClient? client) => (super.noSuchMethod( Invocation.method( #setWebChromeClient, [client], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i9.Future setBackgroundColor(_i4.Color? color) => (super.noSuchMethod( Invocation.method( #setBackgroundColor, [color], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i2.WebView copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWebView_7( this, Invocation.method( #copy, [], ), ), returnValueForMissingStub: _FakeWebView_7( this, Invocation.method( #copy, [], ), ), ) as _i2.WebView); } /// A class which mocks [WebViewClient]. /// /// See the documentation for Mockito's code generation for more information. class MockWebViewClient extends _i1.Mock implements _i2.WebViewClient { @override _i9.Future setSynchronousReturnValueForShouldOverrideUrlLoading( bool? value) => (super.noSuchMethod( Invocation.method( #setSynchronousReturnValueForShouldOverrideUrlLoading, [value], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i2.WebViewClient copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWebViewClient_1( this, Invocation.method( #copy, [], ), ), returnValueForMissingStub: _FakeWebViewClient_1( this, Invocation.method( #copy, [], ), ), ) as _i2.WebViewClient); } /// A class which mocks [WebStorage]. /// /// See the documentation for Mockito's code generation for more information. class MockWebStorage extends _i1.Mock implements _i2.WebStorage { @override _i9.Future deleteAllData() => (super.noSuchMethod( Invocation.method( #deleteAllData, [], ), returnValue: _i9.Future.value(), returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override _i2.WebStorage copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWebStorage_17( this, Invocation.method( #copy, [], ), ), returnValueForMissingStub: _FakeWebStorage_17( this, Invocation.method( #copy, [], ), ), ) as _i2.WebStorage); } /// A class which mocks [InstanceManager]. /// /// See the documentation for Mockito's code generation for more information. class MockInstanceManager extends _i1.Mock implements _i5.InstanceManager { @override void Function(int) get onWeakReferenceRemoved => (super.noSuchMethod( Invocation.getter(#onWeakReferenceRemoved), returnValue: (int __p0) {}, returnValueForMissingStub: (int __p0) {}, ) as void Function(int)); @override set onWeakReferenceRemoved(void Function(int)? _onWeakReferenceRemoved) => super.noSuchMethod( Invocation.setter( #onWeakReferenceRemoved, _onWeakReferenceRemoved, ), returnValueForMissingStub: null, ); @override int addDartCreatedInstance(_i5.Copyable? instance) => (super.noSuchMethod( Invocation.method( #addDartCreatedInstance, [instance], ), returnValue: 0, returnValueForMissingStub: 0, ) as int); @override int? removeWeakReference(_i5.Copyable? instance) => (super.noSuchMethod( Invocation.method( #removeWeakReference, [instance], ), returnValueForMissingStub: null, ) as int?); @override T? remove(int? identifier) => (super.noSuchMethod( Invocation.method( #remove, [identifier], ), returnValueForMissingStub: null, ) as T?); @override T? getInstanceWithWeakReference(int? identifier) => (super.noSuchMethod( Invocation.method( #getInstanceWithWeakReference, [identifier], ), returnValueForMissingStub: null, ) as T?); @override int? getIdentifier(_i5.Copyable? instance) => (super.noSuchMethod( Invocation.method( #getIdentifier, [instance], ), returnValueForMissingStub: null, ) as int?); @override void addHostCreatedInstance( _i5.Copyable? instance, int? identifier, ) => super.noSuchMethod( Invocation.method( #addHostCreatedInstance, [ instance, identifier, ], ), returnValueForMissingStub: null, ); @override bool containsIdentifier(int? identifier) => (super.noSuchMethod( Invocation.method( #containsIdentifier, [identifier], ), returnValue: false, returnValueForMissingStub: false, ) as bool); } ================================================ FILE: packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.dart ================================================ // Copyright 2013 The Flutter 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:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_android/src/android_webview.dart' as android_webview; import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_platform_interface/src/webview_platform.dart'; import 'android_webview_cookie_manager_test.mocks.dart'; @GenerateMocks([android_webview.CookieManager]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); test('clearCookies should call android_webview.clearCookies', () async { final android_webview.CookieManager mockCookieManager = MockCookieManager(); when(mockCookieManager.clearCookies()) .thenAnswer((_) => Future.value(true)); final AndroidWebViewCookieManagerCreationParams params = AndroidWebViewCookieManagerCreationParams .fromPlatformWebViewCookieManagerCreationParams( const PlatformWebViewCookieManagerCreationParams()); final bool hasClearedCookies = await AndroidWebViewCookieManager(params, cookieManager: mockCookieManager) .clearCookies(); expect(hasClearedCookies, true); verify(mockCookieManager.clearCookies()); }); test('setCookie should throw ArgumentError for cookie with invalid path', () { final AndroidWebViewCookieManagerCreationParams params = AndroidWebViewCookieManagerCreationParams .fromPlatformWebViewCookieManagerCreationParams( const PlatformWebViewCookieManagerCreationParams()); final AndroidWebViewCookieManager androidCookieManager = AndroidWebViewCookieManager(params, cookieManager: MockCookieManager()); expect( () => androidCookieManager.setCookie(const WebViewCookie( name: 'foo', value: 'bar', domain: 'flutter.dev', path: 'invalid;path', )), throwsA(const TypeMatcher()), ); }); test( 'setCookie should call android_webview.csetCookie with properly formatted cookie value', () { final android_webview.CookieManager mockCookieManager = MockCookieManager(); final AndroidWebViewCookieManagerCreationParams params = AndroidWebViewCookieManagerCreationParams .fromPlatformWebViewCookieManagerCreationParams( const PlatformWebViewCookieManagerCreationParams()); AndroidWebViewCookieManager(params, cookieManager: mockCookieManager) .setCookie(const WebViewCookie( name: 'foo&', value: 'bar@', domain: 'flutter.dev', )); verify(mockCookieManager.setCookie( 'flutter.dev', 'foo%26=bar%40; path=/', )); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_android/test/android_webview_cookie_manager_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i3; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_android/src/android_webview.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 [CookieManager]. /// /// See the documentation for Mockito's code generation for more information. class MockCookieManager extends _i1.Mock implements _i2.CookieManager { MockCookieManager() { _i1.throwOnMissingStub(this); } @override _i3.Future setCookie( String? url, String? value, ) => (super.noSuchMethod( Invocation.method( #setCookie, [ url, value, ], ), returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); @override _i3.Future clearCookies() => (super.noSuchMethod( Invocation.method( #clearCookies, [], ), returnValue: _i3.Future.value(false), ) as _i3.Future); } ================================================ FILE: packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart ================================================ // Copyright 2013 The Flutter 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: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.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.g.dart'; @GenerateMocks([ CookieManagerHostApi, DownloadListener, JavaScriptChannel, TestDownloadListenerHostApi, TestJavaObjectHostApi, TestJavaScriptChannelHostApi, TestWebChromeClientHostApi, TestWebSettingsHostApi, TestWebStorageHostApi, TestWebViewClientHostApi, TestWebViewHostApi, TestAssetManagerHostApi, WebChromeClient, WebView, WebViewClient, ]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('Android WebView', () { group('JavaObject', () { late MockTestJavaObjectHostApi mockPlatformHostApi; setUp(() { mockPlatformHostApi = MockTestJavaObjectHostApi(); TestJavaObjectHostApi.setup(mockPlatformHostApi); }); tearDown(() { TestJavaObjectHostApi.setup(null); }); test('JavaObject.dispose', () async { int? callbackIdentifier; final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (int identifier) { callbackIdentifier = identifier; }, ); final JavaObject object = JavaObject.detached( instanceManager: instanceManager, ); instanceManager.addHostCreatedInstance(object, 0); JavaObject.dispose(object); expect(callbackIdentifier, 0); }); test('JavaObjectFlutterApi.dispose', () { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); final JavaObject object = JavaObject.detached( instanceManager: instanceManager, ); instanceManager.addHostCreatedInstance(object, 0); instanceManager.removeWeakReference(object); expect(instanceManager.containsIdentifier(0), isTrue); final JavaObjectFlutterApiImpl flutterApi = JavaObjectFlutterApiImpl( instanceManager: instanceManager, ); flutterApi.dispose(0); expect(instanceManager.containsIdentifier(0), isFalse); }); }); group('WebView', () { late MockTestWebViewHostApi mockPlatformHostApi; late InstanceManager instanceManager; late WebView webView; late int webViewInstanceId; setUp(() { mockPlatformHostApi = MockTestWebViewHostApi(); TestWebViewHostApi.setup(mockPlatformHostApi); instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); WebView.api = WebViewHostApiImpl(instanceManager: instanceManager); webView = WebView(); webViewInstanceId = instanceManager.getIdentifier(webView)!; }); test('create', () { verify(mockPlatformHostApi.create(webViewInstanceId, false)); }); test('setWebContentsDebuggingEnabled true', () { WebView.setWebContentsDebuggingEnabled(true); verify(mockPlatformHostApi.setWebContentsDebuggingEnabled(true)); }); test('setWebContentsDebuggingEnabled false', () { WebView.setWebContentsDebuggingEnabled(false); verify(mockPlatformHostApi.setWebContentsDebuggingEnabled(false)); }); test('loadData', () { webView.loadData( data: 'hello', mimeType: 'text/plain', encoding: 'base64', ); verify(mockPlatformHostApi.loadData( webViewInstanceId, 'hello', 'text/plain', 'base64', )); }); test('loadData with null values', () { webView.loadData(data: 'hello'); verify(mockPlatformHostApi.loadData( webViewInstanceId, 'hello', null, null, )); }); test('loadDataWithBaseUrl', () { webView.loadDataWithBaseUrl( baseUrl: 'https://base.url', data: 'hello', mimeType: 'text/plain', encoding: 'base64', historyUrl: 'https://history.url', ); verify(mockPlatformHostApi.loadDataWithBaseUrl( webViewInstanceId, 'https://base.url', 'hello', 'text/plain', 'base64', 'https://history.url', )); }); test('loadDataWithBaseUrl with null values', () { webView.loadDataWithBaseUrl(data: 'hello'); verify(mockPlatformHostApi.loadDataWithBaseUrl( webViewInstanceId, null, 'hello', null, null, null, )); }); test('loadUrl', () { webView.loadUrl('hello', {'a': 'header'}); verify(mockPlatformHostApi.loadUrl( webViewInstanceId, 'hello', {'a': 'header'}, )); }); test('canGoBack', () { when(mockPlatformHostApi.canGoBack(webViewInstanceId)) .thenReturn(false); expect(webView.canGoBack(), completion(false)); }); test('canGoForward', () { when(mockPlatformHostApi.canGoForward(webViewInstanceId)) .thenReturn(true); expect(webView.canGoForward(), completion(true)); }); test('goBack', () { webView.goBack(); verify(mockPlatformHostApi.goBack(webViewInstanceId)); }); test('goForward', () { webView.goForward(); verify(mockPlatformHostApi.goForward(webViewInstanceId)); }); test('reload', () { webView.reload(); verify(mockPlatformHostApi.reload(webViewInstanceId)); }); test('clearCache', () { webView.clearCache(false); verify(mockPlatformHostApi.clearCache(webViewInstanceId, false)); }); test('evaluateJavascript', () { when( mockPlatformHostApi.evaluateJavascript( webViewInstanceId, 'runJavaScript'), ).thenAnswer((_) => Future.value('returnValue')); expect( webView.evaluateJavascript('runJavaScript'), completion('returnValue'), ); }); test('getTitle', () { when(mockPlatformHostApi.getTitle(webViewInstanceId)) .thenReturn('aTitle'); expect(webView.getTitle(), completion('aTitle')); }); test('scrollTo', () { webView.scrollTo(12, 13); verify(mockPlatformHostApi.scrollTo(webViewInstanceId, 12, 13)); }); test('scrollBy', () { webView.scrollBy(12, 14); verify(mockPlatformHostApi.scrollBy(webViewInstanceId, 12, 14)); }); test('getScrollX', () { when(mockPlatformHostApi.getScrollX(webViewInstanceId)).thenReturn(67); expect(webView.getScrollX(), completion(67)); }); test('getScrollY', () { when(mockPlatformHostApi.getScrollY(webViewInstanceId)).thenReturn(56); expect(webView.getScrollY(), completion(56)); }); test('getScrollPosition', () async { when(mockPlatformHostApi.getScrollPosition(webViewInstanceId)) .thenReturn(WebViewPoint(x: 2, y: 16)); await expectLater( webView.getScrollPosition(), completion(const Offset(2.0, 16.0)), ); }); test('setWebViewClient', () { TestWebViewClientHostApi.setup(MockTestWebViewClientHostApi()); WebViewClient.api = WebViewClientHostApiImpl( instanceManager: instanceManager, ); final WebViewClient mockWebViewClient = MockWebViewClient(); when(mockWebViewClient.copy()).thenReturn(MockWebViewClient()); instanceManager.addDartCreatedInstance(mockWebViewClient); webView.setWebViewClient(mockWebViewClient); final int webViewClientInstanceId = instanceManager.getIdentifier(mockWebViewClient)!; verify(mockPlatformHostApi.setWebViewClient( webViewInstanceId, webViewClientInstanceId, )); }); test('addJavaScriptChannel', () { TestJavaScriptChannelHostApi.setup(MockTestJavaScriptChannelHostApi()); JavaScriptChannel.api = JavaScriptChannelHostApiImpl( instanceManager: instanceManager, ); final JavaScriptChannel mockJavaScriptChannel = MockJavaScriptChannel(); when(mockJavaScriptChannel.copy()).thenReturn(MockJavaScriptChannel()); when(mockJavaScriptChannel.channelName).thenReturn('aChannel'); webView.addJavaScriptChannel(mockJavaScriptChannel); final int javaScriptChannelInstanceId = instanceManager.getIdentifier(mockJavaScriptChannel)!; verify(mockPlatformHostApi.addJavaScriptChannel( webViewInstanceId, javaScriptChannelInstanceId, )); }); test('removeJavaScriptChannel', () { TestJavaScriptChannelHostApi.setup(MockTestJavaScriptChannelHostApi()); JavaScriptChannel.api = JavaScriptChannelHostApiImpl( instanceManager: instanceManager, ); final JavaScriptChannel mockJavaScriptChannel = MockJavaScriptChannel(); when(mockJavaScriptChannel.copy()).thenReturn(MockJavaScriptChannel()); when(mockJavaScriptChannel.channelName).thenReturn('aChannel'); expect( webView.removeJavaScriptChannel(mockJavaScriptChannel), completes, ); webView.addJavaScriptChannel(mockJavaScriptChannel); webView.removeJavaScriptChannel(mockJavaScriptChannel); final int javaScriptChannelInstanceId = instanceManager.getIdentifier(mockJavaScriptChannel)!; verify(mockPlatformHostApi.removeJavaScriptChannel( webViewInstanceId, javaScriptChannelInstanceId, )); }); test('setDownloadListener', () { TestDownloadListenerHostApi.setup(MockTestDownloadListenerHostApi()); DownloadListener.api = DownloadListenerHostApiImpl( instanceManager: instanceManager, ); final DownloadListener mockDownloadListener = MockDownloadListener(); when(mockDownloadListener.copy()).thenReturn(MockDownloadListener()); instanceManager.addDartCreatedInstance(mockDownloadListener); webView.setDownloadListener(mockDownloadListener); final int downloadListenerInstanceId = instanceManager.getIdentifier(mockDownloadListener)!; verify(mockPlatformHostApi.setDownloadListener( webViewInstanceId, downloadListenerInstanceId, )); }); test('setWebChromeClient', () { TestWebChromeClientHostApi.setup(MockTestWebChromeClientHostApi()); WebChromeClient.api = WebChromeClientHostApiImpl( instanceManager: instanceManager, ); final WebChromeClient mockWebChromeClient = MockWebChromeClient(); when(mockWebChromeClient.copy()).thenReturn(MockWebChromeClient()); instanceManager.addDartCreatedInstance(mockWebChromeClient); webView.setWebChromeClient(mockWebChromeClient); final int webChromeClientInstanceId = instanceManager.getIdentifier(mockWebChromeClient)!; verify(mockPlatformHostApi.setWebChromeClient( webViewInstanceId, webChromeClientInstanceId, )); }); test('copy', () { expect(webView.copy(), isA()); }); }); group('WebSettings', () { late MockTestWebSettingsHostApi mockPlatformHostApi; late InstanceManager instanceManager; late WebSettings webSettings; late int webSettingsInstanceId; setUp(() { instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); TestWebViewHostApi.setup(MockTestWebViewHostApi()); WebView.api = WebViewHostApiImpl(instanceManager: instanceManager); mockPlatformHostApi = MockTestWebSettingsHostApi(); TestWebSettingsHostApi.setup(mockPlatformHostApi); WebSettings.api = WebSettingsHostApiImpl( instanceManager: instanceManager, ); webSettings = WebSettings(WebView()); webSettingsInstanceId = instanceManager.getIdentifier(webSettings)!; }); test('create', () { verify(mockPlatformHostApi.create(webSettingsInstanceId, any)); }); test('setDomStorageEnabled', () { webSettings.setDomStorageEnabled(false); verify(mockPlatformHostApi.setDomStorageEnabled( webSettingsInstanceId, false, )); }); test('setJavaScriptCanOpenWindowsAutomatically', () { webSettings.setJavaScriptCanOpenWindowsAutomatically(true); verify(mockPlatformHostApi.setJavaScriptCanOpenWindowsAutomatically( webSettingsInstanceId, true, )); }); test('setSupportMultipleWindows', () { webSettings.setSupportMultipleWindows(false); verify(mockPlatformHostApi.setSupportMultipleWindows( webSettingsInstanceId, false, )); }); test('setJavaScriptEnabled', () { webSettings.setJavaScriptEnabled(true); verify(mockPlatformHostApi.setJavaScriptEnabled( webSettingsInstanceId, true, )); }); test('setUserAgentString', () { webSettings.setUserAgentString('hola'); verify(mockPlatformHostApi.setUserAgentString( webSettingsInstanceId, 'hola', )); }); test('setMediaPlaybackRequiresUserGesture', () { webSettings.setMediaPlaybackRequiresUserGesture(false); verify(mockPlatformHostApi.setMediaPlaybackRequiresUserGesture( webSettingsInstanceId, false, )); }); test('setSupportZoom', () { webSettings.setSupportZoom(true); verify(mockPlatformHostApi.setSupportZoom( webSettingsInstanceId, true, )); }); test('setLoadWithOverviewMode', () { webSettings.setLoadWithOverviewMode(false); verify(mockPlatformHostApi.setLoadWithOverviewMode( webSettingsInstanceId, false, )); }); test('setUseWideViewPort', () { webSettings.setUseWideViewPort(true); verify(mockPlatformHostApi.setUseWideViewPort( webSettingsInstanceId, true, )); }); test('setDisplayZoomControls', () { webSettings.setDisplayZoomControls(false); verify(mockPlatformHostApi.setDisplayZoomControls( webSettingsInstanceId, false, )); }); test('setBuiltInZoomControls', () { webSettings.setBuiltInZoomControls(true); verify(mockPlatformHostApi.setBuiltInZoomControls( webSettingsInstanceId, true, )); }); test('setAllowFileAccess', () { webSettings.setAllowFileAccess(true); verify(mockPlatformHostApi.setAllowFileAccess( webSettingsInstanceId, true, )); }); test('copy', () { expect(webSettings.copy(), isA()); }); }); group('JavaScriptChannel', () { late JavaScriptChannelFlutterApiImpl flutterApi; late InstanceManager instanceManager; late MockJavaScriptChannel mockJavaScriptChannel; late int mockJavaScriptChannelInstanceId; setUp(() { instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); flutterApi = JavaScriptChannelFlutterApiImpl( instanceManager: instanceManager, ); mockJavaScriptChannel = MockJavaScriptChannel(); when(mockJavaScriptChannel.copy()).thenReturn(MockJavaScriptChannel()); mockJavaScriptChannelInstanceId = instanceManager.addDartCreatedInstance(mockJavaScriptChannel); }); test('postMessage', () { late final String result; when(mockJavaScriptChannel.postMessage).thenReturn((String message) { result = message; }); flutterApi.postMessage( mockJavaScriptChannelInstanceId, 'Hello, World!', ); expect(result, 'Hello, World!'); }); test('copy', () { expect( JavaScriptChannel.detached('channel', postMessage: (_) {}).copy(), isA(), ); }); }); group('WebViewClient', () { late WebViewClientFlutterApiImpl flutterApi; late InstanceManager instanceManager; late MockWebViewClient mockWebViewClient; late int mockWebViewClientInstanceId; late MockWebView mockWebView; late int mockWebViewInstanceId; setUp(() { instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); flutterApi = WebViewClientFlutterApiImpl( instanceManager: instanceManager, ); mockWebViewClient = MockWebViewClient(); when(mockWebViewClient.copy()).thenReturn(MockWebViewClient()); mockWebViewClientInstanceId = instanceManager.addDartCreatedInstance(mockWebViewClient); mockWebView = MockWebView(); when(mockWebView.copy()).thenReturn(MockWebView()); mockWebViewInstanceId = instanceManager.addDartCreatedInstance(mockWebView); }); test('onPageStarted', () { late final List result; when(mockWebViewClient.onPageStarted).thenReturn( (WebView webView, String url) { result = [webView, url]; }, ); flutterApi.onPageStarted( mockWebViewClientInstanceId, mockWebViewInstanceId, 'https://www.google.com', ); expect(result, [mockWebView, 'https://www.google.com']); }); test('onPageFinished', () { late final List result; when(mockWebViewClient.onPageFinished).thenReturn( (WebView webView, String url) { result = [webView, url]; }, ); flutterApi.onPageFinished( mockWebViewClientInstanceId, mockWebViewInstanceId, 'https://www.google.com', ); expect(result, [mockWebView, 'https://www.google.com']); }); test('onReceivedRequestError', () { late final List result; when(mockWebViewClient.onReceivedRequestError).thenReturn( ( WebView webView, WebResourceRequest request, WebResourceError error, ) { result = [webView, request, error]; }, ); flutterApi.onReceivedRequestError( mockWebViewClientInstanceId, mockWebViewInstanceId, WebResourceRequestData( url: 'https://www.google.com', isForMainFrame: true, hasGesture: true, method: 'POST', isRedirect: false, requestHeaders: {}, ), WebResourceErrorData(errorCode: 34, description: 'error description'), ); expect( result, containsAllInOrder([mockWebView, isNotNull, isNotNull]), ); }); test('onReceivedError', () { late final List result; when(mockWebViewClient.onReceivedError).thenReturn( ( WebView webView, int errorCode, String description, String failingUrl, ) { result = [webView, errorCode, description, failingUrl]; }, ); flutterApi.onReceivedError( mockWebViewClientInstanceId, mockWebViewInstanceId, 14, 'desc', 'https://www.google.com', ); expect( result, containsAllInOrder( [mockWebView, 14, 'desc', 'https://www.google.com'], ), ); }); test('requestLoading', () { late final List result; when(mockWebViewClient.requestLoading).thenReturn( (WebView webView, WebResourceRequest request) { result = [webView, request]; }, ); flutterApi.requestLoading( mockWebViewClientInstanceId, mockWebViewInstanceId, WebResourceRequestData( url: 'https://www.google.com', isForMainFrame: true, hasGesture: true, method: 'POST', isRedirect: true, requestHeaders: {}, ), ); expect( result, containsAllInOrder([mockWebView, isNotNull]), ); }); test('urlLoading', () { late final List result; when(mockWebViewClient.urlLoading).thenReturn( (WebView webView, String url) { result = [webView, url]; }, ); flutterApi.urlLoading(mockWebViewClientInstanceId, mockWebViewInstanceId, 'https://www.google.com'); expect( result, containsAllInOrder([mockWebView, 'https://www.google.com']), ); }); test('copy', () { expect(WebViewClient.detached().copy(), isA()); }); }); group('DownloadListener', () { late DownloadListenerFlutterApiImpl flutterApi; late InstanceManager instanceManager; late MockDownloadListener mockDownloadListener; late int mockDownloadListenerInstanceId; setUp(() { instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); flutterApi = DownloadListenerFlutterApiImpl( instanceManager: instanceManager, ); mockDownloadListener = MockDownloadListener(); when(mockDownloadListener.copy()).thenReturn(MockDownloadListener()); mockDownloadListenerInstanceId = instanceManager.addDartCreatedInstance(mockDownloadListener); }); test('onDownloadStart', () { late final List result; when(mockDownloadListener.onDownloadStart).thenReturn( ( String url, String userAgent, String contentDisposition, String mimetype, int contentLength, ) { result = [ url, userAgent, contentDisposition, mimetype, contentLength, ]; }, ); flutterApi.onDownloadStart( mockDownloadListenerInstanceId, 'url', 'userAgent', 'contentDescription', 'mimetype', 45, ); expect( result, containsAllInOrder([ 'url', 'userAgent', 'contentDescription', 'mimetype', 45, ]), ); }); test('copy', () { expect( DownloadListener.detached( onDownloadStart: (_, __, ____, _____, ______) {}, ).copy(), isA(), ); }); }); group('WebChromeClient', () { late WebChromeClientFlutterApiImpl flutterApi; late InstanceManager instanceManager; late MockWebChromeClient mockWebChromeClient; late int mockWebChromeClientInstanceId; late MockWebView mockWebView; late int mockWebViewInstanceId; setUp(() { instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); flutterApi = WebChromeClientFlutterApiImpl( instanceManager: instanceManager, ); mockWebChromeClient = MockWebChromeClient(); when(mockWebChromeClient.copy()).thenReturn(MockWebChromeClient()); mockWebChromeClientInstanceId = instanceManager.addDartCreatedInstance(mockWebChromeClient); mockWebView = MockWebView(); when(mockWebView.copy()).thenReturn(MockWebView()); mockWebViewInstanceId = instanceManager.addDartCreatedInstance(mockWebView); }); test('onProgressChanged', () { late final List result; when(mockWebChromeClient.onProgressChanged).thenReturn( (WebView webView, int progress) { result = [webView, progress]; }, ); flutterApi.onProgressChanged( mockWebChromeClientInstanceId, mockWebViewInstanceId, 76, ); 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', () { test('setCookie calls setCookie on CookieManagerHostApi', () { CookieManager.api = MockCookieManagerHostApi(); CookieManager.instance.setCookie('foo', 'bar'); verify(CookieManager.api.setCookie('foo', 'bar')); }); test('clearCookies calls clearCookies on CookieManagerHostApi', () { CookieManager.api = MockCookieManagerHostApi(); when(CookieManager.api.clearCookies()) .thenAnswer((_) => Future.value(true)); CookieManager.instance.clearCookies(); verify(CookieManager.api.clearCookies()); }); }); group('WebStorage', () { late MockTestWebStorageHostApi mockPlatformHostApi; late WebStorage webStorage; late int webStorageInstanceId; setUp(() { mockPlatformHostApi = MockTestWebStorageHostApi(); TestWebStorageHostApi.setup(mockPlatformHostApi); webStorage = WebStorage(); webStorageInstanceId = WebStorage.api.instanceManager.getIdentifier(webStorage)!; }); test('create', () { verify(mockPlatformHostApi.create(webStorageInstanceId)); }); test('deleteAllData', () { webStorage.deleteAllData(); verify(mockPlatformHostApi.deleteAllData(webStorageInstanceId)); }); test('copy', () { expect(WebStorage.detached().copy(), isA()); }); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_android/test/android_webview_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i5; import 'dart:typed_data' as _i7; 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.g.dart' as _i3; import 'test_android_webview.g.dart' as _i6; // 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 class _FakeDownloadListener_0 extends _i1.SmartFake implements _i2.DownloadListener { _FakeDownloadListener_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeJavaScriptChannel_1 extends _i1.SmartFake implements _i2.JavaScriptChannel { _FakeJavaScriptChannel_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWebViewPoint_2 extends _i1.SmartFake implements _i3.WebViewPoint { _FakeWebViewPoint_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWebChromeClient_3 extends _i1.SmartFake implements _i2.WebChromeClient { _FakeWebChromeClient_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWebSettings_4 extends _i1.SmartFake implements _i2.WebSettings { _FakeWebSettings_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeOffset_5 extends _i1.SmartFake implements _i4.Offset { _FakeOffset_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWebView_6 extends _i1.SmartFake implements _i2.WebView { _FakeWebView_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWebViewClient_7 extends _i1.SmartFake implements _i2.WebViewClient { _FakeWebViewClient_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [CookieManagerHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockCookieManagerHostApi extends _i1.Mock implements _i3.CookieManagerHostApi { MockCookieManagerHostApi() { _i1.throwOnMissingStub(this); } @override _i5.Future clearCookies() => (super.noSuchMethod( Invocation.method( #clearCookies, [], ), returnValue: _i5.Future.value(false), ) as _i5.Future); @override _i5.Future setCookie( String? arg_url, String? arg_value, ) => (super.noSuchMethod( Invocation.method( #setCookie, [ arg_url, arg_value, ], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } /// A class which mocks [DownloadListener]. /// /// See the documentation for Mockito's code generation for more information. class MockDownloadListener extends _i1.Mock implements _i2.DownloadListener { MockDownloadListener() { _i1.throwOnMissingStub(this); } @override void Function( String, String, String, String, int, ) get onDownloadStart => (super.noSuchMethod( Invocation.getter(#onDownloadStart), returnValue: ( String url, String userAgent, String contentDisposition, String mimetype, int contentLength, ) {}, ) as void Function( String, String, String, String, int, )); @override _i2.DownloadListener copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeDownloadListener_0( this, Invocation.method( #copy, [], ), ), ) as _i2.DownloadListener); } /// A class which mocks [JavaScriptChannel]. /// /// See the documentation for Mockito's code generation for more information. class MockJavaScriptChannel extends _i1.Mock implements _i2.JavaScriptChannel { MockJavaScriptChannel() { _i1.throwOnMissingStub(this); } @override String get channelName => (super.noSuchMethod( Invocation.getter(#channelName), returnValue: '', ) as String); @override void Function(String) get postMessage => (super.noSuchMethod( Invocation.getter(#postMessage), returnValue: (String message) {}, ) as void Function(String)); @override _i2.JavaScriptChannel copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeJavaScriptChannel_1( this, Invocation.method( #copy, [], ), ), ) as _i2.JavaScriptChannel); } /// A class which mocks [TestDownloadListenerHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestDownloadListenerHostApi extends _i1.Mock implements _i6.TestDownloadListenerHostApi { MockTestDownloadListenerHostApi() { _i1.throwOnMissingStub(this); } @override void create(int? instanceId) => super.noSuchMethod( Invocation.method( #create, [instanceId], ), returnValueForMissingStub: null, ); } /// A class which mocks [TestJavaObjectHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestJavaObjectHostApi extends _i1.Mock implements _i6.TestJavaObjectHostApi { MockTestJavaObjectHostApi() { _i1.throwOnMissingStub(this); } @override void dispose(int? identifier) => super.noSuchMethod( Invocation.method( #dispose, [identifier], ), returnValueForMissingStub: null, ); } /// A class which mocks [TestJavaScriptChannelHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestJavaScriptChannelHostApi extends _i1.Mock implements _i6.TestJavaScriptChannelHostApi { MockTestJavaScriptChannelHostApi() { _i1.throwOnMissingStub(this); } @override void create( int? instanceId, String? channelName, ) => super.noSuchMethod( Invocation.method( #create, [ instanceId, channelName, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [TestWebChromeClientHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestWebChromeClientHostApi extends _i1.Mock implements _i6.TestWebChromeClientHostApi { MockTestWebChromeClientHostApi() { _i1.throwOnMissingStub(this); } @override void create(int? instanceId) => super.noSuchMethod( Invocation.method( #create, [instanceId], ), returnValueForMissingStub: null, ); @override void setSynchronousReturnValueForOnShowFileChooser( int? instanceId, bool? value, ) => super.noSuchMethod( Invocation.method( #setSynchronousReturnValueForOnShowFileChooser, [ instanceId, value, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [TestWebSettingsHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestWebSettingsHostApi extends _i1.Mock implements _i6.TestWebSettingsHostApi { MockTestWebSettingsHostApi() { _i1.throwOnMissingStub(this); } @override void create( int? instanceId, int? webViewInstanceId, ) => super.noSuchMethod( Invocation.method( #create, [ instanceId, webViewInstanceId, ], ), returnValueForMissingStub: null, ); @override void setDomStorageEnabled( int? instanceId, bool? flag, ) => super.noSuchMethod( Invocation.method( #setDomStorageEnabled, [ instanceId, flag, ], ), returnValueForMissingStub: null, ); @override void setJavaScriptCanOpenWindowsAutomatically( int? instanceId, bool? flag, ) => super.noSuchMethod( Invocation.method( #setJavaScriptCanOpenWindowsAutomatically, [ instanceId, flag, ], ), returnValueForMissingStub: null, ); @override void setSupportMultipleWindows( int? instanceId, bool? support, ) => super.noSuchMethod( Invocation.method( #setSupportMultipleWindows, [ instanceId, support, ], ), returnValueForMissingStub: null, ); @override void setJavaScriptEnabled( int? instanceId, bool? flag, ) => super.noSuchMethod( Invocation.method( #setJavaScriptEnabled, [ instanceId, flag, ], ), returnValueForMissingStub: null, ); @override void setUserAgentString( int? instanceId, String? userAgentString, ) => super.noSuchMethod( Invocation.method( #setUserAgentString, [ instanceId, userAgentString, ], ), returnValueForMissingStub: null, ); @override void setMediaPlaybackRequiresUserGesture( int? instanceId, bool? require, ) => super.noSuchMethod( Invocation.method( #setMediaPlaybackRequiresUserGesture, [ instanceId, require, ], ), returnValueForMissingStub: null, ); @override void setSupportZoom( int? instanceId, bool? support, ) => super.noSuchMethod( Invocation.method( #setSupportZoom, [ instanceId, support, ], ), returnValueForMissingStub: null, ); @override void setLoadWithOverviewMode( int? instanceId, bool? overview, ) => super.noSuchMethod( Invocation.method( #setLoadWithOverviewMode, [ instanceId, overview, ], ), returnValueForMissingStub: null, ); @override void setUseWideViewPort( int? instanceId, bool? use, ) => super.noSuchMethod( Invocation.method( #setUseWideViewPort, [ instanceId, use, ], ), returnValueForMissingStub: null, ); @override void setDisplayZoomControls( int? instanceId, bool? enabled, ) => super.noSuchMethod( Invocation.method( #setDisplayZoomControls, [ instanceId, enabled, ], ), returnValueForMissingStub: null, ); @override void setBuiltInZoomControls( int? instanceId, bool? enabled, ) => super.noSuchMethod( Invocation.method( #setBuiltInZoomControls, [ instanceId, enabled, ], ), returnValueForMissingStub: null, ); @override void setAllowFileAccess( int? instanceId, bool? enabled, ) => super.noSuchMethod( Invocation.method( #setAllowFileAccess, [ instanceId, enabled, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [TestWebStorageHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestWebStorageHostApi extends _i1.Mock implements _i6.TestWebStorageHostApi { MockTestWebStorageHostApi() { _i1.throwOnMissingStub(this); } @override void create(int? instanceId) => super.noSuchMethod( Invocation.method( #create, [instanceId], ), returnValueForMissingStub: null, ); @override void deleteAllData(int? instanceId) => super.noSuchMethod( Invocation.method( #deleteAllData, [instanceId], ), returnValueForMissingStub: null, ); } /// A class which mocks [TestWebViewClientHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestWebViewClientHostApi extends _i1.Mock implements _i6.TestWebViewClientHostApi { MockTestWebViewClientHostApi() { _i1.throwOnMissingStub(this); } @override void create(int? instanceId) => super.noSuchMethod( Invocation.method( #create, [instanceId], ), returnValueForMissingStub: null, ); @override void setSynchronousReturnValueForShouldOverrideUrlLoading( int? instanceId, bool? value, ) => super.noSuchMethod( Invocation.method( #setSynchronousReturnValueForShouldOverrideUrlLoading, [ instanceId, value, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [TestWebViewHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestWebViewHostApi extends _i1.Mock implements _i6.TestWebViewHostApi { MockTestWebViewHostApi() { _i1.throwOnMissingStub(this); } @override void create( int? instanceId, bool? useHybridComposition, ) => super.noSuchMethod( Invocation.method( #create, [ instanceId, useHybridComposition, ], ), returnValueForMissingStub: null, ); @override void loadData( int? instanceId, String? data, String? mimeType, String? encoding, ) => super.noSuchMethod( Invocation.method( #loadData, [ instanceId, data, mimeType, encoding, ], ), returnValueForMissingStub: null, ); @override void loadDataWithBaseUrl( int? instanceId, String? baseUrl, String? data, String? mimeType, String? encoding, String? historyUrl, ) => super.noSuchMethod( Invocation.method( #loadDataWithBaseUrl, [ instanceId, baseUrl, data, mimeType, encoding, historyUrl, ], ), returnValueForMissingStub: null, ); @override void loadUrl( int? instanceId, String? url, Map? headers, ) => super.noSuchMethod( Invocation.method( #loadUrl, [ instanceId, url, headers, ], ), returnValueForMissingStub: null, ); @override void postUrl( int? instanceId, String? url, _i7.Uint8List? data, ) => super.noSuchMethod( Invocation.method( #postUrl, [ instanceId, url, data, ], ), returnValueForMissingStub: null, ); @override String? getUrl(int? instanceId) => (super.noSuchMethod(Invocation.method( #getUrl, [instanceId], )) as String?); @override bool canGoBack(int? instanceId) => (super.noSuchMethod( Invocation.method( #canGoBack, [instanceId], ), returnValue: false, ) as bool); @override bool canGoForward(int? instanceId) => (super.noSuchMethod( Invocation.method( #canGoForward, [instanceId], ), returnValue: false, ) as bool); @override void goBack(int? instanceId) => super.noSuchMethod( Invocation.method( #goBack, [instanceId], ), returnValueForMissingStub: null, ); @override void goForward(int? instanceId) => super.noSuchMethod( Invocation.method( #goForward, [instanceId], ), returnValueForMissingStub: null, ); @override void reload(int? instanceId) => super.noSuchMethod( Invocation.method( #reload, [instanceId], ), returnValueForMissingStub: null, ); @override void clearCache( int? instanceId, bool? includeDiskFiles, ) => super.noSuchMethod( Invocation.method( #clearCache, [ instanceId, includeDiskFiles, ], ), returnValueForMissingStub: null, ); @override _i5.Future evaluateJavascript( int? instanceId, String? javascriptString, ) => (super.noSuchMethod( Invocation.method( #evaluateJavascript, [ instanceId, javascriptString, ], ), returnValue: _i5.Future.value(), ) as _i5.Future); @override String? getTitle(int? instanceId) => (super.noSuchMethod(Invocation.method( #getTitle, [instanceId], )) as String?); @override void scrollTo( int? instanceId, int? x, int? y, ) => super.noSuchMethod( Invocation.method( #scrollTo, [ instanceId, x, y, ], ), returnValueForMissingStub: null, ); @override void scrollBy( int? instanceId, int? x, int? y, ) => super.noSuchMethod( Invocation.method( #scrollBy, [ instanceId, x, y, ], ), returnValueForMissingStub: null, ); @override int getScrollX(int? instanceId) => (super.noSuchMethod( Invocation.method( #getScrollX, [instanceId], ), returnValue: 0, ) as int); @override int getScrollY(int? instanceId) => (super.noSuchMethod( Invocation.method( #getScrollY, [instanceId], ), returnValue: 0, ) as int); @override _i3.WebViewPoint getScrollPosition(int? instanceId) => (super.noSuchMethod( Invocation.method( #getScrollPosition, [instanceId], ), returnValue: _FakeWebViewPoint_2( this, Invocation.method( #getScrollPosition, [instanceId], ), ), ) as _i3.WebViewPoint); @override void setWebContentsDebuggingEnabled(bool? enabled) => super.noSuchMethod( Invocation.method( #setWebContentsDebuggingEnabled, [enabled], ), returnValueForMissingStub: null, ); @override void setWebViewClient( int? instanceId, int? webViewClientInstanceId, ) => super.noSuchMethod( Invocation.method( #setWebViewClient, [ instanceId, webViewClientInstanceId, ], ), returnValueForMissingStub: null, ); @override void addJavaScriptChannel( int? instanceId, int? javaScriptChannelInstanceId, ) => super.noSuchMethod( Invocation.method( #addJavaScriptChannel, [ instanceId, javaScriptChannelInstanceId, ], ), returnValueForMissingStub: null, ); @override void removeJavaScriptChannel( int? instanceId, int? javaScriptChannelInstanceId, ) => super.noSuchMethod( Invocation.method( #removeJavaScriptChannel, [ instanceId, javaScriptChannelInstanceId, ], ), returnValueForMissingStub: null, ); @override void setDownloadListener( int? instanceId, int? listenerInstanceId, ) => super.noSuchMethod( Invocation.method( #setDownloadListener, [ instanceId, listenerInstanceId, ], ), returnValueForMissingStub: null, ); @override void setWebChromeClient( int? instanceId, int? clientInstanceId, ) => super.noSuchMethod( Invocation.method( #setWebChromeClient, [ instanceId, clientInstanceId, ], ), returnValueForMissingStub: null, ); @override void setBackgroundColor( int? instanceId, int? color, ) => super.noSuchMethod( Invocation.method( #setBackgroundColor, [ instanceId, color, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [TestAssetManagerHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestAssetManagerHostApi extends _i1.Mock implements _i6.TestAssetManagerHostApi { MockTestAssetManagerHostApi() { _i1.throwOnMissingStub(this); } @override List list(String? path) => (super.noSuchMethod( Invocation.method( #list, [path], ), returnValue: [], ) as List); @override String getAssetFilePathByName(String? name) => (super.noSuchMethod( Invocation.method( #getAssetFilePathByName, [name], ), returnValue: '', ) as String); } /// A class which mocks [WebChromeClient]. /// /// See the documentation for Mockito's code generation for more information. class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { MockWebChromeClient() { _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( #copy, [], ), returnValue: _FakeWebChromeClient_3( this, Invocation.method( #copy, [], ), ), ) as _i2.WebChromeClient); } /// A class which mocks [WebView]. /// /// See the documentation for Mockito's code generation for more information. class MockWebView extends _i1.Mock implements _i2.WebView { MockWebView() { _i1.throwOnMissingStub(this); } @override bool get useHybridComposition => (super.noSuchMethod( Invocation.getter(#useHybridComposition), returnValue: false, ) as bool); @override _i2.WebSettings get settings => (super.noSuchMethod( Invocation.getter(#settings), returnValue: _FakeWebSettings_4( this, Invocation.getter(#settings), ), ) as _i2.WebSettings); @override _i5.Future loadData({ required String? data, String? mimeType, String? encoding, }) => (super.noSuchMethod( Invocation.method( #loadData, [], { #data: data, #mimeType: mimeType, #encoding: encoding, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future loadDataWithBaseUrl({ String? baseUrl, required String? data, String? mimeType, String? encoding, String? historyUrl, }) => (super.noSuchMethod( Invocation.method( #loadDataWithBaseUrl, [], { #baseUrl: baseUrl, #data: data, #mimeType: mimeType, #encoding: encoding, #historyUrl: historyUrl, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future loadUrl( String? url, Map? headers, ) => (super.noSuchMethod( Invocation.method( #loadUrl, [ url, headers, ], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future postUrl( String? url, _i7.Uint8List? data, ) => (super.noSuchMethod( Invocation.method( #postUrl, [ url, data, ], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future getUrl() => (super.noSuchMethod( Invocation.method( #getUrl, [], ), returnValue: _i5.Future.value(), ) as _i5.Future); @override _i5.Future canGoBack() => (super.noSuchMethod( Invocation.method( #canGoBack, [], ), returnValue: _i5.Future.value(false), ) as _i5.Future); @override _i5.Future canGoForward() => (super.noSuchMethod( Invocation.method( #canGoForward, [], ), returnValue: _i5.Future.value(false), ) as _i5.Future); @override _i5.Future goBack() => (super.noSuchMethod( Invocation.method( #goBack, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future goForward() => (super.noSuchMethod( Invocation.method( #goForward, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future reload() => (super.noSuchMethod( Invocation.method( #reload, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future clearCache(bool? includeDiskFiles) => (super.noSuchMethod( Invocation.method( #clearCache, [includeDiskFiles], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future evaluateJavascript(String? javascriptString) => (super.noSuchMethod( Invocation.method( #evaluateJavascript, [javascriptString], ), returnValue: _i5.Future.value(), ) as _i5.Future); @override _i5.Future getTitle() => (super.noSuchMethod( Invocation.method( #getTitle, [], ), returnValue: _i5.Future.value(), ) as _i5.Future); @override _i5.Future scrollTo( int? x, int? y, ) => (super.noSuchMethod( Invocation.method( #scrollTo, [ x, y, ], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future scrollBy( int? x, int? y, ) => (super.noSuchMethod( Invocation.method( #scrollBy, [ x, y, ], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future getScrollX() => (super.noSuchMethod( Invocation.method( #getScrollX, [], ), returnValue: _i5.Future.value(0), ) as _i5.Future); @override _i5.Future getScrollY() => (super.noSuchMethod( Invocation.method( #getScrollY, [], ), returnValue: _i5.Future.value(0), ) as _i5.Future); @override _i5.Future<_i4.Offset> getScrollPosition() => (super.noSuchMethod( Invocation.method( #getScrollPosition, [], ), returnValue: _i5.Future<_i4.Offset>.value(_FakeOffset_5( this, Invocation.method( #getScrollPosition, [], ), )), ) as _i5.Future<_i4.Offset>); @override _i5.Future setWebViewClient(_i2.WebViewClient? webViewClient) => (super.noSuchMethod( Invocation.method( #setWebViewClient, [webViewClient], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future addJavaScriptChannel( _i2.JavaScriptChannel? javaScriptChannel) => (super.noSuchMethod( Invocation.method( #addJavaScriptChannel, [javaScriptChannel], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeJavaScriptChannel( _i2.JavaScriptChannel? javaScriptChannel) => (super.noSuchMethod( Invocation.method( #removeJavaScriptChannel, [javaScriptChannel], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setDownloadListener(_i2.DownloadListener? listener) => (super.noSuchMethod( Invocation.method( #setDownloadListener, [listener], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setWebChromeClient(_i2.WebChromeClient? client) => (super.noSuchMethod( Invocation.method( #setWebChromeClient, [client], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setBackgroundColor(_i4.Color? color) => (super.noSuchMethod( Invocation.method( #setBackgroundColor, [color], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i2.WebView copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWebView_6( this, Invocation.method( #copy, [], ), ), ) as _i2.WebView); } /// A class which mocks [WebViewClient]. /// /// See the documentation for Mockito's code generation for more information. class MockWebViewClient extends _i1.Mock implements _i2.WebViewClient { MockWebViewClient() { _i1.throwOnMissingStub(this); } @override _i5.Future setSynchronousReturnValueForShouldOverrideUrlLoading( bool? value) => (super.noSuchMethod( Invocation.method( #setSynchronousReturnValueForShouldOverrideUrlLoading, [value], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i2.WebViewClient copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWebViewClient_7( this, Invocation.method( #copy, [], ), ), ) as _i2.WebViewClient); } ================================================ FILE: packages/webview_flutter/webview_flutter_android/test/instance_manager_test.dart ================================================ // Copyright 2013 The Flutter 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:webview_flutter_android/src/instance_manager.dart'; void main() { group('InstanceManager', () { test('addHostCreatedInstance', () { final CopyableObject object = CopyableObject(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addHostCreatedInstance(object, 0); expect(instanceManager.getIdentifier(object), 0); expect( instanceManager.getInstanceWithWeakReference(0), object, ); }); test('addHostCreatedInstance prevents already used objects and ids', () { final CopyableObject object = CopyableObject(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addHostCreatedInstance(object, 0); expect( () => instanceManager.addHostCreatedInstance(object, 0), throwsAssertionError, ); expect( () => instanceManager.addHostCreatedInstance(CopyableObject(), 0), throwsAssertionError, ); }); test('addFlutterCreatedInstance', () { final CopyableObject object = CopyableObject(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addDartCreatedInstance(object); final int? instanceId = instanceManager.getIdentifier(object); expect(instanceId, isNotNull); expect( instanceManager.getInstanceWithWeakReference(instanceId!), object, ); }); test('removeWeakReference', () { final CopyableObject object = CopyableObject(); int? weakInstanceId; final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (int instanceId) { weakInstanceId = instanceId; }); instanceManager.addHostCreatedInstance(object, 0); expect(instanceManager.removeWeakReference(object), 0); expect( instanceManager.getInstanceWithWeakReference(0), isA(), ); expect(weakInstanceId, 0); }); test('removeWeakReference removes only weak reference', () { final CopyableObject object = CopyableObject(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addHostCreatedInstance(object, 0); expect(instanceManager.removeWeakReference(object), 0); final CopyableObject copy = instanceManager.getInstanceWithWeakReference( 0, )!; expect(identical(object, copy), isFalse); }); test('removeStrongReference', () { final CopyableObject object = CopyableObject(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addHostCreatedInstance(object, 0); instanceManager.removeWeakReference(object); expect(instanceManager.remove(0), isA()); expect(instanceManager.containsIdentifier(0), isFalse); }); test('removeStrongReference removes only strong reference', () { final CopyableObject object = CopyableObject(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addHostCreatedInstance(object, 0); expect(instanceManager.remove(0), isA()); expect( instanceManager.getInstanceWithWeakReference(0), object, ); }); test('getInstance can add a new weak reference', () { final CopyableObject object = CopyableObject(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addHostCreatedInstance(object, 0); instanceManager.removeWeakReference(object); final CopyableObject newWeakCopy = instanceManager.getInstanceWithWeakReference( 0, )!; expect(identical(object, newWeakCopy), isFalse); }); }); } class CopyableObject with Copyable { @override Copyable copy() { return CopyableObject(); } } ================================================ FILE: packages/webview_flutter/webview_flutter_android/test/legacy/surface_android_test.dart ================================================ // Copyright 2013 The Flutter 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/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:webview_flutter_android/src/legacy/webview_surface_android.dart'; import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('SurfaceAndroidWebView', () { late List log; setUpAll(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler( SystemChannels.platform_views, (MethodCall call) async { log.add(call); if (call.method == 'resize') { final Map arguments = (call.arguments as Map) .cast(); return { 'width': arguments['width'], 'height': arguments['height'], }; } return null; }, ); }); tearDownAll(() { _ambiguate(TestDefaultBinaryMessengerBinding.instance)! .defaultBinaryMessenger .setMockMethodCallHandler(SystemChannels.platform_views, null); }); setUp(() { log = []; }); testWidgets( 'uses hybrid composition when background color is not 100% opaque', (WidgetTester tester) async { await tester.pumpWidget(Builder(builder: (BuildContext context) { return SurfaceAndroidWebView().build( context: context, creationParams: CreationParams( backgroundColor: Colors.transparent, webSettings: WebSettings( userAgent: const WebSetting.absent(), hasNavigationDelegate: false, )), javascriptChannelRegistry: JavascriptChannelRegistry(null), webViewPlatformCallbacksHandler: TestWebViewPlatformCallbacksHandler(), ); })); await tester.pumpAndSettle(); final MethodCall createMethodCall = log[0]; expect(createMethodCall.method, 'create'); expect(createMethodCall.arguments, containsPair('hybrid', true)); }); testWidgets('default text direction is ltr', (WidgetTester tester) async { await tester.pumpWidget(Builder(builder: (BuildContext context) { return SurfaceAndroidWebView().build( context: context, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), hasNavigationDelegate: false, )), javascriptChannelRegistry: JavascriptChannelRegistry(null), webViewPlatformCallbacksHandler: TestWebViewPlatformCallbacksHandler(), ); })); await tester.pumpAndSettle(); final MethodCall createMethodCall = log[0]; expect(createMethodCall.method, 'create'); expect( createMethodCall.arguments, containsPair( 'direction', AndroidViewController.kAndroidLayoutDirectionLtr, ), ); }); }); } class TestWebViewPlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { @override FutureOr onNavigationRequest({ required String url, required bool isForMainFrame, }) { throw UnimplementedError(); } @override void onPageFinished(String url) {} @override void onPageStarted(String url) {} @override void onProgress(int progress) {} @override void onWebResourceError(WebResourceError error) {} } /// 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; ================================================ FILE: packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_cookie_manager_test.dart ================================================ // Copyright 2013 The Flutter 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:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_android/src/android_webview.dart' as android_webview; import 'package:webview_flutter_android/src/legacy/webview_android_cookie_manager.dart'; import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; import 'webview_android_cookie_manager_test.mocks.dart'; @GenerateMocks([android_webview.CookieManager]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); setUp(() { android_webview.CookieManager.instance = MockCookieManager(); }); test('clearCookies should call android_webview.clearCookies', () { when(android_webview.CookieManager.instance.clearCookies()) .thenAnswer((_) => Future.value(true)); WebViewAndroidCookieManager().clearCookies(); verify(android_webview.CookieManager.instance.clearCookies()); }); test('setCookie should throw ArgumentError for cookie with invalid path', () { expect( () => WebViewAndroidCookieManager().setCookie(const WebViewCookie( name: 'foo', value: 'bar', domain: 'flutter.dev', path: 'invalid;path', )), throwsA(const TypeMatcher()), ); }); test( 'setCookie should call android_webview.csetCookie with properly formatted cookie value', () { WebViewAndroidCookieManager().setCookie(const WebViewCookie( name: 'foo&', value: 'bar@', domain: 'flutter.dev', )); verify(android_webview.CookieManager.instance .setCookie('flutter.dev', 'foo%26=bar%40; path=/')); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_cookie_manager_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_android/test/legacy/webview_android_cookie_manager_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i3; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_android/src/android_webview.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 [CookieManager]. /// /// See the documentation for Mockito's code generation for more information. class MockCookieManager extends _i1.Mock implements _i2.CookieManager { MockCookieManager() { _i1.throwOnMissingStub(this); } @override _i3.Future setCookie( String? url, String? value, ) => (super.noSuchMethod( Invocation.method( #setCookie, [ url, value, ], ), returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); @override _i3.Future clearCookies() => (super.noSuchMethod( Invocation.method( #clearCookies, [], ), returnValue: _i3.Future.value(false), ) as _i3.Future); } ================================================ FILE: packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:typed_data'; import 'package:flutter/widgets.dart'; 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' as android_webview; 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/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.g.dart'; import 'webview_android_widget_test.mocks.dart'; @GenerateMocks([ android_webview.FlutterAssetManager, android_webview.WebSettings, android_webview.WebStorage, android_webview.WebView, android_webview.WebResourceRequest, android_webview.DownloadListener, WebViewAndroidJavaScriptChannel, android_webview.WebChromeClient, android_webview.WebViewClient, JavascriptChannelRegistry, WebViewPlatformCallbacksHandler, WebViewProxy, ]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('WebViewAndroidWidget', () { late MockFlutterAssetManager mockFlutterAssetManager; late MockWebView mockWebView; late MockWebSettings mockWebSettings; late MockWebStorage mockWebStorage; late MockWebViewProxy mockWebViewProxy; late MockWebViewPlatformCallbacksHandler mockCallbacksHandler; late MockWebViewClient mockWebViewClient; late android_webview.DownloadListener downloadListener; late android_webview.WebChromeClient webChromeClient; late MockJavascriptChannelRegistry mockJavascriptChannelRegistry; late WebViewAndroidPlatformController testController; setUp(() { mockFlutterAssetManager = MockFlutterAssetManager(); mockWebView = MockWebView(); mockWebSettings = MockWebSettings(); mockWebStorage = MockWebStorage(); mockWebViewClient = MockWebViewClient(); when(mockWebView.settings).thenReturn(mockWebSettings); mockWebViewProxy = MockWebViewProxy(); when(mockWebViewProxy.createWebView( useHybridComposition: anyNamed('useHybridComposition'), )).thenReturn(mockWebView); when(mockWebViewProxy.createWebViewClient( onPageStarted: anyNamed('onPageStarted'), onPageFinished: anyNamed('onPageFinished'), onReceivedError: anyNamed('onReceivedError'), onReceivedRequestError: anyNamed('onReceivedRequestError'), requestLoading: anyNamed('requestLoading'), urlLoading: anyNamed('urlLoading'), )).thenReturn(mockWebViewClient); mockCallbacksHandler = MockWebViewPlatformCallbacksHandler(); mockJavascriptChannelRegistry = MockJavascriptChannelRegistry(); }); // Builds a AndroidWebViewWidget with default parameters. Future buildWidget( WidgetTester tester, { CreationParams? creationParams, bool hasNavigationDelegate = false, bool hasProgressTracking = false, bool useHybridComposition = false, }) async { await tester.pumpWidget(WebViewAndroidWidget( useHybridComposition: useHybridComposition, creationParams: creationParams ?? CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), hasNavigationDelegate: hasNavigationDelegate, hasProgressTracking: hasProgressTracking, )), callbacksHandler: mockCallbacksHandler, javascriptChannelRegistry: mockJavascriptChannelRegistry, webViewProxy: mockWebViewProxy, flutterAssetManager: mockFlutterAssetManager, webStorage: mockWebStorage, onBuildWidget: (WebViewAndroidPlatformController controller) { testController = controller; return Container(); }, )); mockWebViewClient = testController.webViewClient as MockWebViewClient; downloadListener = testController.downloadListener; webChromeClient = testController.webChromeClient; } testWidgets('WebViewAndroidWidget', (WidgetTester tester) async { await buildWidget(tester); verify(mockWebSettings.setDomStorageEnabled(true)); verify(mockWebSettings.setJavaScriptCanOpenWindowsAutomatically(true)); verify(mockWebSettings.setSupportMultipleWindows(true)); verify(mockWebSettings.setLoadWithOverviewMode(true)); verify(mockWebSettings.setUseWideViewPort(true)); verify(mockWebSettings.setDisplayZoomControls(false)); verify(mockWebSettings.setBuiltInZoomControls(true)); verifyInOrder(>[ mockWebView.setDownloadListener(downloadListener), mockWebView.setWebChromeClient(webChromeClient), mockWebView.setWebViewClient(mockWebViewClient), ]); }); testWidgets( 'Create Widget with Hybrid Composition', (WidgetTester tester) async { await buildWidget(tester, useHybridComposition: true); verify(mockWebViewProxy.createWebView(useHybridComposition: true)); }, ); group('CreationParams', () { testWidgets('initialUrl', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( initialUrl: 'https://www.google.com', webSettings: WebSettings( userAgent: const WebSetting.absent(), hasNavigationDelegate: false, ), ), ); verify(mockWebView.loadUrl( 'https://www.google.com', {}, )); }); testWidgets('userAgent', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( userAgent: 'MyUserAgent', webSettings: WebSettings( userAgent: const WebSetting.absent(), hasNavigationDelegate: false, ), ), ); verify(mockWebSettings.setUserAgentString('MyUserAgent')); }); testWidgets('autoMediaPlaybackPolicy true', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), hasNavigationDelegate: false, ), ), ); verify(mockWebSettings.setMediaPlaybackRequiresUserGesture(any)); }); testWidgets('autoMediaPlaybackPolicy false', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( autoMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, webSettings: WebSettings( userAgent: const WebSetting.absent(), hasNavigationDelegate: false, ), ), ); verify(mockWebSettings.setMediaPlaybackRequiresUserGesture(false)); }); testWidgets('javascriptChannelNames', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( javascriptChannelNames: {'a', 'b'}, webSettings: WebSettings( userAgent: const WebSetting.absent(), hasNavigationDelegate: false, ), ), ); final List javaScriptChannels = verify(mockWebView.addJavaScriptChannel(captureAny)) .captured .cast(); expect(javaScriptChannels[0].channelName, 'a'); expect(javaScriptChannels[1].channelName, 'b'); }); group('WebSettings', () { testWidgets('javascriptMode', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), javascriptMode: JavascriptMode.unrestricted, hasNavigationDelegate: false, ), ), ); verify(mockWebSettings.setJavaScriptEnabled(true)); }); testWidgets('hasNavigationDelegate', (WidgetTester tester) async { final MockWebViewClient mockWebViewClient = MockWebViewClient(); when(mockWebViewProxy.createWebViewClient( onPageStarted: anyNamed('onPageStarted'), onPageFinished: anyNamed('onPageFinished'), onReceivedError: anyNamed('onReceivedError'), onReceivedRequestError: anyNamed('onReceivedRequestError'), requestLoading: anyNamed('requestLoading'), urlLoading: anyNamed('urlLoading'), )).thenReturn(mockWebViewClient); await buildWidget( tester, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), hasNavigationDelegate: true, ), ), ); verify( mockWebViewClient .setSynchronousReturnValueForShouldOverrideUrlLoading(true), ); }); testWidgets('debuggingEnabled true', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), debuggingEnabled: true, hasNavigationDelegate: false, ), ), ); verify(mockWebViewProxy.setWebContentsDebuggingEnabled(true)); }); testWidgets('debuggingEnabled false', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), debuggingEnabled: false, hasNavigationDelegate: false, ), ), ); verify(mockWebViewProxy.setWebContentsDebuggingEnabled(false)); }); testWidgets('userAgent', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.of('myUserAgent'), hasNavigationDelegate: false, ), ), ); verify(mockWebSettings.setUserAgentString('myUserAgent')); }); testWidgets('zoomEnabled', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), zoomEnabled: false, hasNavigationDelegate: false, ), ), ); verify(mockWebSettings.setSupportZoom(false)); }); }); }); group('WebViewPlatformController', () { testWidgets('loadFile without "file://" prefix', (WidgetTester tester) async { await buildWidget(tester); const String filePath = '/path/to/file.html'; await testController.loadFile(filePath); verify(mockWebView.loadUrl( 'file://$filePath', {}, )); }); testWidgets('loadFile with "file://" prefix', (WidgetTester tester) async { await buildWidget(tester); await testController.loadFile('file:///path/to/file.html'); verify(mockWebView.loadUrl( 'file:///path/to/file.html', {}, )); }); testWidgets('loadFile should setAllowFileAccess to true', (WidgetTester tester) async { await buildWidget(tester); await testController.loadFile('file:///path/to/file.html'); verify(mockWebSettings.setAllowFileAccess(true)); }); testWidgets('loadFlutterAsset', (WidgetTester tester) async { await buildWidget(tester); const String assetKey = 'test_assets/index.html'; when(mockFlutterAssetManager.getAssetFilePathByName(assetKey)) .thenAnswer( (_) => Future.value('flutter_assets/$assetKey')); when(mockFlutterAssetManager.list('flutter_assets/test_assets')) .thenAnswer( (_) => Future>.value(['index.html'])); await testController.loadFlutterAsset(assetKey); verify(mockWebView.loadUrl( 'file:///android_asset/flutter_assets/$assetKey', {}, )); }); testWidgets('loadFlutterAsset with file in root', (WidgetTester tester) async { await buildWidget(tester); const String assetKey = 'index.html'; when(mockFlutterAssetManager.getAssetFilePathByName(assetKey)) .thenAnswer( (_) => Future.value('flutter_assets/$assetKey')); when(mockFlutterAssetManager.list('flutter_assets')).thenAnswer( (_) => Future>.value(['index.html'])); await testController.loadFlutterAsset(assetKey); verify(mockWebView.loadUrl( 'file:///android_asset/flutter_assets/$assetKey', {}, )); }); testWidgets( 'loadFlutterAsset throws ArgumentError when asset does not exists', (WidgetTester tester) async { await buildWidget(tester); const String assetKey = 'test_assets/index.html'; when(mockFlutterAssetManager.getAssetFilePathByName(assetKey)) .thenAnswer( (_) => Future.value('flutter_assets/$assetKey')); when(mockFlutterAssetManager.list('flutter_assets/test_assets')) .thenAnswer((_) => Future>.value([''])); expect( () => testController.loadFlutterAsset(assetKey), throwsA( isA() .having((ArgumentError error) => error.name, 'name', 'key') .having((ArgumentError error) => error.message, 'message', 'Asset for key "$assetKey" not found.'), ), ); }); testWidgets('loadHtmlString without base URL', (WidgetTester tester) async { await buildWidget(tester); const String htmlString = 'Test data.'; await testController.loadHtmlString(htmlString); verify(mockWebView.loadDataWithBaseUrl( data: htmlString, mimeType: 'text/html', )); }); testWidgets('loadHtmlString with base URL', (WidgetTester tester) async { await buildWidget(tester); const String htmlString = 'Test data.'; await testController.loadHtmlString( htmlString, baseUrl: 'https://flutter.dev', ); verify(mockWebView.loadDataWithBaseUrl( baseUrl: 'https://flutter.dev', data: htmlString, mimeType: 'text/html', )); }); testWidgets('loadUrl', (WidgetTester tester) async { await buildWidget(tester); await testController.loadUrl( 'https://www.google.com', {'a': 'header'}, ); verify(mockWebView.loadUrl( 'https://www.google.com', {'a': 'header'}, )); }); group('loadRequest', () { testWidgets('Throws ArgumentError for empty scheme', (WidgetTester tester) async { await buildWidget(tester); expect( () async => testController.loadRequest( WebViewRequest( uri: Uri.parse('www.google.com'), method: WebViewRequestMethod.get, ), ), throwsA(const TypeMatcher())); }); testWidgets('GET without headers', (WidgetTester tester) async { await buildWidget(tester); await testController.loadRequest(WebViewRequest( uri: Uri.parse('https://www.google.com'), method: WebViewRequestMethod.get, )); verify(mockWebView.loadUrl( 'https://www.google.com', {}, )); }); testWidgets('GET with headers', (WidgetTester tester) async { await buildWidget(tester); await testController.loadRequest(WebViewRequest( uri: Uri.parse('https://www.google.com'), method: WebViewRequestMethod.get, headers: {'a': 'header'}, )); verify(mockWebView.loadUrl( 'https://www.google.com', {'a': 'header'}, )); }); testWidgets('POST without body', (WidgetTester tester) async { await buildWidget(tester); await testController.loadRequest(WebViewRequest( uri: Uri.parse('https://www.google.com'), method: WebViewRequestMethod.post, )); verify(mockWebView.postUrl( 'https://www.google.com', Uint8List(0), )); }); testWidgets('POST with body', (WidgetTester tester) async { await buildWidget(tester); final Uint8List body = Uint8List.fromList('Test Body'.codeUnits); await testController.loadRequest(WebViewRequest( uri: Uri.parse('https://www.google.com'), method: WebViewRequestMethod.post, body: body)); verify(mockWebView.postUrl( 'https://www.google.com', body, )); }); }); testWidgets('no update to userAgentString when there is no change', (WidgetTester tester) async { await buildWidget(tester); reset(mockWebSettings); await testController.updateSettings(WebSettings( userAgent: const WebSetting.absent(), )); verifyNever(mockWebSettings.setUserAgentString(any)); }); testWidgets('update null userAgentString with empty string', (WidgetTester tester) async { await buildWidget(tester); reset(mockWebSettings); await testController.updateSettings(WebSettings( userAgent: const WebSetting.of(null), )); verify(mockWebSettings.setUserAgentString('')); }); testWidgets('currentUrl', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.getUrl()) .thenAnswer((_) => Future.value('https://www.google.com')); expect( testController.currentUrl(), completion('https://www.google.com')); }); testWidgets('canGoBack', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.canGoBack()).thenAnswer( (_) => Future.value(false), ); expect(testController.canGoBack(), completion(false)); }); testWidgets('canGoForward', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.canGoForward()).thenAnswer( (_) => Future.value(true), ); expect(testController.canGoForward(), completion(true)); }); testWidgets('goBack', (WidgetTester tester) async { await buildWidget(tester); await testController.goBack(); verify(mockWebView.goBack()); }); testWidgets('goForward', (WidgetTester tester) async { await buildWidget(tester); await testController.goForward(); verify(mockWebView.goForward()); }); testWidgets('reload', (WidgetTester tester) async { await buildWidget(tester); await testController.reload(); verify(mockWebView.reload()); }); testWidgets('clearCache', (WidgetTester tester) async { await buildWidget(tester); await testController.clearCache(); verify(mockWebView.clearCache(true)); verify(mockWebStorage.deleteAllData()); }); testWidgets('evaluateJavascript', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.evaluateJavascript('runJavaScript')).thenAnswer( (_) => Future.value('returnString'), ); expect( testController.evaluateJavascript('runJavaScript'), completion('returnString'), ); }); testWidgets('runJavascriptReturningResult', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.evaluateJavascript('runJavaScript')).thenAnswer( (_) => Future.value('returnString'), ); expect( testController.runJavascriptReturningResult('runJavaScript'), completion('returnString'), ); }); testWidgets('runJavascript', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.evaluateJavascript('runJavaScript')).thenAnswer( (_) => Future.value('returnString'), ); expect( testController.runJavascript('runJavaScript'), completes, ); }); testWidgets('addJavascriptChannels', (WidgetTester tester) async { await buildWidget(tester); await testController.addJavascriptChannels({'c', 'd'}); final List javaScriptChannels = verify(mockWebView.addJavaScriptChannel(captureAny)) .captured .cast(); expect(javaScriptChannels[0].channelName, 'c'); expect(javaScriptChannels[1].channelName, 'd'); }); testWidgets('removeJavascriptChannels', (WidgetTester tester) async { await buildWidget(tester); await testController.addJavascriptChannels({'c', 'd'}); await testController.removeJavascriptChannels({'c', 'd'}); final List javaScriptChannels = verify(mockWebView.removeJavaScriptChannel(captureAny)) .captured .cast(); expect(javaScriptChannels[0].channelName, 'c'); expect(javaScriptChannels[1].channelName, 'd'); }); testWidgets('getTitle', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.getTitle()) .thenAnswer((_) => Future.value('Web Title')); expect(testController.getTitle(), completion('Web Title')); }); testWidgets('scrollTo', (WidgetTester tester) async { await buildWidget(tester); await testController.scrollTo(1, 2); verify(mockWebView.scrollTo(1, 2)); }); testWidgets('scrollBy', (WidgetTester tester) async { await buildWidget(tester); await testController.scrollBy(3, 4); verify(mockWebView.scrollBy(3, 4)); }); testWidgets('getScrollX', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.getScrollX()).thenAnswer((_) => Future.value(23)); expect(testController.getScrollX(), completion(23)); }); testWidgets('getScrollY', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.getScrollY()).thenAnswer((_) => Future.value(25)); expect(testController.getScrollY(), completion(25)); }); }); group('WebViewPlatformCallbacksHandler', () { testWidgets('onPageStarted', (WidgetTester tester) async { await buildWidget(tester); final void Function(android_webview.WebView, String) onPageStarted = verify(mockWebViewProxy.createWebViewClient( onPageStarted: captureAnyNamed('onPageStarted'), onPageFinished: anyNamed('onPageFinished'), onReceivedError: anyNamed('onReceivedError'), onReceivedRequestError: anyNamed('onReceivedRequestError'), requestLoading: anyNamed('requestLoading'), urlLoading: anyNamed('urlLoading'), )).captured.single as Function(android_webview.WebView, String); onPageStarted(mockWebView, 'https://google.com'); verify(mockCallbacksHandler.onPageStarted('https://google.com')); }); testWidgets('onPageFinished', (WidgetTester tester) async { await buildWidget(tester); final void Function(android_webview.WebView, String) onPageFinished = verify(mockWebViewProxy.createWebViewClient( onPageStarted: anyNamed('onPageStarted'), onPageFinished: captureAnyNamed('onPageFinished'), onReceivedError: anyNamed('onReceivedError'), onReceivedRequestError: anyNamed('onReceivedRequestError'), requestLoading: anyNamed('requestLoading'), urlLoading: anyNamed('urlLoading'), )).captured.single as Function(android_webview.WebView, String); onPageFinished(mockWebView, 'https://google.com'); verify(mockCallbacksHandler.onPageFinished('https://google.com')); }); testWidgets('onWebResourceError from onReceivedError', (WidgetTester tester) async { await buildWidget(tester); final void Function(android_webview.WebView, int, String, String) onReceivedError = verify(mockWebViewProxy.createWebViewClient( onPageStarted: anyNamed('onPageStarted'), onPageFinished: anyNamed('onPageFinished'), onReceivedError: captureAnyNamed('onReceivedError'), onReceivedRequestError: anyNamed('onReceivedRequestError'), requestLoading: anyNamed('requestLoading'), urlLoading: anyNamed('urlLoading'), )).captured.single as Function( android_webview.WebView, int, String, String); onReceivedError( mockWebView, android_webview.WebViewClient.errorAuthentication, 'description', 'https://google.com', ); final WebResourceError error = verify(mockCallbacksHandler.onWebResourceError(captureAny)) .captured .single as WebResourceError; expect(error.description, 'description'); expect(error.errorCode, -4); expect(error.failingUrl, 'https://google.com'); expect(error.domain, isNull); expect(error.errorType, WebResourceErrorType.authentication); }); testWidgets('onWebResourceError from onReceivedRequestError', (WidgetTester tester) async { await buildWidget(tester); final void Function( android_webview.WebView, android_webview.WebResourceRequest, android_webview.WebResourceError, ) onReceivedRequestError = verify(mockWebViewProxy.createWebViewClient( onPageStarted: anyNamed('onPageStarted'), onPageFinished: anyNamed('onPageFinished'), onReceivedError: anyNamed('onReceivedError'), onReceivedRequestError: captureAnyNamed('onReceivedRequestError'), requestLoading: anyNamed('requestLoading'), urlLoading: anyNamed('urlLoading'), )).captured.single as Function( android_webview.WebView, android_webview.WebResourceRequest, android_webview.WebResourceError, ); onReceivedRequestError( mockWebView, android_webview.WebResourceRequest( url: 'https://google.com', isForMainFrame: true, isRedirect: false, hasGesture: false, method: 'POST', requestHeaders: {}, ), android_webview.WebResourceError( errorCode: android_webview.WebViewClient.errorUnsafeResource, description: 'description', ), ); final WebResourceError error = verify(mockCallbacksHandler.onWebResourceError(captureAny)) .captured .single as WebResourceError; expect(error.description, 'description'); expect(error.errorCode, -16); expect(error.failingUrl, 'https://google.com'); expect(error.domain, isNull); expect(error.errorType, WebResourceErrorType.unsafeResource); }); testWidgets('onNavigationRequest from urlLoading', (WidgetTester tester) async { await buildWidget(tester, hasNavigationDelegate: true); when(mockCallbacksHandler.onNavigationRequest( isForMainFrame: argThat(isTrue, named: 'isForMainFrame'), url: 'https://google.com', )).thenReturn(true); final void Function(android_webview.WebView, String) urlLoading = verify(mockWebViewProxy.createWebViewClient( onPageStarted: anyNamed('onPageStarted'), onPageFinished: anyNamed('onPageFinished'), onReceivedError: anyNamed('onReceivedError'), onReceivedRequestError: anyNamed('onReceivedRequestError'), requestLoading: anyNamed('requestLoading'), urlLoading: captureAnyNamed('urlLoading'), )).captured.single as Function(android_webview.WebView, String); urlLoading(mockWebView, 'https://google.com'); verify(mockCallbacksHandler.onNavigationRequest( url: 'https://google.com', isForMainFrame: true, )); verify(mockWebView.loadUrl('https://google.com', {})); }); testWidgets('onNavigationRequest from requestLoading', (WidgetTester tester) async { await buildWidget(tester, hasNavigationDelegate: true); when(mockCallbacksHandler.onNavigationRequest( isForMainFrame: argThat(isTrue, named: 'isForMainFrame'), url: 'https://google.com', )).thenReturn(true); final void Function( android_webview.WebView, android_webview.WebResourceRequest, ) requestLoading = verify(mockWebViewProxy.createWebViewClient( onPageStarted: anyNamed('onPageStarted'), onPageFinished: anyNamed('onPageFinished'), onReceivedError: anyNamed('onReceivedError'), onReceivedRequestError: anyNamed('onReceivedRequestError'), requestLoading: captureAnyNamed('requestLoading'), urlLoading: anyNamed('urlLoading'), )).captured.single as Function( android_webview.WebView, android_webview.WebResourceRequest, ); requestLoading( mockWebView, android_webview.WebResourceRequest( url: 'https://google.com', isForMainFrame: true, isRedirect: false, hasGesture: false, method: 'POST', requestHeaders: {}, ), ); verify(mockCallbacksHandler.onNavigationRequest( url: 'https://google.com', isForMainFrame: true, )); verify(mockWebView.loadUrl('https://google.com', {})); }); group('JavascriptChannelRegistry', () { testWidgets('onJavascriptChannelMessage', (WidgetTester tester) async { await buildWidget(tester); await testController.addJavascriptChannels({'hello'}); final WebViewAndroidJavaScriptChannel javaScriptChannel = verify(mockWebView.addJavaScriptChannel(captureAny)) .captured .single as WebViewAndroidJavaScriptChannel; javaScriptChannel.postMessage('goodbye'); verify(mockJavascriptChannelRegistry.onJavascriptChannelMessage( 'hello', 'goodbye', )); }); }); }); }); group('WebViewProxy', () { late MockTestWebViewHostApi mockPlatformHostApi; late InstanceManager instanceManager; setUp(() { // WebViewProxy calls static methods that can't be mocked, so the mocks // have to be set up at the next layer down, by mocking the implementation // of WebView itstelf. mockPlatformHostApi = MockTestWebViewHostApi(); TestWebViewHostApi.setup(mockPlatformHostApi); instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); android_webview.WebView.api = WebViewHostApiImpl(instanceManager: instanceManager); }); test('setWebContentsDebuggingEnabled true', () { const WebViewProxy webViewProxy = WebViewProxy(); webViewProxy.setWebContentsDebuggingEnabled(true); verify(mockPlatformHostApi.setWebContentsDebuggingEnabled(true)); }); test('setWebContentsDebuggingEnabled false', () { const WebViewProxy webViewProxy = WebViewProxy(); webViewProxy.setWebContentsDebuggingEnabled(false); verify(mockPlatformHostApi.setWebContentsDebuggingEnabled(false)); }); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_android/test/legacy/webview_android_widget_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i5; import 'dart:typed_data' as _i6; import 'dart:ui' as _i3; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_android/src/android_webview.dart' as _i2; import 'package:webview_flutter_android/src/legacy/webview_android_widget.dart' as _i7; import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart' as _i4; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class class _FakeWebSettings_0 extends _i1.SmartFake implements _i2.WebSettings { _FakeWebSettings_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWebStorage_1 extends _i1.SmartFake implements _i2.WebStorage { _FakeWebStorage_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeOffset_2 extends _i1.SmartFake implements _i3.Offset { _FakeOffset_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWebView_3 extends _i1.SmartFake implements _i2.WebView { _FakeWebView_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeDownloadListener_4 extends _i1.SmartFake implements _i2.DownloadListener { _FakeDownloadListener_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeJavascriptChannelRegistry_5 extends _i1.SmartFake implements _i4.JavascriptChannelRegistry { _FakeJavascriptChannelRegistry_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeJavaScriptChannel_6 extends _i1.SmartFake implements _i2.JavaScriptChannel { _FakeJavaScriptChannel_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWebChromeClient_7 extends _i1.SmartFake implements _i2.WebChromeClient { _FakeWebChromeClient_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWebViewClient_8 extends _i1.SmartFake implements _i2.WebViewClient { _FakeWebViewClient_8( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [FlutterAssetManager]. /// /// See the documentation for Mockito's code generation for more information. class MockFlutterAssetManager extends _i1.Mock implements _i2.FlutterAssetManager { MockFlutterAssetManager() { _i1.throwOnMissingStub(this); } @override _i5.Future> list(String? path) => (super.noSuchMethod( Invocation.method( #list, [path], ), returnValue: _i5.Future>.value([]), ) as _i5.Future>); @override _i5.Future getAssetFilePathByName(String? name) => (super.noSuchMethod( Invocation.method( #getAssetFilePathByName, [name], ), returnValue: _i5.Future.value(''), ) as _i5.Future); } /// A class which mocks [WebSettings]. /// /// See the documentation for Mockito's code generation for more information. class MockWebSettings extends _i1.Mock implements _i2.WebSettings { MockWebSettings() { _i1.throwOnMissingStub(this); } @override _i5.Future setDomStorageEnabled(bool? flag) => (super.noSuchMethod( Invocation.method( #setDomStorageEnabled, [flag], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setJavaScriptCanOpenWindowsAutomatically(bool? flag) => (super.noSuchMethod( Invocation.method( #setJavaScriptCanOpenWindowsAutomatically, [flag], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setSupportMultipleWindows(bool? support) => (super.noSuchMethod( Invocation.method( #setSupportMultipleWindows, [support], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setJavaScriptEnabled(bool? flag) => (super.noSuchMethod( Invocation.method( #setJavaScriptEnabled, [flag], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setUserAgentString(String? userAgentString) => (super.noSuchMethod( Invocation.method( #setUserAgentString, [userAgentString], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setMediaPlaybackRequiresUserGesture(bool? require) => (super.noSuchMethod( Invocation.method( #setMediaPlaybackRequiresUserGesture, [require], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setSupportZoom(bool? support) => (super.noSuchMethod( Invocation.method( #setSupportZoom, [support], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setLoadWithOverviewMode(bool? overview) => (super.noSuchMethod( Invocation.method( #setLoadWithOverviewMode, [overview], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setUseWideViewPort(bool? use) => (super.noSuchMethod( Invocation.method( #setUseWideViewPort, [use], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setDisplayZoomControls(bool? enabled) => (super.noSuchMethod( Invocation.method( #setDisplayZoomControls, [enabled], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setBuiltInZoomControls(bool? enabled) => (super.noSuchMethod( Invocation.method( #setBuiltInZoomControls, [enabled], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setAllowFileAccess(bool? enabled) => (super.noSuchMethod( Invocation.method( #setAllowFileAccess, [enabled], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i2.WebSettings copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWebSettings_0( this, Invocation.method( #copy, [], ), ), ) as _i2.WebSettings); } /// A class which mocks [WebStorage]. /// /// See the documentation for Mockito's code generation for more information. class MockWebStorage extends _i1.Mock implements _i2.WebStorage { MockWebStorage() { _i1.throwOnMissingStub(this); } @override _i5.Future deleteAllData() => (super.noSuchMethod( Invocation.method( #deleteAllData, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i2.WebStorage copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWebStorage_1( this, Invocation.method( #copy, [], ), ), ) as _i2.WebStorage); } /// A class which mocks [WebView]. /// /// See the documentation for Mockito's code generation for more information. class MockWebView extends _i1.Mock implements _i2.WebView { MockWebView() { _i1.throwOnMissingStub(this); } @override bool get useHybridComposition => (super.noSuchMethod( Invocation.getter(#useHybridComposition), returnValue: false, ) as bool); @override _i2.WebSettings get settings => (super.noSuchMethod( Invocation.getter(#settings), returnValue: _FakeWebSettings_0( this, Invocation.getter(#settings), ), ) as _i2.WebSettings); @override _i5.Future loadData({ required String? data, String? mimeType, String? encoding, }) => (super.noSuchMethod( Invocation.method( #loadData, [], { #data: data, #mimeType: mimeType, #encoding: encoding, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future loadDataWithBaseUrl({ String? baseUrl, required String? data, String? mimeType, String? encoding, String? historyUrl, }) => (super.noSuchMethod( Invocation.method( #loadDataWithBaseUrl, [], { #baseUrl: baseUrl, #data: data, #mimeType: mimeType, #encoding: encoding, #historyUrl: historyUrl, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future loadUrl( String? url, Map? headers, ) => (super.noSuchMethod( Invocation.method( #loadUrl, [ url, headers, ], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future postUrl( String? url, _i6.Uint8List? data, ) => (super.noSuchMethod( Invocation.method( #postUrl, [ url, data, ], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future getUrl() => (super.noSuchMethod( Invocation.method( #getUrl, [], ), returnValue: _i5.Future.value(), ) as _i5.Future); @override _i5.Future canGoBack() => (super.noSuchMethod( Invocation.method( #canGoBack, [], ), returnValue: _i5.Future.value(false), ) as _i5.Future); @override _i5.Future canGoForward() => (super.noSuchMethod( Invocation.method( #canGoForward, [], ), returnValue: _i5.Future.value(false), ) as _i5.Future); @override _i5.Future goBack() => (super.noSuchMethod( Invocation.method( #goBack, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future goForward() => (super.noSuchMethod( Invocation.method( #goForward, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future reload() => (super.noSuchMethod( Invocation.method( #reload, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future clearCache(bool? includeDiskFiles) => (super.noSuchMethod( Invocation.method( #clearCache, [includeDiskFiles], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future evaluateJavascript(String? javascriptString) => (super.noSuchMethod( Invocation.method( #evaluateJavascript, [javascriptString], ), returnValue: _i5.Future.value(), ) as _i5.Future); @override _i5.Future getTitle() => (super.noSuchMethod( Invocation.method( #getTitle, [], ), returnValue: _i5.Future.value(), ) as _i5.Future); @override _i5.Future scrollTo( int? x, int? y, ) => (super.noSuchMethod( Invocation.method( #scrollTo, [ x, y, ], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future scrollBy( int? x, int? y, ) => (super.noSuchMethod( Invocation.method( #scrollBy, [ x, y, ], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future getScrollX() => (super.noSuchMethod( Invocation.method( #getScrollX, [], ), returnValue: _i5.Future.value(0), ) as _i5.Future); @override _i5.Future getScrollY() => (super.noSuchMethod( Invocation.method( #getScrollY, [], ), returnValue: _i5.Future.value(0), ) as _i5.Future); @override _i5.Future<_i3.Offset> getScrollPosition() => (super.noSuchMethod( Invocation.method( #getScrollPosition, [], ), returnValue: _i5.Future<_i3.Offset>.value(_FakeOffset_2( this, Invocation.method( #getScrollPosition, [], ), )), ) as _i5.Future<_i3.Offset>); @override _i5.Future setWebViewClient(_i2.WebViewClient? webViewClient) => (super.noSuchMethod( Invocation.method( #setWebViewClient, [webViewClient], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future addJavaScriptChannel( _i2.JavaScriptChannel? javaScriptChannel) => (super.noSuchMethod( Invocation.method( #addJavaScriptChannel, [javaScriptChannel], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeJavaScriptChannel( _i2.JavaScriptChannel? javaScriptChannel) => (super.noSuchMethod( Invocation.method( #removeJavaScriptChannel, [javaScriptChannel], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setDownloadListener(_i2.DownloadListener? listener) => (super.noSuchMethod( Invocation.method( #setDownloadListener, [listener], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setWebChromeClient(_i2.WebChromeClient? client) => (super.noSuchMethod( Invocation.method( #setWebChromeClient, [client], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setBackgroundColor(_i3.Color? color) => (super.noSuchMethod( Invocation.method( #setBackgroundColor, [color], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i2.WebView copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWebView_3( this, Invocation.method( #copy, [], ), ), ) as _i2.WebView); } /// A class which mocks [WebResourceRequest]. /// /// See the documentation for Mockito's code generation for more information. class MockWebResourceRequest extends _i1.Mock implements _i2.WebResourceRequest { MockWebResourceRequest() { _i1.throwOnMissingStub(this); } @override String get url => (super.noSuchMethod( Invocation.getter(#url), returnValue: '', ) as String); @override bool get isForMainFrame => (super.noSuchMethod( Invocation.getter(#isForMainFrame), returnValue: false, ) as bool); @override bool get hasGesture => (super.noSuchMethod( Invocation.getter(#hasGesture), returnValue: false, ) as bool); @override String get method => (super.noSuchMethod( Invocation.getter(#method), returnValue: '', ) as String); @override Map get requestHeaders => (super.noSuchMethod( Invocation.getter(#requestHeaders), returnValue: {}, ) as Map); } /// A class which mocks [DownloadListener]. /// /// See the documentation for Mockito's code generation for more information. class MockDownloadListener extends _i1.Mock implements _i2.DownloadListener { MockDownloadListener() { _i1.throwOnMissingStub(this); } @override void Function( String, String, String, String, int, ) get onDownloadStart => (super.noSuchMethod( Invocation.getter(#onDownloadStart), returnValue: ( String url, String userAgent, String contentDisposition, String mimetype, int contentLength, ) {}, ) as void Function( String, String, String, String, int, )); @override _i2.DownloadListener copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeDownloadListener_4( this, Invocation.method( #copy, [], ), ), ) as _i2.DownloadListener); } /// A class which mocks [WebViewAndroidJavaScriptChannel]. /// /// See the documentation for Mockito's code generation for more information. class MockWebViewAndroidJavaScriptChannel extends _i1.Mock implements _i7.WebViewAndroidJavaScriptChannel { MockWebViewAndroidJavaScriptChannel() { _i1.throwOnMissingStub(this); } @override _i4.JavascriptChannelRegistry get javascriptChannelRegistry => (super.noSuchMethod( Invocation.getter(#javascriptChannelRegistry), returnValue: _FakeJavascriptChannelRegistry_5( this, Invocation.getter(#javascriptChannelRegistry), ), ) as _i4.JavascriptChannelRegistry); @override String get channelName => (super.noSuchMethod( Invocation.getter(#channelName), returnValue: '', ) as String); @override void Function(String) get postMessage => (super.noSuchMethod( Invocation.getter(#postMessage), returnValue: (String message) {}, ) as void Function(String)); @override _i2.JavaScriptChannel copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeJavaScriptChannel_6( this, Invocation.method( #copy, [], ), ), ) as _i2.JavaScriptChannel); } /// A class which mocks [WebChromeClient]. /// /// See the documentation for Mockito's code generation for more information. class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { MockWebChromeClient() { _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( #copy, [], ), returnValue: _FakeWebChromeClient_7( this, Invocation.method( #copy, [], ), ), ) as _i2.WebChromeClient); } /// A class which mocks [WebViewClient]. /// /// See the documentation for Mockito's code generation for more information. class MockWebViewClient extends _i1.Mock implements _i2.WebViewClient { MockWebViewClient() { _i1.throwOnMissingStub(this); } @override _i5.Future setSynchronousReturnValueForShouldOverrideUrlLoading( bool? value) => (super.noSuchMethod( Invocation.method( #setSynchronousReturnValueForShouldOverrideUrlLoading, [value], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i2.WebViewClient copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWebViewClient_8( this, Invocation.method( #copy, [], ), ), ) as _i2.WebViewClient); } /// A class which mocks [JavascriptChannelRegistry]. /// /// See the documentation for Mockito's code generation for more information. class MockJavascriptChannelRegistry extends _i1.Mock implements _i4.JavascriptChannelRegistry { MockJavascriptChannelRegistry() { _i1.throwOnMissingStub(this); } @override Map get channels => (super.noSuchMethod( Invocation.getter(#channels), returnValue: {}, ) as Map); @override void onJavascriptChannelMessage( String? channel, String? message, ) => super.noSuchMethod( Invocation.method( #onJavascriptChannelMessage, [ channel, message, ], ), returnValueForMissingStub: null, ); @override void updateJavascriptChannelsFromSet(Set<_i4.JavascriptChannel>? channels) => super.noSuchMethod( Invocation.method( #updateJavascriptChannelsFromSet, [channels], ), returnValueForMissingStub: null, ); } /// A class which mocks [WebViewPlatformCallbacksHandler]. /// /// See the documentation for Mockito's code generation for more information. class MockWebViewPlatformCallbacksHandler extends _i1.Mock implements _i4.WebViewPlatformCallbacksHandler { MockWebViewPlatformCallbacksHandler() { _i1.throwOnMissingStub(this); } @override _i5.FutureOr onNavigationRequest({ required String? url, required bool? isForMainFrame, }) => (super.noSuchMethod( Invocation.method( #onNavigationRequest, [], { #url: url, #isForMainFrame: isForMainFrame, }, ), returnValue: _i5.Future.value(false), ) as _i5.FutureOr); @override void onPageStarted(String? url) => super.noSuchMethod( Invocation.method( #onPageStarted, [url], ), returnValueForMissingStub: null, ); @override void onPageFinished(String? url) => super.noSuchMethod( Invocation.method( #onPageFinished, [url], ), returnValueForMissingStub: null, ); @override void onProgress(int? progress) => super.noSuchMethod( Invocation.method( #onProgress, [progress], ), returnValueForMissingStub: null, ); @override void onWebResourceError(_i4.WebResourceError? error) => super.noSuchMethod( Invocation.method( #onWebResourceError, [error], ), returnValueForMissingStub: null, ); } /// A class which mocks [WebViewProxy]. /// /// See the documentation for Mockito's code generation for more information. class MockWebViewProxy extends _i1.Mock implements _i7.WebViewProxy { MockWebViewProxy() { _i1.throwOnMissingStub(this); } @override _i2.WebView createWebView({required bool? useHybridComposition}) => (super.noSuchMethod( Invocation.method( #createWebView, [], {#useHybridComposition: useHybridComposition}, ), returnValue: _FakeWebView_3( this, Invocation.method( #createWebView, [], {#useHybridComposition: useHybridComposition}, ), ), ) as _i2.WebView); @override _i2.WebViewClient createWebViewClient({ void Function( _i2.WebView, String, )? onPageStarted, void Function( _i2.WebView, String, )? onPageFinished, void Function( _i2.WebView, _i2.WebResourceRequest, _i2.WebResourceError, )? onReceivedRequestError, void Function( _i2.WebView, int, String, String, )? onReceivedError, void Function( _i2.WebView, _i2.WebResourceRequest, )? requestLoading, void Function( _i2.WebView, String, )? urlLoading, }) => (super.noSuchMethod( Invocation.method( #createWebViewClient, [], { #onPageStarted: onPageStarted, #onPageFinished: onPageFinished, #onReceivedRequestError: onReceivedRequestError, #onReceivedError: onReceivedError, #requestLoading: requestLoading, #urlLoading: urlLoading, }, ), returnValue: _FakeWebViewClient_8( this, Invocation.method( #createWebViewClient, [], { #onPageStarted: onPageStarted, #onPageFinished: onPageFinished, #onReceivedRequestError: onReceivedRequestError, #onReceivedError: onReceivedError, #requestLoading: requestLoading, #urlLoading: urlLoading, }, ), ), ) as _i2.WebViewClient); @override _i5.Future setWebContentsDebuggingEnabled(bool? enabled) => (super.noSuchMethod( Invocation.method( #setWebContentsDebuggingEnabled, [enabled], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } ================================================ FILE: packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.dart ================================================ // Copyright 2013 The Flutter 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:webview_flutter_android/src/android_webview.g.dart'; /// Handles methods calls to the native Java Object class. /// /// Also handles calls to remove the reference to an instance with `dispose`. /// /// See https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html. abstract class TestJavaObjectHostApi { static const MessageCodec codec = StandardMessageCodec(); void dispose(int identifier); static void setup(TestJavaObjectHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.JavaObjectHostApi.dispose', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.JavaObjectHostApi.dispose 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.JavaObjectHostApi.dispose was null, expected non-null int.'); api.dispose(arg_identifier!); return []; }); } } } } class _TestWebViewHostApiCodec extends StandardMessageCodec { const _TestWebViewHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is WebViewPoint) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return WebViewPoint.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } 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}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.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.WebViewHostApi.create was null, expected non-null int.'); final bool? arg_useHybridComposition = (args[1] as bool?); assert(arg_useHybridComposition != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.create was null, expected non-null bool.'); api.create(arg_instanceId!, arg_useHybridComposition!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.loadData', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.loadData 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.WebViewHostApi.loadData was null, expected non-null int.'); final String? arg_data = (args[1] as String?); assert(arg_data != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.loadData was null, expected non-null String.'); 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 []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.loadDataWithBaseUrl', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.loadDataWithBaseUrl 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.WebViewHostApi.loadDataWithBaseUrl was null, expected non-null int.'); final String? arg_baseUrl = (args[1] as String?); final String? arg_data = (args[2] as String?); assert(arg_data != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.loadDataWithBaseUrl was null, expected non-null String.'); final String? arg_mimeType = (args[3] as String?); final String? arg_encoding = (args[4] as String?); final String? arg_historyUrl = (args[5] as String?); api.loadDataWithBaseUrl(arg_instanceId!, arg_baseUrl, arg_data!, arg_mimeType, arg_encoding, arg_historyUrl); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.loadUrl', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.loadUrl 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.WebViewHostApi.loadUrl was null, expected non-null int.'); final String? arg_url = (args[1] as String?); assert(arg_url != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.loadUrl was null, expected non-null String.'); final Map? arg_headers = (args[2] as Map?)?.cast(); 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 []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.postUrl', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.postUrl 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.WebViewHostApi.postUrl was null, expected non-null int.'); final String? arg_url = (args[1] as String?); assert(arg_url != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.postUrl was null, expected non-null String.'); final Uint8List? arg_data = (args[2] as Uint8List?); 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 []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getUrl', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getUrl 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.WebViewHostApi.getUrl was null, expected non-null int.'); final String? output = api.getUrl(arg_instanceId!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.canGoBack', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.canGoBack 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.WebViewHostApi.canGoBack was null, expected non-null int.'); final bool output = api.canGoBack(arg_instanceId!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.canGoForward', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.canGoForward 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.WebViewHostApi.canGoForward was null, expected non-null int.'); final bool output = api.canGoForward(arg_instanceId!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.goBack', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.goBack 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.WebViewHostApi.goBack was null, expected non-null int.'); api.goBack(arg_instanceId!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.goForward', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.goForward 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.WebViewHostApi.goForward was null, expected non-null int.'); api.goForward(arg_instanceId!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.reload', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.reload 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.WebViewHostApi.reload was null, expected non-null int.'); api.reload(arg_instanceId!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.clearCache', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.clearCache 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.WebViewHostApi.clearCache was null, expected non-null int.'); final bool? arg_includeDiskFiles = (args[1] as bool?); assert(arg_includeDiskFiles != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.clearCache was null, expected non-null bool.'); api.clearCache(arg_instanceId!, arg_includeDiskFiles!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.evaluateJavascript', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.evaluateJavascript 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.WebViewHostApi.evaluateJavascript was null, expected non-null int.'); final String? arg_javascriptString = (args[1] as String?); assert(arg_javascriptString != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.evaluateJavascript was null, expected non-null String.'); final String? output = await api.evaluateJavascript( arg_instanceId!, arg_javascriptString!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getTitle', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getTitle 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.WebViewHostApi.getTitle was null, expected non-null int.'); final String? output = api.getTitle(arg_instanceId!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.scrollTo', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.scrollTo 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.WebViewHostApi.scrollTo was null, expected non-null int.'); final int? arg_x = (args[1] as int?); assert(arg_x != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.scrollTo was null, expected non-null int.'); final int? arg_y = (args[2] as int?); 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 []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.scrollBy', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.scrollBy 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.WebViewHostApi.scrollBy was null, expected non-null int.'); final int? arg_x = (args[1] as int?); assert(arg_x != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.scrollBy was null, expected non-null int.'); final int? arg_y = (args[2] as int?); 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 []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getScrollX', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getScrollX 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.WebViewHostApi.getScrollX was null, expected non-null int.'); final int output = api.getScrollX(arg_instanceId!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getScrollY', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getScrollY 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.WebViewHostApi.getScrollY was null, expected non-null int.'); final int output = api.getScrollY(arg_instanceId!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.getScrollPosition', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.getScrollPosition 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.WebViewHostApi.getScrollPosition was null, expected non-null int.'); final WebViewPoint output = api.getScrollPosition(arg_instanceId!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.setWebContentsDebuggingEnabled', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebContentsDebuggingEnabled was null.'); final List args = (message as List?)!; final bool? arg_enabled = (args[0] as bool?); assert(arg_enabled != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebContentsDebuggingEnabled was null, expected non-null bool.'); api.setWebContentsDebuggingEnabled(arg_enabled!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.setWebViewClient', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebViewClient 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.WebViewHostApi.setWebViewClient was null, expected non-null int.'); final int? arg_webViewClientInstanceId = (args[1] as int?); assert(arg_webViewClientInstanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebViewClient was null, expected non-null int.'); api.setWebViewClient(arg_instanceId!, arg_webViewClientInstanceId!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.addJavaScriptChannel', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.addJavaScriptChannel 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.WebViewHostApi.addJavaScriptChannel was null, expected non-null int.'); final int? arg_javaScriptChannelInstanceId = (args[1] as int?); assert(arg_javaScriptChannelInstanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.addJavaScriptChannel was null, expected non-null int.'); api.addJavaScriptChannel( arg_instanceId!, arg_javaScriptChannelInstanceId!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.removeJavaScriptChannel', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.removeJavaScriptChannel 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.WebViewHostApi.removeJavaScriptChannel was null, expected non-null int.'); final int? arg_javaScriptChannelInstanceId = (args[1] as int?); assert(arg_javaScriptChannelInstanceId != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.removeJavaScriptChannel was null, expected non-null int.'); api.removeJavaScriptChannel( arg_instanceId!, arg_javaScriptChannelInstanceId!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.setDownloadListener', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setDownloadListener 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.WebViewHostApi.setDownloadListener was null, expected non-null int.'); final int? arg_listenerInstanceId = (args[1] as int?); api.setDownloadListener(arg_instanceId!, arg_listenerInstanceId); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.setWebChromeClient', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setWebChromeClient 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.WebViewHostApi.setWebChromeClient was null, expected non-null int.'); final int? arg_clientInstanceId = (args[1] as int?); api.setWebChromeClient(arg_instanceId!, arg_clientInstanceId); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewHostApi.setBackgroundColor', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setBackgroundColor 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.WebViewHostApi.setBackgroundColor was null, expected non-null int.'); final int? arg_color = (args[1] as int?); assert(arg_color != null, 'Argument for dev.flutter.pigeon.WebViewHostApi.setBackgroundColor was null, expected non-null int.'); api.setBackgroundColor(arg_instanceId!, arg_color!); return []; }); } } } } 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}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.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.WebSettingsHostApi.create was null, expected non-null int.'); final int? arg_webViewInstanceId = (args[1] as int?); assert(arg_webViewInstanceId != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.create was null, expected non-null int.'); api.create(arg_instanceId!, arg_webViewInstanceId!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setDomStorageEnabled', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setDomStorageEnabled 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.WebSettingsHostApi.setDomStorageEnabled was null, expected non-null int.'); final bool? arg_flag = (args[1] as bool?); assert(arg_flag != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setDomStorageEnabled was null, expected non-null bool.'); api.setDomStorageEnabled(arg_instanceId!, arg_flag!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptCanOpenWindowsAutomatically', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptCanOpenWindowsAutomatically 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.WebSettingsHostApi.setJavaScriptCanOpenWindowsAutomatically was null, expected non-null int.'); final bool? arg_flag = (args[1] as bool?); assert(arg_flag != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptCanOpenWindowsAutomatically was null, expected non-null bool.'); api.setJavaScriptCanOpenWindowsAutomatically( arg_instanceId!, arg_flag!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setSupportMultipleWindows', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setSupportMultipleWindows 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.WebSettingsHostApi.setSupportMultipleWindows was null, expected non-null int.'); final bool? arg_support = (args[1] as bool?); assert(arg_support != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setSupportMultipleWindows was null, expected non-null bool.'); api.setSupportMultipleWindows(arg_instanceId!, arg_support!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptEnabled', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptEnabled 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.WebSettingsHostApi.setJavaScriptEnabled was null, expected non-null int.'); final bool? arg_flag = (args[1] as bool?); assert(arg_flag != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setJavaScriptEnabled was null, expected non-null bool.'); api.setJavaScriptEnabled(arg_instanceId!, arg_flag!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setUserAgentString', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setUserAgentString 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.WebSettingsHostApi.setUserAgentString was null, expected non-null int.'); final String? arg_userAgentString = (args[1] as String?); api.setUserAgentString(arg_instanceId!, arg_userAgentString); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setMediaPlaybackRequiresUserGesture', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setMediaPlaybackRequiresUserGesture 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.WebSettingsHostApi.setMediaPlaybackRequiresUserGesture was null, expected non-null int.'); final bool? arg_require = (args[1] as bool?); assert(arg_require != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setMediaPlaybackRequiresUserGesture was null, expected non-null bool.'); api.setMediaPlaybackRequiresUserGesture( arg_instanceId!, arg_require!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setSupportZoom', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setSupportZoom 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.WebSettingsHostApi.setSupportZoom was null, expected non-null int.'); final bool? arg_support = (args[1] as bool?); assert(arg_support != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setSupportZoom was null, expected non-null bool.'); api.setSupportZoom(arg_instanceId!, arg_support!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setLoadWithOverviewMode', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setLoadWithOverviewMode 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.WebSettingsHostApi.setLoadWithOverviewMode was null, expected non-null int.'); final bool? arg_overview = (args[1] as bool?); assert(arg_overview != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setLoadWithOverviewMode was null, expected non-null bool.'); api.setLoadWithOverviewMode(arg_instanceId!, arg_overview!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setUseWideViewPort', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setUseWideViewPort 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.WebSettingsHostApi.setUseWideViewPort was null, expected non-null int.'); final bool? arg_use = (args[1] as bool?); assert(arg_use != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setUseWideViewPort was null, expected non-null bool.'); api.setUseWideViewPort(arg_instanceId!, arg_use!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setDisplayZoomControls', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setDisplayZoomControls 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.WebSettingsHostApi.setDisplayZoomControls was null, expected non-null int.'); final bool? arg_enabled = (args[1] as bool?); assert(arg_enabled != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setDisplayZoomControls was null, expected non-null bool.'); api.setDisplayZoomControls(arg_instanceId!, arg_enabled!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setBuiltInZoomControls', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setBuiltInZoomControls 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.WebSettingsHostApi.setBuiltInZoomControls was null, expected non-null int.'); final bool? arg_enabled = (args[1] as bool?); assert(arg_enabled != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setBuiltInZoomControls was null, expected non-null bool.'); api.setBuiltInZoomControls(arg_instanceId!, arg_enabled!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess 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.WebSettingsHostApi.setAllowFileAccess was null, expected non-null int.'); final bool? arg_enabled = (args[1] as bool?); assert(arg_enabled != null, 'Argument for dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess was null, expected non-null bool.'); api.setAllowFileAccess(arg_instanceId!, arg_enabled!); return []; }); } } } } abstract class TestJavaScriptChannelHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId, String channelName); static void setup(TestJavaScriptChannelHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.JavaScriptChannelHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.JavaScriptChannelHostApi.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.JavaScriptChannelHostApi.create was null, expected non-null int.'); final String? arg_channelName = (args[1] as String?); assert(arg_channelName != null, 'Argument for dev.flutter.pigeon.JavaScriptChannelHostApi.create was null, expected non-null String.'); api.create(arg_instanceId!, arg_channelName!); return []; }); } } } } 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}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewClientHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewClientHostApi.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.WebViewClientHostApi.create was null, expected non-null int.'); api.create(arg_instanceId!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewClientHostApi.setSynchronousReturnValueForShouldOverrideUrlLoading', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebViewClientHostApi.setSynchronousReturnValueForShouldOverrideUrlLoading 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.WebViewClientHostApi.setSynchronousReturnValueForShouldOverrideUrlLoading was null, expected non-null int.'); final bool? arg_value = (args[1] as bool?); assert(arg_value != null, 'Argument for dev.flutter.pigeon.WebViewClientHostApi.setSynchronousReturnValueForShouldOverrideUrlLoading was null, expected non-null bool.'); api.setSynchronousReturnValueForShouldOverrideUrlLoading( arg_instanceId!, arg_value!); return []; }); } } } } abstract class TestDownloadListenerHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId); static void setup(TestDownloadListenerHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.DownloadListenerHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.DownloadListenerHostApi.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.DownloadListenerHostApi.create was null, expected non-null int.'); api.create(arg_instanceId!); return []; }); } } } } 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}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebChromeClientHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebChromeClientHostApi.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.WebChromeClientHostApi.create was null, expected non-null int.'); api.create(arg_instanceId!); 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 []; }); } } } } abstract class TestAssetManagerHostApi { static const MessageCodec codec = StandardMessageCodec(); List list(String path); String getAssetFilePathByName(String name); static void setup(TestAssetManagerHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.FlutterAssetManagerHostApi.list', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.FlutterAssetManagerHostApi.list was null.'); final List args = (message as List?)!; final String? arg_path = (args[0] as String?); 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 [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.FlutterAssetManagerHostApi.getAssetFilePathByName was null.'); final List args = (message as List?)!; final String? arg_name = (args[0] as String?); 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 [output]; }); } } } } abstract class TestWebStorageHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int instanceId); void deleteAllData(int instanceId); static void setup(TestWebStorageHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebStorageHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebStorageHostApi.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.WebStorageHostApi.create was null, expected non-null int.'); api.create(arg_instanceId!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebStorageHostApi.deleteAllData', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WebStorageHostApi.deleteAllData 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.WebStorageHostApi.deleteAllData was null, expected non-null int.'); api.deleteAllData(arg_instanceId!); return []; }); } } } } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/AUTHORS ================================================ # 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> Maurits van Beusekom ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/CHANGELOG.md ================================================ ## 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) for more details. * **Breaking Change**: Removes MethodChannel implementation of interface. All platform implementations will now need to create their own by implementing `WebViewPlatform`. ## 1.9.5 * Updates code for `no_leading_underscores_for_local_identifiers` lint. ## 1.9.4 * Updates imports for `prefer_relative_imports`. ## 1.9.3 * Updates minimum Flutter version to 2.10. * Removes `BuildParams` from v4 interface and adds `layoutDirection` to the creation params. ## 1.9.2 * Fixes avoid_redundant_argument_values lint warnings and minor typos. * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/106316). * Adds missing build params for v4 WebViewWidget interface. ## 1.9.1 * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/104231). ## 1.9.0 * Adds the first iteration of the v4 webview_flutter interface implementation. * Removes unnecessary imports. ## 1.8.2 * Migrates from `ui.hash*` to `Object.hash*`. * Updates minimum Flutter version to 2.5.0. ## 1.8.1 * Update to use the `verify` method introduced in platform_plugin_interface 2.1.0. ## 1.8.0 * Adds the `loadFlutterAsset` method to the platform interface. ## 1.7.0 * Add an option to set the background color of the webview. ## 1.6.1 * Revert deprecation of `clearCookies` in WebViewPlatform for later deprecation. ## 1.6.0 * Adds platform interface for cookie manager. * Deprecates `clearCookies` in WebViewPlatform in favour of `CookieManager#clearCookies`. * Expanded `CreationParams` to include cookies to be set at webview creation. ## 1.5.2 * Mirgrates from analysis_options_legacy.yaml to the more strict analysis_options.yaml. ## 1.5.1 * Reverts the addition of `onUrlChanged`, which was unintentionally a breaking change. ## 1.5.0 * Added `onUrlChanged` callback to platform callback handler. ## 1.4.0 * Added `loadFile` and `loadHtml` interface methods. ## 1.3.0 * Added `loadRequest` method to platform interface. ## 1.2.0 * Added `runJavascript` and `runJavascriptReturningResult` interface methods to supersede `evaluateJavascript`. ## 1.1.0 * Add `zoomEnabled` functionality to `WebSettings`. ## 1.0.0 * Extracted platform interface from `webview_flutter`. ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/LICENSE ================================================ 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. ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/README.md ================================================ # webview_flutter_platform_interface A common platform interface for the [`webview_flutter`](https://pub.dev/packages/webview_flutter) plugin. This interface allows platform-specific implementations of the `webview_flutter` plugin, as well as the plugin itself, to ensure they are supporting the same interface. # Usage To implement a new platform-specific implementation of `webview_flutter`, extend [`WebviewPlatform`](lib/src/webview_platform.dart) with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `WebviewPlatform` by calling `WebviewPlatform.instance = MyPlatformWebview()`. # Note on breaking changes Strongly prefer non-breaking changes (such as adding a method to the interface) over breaking changes for this package. See https://flutter.dev/go/platform-interface-breaking-changes for a discussion on why a less-clean interface is preferable to a breaking change. ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/legacy/platform_interface/javascript_channel_registry.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import '../types/javascript_channel.dart'; import '../types/javascript_message.dart'; /// Utility class for managing named JavaScript channels and forwarding incoming /// messages on the correct channel. class JavascriptChannelRegistry { /// Constructs a [JavascriptChannelRegistry] initializing it with the given /// set of [JavascriptChannel]s. JavascriptChannelRegistry(Set? channels) { updateJavascriptChannelsFromSet(channels); } /// Maps a channel name to a channel. final Map channels = {}; /// Invoked when a JavaScript channel message is received. void onJavascriptChannelMessage(String channel, String message) { final JavascriptChannel? javascriptChannel = channels[channel]; if (javascriptChannel == null) { throw ArgumentError('No channel registered with name $channel.'); } javascriptChannel.onMessageReceived(JavascriptMessage(message)); } /// Updates the set of [JavascriptChannel]s with the new set. void updateJavascriptChannelsFromSet(Set? channels) { this.channels.clear(); if (channels == null) { return; } for (final JavascriptChannel channel in channels) { this.channels[channel.name] = channel; } } } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/legacy/platform_interface/platform_interface.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'javascript_channel_registry.dart'; export 'webview_cookie_manager.dart'; export 'webview_platform.dart'; export 'webview_platform_callbacks_handler.dart'; export 'webview_platform_controller.dart'; ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/legacy/platform_interface/webview_cookie_manager.dart ================================================ // Copyright 2013 The Flutter 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:plugin_platform_interface/plugin_platform_interface.dart'; import '../types/webview_cookie.dart'; /// Interface for a platform implementation of a cookie manager. /// /// Platform implementations should extend this class rather than implement it as `webview_flutter` /// does not consider newly added methods to be breaking changes. Extending this class /// (using `extends`) ensures that the subclass will get the default implementation, while /// platform implementations that `implements` this interface will be broken by newly added /// [WebViewCookieManagerPlatform] methods. abstract class WebViewCookieManagerPlatform extends PlatformInterface { /// Constructs a WebViewCookieManagerPlatform. WebViewCookieManagerPlatform() : super(token: _token); static final Object _token = Object(); static WebViewCookieManagerPlatform? _instance; /// The instance of [WebViewCookieManagerPlatform] to use. static WebViewCookieManagerPlatform? get instance => _instance; /// Platform-specific plugins should set this with their own platform-specific /// class that extends [WebViewCookieManagerPlatform] when they register themselves. static set instance(WebViewCookieManagerPlatform? instance) { if (instance == null) { throw AssertionError( 'Platform interfaces can only be set to a non-null instance'); } PlatformInterface.verify(instance, _token); _instance = instance; } /// Clears all cookies for all [WebView] instances. /// /// Returns true if cookies were present before clearing, else false. Future clearCookies() { throw UnimplementedError( 'clearCookies is not implemented on the current platform'); } /// Sets a cookie for all [WebView] instances. Future setCookie(WebViewCookie cookie) { throw UnimplementedError( 'setCookie is not implemented on the current platform'); } } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/legacy/platform_interface/webview_platform.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import '../platform_interface/javascript_channel_registry.dart'; import '../types/types.dart'; import 'webview_platform_callbacks_handler.dart'; import 'webview_platform_controller.dart'; /// Signature for callbacks reporting that a [WebViewPlatformController] was created. /// /// See also the `onWebViewPlatformCreated` argument for [WebViewPlatform.build]. typedef WebViewPlatformCreatedCallback = void Function( WebViewPlatformController? webViewPlatformController); /// Interface for a platform implementation of a WebView. /// /// [WebView.platform] controls the builder that is used by [WebView]. /// [AndroidWebViewPlatform] and [CupertinoWebViewPlatform] are the default implementations /// for Android and iOS respectively. abstract class WebViewPlatform { /// Builds a new WebView. /// /// Returns a Widget tree that embeds the created webview. /// /// `creationParams` are the initial parameters used to setup the webview. /// /// `webViewPlatformHandler` will be used for handling callbacks that are made by the created /// [WebViewPlatformController]. /// /// `onWebViewPlatformCreated` will be invoked after the platform specific [WebViewPlatformController] /// implementation is created with the [WebViewPlatformController] instance as a parameter. /// /// `gestureRecognizers` specifies which gestures should be consumed by the web view. /// It is possible for other gesture recognizers to be competing with the web view on pointer /// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle /// vertical drags. The web view will claim gestures that are recognized by any of the /// recognizers on this list. /// When `gestureRecognizers` is empty or null, the web view will only handle pointer events for gestures that /// were not claimed by any other gesture recognizer. /// /// `webViewPlatformHandler` must not be null. Widget build({ required BuildContext context, // TODO(amirh): convert this to be the actual parameters. // I'm starting without it as the PR is starting to become pretty big. // I'll followup with the conversion PR. required CreationParams creationParams, required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, required JavascriptChannelRegistry javascriptChannelRegistry, WebViewPlatformCreatedCallback? onWebViewPlatformCreated, Set>? gestureRecognizers, }); /// Clears all cookies for all [WebView] instances. /// /// Returns true if cookies were present before clearing, else false. /// Soon to be deprecated. 'Use `WebViewCookieManagerPlatform.clearCookies` instead. Future clearCookies() { throw UnimplementedError( 'WebView clearCookies is not implemented on the current platform'); } } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/legacy/platform_interface/webview_platform_callbacks_handler.dart ================================================ // Copyright 2013 The Flutter 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 '../types/types.dart'; /// Interface for callbacks made by [WebViewPlatformController]. /// /// The webview plugin implements this class, and passes an instance to the [WebViewPlatformController]. /// [WebViewPlatformController] is notifying this handler on events that happened on the platform's webview. abstract class WebViewPlatformCallbacksHandler { /// Invoked by [WebViewPlatformController] when a navigation request is pending. /// /// If true is returned the navigation is allowed, otherwise it is blocked. FutureOr onNavigationRequest( {required String url, required bool isForMainFrame}); /// Invoked by [WebViewPlatformController] when a page has started loading. void onPageStarted(String url); /// Invoked by [WebViewPlatformController] when a page has finished loading. void onPageFinished(String url); /// Invoked by [WebViewPlatformController] when a page is loading. /// /// Only works when [WebSettings.hasProgressTracking] is set to `true`. void onProgress(int progress); /// Report web resource loading error to the host application. void onWebResourceError(WebResourceError error); } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/legacy/platform_interface/webview_platform_controller.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import '../types/types.dart'; import 'webview_platform_callbacks_handler.dart'; /// Interface for talking to the webview's platform implementation. /// /// An instance implementing this interface is passed to the `onWebViewPlatformCreated` callback that is /// passed to [WebViewPlatformBuilder#onWebViewPlatformCreated]. /// /// Platform implementations that live in a separate package should extend this class rather than /// implement it as webview_flutter does not consider newly added methods to be breaking changes. /// Extending this class (using `extends`) ensures that the subclass will get the default /// implementation, while platform implementations that `implements` this interface will be broken /// by newly added [WebViewPlatformController] methods. abstract class WebViewPlatformController { /// Creates a new WebViewPlatform. /// /// Callbacks made by the WebView will be delegated to `handler`. /// /// The `handler` parameter must not be null. // TODO(mvanbeusekom): Remove unused constructor parameter with the next // breaking change (see issue https://github.com/flutter/flutter/issues/94292). // ignore: avoid_unused_constructor_parameters WebViewPlatformController(WebViewPlatformCallbacksHandler handler); /// Loads the file located on the specified [absoluteFilePath]. /// /// The [absoluteFilePath] parameter should contain the absolute path to the /// file as it is stored on the device. For example: /// `/Users/username/Documents/www/index.html`. /// /// Throws an ArgumentError if the [absoluteFilePath] does not exist. Future loadFile( String absoluteFilePath, ) { throw UnimplementedError( 'WebView loadFile is not implemented on the current platform'); } /// Loads the Flutter asset specified in the pubspec.yaml file. /// /// Throws an ArgumentError if [key] is not part of the specified assets /// in the pubspec.yaml file. Future loadFlutterAsset( String key, ) { throw UnimplementedError( 'WebView loadFlutterAsset is not implemented on the current platform'); } /// Loads the supplied HTML string. /// /// The [baseUrl] parameter is used when resolving relative URLs within the /// HTML string. Future loadHtmlString( String html, { String? baseUrl, }) { throw UnimplementedError( 'WebView loadHtmlString is not implemented on the current platform'); } /// Loads the specified URL. /// /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will /// be added as key value pairs of HTTP headers for the request. /// /// `url` must not be null. /// /// Throws an ArgumentError if `url` is not a valid URL string. Future loadUrl( String url, Map? headers, ) { throw UnimplementedError( 'WebView loadUrl is not implemented on the current platform'); } /// Makes a specific HTTP request ands loads the response in the webview. /// /// [WebViewRequest.method] must be one of the supported HTTP methods /// in [WebViewRequestMethod]. /// /// If [WebViewRequest.headers] is not empty, its key-value pairs will be /// added as the headers for the request. /// /// If [WebViewRequest.body] is not null, it will be added as the body /// for the request. /// /// Throws an ArgumentError if [WebViewRequest.uri] has empty scheme. Future loadRequest( WebViewRequest request, ) { throw UnimplementedError( 'WebView loadRequest is not implemented on the current platform'); } /// Updates the webview settings. /// /// Any non null field in `settings` will be set as the new setting value. /// All null fields in `settings` are ignored. Future updateSettings(WebSettings setting) { throw UnimplementedError( 'WebView updateSettings is not implemented on the current platform'); } /// Accessor to the current URL that the WebView is displaying. /// /// If no URL was ever loaded, returns `null`. Future currentUrl() { throw UnimplementedError( 'WebView currentUrl is not implemented on the current platform'); } /// Checks whether there's a back history item. Future canGoBack() { throw UnimplementedError( 'WebView canGoBack is not implemented on the current platform'); } /// Checks whether there's a forward history item. Future canGoForward() { throw UnimplementedError( 'WebView canGoForward is not implemented on the current platform'); } /// Goes back in the history of this WebView. /// /// If there is no back history item this is a no-op. Future goBack() { throw UnimplementedError( 'WebView goBack is not implemented on the current platform'); } /// Goes forward in the history of this WebView. /// /// If there is no forward history item this is a no-op. Future goForward() { throw UnimplementedError( 'WebView goForward is not implemented on the current platform'); } /// Reloads the current URL. Future reload() { throw UnimplementedError( 'WebView reload is not implemented on the current platform'); } /// Clears all caches used by the [WebView]. /// /// The following caches are cleared: /// 1. Browser HTTP Cache. /// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches. /// These are not yet supported in iOS WkWebView. Service workers tend to use this cache. /// 3. Application cache. /// 4. Local Storage. Future clearCache() { throw UnimplementedError( 'WebView clearCache is not implemented on the current platform'); } /// Evaluates a JavaScript expression in the context of the current page. /// /// The Future completes with an error if a JavaScript error occurred, or if the type of the /// evaluated expression is not supported (e.g on iOS not all non-primitive types can be evaluated). Future evaluateJavascript(String javascript) { throw UnimplementedError( 'WebView evaluateJavascript is not implemented on the current platform'); } /// Runs the given JavaScript in the context of the current page. /// /// The Future completes with an error if a JavaScript error occurred. Future runJavascript(String javascript) { throw UnimplementedError( 'WebView runJavascript is not implemented on the current platform'); } /// Runs the given JavaScript in the context of the current page, and returns the result. /// /// The Future completes with an error if a JavaScript error occurred, or if the /// type the given expression evaluates to is unsupported. Unsupported values include /// certain non-primitive types on iOS, as well as `undefined` or `null` on iOS 14+. Future runJavascriptReturningResult(String javascript) { throw UnimplementedError( 'WebView runJavascriptReturningResult is not implemented on the current platform'); } /// Adds new JavaScript channels to the set of enabled channels. /// /// For each value in this list the platform's webview should make sure that a corresponding /// property with a postMessage method is set on `window`. For example for a JavaScript channel /// named `Foo` it should be possible for JavaScript code executing in the webview to do /// /// ```javascript /// Foo.postMessage('hello'); /// ``` /// /// See also: [CreationParams.javascriptChannelNames]. Future addJavascriptChannels(Set javascriptChannelNames) { throw UnimplementedError( 'WebView addJavascriptChannels is not implemented on the current platform'); } /// Removes JavaScript channel names from the set of enabled channels. /// /// This disables channels that were previously enabled by [addJavascriptChannels] or through /// [CreationParams.javascriptChannelNames]. Future removeJavascriptChannels(Set javascriptChannelNames) { throw UnimplementedError( 'WebView removeJavascriptChannels is not implemented on the current platform'); } /// Returns the title of the currently loaded page. Future getTitle() { throw UnimplementedError( 'WebView getTitle is not implemented on the current platform'); } /// Set the scrolled position of this view. /// /// The parameters `x` and `y` specify the position to scroll to in WebView pixels. Future scrollTo(int x, int y) { throw UnimplementedError( 'WebView scrollTo is not implemented on the current platform'); } /// Move the scrolled position of this view. /// /// The parameters `x` and `y` specify the amount of WebView pixels to scroll by. Future scrollBy(int x, int y) { throw UnimplementedError( 'WebView scrollBy is not implemented on the current platform'); } /// Return the horizontal scroll position of this view. /// /// Scroll position is measured from left. Future getScrollX() { throw UnimplementedError( 'WebView getScrollX is not implemented on the current platform'); } /// Return the vertical scroll position of this view. /// /// Scroll position is measured from top. Future getScrollY() { throw UnimplementedError( 'WebView getScrollY is not implemented on the current platform'); } } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/legacy/types/auto_media_playback_policy.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Specifies possible restrictions on automatic media playback. /// /// This is typically used in [WebView.initialMediaPlaybackPolicy]. // The method channel implementation is marshalling this enum to the value's index, so the order // is important. enum AutoMediaPlaybackPolicy { /// Starting any kind of media playback requires a user action. /// /// For example: JavaScript code cannot start playing media unless the code was executed /// as a result of a user action (like a touch event). require_user_action_for_all_media_types, /// Starting any kind of media playback is always allowed. /// /// For example: JavaScript code that's triggered when the page is loaded can start playing /// video or audio. always_allow, } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/legacy/types/creation_params.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/widgets.dart'; import 'types.dart'; /// Configuration to use when creating a new [WebViewPlatformController]. /// /// The `autoMediaPlaybackPolicy` parameter must not be null. class CreationParams { /// Constructs an instance to use when creating a new /// [WebViewPlatformController]. /// /// The `autoMediaPlaybackPolicy` parameter must not be null. CreationParams({ this.initialUrl, this.webSettings, this.javascriptChannelNames = const {}, this.userAgent, this.autoMediaPlaybackPolicy = AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, this.backgroundColor, this.cookies = const [], }) : assert(autoMediaPlaybackPolicy != null); /// The initialUrl to load in the webview. /// /// When null the webview will be created without loading any page. final String? initialUrl; /// The initial [WebSettings] for the new webview. /// /// This can later be updated with [WebViewPlatformController.updateSettings]. final WebSettings? webSettings; /// The initial set of JavaScript channels that are configured for this webview. /// /// For each value in this set the platform's webview should make sure that a corresponding /// property with a postMessage method is set on `window`. For example for a JavaScript channel /// named `Foo` it should be possible for JavaScript code executing in the webview to do /// /// ```javascript /// Foo.postMessage('hello'); /// ``` // TODO(amirh): describe what should happen when postMessage is called once that code is migrated // to PlatformWebView. final Set javascriptChannelNames; /// The value used for the HTTP User-Agent: request header. /// /// When null the platform's webview default is used for the User-Agent header. final String? userAgent; /// Which restrictions apply on automatic media playback. final AutoMediaPlaybackPolicy autoMediaPlaybackPolicy; /// The background color of the webview. /// /// When null the platform's webview default background color is used. final Color? backgroundColor; /// The initial set of cookies to set before the webview does its first load. final List cookies; @override String toString() { return 'CreationParams(initialUrl: $initialUrl, settings: $webSettings, javascriptChannelNames: $javascriptChannelNames, UserAgent: $userAgent, backgroundColor: $backgroundColor, cookies: $cookies)'; } } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/legacy/types/javascript_channel.dart ================================================ // Copyright 2013 The Flutter 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 'javascript_message.dart'; /// Callback type for handling messages sent from JavaScript running in a web view. typedef JavascriptMessageHandler = void Function(JavascriptMessage message); final RegExp _validChannelNames = RegExp(r'^[a-zA-Z_][a-zA-Z0-9_]*$'); /// A named channel for receiving messaged from JavaScript code running inside a web view. class JavascriptChannel { /// Constructs a JavaScript channel. /// /// The parameters `name` and `onMessageReceived` must not be null. JavascriptChannel({ required this.name, required this.onMessageReceived, }) : assert(name != null), assert(onMessageReceived != null), assert(_validChannelNames.hasMatch(name)); /// The channel's name. /// /// Passing this channel object as part of a [WebView.javascriptChannels] adds a channel object to /// the JavaScript window object's property named `name`. /// /// The name must start with a letter or underscore(_), followed by any combination of those /// characters plus digits. /// /// Note that any JavaScript existing `window` property with this name will be overriden. /// /// See also [WebView.javascriptChannels] for more details on the channel registration mechanism. final String name; /// A callback that's invoked when a message is received through the channel. final JavascriptMessageHandler onMessageReceived; } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/legacy/types/javascript_message.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// A message that was sent by JavaScript code running in a [WebView]. class JavascriptMessage { /// Constructs a JavaScript message object. /// /// The `message` parameter must not be null. const JavascriptMessage(this.message) : assert(message != null); /// The contents of the message that was sent by the JavaScript code. final String message; } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/legacy/types/javascript_mode.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Describes the state of JavaScript support in a given web view. enum JavascriptMode { /// JavaScript execution is disabled. disabled, /// JavaScript execution is not restricted. unrestricted, } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/legacy/types/types.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'auto_media_playback_policy.dart'; export 'creation_params.dart'; export 'javascript_channel.dart'; export 'javascript_message.dart'; export 'javascript_mode.dart'; export 'web_resource_error.dart'; export 'web_resource_error_type.dart'; export 'web_settings.dart'; export 'webview_cookie.dart'; export 'webview_request.dart'; ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/legacy/types/web_resource_error.dart ================================================ // Copyright 2013 The Flutter 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 'web_resource_error_type.dart'; /// Error returned in `WebView.onWebResourceError` when a web resource loading error has occurred. class WebResourceError { /// Creates a new [WebResourceError] /// /// A user should not need to instantiate this class, but will receive one in /// [WebResourceErrorCallback]. WebResourceError({ required this.errorCode, required this.description, this.domain, this.errorType, this.failingUrl, }) : assert(errorCode != null), assert(description != null); /// Raw code of the error from the respective platform. /// /// On Android, the error code will be a constant from a /// [WebViewClient](https://developer.android.com/reference/android/webkit/WebViewClient#summary) and /// will have a corresponding [errorType]. /// /// On iOS, the error code will be a constant from `NSError.code` in /// Objective-C. See /// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/ErrorObjectsDomains/ErrorObjectsDomains.html /// for more information on error handling on iOS. Some possible error codes /// can be found at https://developer.apple.com/documentation/webkit/wkerrorcode?language=objc. final int errorCode; /// The domain of where to find the error code. /// /// This field is only available on iOS and represents a "domain" from where /// the [errorCode] is from. This value is taken directly from an `NSError` /// in Objective-C. See /// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/ErrorObjectsDomains/ErrorObjectsDomains.html /// for more information on error handling on iOS. final String? domain; /// Description of the error that can be used to communicate the problem to the user. final String description; /// The type this error can be categorized as. /// /// This will never be `null` on Android, but can be `null` on iOS. final WebResourceErrorType? errorType; /// Gets the URL for which the resource request was made. /// /// This value is not provided on iOS. Alternatively, you can keep track of /// the last values provided to [WebViewPlatformController.loadUrl]. final String? failingUrl; } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/legacy/types/web_resource_error_type.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Possible error type categorizations used by [WebResourceError]. enum WebResourceErrorType { /// User authentication failed on server. authentication, /// Malformed URL. badUrl, /// Failed to connect to the server. connect, /// Failed to perform SSL handshake. failedSslHandshake, /// Generic file error. file, /// File not found. fileNotFound, /// Server or proxy hostname lookup failed. hostLookup, /// Failed to read or write to the server. io, /// User authentication failed on proxy. proxyAuthentication, /// Too many redirects. redirectLoop, /// Connection timed out. timeout, /// Too many requests during this load. tooManyRequests, /// Generic error. unknown, /// Resource load was canceled by Safe Browsing. unsafeResource, /// Unsupported authentication scheme (not basic or digest). unsupportedAuthScheme, /// Unsupported URI scheme. unsupportedScheme, /// The web content process was terminated. webContentProcessTerminated, /// The web view was invalidated. webViewInvalidated, /// A JavaScript exception occurred. javaScriptExceptionOccurred, /// The result of JavaScript execution could not be returned. javaScriptResultTypeIsUnsupported, } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/legacy/types/web_settings.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/widgets.dart'; import 'javascript_mode.dart'; /// A single setting for configuring a WebViewPlatform which may be absent. @immutable class WebSetting { /// Constructs an absent setting instance. /// /// The [isPresent] field for the instance will be false. /// /// Accessing [value] for an absent instance will throw. const WebSetting.absent() : _value = null, isPresent = false; /// Constructs a setting of the given `value`. /// /// The [isPresent] field for the instance will be true. const WebSetting.of(T value) : _value = value, isPresent = true; final T? _value; /// The setting's value. /// /// Throws if [WebSetting.isPresent] is false. T get value { if (!isPresent) { throw StateError('Cannot access a value of an absent WebSetting'); } assert(isPresent); // The intention of this getter is to return T whether it is nullable or // not whereas _value is of type T? since _value can be null even when // T is not nullable (when isPresent == false). // // We promote _value to T using `as T` instead of `!` operator to handle // the case when _value is legitimately null (and T is a nullable type). // `!` operator would always throw if _value is null. return _value as T; } /// True when this web setting instance contains a value. /// /// When false the [WebSetting.value] getter throws. final bool isPresent; @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) { return false; } return other is WebSetting && other.isPresent == isPresent && other._value == _value; } @override int get hashCode => Object.hash(_value, isPresent); } /// Settings for configuring a WebViewPlatform. /// /// Initial settings are passed as part of [CreationParams], settings updates are sent with /// [WebViewPlatform#updateSettings]. /// /// The `userAgent` parameter must not be null. class WebSettings { /// Construct an instance with initial settings. Future setting changes can be /// sent with [WebviewPlatform#updateSettings]. /// /// The `userAgent` parameter must not be null. WebSettings({ this.javascriptMode, this.hasNavigationDelegate, this.hasProgressTracking, this.debuggingEnabled, this.gestureNavigationEnabled, this.allowsInlineMediaPlayback, this.zoomEnabled, required this.userAgent, }) : assert(userAgent != null); /// The JavaScript execution mode to be used by the webview. final JavascriptMode? javascriptMode; /// Whether the [WebView] has a [NavigationDelegate] set. final bool? hasNavigationDelegate; /// Whether the [WebView] should track page loading progress. /// See also: [WebViewPlatformCallbacksHandler.onProgress] to get the progress. final bool? hasProgressTracking; /// Whether to enable the platform's webview content debugging tools. /// /// See also: [WebView.debuggingEnabled]. final bool? debuggingEnabled; /// Whether to play HTML5 videos inline or use the native full-screen controller on iOS. /// /// This will have no effect on Android. final bool? allowsInlineMediaPlayback; /// The value used for the HTTP `User-Agent:` request header. /// /// If [userAgent.value] is null the platform's default user agent should be used. /// /// An absent value ([userAgent.isPresent] is false) represents no change to this setting from the /// last time it was set. /// /// See also [WebView.userAgent]. final WebSetting userAgent; /// Sets whether the WebView should support zooming using its on-screen zoom controls and gestures. final bool? zoomEnabled; /// Whether to allow swipe based navigation in iOS. /// /// See also: [WebView.gestureNavigationEnabled] final bool? gestureNavigationEnabled; @override String toString() { return 'WebSettings(javascriptMode: $javascriptMode, hasNavigationDelegate: $hasNavigationDelegate, hasProgressTracking: $hasProgressTracking, debuggingEnabled: $debuggingEnabled, gestureNavigationEnabled: $gestureNavigationEnabled, userAgent: $userAgent, allowsInlineMediaPlayback: $allowsInlineMediaPlayback)'; } } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/legacy/types/webview_cookie.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// A cookie that can be set globally for all web views /// using [WebViewCookieManagerPlatform]. class WebViewCookie { /// Constructs a new [WebViewCookie]. const WebViewCookie( {required this.name, required this.value, required this.domain, this.path = '/'}); /// The cookie-name of the cookie. /// /// Its value should match "cookie-name" in RFC6265bis: /// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 final String name; /// The cookie-value of the cookie. /// /// Its value should match "cookie-value" in RFC6265bis: /// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 final String value; /// The domain-value of the cookie. /// /// Its value should match "domain-value" in RFC6265bis: /// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 final String domain; /// The path-value of the cookie. /// Is set to `/` in the constructor by default. /// /// Its value should match "path-value" in RFC6265bis: /// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 final String path; /// Serializes the [WebViewCookie] to a Map. Map toJson() { return { 'name': name, 'value': value, 'domain': domain, 'path': path }; } } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/legacy/types/webview_request.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:typed_data'; /// Defines the supported HTTP methods for loading a page in [WebView]. enum WebViewRequestMethod { /// HTTP GET method. get, /// HTTP POST method. post, } /// Extension methods on the [WebViewRequestMethod] enum. extension WebViewRequestMethodExtensions on WebViewRequestMethod { /// Converts [WebViewRequestMethod] to [String] format. String serialize() { switch (this) { case WebViewRequestMethod.get: return 'get'; case WebViewRequestMethod.post: return 'post'; } } } /// Defines the parameters that can be used to load a page in the [WebView]. class WebViewRequest { /// Creates the [WebViewRequest]. WebViewRequest({ required this.uri, required this.method, this.headers = const {}, this.body, }); /// URI for the request. final Uri uri; /// HTTP method used to make the request. final WebViewRequestMethod method; /// Headers for the request. final Map headers; /// HTTP body for the request. final Uint8List? body; /// Serializes the [WebViewRequest] to JSON. Map toJson() => { 'uri': uri.toString(), 'method': method.serialize(), 'headers': headers, 'body': body, }; } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_navigation_delegate.dart ================================================ // Copyright 2013 The Flutter 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:plugin_platform_interface/plugin_platform_interface.dart'; import 'webview_platform.dart'; /// Signature for callbacks that report a pending navigation request. typedef NavigationRequestCallback = FutureOr Function( NavigationRequest navigationRequest); /// Signature for callbacks that report page events triggered by the native web view. typedef PageEventCallback = void Function(String url); /// Signature for callbacks that report loading progress of a page. typedef ProgressCallback = void Function(int progress); /// Signature for callbacks that report a resource loading error. typedef WebResourceErrorCallback = void Function(WebResourceError error); /// An interface defining navigation events that occur on the native platform. /// /// The [PlatformWebViewController] is notifying this delegate on events that /// happened on the platform's webview. Platform implementations should /// implement this class and pass an instance to the [PlatformWebViewController]. 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); return callbackDelegate; } /// Used by the platform implementation to create a new [PlatformNavigationDelegate]. /// /// Should only be used by platform implementations because they can't extend /// a class that only contains a factory constructor. @protected PlatformNavigationDelegate.implementation(this.params) : super(token: _token); static final Object _token = Object(); /// The parameters used to initialize the [PlatformNavigationDelegate]. final PlatformNavigationDelegateCreationParams params; /// Invoked when a navigation request is pending. /// /// See [PlatformWebViewController.setPlatformNavigationDelegate]. Future setOnNavigationRequest( NavigationRequestCallback onNavigationRequest, ) { throw UnimplementedError( 'setOnNavigationRequest is not implemented on the current platform.'); } /// Invoked when a page has started loading. /// /// See [PlatformWebViewController.setPlatformNavigationDelegate]. Future setOnPageStarted( PageEventCallback onPageStarted, ) { throw UnimplementedError( 'setOnPageStarted is not implemented on the current platform.'); } /// Invoked when a page has finished loading. /// /// See [PlatformWebViewController.setPlatformNavigationDelegate]. Future setOnPageFinished( PageEventCallback onPageFinished, ) { throw UnimplementedError( 'setOnPageFinished is not implemented on the current platform.'); } /// Invoked when a page is loading to report the progress. /// /// See [PlatformWebViewController.setPlatformNavigationDelegate]. Future setOnProgress( ProgressCallback onProgress, ) { throw UnimplementedError( 'setOnProgress is not implemented on the current platform.'); } /// Invoked when a resource loading error occurred. /// /// See [PlatformWebViewController.setPlatformNavigationDelegate]. Future setOnWebResourceError( WebResourceErrorCallback onWebResourceError, ) { throw UnimplementedError( 'setOnWebResourceError is not implemented on the current platform.'); } } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_webview_controller.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/painting.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import '../../src/platform_navigation_delegate.dart'; import 'webview_platform.dart'; /// Interface for a platform implementation of a web view controller. /// /// Platform implementations should extend this class rather than implement it /// as `webview_flutter` does not consider newly added methods to be breaking /// changes. Extending this class (using `extends`) ensures that the subclass /// will get the default implementation, while platform implementations that /// `implements` this interface will be broken by newly added /// [PlatformWebViewCookieManager] methods. 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); return webViewControllerDelegate; } /// Used by the platform implementation to create a new [PlatformWebViewController]. /// /// Should only be used by platform implementations because they can't extend /// a class that only contains a factory constructor. @protected PlatformWebViewController.implementation(this.params) : super(token: _token); static final Object _token = Object(); /// The parameters used to initialize the [PlatformWebViewController]. final PlatformWebViewControllerCreationParams params; /// Loads the file located on the specified [absoluteFilePath]. /// /// The [absoluteFilePath] parameter should contain the absolute path to the /// file as it is stored on the device. For example: /// `/Users/username/Documents/www/index.html`. /// /// Throws an ArgumentError if the [absoluteFilePath] does not exist. Future loadFile( String absoluteFilePath, ) { throw UnimplementedError( 'loadFile is not implemented on the current platform'); } /// Loads the Flutter asset specified in the pubspec.yaml file. /// /// Throws an ArgumentError if [key] is not part of the specified assets /// in the pubspec.yaml file. Future loadFlutterAsset( String key, ) { throw UnimplementedError( 'loadFlutterAsset is not implemented on the current platform'); } /// Loads the supplied HTML string. /// /// The [baseUrl] parameter is used when resolving relative URLs within the /// HTML string. Future loadHtmlString( String html, { String? baseUrl, }) { throw UnimplementedError( 'loadHtmlString is not implemented on the current platform'); } /// Makes a specific HTTP request ands loads the response in the webview. /// /// [WebViewRequest.method] must be one of the supported HTTP methods /// in [WebViewRequestMethod]. /// /// If [WebViewRequest.headers] is not empty, its key-value pairs will be /// added as the headers for the request. /// /// If [WebViewRequest.body] is not null, it will be added as the body /// for the request. /// /// Throws an ArgumentError if [WebViewRequest.uri] has empty scheme. Future loadRequest( LoadRequestParams params, ) { throw UnimplementedError( 'loadRequest is not implemented on the current platform'); } /// Accessor to the current URL that the WebView is displaying. /// /// If no URL was ever loaded, returns `null`. Future currentUrl() { throw UnimplementedError( 'currentUrl is not implemented on the current platform'); } /// Checks whether there's a back history item. Future canGoBack() { throw UnimplementedError( 'canGoBack is not implemented on the current platform'); } /// Checks whether there's a forward history item. Future canGoForward() { throw UnimplementedError( 'canGoForward is not implemented on the current platform'); } /// Goes back in the history of this WebView. /// /// If there is no back history item this is a no-op. Future goBack() { throw UnimplementedError( 'goBack is not implemented on the current platform'); } /// Goes forward in the history of this WebView. /// /// If there is no forward history item this is a no-op. Future goForward() { throw UnimplementedError( 'goForward is not implemented on the current platform'); } /// Reloads the current URL. Future reload() { throw UnimplementedError( 'reload is not implemented on the current platform'); } /// Clears all caches used by the [WebView]. /// /// The following caches are cleared: /// 1. Browser HTTP Cache. /// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches. /// These are not yet supported in iOS WkWebView. Service workers tend to use this cache. /// 3. Application cache. Future clearCache() { throw UnimplementedError( 'clearCache is not implemented on the current platform'); } /// Clears the local storage used by the [WebView]. Future clearLocalStorage() { throw UnimplementedError( 'clearLocalStorage is not implemented on the current platform'); } /// Sets the [PlatformNavigationDelegate] containing the callback methods that /// are called during navigation events. Future setPlatformNavigationDelegate( PlatformNavigationDelegate handler) { throw UnimplementedError( 'setPlatformNavigationDelegate is not implemented on the current platform'); } /// Runs the given JavaScript in the context of the current page. /// /// The Future completes with an error if a JavaScript error occurred. Future runJavaScript(String javaScript) { throw UnimplementedError( 'runJavaScript is not implemented on the current platform'); } /// Runs the given JavaScript in the context of the current page, and returns the result. /// /// The Future completes with an error if a JavaScript error occurred, or if the /// type the given expression evaluates to is unsupported. Unsupported values include /// certain non-primitive types on iOS, as well as `undefined` or `null` on iOS 14+. Future runJavaScriptReturningResult(String javaScript) { throw UnimplementedError( 'runJavaScriptReturningResult is not implemented on the current platform'); } /// Adds a new JavaScript channel to the set of enabled channels. Future addJavaScriptChannel( JavaScriptChannelParams javaScriptChannelParams, ) { throw UnimplementedError( 'addJavaScriptChannel is not implemented on the current platform'); } /// Removes the JavaScript channel with the matching name from the set of /// enabled channels. /// /// This disables the channel with the matching name if it was previously /// enabled through the [addJavaScriptChannel]. Future removeJavaScriptChannel(String javaScriptChannelName) { throw UnimplementedError( 'removeJavaScriptChannel is not implemented on the current platform'); } /// Returns the title of the currently loaded page. Future getTitle() { throw UnimplementedError( 'getTitle is not implemented on the current platform'); } /// Set the scrolled position of this view. /// /// The parameters `x` and `y` specify the position to scroll to in WebView pixels. Future scrollTo(int x, int y) { throw UnimplementedError( 'scrollTo is not implemented on the current platform'); } /// Move the scrolled position of this view. /// /// The parameters `x` and `y` specify the amount of WebView pixels to scroll by. Future scrollBy(int x, int y) { throw UnimplementedError( 'scrollBy is not implemented on the current platform'); } /// Return the current scroll position of this view. /// /// Scroll position is measured from the top left. Future getScrollPosition() { throw UnimplementedError( 'getScrollPosition is not implemented on the current platform'); } /// Whether to support zooming using its on-screen zoom controls and gestures. Future enableZoom(bool enabled) { throw UnimplementedError( 'enableZoom is not implemented on the current platform'); } /// Set the current background color of this view. Future setBackgroundColor(Color color) { throw UnimplementedError( 'setBackgroundColor is not implemented on the current platform'); } /// Sets the JavaScript execution mode to be used by the webview. Future setJavaScriptMode(JavaScriptMode javaScriptMode) { throw UnimplementedError( 'setJavaScriptMode is not implemented on the current platform'); } /// Sets the value used for the HTTP `User-Agent:` request header. Future setUserAgent(String? userAgent) { throw UnimplementedError( 'setUserAgent is not implemented on the current platform'); } } /// Describes the parameters necessary for registering a JavaScript channel. @immutable class JavaScriptChannelParams { /// Creates a new [JavaScriptChannelParams] object. const JavaScriptChannelParams({ required this.name, required this.onMessageReceived, }); /// The name that identifies the JavaScript channel. final String name; /// The callback method that is invoked when a [JavaScriptMessage] is /// received. final void Function(JavaScriptMessage) onMessageReceived; } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_webview_cookie_manager.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'webview_platform.dart'; /// Interface for a platform implementation of a cookie manager. /// /// Platform implementations should extend this class rather than implement it /// as `webview_flutter` does not consider newly added methods to be breaking /// changes. Extending this class (using `extends`) ensures that the subclass /// will get the default implementation, while platform implementations that /// `implements` this interface will be broken by newly added /// [PlatformWebViewCookieManager] methods. 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); return cookieManagerDelegate; } /// Used by the platform implementation to create a new /// [PlatformWebViewCookieManager]. /// /// Should only be used by platform implementations because they can't extend /// a class that only contains a factory constructor. @protected PlatformWebViewCookieManager.implementation(this.params) : super(token: _token); static final Object _token = Object(); /// The parameters used to initialize the [PlatformWebViewCookieManager]. final PlatformWebViewCookieManagerCreationParams params; /// Clears all cookies for all [WebView] instances. /// /// Returns true if cookies were present before clearing, else false. Future clearCookies() { throw UnimplementedError( 'clearCookies is not implemented on the current platform'); } /// Sets a cookie for all [WebView] instances. Future setCookie(WebViewCookie cookie) { throw UnimplementedError( 'setCookie is not implemented on the current platform'); } } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/platform_webview_widget.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/widgets.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'webview_platform.dart'; /// Interface for a platform implementation of a web view widget. 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); return webViewWidgetDelegate; } /// Used by the platform implementation to create a new /// [PlatformWebViewWidget]. /// /// Should only be used by platform implementations because they can't extend /// a class that only contains a factory constructor. @protected PlatformWebViewWidget.implementation(this.params) : super(token: _token); static final Object _token = Object(); /// The parameters used to initialize the [PlatformWebViewWidget]. final PlatformWebViewWidgetCreationParams params; /// Builds a new WebView. /// /// Returns a Widget tree that embeds the created web view. Widget build(BuildContext context); } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/javascript_message.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; /// A message that was sent by JavaScript code running in a [WebView]. /// /// Platform specific implementations can add additional fields by extending /// this class and providing a factory method that takes the /// [JavaScriptMessage] as a parameter. /// /// {@tool sample} /// This example demonstrates how to extend the [JavaScriptMessage] to /// provide additional platform specific parameters. /// /// When extending [JavaScriptMessage] additional parameters should always /// accept `null` or have a default value to prevent breaking changes. /// /// ```dart /// @immutable /// class WKWebViewScriptMessage extends JavaScriptMessage { /// WKWebViewScriptMessage._( /// JavaScriptMessage javaScriptMessage, /// this.extraData, /// ) : super(javaScriptMessage.message); /// /// factory WKWebViewScriptMessage.fromJavaScripMessage( /// JavaScriptMessage javaScripMessage, { /// String? extraData, /// }) { /// return WKWebViewScriptMessage._( /// javaScriptMessage, /// extraData: extraData, /// ); /// } /// /// final String? extraData; /// } /// ``` /// {@end-tool} @immutable class JavaScriptMessage { /// Creates a new JavaScript message object. const JavaScriptMessage({ required this.message, }); /// The contents of the message that was sent by the JavaScript code. final String message; } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/javascript_mode.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Describes the state of JavaScript support in a given web view. enum JavaScriptMode { /// JavaScript execution is disabled. disabled, /// JavaScript execution is not restricted. unrestricted, } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/load_request_params.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import '../platform_webview_controller.dart'; /// Defines the supported HTTP methods for loading a page in [PlatformWebViewController]. enum LoadRequestMethod { /// HTTP GET method. get, /// HTTP POST method. post, } /// Extension methods on the [LoadRequestMethod] enum. extension LoadRequestMethodExtensions on LoadRequestMethod { /// Converts [LoadRequestMethod] to [String] format. String serialize() { switch (this) { case LoadRequestMethod.get: return 'get'; case LoadRequestMethod.post: return 'post'; } } } /// Defines the parameters that can be used to load a page with the [PlatformWebViewController]. /// /// Platform specific implementations can add additional fields by extending /// this class. /// /// {@tool sample} /// This example demonstrates how to extend the [LoadRequestParams] to /// provide additional platform specific parameters. /// /// When extending [LoadRequestParams] additional parameters should always /// accept `null` or have a default value to prevent breaking changes. /// /// ```dart /// class AndroidLoadRequestParams extends LoadRequestParams { /// AndroidLoadRequestParams._({ /// required LoadRequestParams params, /// this.historyUrl, /// }) : super( /// uri: params.uri, /// method: params.method, /// body: params.body, /// headers: params.headers, /// ); /// /// factory AndroidLoadRequestParams.fromLoadRequestParams( /// LoadRequestParams params, { /// Uri? historyUrl, /// }) { /// return AndroidLoadRequestParams._(params, historyUrl: historyUrl); /// } /// /// final Uri? historyUrl; /// } /// ``` /// {@end-tool} @immutable class LoadRequestParams { /// Used by the platform implementation to create a new [LoadRequestParams]. const LoadRequestParams({ required this.uri, this.method = LoadRequestMethod.get, this.headers = const {}, this.body, }); /// URI for the request. final Uri uri; /// HTTP method used to make the request. /// /// Defaults to [LoadRequestMethod.get]. final LoadRequestMethod method; /// Headers for the request. final Map headers; /// HTTP body for the request. final Uint8List? body; } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/navigation_decision.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// A decision on how to handle a navigation request. enum NavigationDecision { /// Prevent the navigation from taking place. prevent, /// Allow the navigation to take place. navigate, } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/navigation_request.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Defines the parameters of the pending navigation callback. class NavigationRequest { /// Creates a [NavigationRequest]. const NavigationRequest({ required this.url, required this.isMainFrame, }); /// The URL of the pending navigation request. final String url; /// Indicates whether the request was made in the web site's main frame or a subframe. final bool isMainFrame; } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/platform_navigation_delegate_creation_params.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; /// Object specifying creation parameters for creating a [PlatformNavigationDelegate]. /// /// Platform specific implementations can add additional fields by extending /// this class. /// /// {@tool sample} /// This example demonstrates how to extend the [PlatformNavigationDelegateCreationParams] to /// provide additional platform specific parameters. /// /// When extending [PlatformNavigationDelegateCreationParams] additional /// parameters should always accept `null` or have a default value to prevent /// breaking changes. /// /// ```dart /// class AndroidNavigationDelegateCreationParams extends PlatformNavigationDelegateCreationParams { /// AndroidNavigationDelegateCreationParams._( /// // This parameter prevents breaking changes later. /// // ignore: avoid_unused_constructor_parameters /// PlatformNavigationDelegateCreationParams params, { /// this.filter, /// }) : super(); /// /// factory AndroidNavigationDelegateCreationParams.fromPlatformNavigationDelegateCreationParams( /// PlatformNavigationDelegateCreationParams params, { /// String? filter, /// }) { /// return AndroidNavigationDelegateCreationParams._(params, filter: filter); /// } /// /// final String? filter; /// } /// ``` /// {@end-tool} @immutable class PlatformNavigationDelegateCreationParams { /// Used by the platform implementation to create a new [PlatformNavigationkDelegate]. const PlatformNavigationDelegateCreationParams(); } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/platform_webview_controller_creation_params.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; /// Object specifying creation parameters for creating a [PlatformWebViewController]. /// /// Platform specific implementations can add additional fields by extending /// this class. /// /// {@tool sample} /// This example demonstrates how to extend the [PlatformWebViewControllerCreationParams] to /// provide additional platform specific parameters. /// /// When extending [PlatformWebViewControllerCreationParams] additional parameters /// should always accept `null` or have a default value to prevent breaking /// changes. /// /// ```dart /// class WKWebViewControllerCreationParams /// extends PlatformWebViewControllerCreationParams { /// WKWebViewControllerCreationParams._( /// // This parameter prevents breaking changes later. /// // ignore: avoid_unused_constructor_parameters /// PlatformWebViewControllerCreationParams params, { /// this.domain, /// }) : super(); /// /// factory WKWebViewControllerCreationParams.fromPlatformWebViewControllerCreationParams( /// PlatformWebViewControllerCreationParams params, { /// String? domain, /// }) { /// return WKWebViewControllerCreationParams._(params, domain: domain); /// } /// /// final String? domain; /// } /// ``` /// {@end-tool} @immutable class PlatformWebViewControllerCreationParams { /// Used by the platform implementation to create a new [PlatformWebViewController]. const PlatformWebViewControllerCreationParams(); } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/platform_webview_cookie_manager_creation_params.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; /// Object specifying creation parameters for creating a [PlatformWebViewCookieManager]. /// /// Platform specific implementations can add additional fields by extending /// this class. /// /// {@tool sample} /// This example demonstrates how to extend the [PlatformWebViewCookieManagerCreationParams] to /// provide additional platform specific parameters. /// /// When extending [PlatformWebViewCookieManagerCreationParams] additional /// parameters should always accept `null` or have a default value to prevent /// breaking changes. /// /// ```dart /// class WKWebViewCookieManagerCreationParams /// extends PlatformWebViewCookieManagerCreationParams { /// WKWebViewCookieManagerCreationParams._( /// // This parameter prevents breaking changes later. /// // ignore: avoid_unused_constructor_parameters /// PlatformWebViewCookieManagerCreationParams params, { /// this.uri, /// }) : super(); /// /// factory WKWebViewCookieManagerCreationParams.fromPlatformWebViewCookieManagerCreationParams( /// PlatformWebViewCookieManagerCreationParams params, { /// Uri? uri, /// }) { /// return WKWebViewCookieManagerCreationParams._(params, uri: uri); /// } /// /// final Uri? uri; /// } /// ``` /// {@end-tool} @immutable class PlatformWebViewCookieManagerCreationParams { /// Used by the platform implementation to create a new [PlatformWebViewCookieManagerDelegate]. const PlatformWebViewCookieManagerCreationParams(); } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/platform_webview_widget_creation_params.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/painting.dart'; import '../platform_webview_controller.dart'; /// Object specifying creation parameters for creating a [WebViewWidgetDelegate]. /// /// Platform specific implementations can add additional fields by extending /// this class. /// /// {@tool sample} /// This example demonstrates how to extend the [PlatformWebViewWidgetCreationParams] to /// provide additional platform specific parameters. /// /// When extending [PlatformWebViewWidgetCreationParams] additional parameters /// should always accept `null` or have a default value to prevent breaking /// changes. /// /// ```dart /// class AndroidWebViewWidgetCreationParams /// extends PlatformWebViewWidgetCreationParams { /// AndroidWebViewWidgetCreationParams({ /// super.key, /// super.layoutDirection, /// super.gestureRecognizers, /// this.platformSpecificFieldExample, /// }); /// /// WKWebViewWidgetCreationParams.fromPlatformWebViewWidgetCreationParams( /// PlatformWebViewWidgetCreationParams params, { /// Object? platformSpecificFieldExample, /// }) : this( /// key: params.key, /// layoutDirection: params.layoutDirection, /// gestureRecognizers: params.gestureRecognizers, /// platformSpecificFieldExample: platformSpecificFieldExample, /// ); /// /// final Object? platformSpecificFieldExample; /// } /// ``` /// {@end-tool} @immutable class PlatformWebViewWidgetCreationParams { /// Used by the platform implementation to create a new [PlatformWebViewWidget]. const PlatformWebViewWidgetCreationParams({ this.key, required this.controller, this.layoutDirection = TextDirection.ltr, this.gestureRecognizers = const >{}, }); /// Controls how one widget replaces another widget in the tree. /// /// See also: /// /// * The discussions at [Key] and [GlobalKey]. final Key? key; /// The [PlatformWebViewController] that allows controlling the native web /// view. final PlatformWebViewController controller; /// The layout direction to use for the embedded WebView. final TextDirection layoutDirection; /// The `gestureRecognizers` specifies which gestures should be consumed by the /// web view. /// /// It is possible for other gesture recognizers to be competing with the web /// view on pointer events, e.g if the web view is inside a [ListView] the /// [ListView] will want to handle vertical drags. The web view will claim /// gestures that are recognized by any of the recognizers on this list. /// /// When `gestureRecognizers` is empty (default), the web view will only handle /// pointer events for gestures that were not claimed by any other gesture /// recognizer. final Set> gestureRecognizers; } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/types.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'javascript_message.dart'; export 'javascript_mode.dart'; export 'load_request_params.dart'; export 'navigation_decision.dart'; export 'navigation_request.dart'; export 'platform_navigation_delegate_creation_params.dart'; export 'platform_webview_controller_creation_params.dart'; export 'platform_webview_cookie_manager_creation_params.dart'; export 'platform_webview_widget_creation_params.dart'; export 'web_resource_error.dart'; export 'webview_cookie.dart'; ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/web_resource_error.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; /// Possible error type categorizations used by [WebResourceError]. enum WebResourceErrorType { /// User authentication failed on server. authentication, /// Malformed URL. badUrl, /// Failed to connect to the server. connect, /// Failed to perform SSL handshake. failedSslHandshake, /// Generic file error. file, /// File not found. fileNotFound, /// Server or proxy hostname lookup failed. hostLookup, /// Failed to read or write to the server. io, /// User authentication failed on proxy. proxyAuthentication, /// Too many redirects. redirectLoop, /// Connection timed out. timeout, /// Too many requests during this load. tooManyRequests, /// Generic error. unknown, /// Resource load was canceled by Safe Browsing. unsafeResource, /// Unsupported authentication scheme (not basic or digest). unsupportedAuthScheme, /// Unsupported URI scheme. unsupportedScheme, /// The web content process was terminated. webContentProcessTerminated, /// The web view was invalidated. webViewInvalidated, /// A JavaScript exception occurred. javaScriptExceptionOccurred, /// The result of JavaScript execution could not be returned. javaScriptResultTypeIsUnsupported, } /// Error returned in `WebView.onWebResourceError` when a web resource loading error has occurred. /// /// Platform specific implementations can add additional fields by extending /// this class. /// /// {@tool sample} /// This example demonstrates how to extend the [WebResourceError] to /// provide additional platform specific parameters. /// /// When extending [WebResourceError] additional parameters should always /// accept `null` or have a default value to prevent breaking changes. /// /// ```dart /// class IOSWebResourceError extends WebResourceError { /// IOSWebResourceError._(WebResourceError error, {required this.domain}) /// : super( /// errorCode: error.errorCode, /// description: error.description, /// errorType: error.errorType, /// ); /// /// factory IOSWebResourceError.fromWebResourceError( /// WebResourceError error, { /// required String? domain, /// }) { /// return IOSWebResourceError._(error, domain: domain); /// } /// /// final String? domain; /// } /// ``` /// {@end-tool} @immutable class WebResourceError { /// Used by the platform implementation to create a new [WebResourceError]. const WebResourceError({ required this.errorCode, required this.description, this.errorType, this.isForMainFrame, }); /// Raw code of the error from the respective platform. final int errorCode; /// Description of the error that can be used to communicate the problem to the user. final String description; /// The type this error can be categorized as. final WebResourceErrorType? errorType; /// Whether the error originated from the main frame. final bool? isForMainFrame; } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/types/webview_cookie.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; /// A cookie that can be set globally for all web views using [WebViewCookieManagerPlatform]. @immutable class WebViewCookie { /// Creates a new [WebViewCookieDelegate] const WebViewCookie({ required this.name, required this.value, required this.domain, this.path = '/', }); /// The cookie-name of the cookie. /// /// Its value should match "cookie-name" in RFC6265bis: /// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 final String name; /// The cookie-value of the cookie. /// /// Its value should match "cookie-value" in RFC6265bis: /// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 final String value; /// The domain-value of the cookie. /// /// Its value should match "domain-value" in RFC6265bis: /// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 final String domain; /// The path-value of the cookie, set to `/` by default. /// /// Its value should match "path-value" in RFC6265bis: /// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 final String path; } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/webview_flutter_platform_interface_legacy.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'legacy/platform_interface/platform_interface.dart'; export 'legacy/types/types.dart'; ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/src/webview_platform.dart ================================================ // Copyright 2013 The Flutter 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:plugin_platform_interface/plugin_platform_interface.dart'; import '../../src/platform_navigation_delegate.dart'; import 'platform_webview_controller.dart'; import 'platform_webview_cookie_manager.dart'; import 'platform_webview_widget.dart'; import 'types/types.dart'; export 'types/types.dart'; /// Interface for a platform implementation of a WebView. abstract class WebViewPlatform extends PlatformInterface { /// Creates a new [WebViewPlatform]. WebViewPlatform() : super(token: _token); static final Object _token = Object(); static WebViewPlatform? _instance; /// The instance of [WebViewPlatform] to use. static WebViewPlatform? get instance => _instance; /// Platform-specific plugins should set this with their own platform-specific /// class that extends [WebViewPlatform] when they register themselves. static set instance(WebViewPlatform? instance) { if (instance == null) { throw AssertionError( 'Platform interfaces can only be set to a non-null instance'); } PlatformInterface.verify(instance, _token); _instance = instance; } /// Creates a new [PlatformWebViewCookieManager]. /// /// This function should only be called by the app-facing package. /// Look at using [WebViewCookieManager] in `webview_flutter` instead. PlatformWebViewCookieManager createPlatformCookieManager( PlatformWebViewCookieManagerCreationParams params, ) { throw UnimplementedError( 'createPlatformCookieManager is not implemented on the current platform.'); } /// Creates a new [PlatformNavigationDelegate]. /// /// This function should only be called by the app-facing package. /// Look at using [NavigationDelegate] in `webview_flutter` instead. PlatformNavigationDelegate createPlatformNavigationDelegate( PlatformNavigationDelegateCreationParams params, ) { throw UnimplementedError( 'createPlatformNavigationDelegate is not implemented on the current platform.'); } /// Create a new [PlatformWebViewController]. /// /// This function should only be called by the app-facing package. /// Look at using [WebViewController] in `webview_flutter` instead. PlatformWebViewController createPlatformWebViewController( PlatformWebViewControllerCreationParams params, ) { throw UnimplementedError( 'createPlatformWebViewController is not implemented on the current platform.'); } /// Create a new [PlatformWebViewWidget]. /// /// This function should only be called by the app-facing package. /// Look at using [WebViewWidget] in `webview_flutter` instead. PlatformWebViewWidget createPlatformWebViewWidget( PlatformWebViewWidgetCreationParams params, ) { throw UnimplementedError( 'createPlatformWebViewWidget is not implemented on the current platform.'); } } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/lib/webview_flutter_platform_interface.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'src/platform_navigation_delegate.dart'; export 'src/platform_webview_controller.dart'; export 'src/platform_webview_cookie_manager.dart'; export 'src/platform_webview_widget.dart'; export 'src/types/types.dart'; export 'src/webview_platform.dart'; ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/pubspec.yaml ================================================ name: webview_flutter_platform_interface description: A common platform interface for the webview_flutter plugin. repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_platform_interface 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.1 environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter meta: ^1.7.0 plugin_platform_interface: ^2.1.0 dev_dependencies: build_runner: ^2.1.8 flutter_test: sdk: flutter mockito: ^5.0.0 ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/test/legacy/platform_interface/javascript_channel_registry_test.dart ================================================ // Copyright 2013 The Flutter 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:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; void main() { final Map log = {}; final Set channels = { JavascriptChannel( name: 'js_channel_1', onMessageReceived: (JavascriptMessage message) => log['js_channel_1'] = message.message, ), JavascriptChannel( name: 'js_channel_2', onMessageReceived: (JavascriptMessage message) => log['js_channel_2'] = message.message, ), JavascriptChannel( name: 'js_channel_3', onMessageReceived: (JavascriptMessage message) => log['js_channel_3'] = message.message, ), }; tearDown(() { log.clear(); }); test('ctor should initialize with channels.', () { final JavascriptChannelRegistry registry = JavascriptChannelRegistry(channels); expect(registry.channels.length, 3); for (final JavascriptChannel channel in channels) { expect(registry.channels[channel.name], channel); } }); test('onJavascriptChannelMessage should forward message on correct channel.', () { final JavascriptChannelRegistry registry = JavascriptChannelRegistry(channels); registry.onJavascriptChannelMessage( 'js_channel_2', 'test message on channel 2', ); expect( log, containsPair( 'js_channel_2', 'test message on channel 2', )); }); test( 'onJavascriptChannelMessage should throw ArgumentError when message arrives on non-existing channel.', () { final JavascriptChannelRegistry registry = JavascriptChannelRegistry(channels); expect( () => registry.onJavascriptChannelMessage( 'js_channel_4', 'test message on channel 2', ), throwsA( isA().having((ArgumentError error) => error.message, 'message', 'No channel registered with name js_channel_4.'), )); }); test( 'updateJavascriptChannelsFromSet should clear all channels when null is supplied.', () { final JavascriptChannelRegistry registry = JavascriptChannelRegistry(channels); expect(registry.channels.length, 3); registry.updateJavascriptChannelsFromSet(null); expect(registry.channels, isEmpty); }); test('updateJavascriptChannelsFromSet should update registry with new set.', () { final JavascriptChannelRegistry registry = JavascriptChannelRegistry(channels); expect(registry.channels.length, 3); final Set newChannels = { JavascriptChannel( name: 'new_js_channel_1', onMessageReceived: (JavascriptMessage message) => log['new_js_channel_1'] = message.message, ), JavascriptChannel( name: 'new_js_channel_2', onMessageReceived: (JavascriptMessage message) => log['new_js_channel_2'] = message.message, ), }; registry.updateJavascriptChannelsFromSet(newChannels); expect(registry.channels.length, 2); for (final JavascriptChannel channel in newChannels) { expect(registry.channels[channel.name], channel); } }); } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/test/legacy/platform_interface/webview_cookie_manager_test.dart ================================================ // Copyright 2013 The Flutter 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:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; void main() { WebViewCookieManagerPlatform? cookieManager; setUp(() { cookieManager = TestWebViewCookieManagerPlatform(); }); test('clearCookies should throw UnimplementedError', () { expect(() => cookieManager!.clearCookies(), throwsUnimplementedError); }); test('setCookie should throw UnimplementedError', () { const WebViewCookie cookie = WebViewCookie(domain: 'flutter.dev', name: 'foo', value: 'bar'); expect(() => cookieManager!.setCookie(cookie), throwsUnimplementedError); }); } class TestWebViewCookieManagerPlatform extends WebViewCookieManagerPlatform {} ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/test/legacy/types/javascript_channel_test.dart ================================================ // Copyright 2013 The Flutter 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:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; void main() { final List validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_'.split(''); final List commonInvalidChars = r'`~!@#$%^&*()-=+[]{}\|"' ':;/?<>,. '.split(''); final List digits = List.generate(10, (int index) => index++); test( 'ctor should create JavascriptChannel when name starts with a valid character followed by a number.', () { for (final String char in validChars) { for (final int digit in digits) { final JavascriptChannel channel = JavascriptChannel(name: '$char$digit', onMessageReceived: (_) {}); expect(channel.name, '$char$digit'); } } }); test('ctor should assert when channel name starts with a number.', () { for (final int i in digits) { expect( () => JavascriptChannel(name: '$i', onMessageReceived: (_) {}), throwsAssertionError, ); } }); test('ctor should assert when channel contains invalid char.', () { for (final String validChar in validChars) { for (final String invalidChar in commonInvalidChars) { expect( () => JavascriptChannel( name: validChar + invalidChar, onMessageReceived: (_) {}), throwsAssertionError, ); } } }); } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/test/legacy/types/webview_cookie_test.dart ================================================ // Copyright 2013 The Flutter 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:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; void main() { test('WebViewCookie should serialize correctly', () { WebViewCookie cookie; Map serializedCookie; // Test serialization cookie = const WebViewCookie( name: 'foo', value: 'bar', domain: 'example.com', path: '/test'); serializedCookie = cookie.toJson(); expect(serializedCookie['name'], 'foo'); expect(serializedCookie['value'], 'bar'); expect(serializedCookie['domain'], 'example.com'); expect(serializedCookie['path'], '/test'); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/test/legacy/types/webview_request_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; void main() { test('WebViewRequestMethod should serialize correctly', () { expect(WebViewRequestMethod.get.serialize(), 'get'); expect(WebViewRequestMethod.post.serialize(), 'post'); }); test('WebViewRequest should serialize correctly', () { WebViewRequest request; Map serializedRequest; // Test serialization without headers or a body request = WebViewRequest( uri: Uri.parse('https://flutter.dev'), method: WebViewRequestMethod.get, ); serializedRequest = request.toJson(); expect(serializedRequest['uri'], 'https://flutter.dev'); expect(serializedRequest['method'], 'get'); expect(serializedRequest['headers'], {}); expect(serializedRequest['body'], null); // Test serialization of headers and body request = WebViewRequest( uri: Uri.parse('https://flutter.dev'), method: WebViewRequestMethod.get, headers: {'foo': 'bar'}, body: Uint8List.fromList('Example Body'.codeUnits), ); serializedRequest = request.toJson(); expect(serializedRequest['headers'], {'foo': 'bar'}); expect(serializedRequest['body'], 'Example Body'.codeUnits); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/test/platform_navigation_delegate_test.dart ================================================ // Copyright 2013 The Flutter 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:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'webview_platform_test.mocks.dart'; void main() { setUp(() { WebViewPlatform.instance = MockWebViewPlatformWithMixin(); }); test('Cannot be implemented with `implements`', () { const PlatformNavigationDelegateCreationParams params = PlatformNavigationDelegateCreationParams(); when(WebViewPlatform.instance!.createPlatformNavigationDelegate(params)) .thenReturn(ImplementsPlatformNavigationDelegate()); expect(() { PlatformNavigationDelegate(params); // In versions of `package:plugin_platform_interface` prior to fixing // https://github.com/flutter/flutter/issues/109339, an attempt to // implement a platform interface using `implements` would sometimes throw // a `NoSuchMethodError` and other times throw an `AssertionError`. After // the issue is fixed, an `AssertionError` will always be thrown. For the // purpose of this test, we don't really care what exception is thrown, so // just allow any exception. }, throwsA(anything)); }); test('Can be extended', () { const PlatformNavigationDelegateCreationParams params = PlatformNavigationDelegateCreationParams(); when(WebViewPlatform.instance!.createPlatformNavigationDelegate(params)) .thenReturn(ExtendsPlatformNavigationDelegate(params)); expect(PlatformNavigationDelegate(params), isNotNull); }); test('Can be mocked with `implements`', () { const PlatformNavigationDelegateCreationParams params = PlatformNavigationDelegateCreationParams(); when(WebViewPlatform.instance!.createPlatformNavigationDelegate(params)) .thenReturn(MockNavigationDelegate()); expect(PlatformNavigationDelegate(params), isNotNull); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of setOnNavigationRequest should throw unimplemented error', () { final PlatformNavigationDelegate callbackDelegate = ExtendsPlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams()); expect( () => callbackDelegate.setOnNavigationRequest( (NavigationRequest navigationRequest) => NavigationDecision.navigate), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of setOnPageStarted should throw unimplemented error', () { final PlatformNavigationDelegate callbackDelegate = ExtendsPlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams()); expect( () => callbackDelegate.setOnPageStarted((String url) {}), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of setOnPageFinished should throw unimplemented error', () { final PlatformNavigationDelegate callbackDelegate = ExtendsPlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams()); expect( () => callbackDelegate.setOnPageFinished((String url) {}), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of setOnProgress should throw unimplemented error', () { final PlatformNavigationDelegate callbackDelegate = ExtendsPlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams()); expect( () => callbackDelegate.setOnProgress((int progress) {}), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of setOnWebResourceError should throw unimplemented error', () { final PlatformNavigationDelegate callbackDelegate = ExtendsPlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams()); expect( () => callbackDelegate.setOnWebResourceError((WebResourceError error) {}), throwsUnimplementedError, ); }); } class MockWebViewPlatformWithMixin extends MockWebViewPlatform with // ignore: prefer_mixin MockPlatformInterfaceMixin {} class ImplementsPlatformNavigationDelegate implements PlatformNavigationDelegate { @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } class MockNavigationDelegate extends Mock with // ignore: prefer_mixin MockPlatformInterfaceMixin implements PlatformNavigationDelegate {} class ExtendsPlatformNavigationDelegate extends PlatformNavigationDelegate { ExtendsPlatformNavigationDelegate( PlatformNavigationDelegateCreationParams params) : super.implementation(params); } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/test/platform_webview_controller_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'platform_navigation_delegate_test.dart'; import 'webview_platform_test.mocks.dart'; @GenerateMocks([PlatformNavigationDelegate]) void main() { setUp(() { WebViewPlatform.instance = MockWebViewPlatformWithMixin(); }); test('Cannot be implemented with `implements`', () { when((WebViewPlatform.instance! as MockWebViewPlatform) .createPlatformWebViewController(any)) .thenReturn(ImplementsPlatformWebViewController()); expect(() { PlatformWebViewController( const PlatformWebViewControllerCreationParams()); // In versions of `package:plugin_platform_interface` prior to fixing // https://github.com/flutter/flutter/issues/109339, an attempt to // implement a platform interface using `implements` would sometimes throw // a `NoSuchMethodError` and other times throw an `AssertionError`. After // the issue is fixed, an `AssertionError` will always be thrown. For the // purpose of this test, we don't really care what exception is thrown, so // just allow any exception. }, throwsA(anything)); }); test('Can be extended', () { const PlatformWebViewControllerCreationParams params = PlatformWebViewControllerCreationParams(); when((WebViewPlatform.instance! as MockWebViewPlatform) .createPlatformWebViewController(any)) .thenReturn(ExtendsPlatformWebViewController(params)); expect(PlatformWebViewController(params), isNotNull); }); test('Can be mocked with `implements`', () { when((WebViewPlatform.instance! as MockWebViewPlatform) .createPlatformWebViewController(any)) .thenReturn(MockWebViewControllerDelegate()); expect( PlatformWebViewController( const PlatformWebViewControllerCreationParams()), isNotNull); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of loadFile should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.loadFile(''), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of loadFlutterAsset should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.loadFlutterAsset(''), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of loadHtmlString should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.loadHtmlString(''), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of loadRequest should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.loadRequest(MockLoadRequestParamsDelegate()), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of currentUrl should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.currentUrl(), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of canGoBack should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.canGoBack(), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of canGoForward should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.canGoForward(), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of goBack should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.goBack(), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of goForward should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.goForward(), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of reload should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.reload(), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of clearCache should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.clearCache(), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of clearLocalStorage should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.clearLocalStorage(), throwsUnimplementedError, ); }); test( 'Default implementation of the setNavigationCallback should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.setPlatformNavigationDelegate(MockNavigationDelegate()), throwsUnimplementedError, ); }, ); test( // ignore: lines_longer_than_80_chars 'Default implementation of runJavaScript should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.runJavaScript('javaScript'), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of runJavaScriptReturningResult should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.runJavaScriptReturningResult('javaScript'), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of addJavaScriptChannel should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.addJavaScriptChannel( JavaScriptChannelParams( name: 'test', onMessageReceived: (_) {}, ), ), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of removeJavaScriptChannel should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.removeJavaScriptChannel('test'), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of getTitle should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.getTitle(), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of scrollTo should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.scrollTo(0, 0), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of scrollBy should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.scrollBy(0, 0), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of getScrollPosition should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.getScrollPosition(), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of enableZoom should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.enableZoom(true), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of setBackgroundColor should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.setBackgroundColor(Colors.blue), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of setJavaScriptMode should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.setJavaScriptMode(JavaScriptMode.disabled), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of setUserAgent should throw unimplemented error', () { final PlatformWebViewController controller = ExtendsPlatformWebViewController( const PlatformWebViewControllerCreationParams()); expect( () => controller.setUserAgent(null), throwsUnimplementedError, ); }); } class MockWebViewPlatformWithMixin extends MockWebViewPlatform with // ignore: prefer_mixin MockPlatformInterfaceMixin {} class ImplementsPlatformWebViewController implements PlatformWebViewController { @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } class MockWebViewControllerDelegate extends Mock with // ignore: prefer_mixin MockPlatformInterfaceMixin implements PlatformWebViewController {} class ExtendsPlatformWebViewController extends PlatformWebViewController { ExtendsPlatformWebViewController( PlatformWebViewControllerCreationParams params) : super.implementation(params); } // ignore: must_be_immutable class MockLoadRequestParamsDelegate extends Mock with //ignore: prefer_mixin MockPlatformInterfaceMixin implements LoadRequestParams {} ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/test/platform_webview_controller_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_platform_interface/test/platform_webview_controller_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_platform_interface/src/platform_navigation_delegate.dart' as _i3; import 'package:webview_flutter_platform_interface/src/webview_platform.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 class _FakePlatformNavigationDelegateCreationParams_0 extends _i1.SmartFake implements _i2.PlatformNavigationDelegateCreationParams { _FakePlatformNavigationDelegateCreationParams_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [PlatformNavigationDelegate]. /// /// See the documentation for Mockito's code generation for more information. class MockPlatformNavigationDelegate extends _i1.Mock implements _i3.PlatformNavigationDelegate { MockPlatformNavigationDelegate() { _i1.throwOnMissingStub(this); } @override _i2.PlatformNavigationDelegateCreationParams get params => (super.noSuchMethod( Invocation.getter(#params), returnValue: _FakePlatformNavigationDelegateCreationParams_0( this, Invocation.getter(#params), ), ) as _i2.PlatformNavigationDelegateCreationParams); @override _i4.Future setOnNavigationRequest( _i3.NavigationRequestCallback? onNavigationRequest) => (super.noSuchMethod( Invocation.method( #setOnNavigationRequest, [onNavigationRequest], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future setOnPageStarted(_i3.PageEventCallback? onPageStarted) => (super.noSuchMethod( Invocation.method( #setOnPageStarted, [onPageStarted], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future setOnPageFinished(_i3.PageEventCallback? onPageFinished) => (super.noSuchMethod( Invocation.method( #setOnPageFinished, [onPageFinished], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future setOnProgress(_i3.ProgressCallback? onProgress) => (super.noSuchMethod( Invocation.method( #setOnProgress, [onProgress], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future setOnWebResourceError( _i3.WebResourceErrorCallback? onWebResourceError) => (super.noSuchMethod( Invocation.method( #setOnWebResourceError, [onWebResourceError], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); } ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/test/platform_webview_widget_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'webview_platform_test.mocks.dart'; void main() { setUp(() { WebViewPlatform.instance = MockWebViewPlatformWithMixin(); }); test('Cannot be implemented with `implements`', () { final MockWebViewControllerDelegate controller = MockWebViewControllerDelegate(); final PlatformWebViewWidgetCreationParams params = PlatformWebViewWidgetCreationParams(controller: controller); when(WebViewPlatform.instance!.createPlatformWebViewWidget(params)) .thenReturn(ImplementsWebViewWidgetDelegate()); expect(() { PlatformWebViewWidget(params); // In versions of `package:plugin_platform_interface` prior to fixing // https://github.com/flutter/flutter/issues/109339, an attempt to // implement a platform interface using `implements` would sometimes throw // a `NoSuchMethodError` and other times throw an `AssertionError`. After // the issue is fixed, an `AssertionError` will always be thrown. For the // purpose of this test, we don't really care what exception is thrown, so // just allow any exception. }, throwsA(anything)); }); test('Can be extended', () { final MockWebViewControllerDelegate controller = MockWebViewControllerDelegate(); final PlatformWebViewWidgetCreationParams params = PlatformWebViewWidgetCreationParams(controller: controller); when(WebViewPlatform.instance!.createPlatformWebViewWidget(params)) .thenReturn(ExtendsWebViewWidgetDelegate(params)); expect(PlatformWebViewWidget(params), isNotNull); }); test('Can be mocked with `implements`', () { final MockWebViewControllerDelegate controller = MockWebViewControllerDelegate(); final PlatformWebViewWidgetCreationParams params = PlatformWebViewWidgetCreationParams(controller: controller); when(WebViewPlatform.instance!.createPlatformWebViewWidget(params)) .thenReturn(MockWebViewWidgetDelegate()); expect(PlatformWebViewWidget(params), isNotNull); }); } class MockWebViewPlatformWithMixin extends MockWebViewPlatform with // ignore: prefer_mixin MockPlatformInterfaceMixin {} class ImplementsWebViewWidgetDelegate implements PlatformWebViewWidget { @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } class MockWebViewWidgetDelegate extends Mock with // ignore: prefer_mixin MockPlatformInterfaceMixin implements PlatformWebViewWidget {} class ExtendsWebViewWidgetDelegate extends PlatformWebViewWidget { ExtendsWebViewWidgetDelegate(PlatformWebViewWidgetCreationParams params) : super.implementation(params); @override Widget build(BuildContext context) { throw UnimplementedError( 'build is not implemented for ExtendedWebViewWidgetDelegate.'); } } class MockWebViewControllerDelegate extends Mock with // ignore: prefer_mixin MockPlatformInterfaceMixin implements PlatformWebViewController {} ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/test/webview_platform_test.dart ================================================ // Copyright 2013 The Flutter 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:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'webview_platform_test.mocks.dart'; @GenerateMocks([WebViewPlatform]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); test('Default instance WebViewPlatform instance should be null', () { 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(); // In versions of `package:plugin_platform_interface` prior to fixing // https://github.com/flutter/flutter/issues/109339, an attempt to // implement a platform interface using `implements` would sometimes throw // a `NoSuchMethodError` and other times throw an `AssertionError`. After // the issue is fixed, an `AssertionError` will always be thrown. For the // purpose of this test, we don't really care what exception is thrown, so // just allow any exception. }, throwsA(anything)); }); test('Can be extended', () { WebViewPlatform.instance = ExtendsWebViewPlatform(); }); test('Can be mocked with `implements`', () { final MockWebViewPlatform mock = MockWebViewPlatformWithMixin(); WebViewPlatform.instance = mock; }); test( // ignore: lines_longer_than_80_chars 'Default implementation of createCookieManagerDelegate should throw unimplemented error', () { final WebViewPlatform webViewPlatform = ExtendsWebViewPlatform(); expect( () => webViewPlatform.createPlatformCookieManager( const PlatformWebViewCookieManagerCreationParams()), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of createNavigationCallbackHandlerDelegate should throw unimplemented error', () { final WebViewPlatform webViewPlatform = ExtendsWebViewPlatform(); expect( () => webViewPlatform.createPlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams()), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of createWebViewControllerDelegate should throw unimplemented error', () { final WebViewPlatform webViewPlatform = ExtendsWebViewPlatform(); expect( () => webViewPlatform.createPlatformWebViewController( const PlatformWebViewControllerCreationParams()), throwsUnimplementedError, ); }); test( // ignore: lines_longer_than_80_chars 'Default implementation of createWebViewWidgetDelegate should throw unimplemented error', () { final WebViewPlatform webViewPlatform = ExtendsWebViewPlatform(); final MockWebViewControllerDelegate controller = MockWebViewControllerDelegate(); expect( () => webViewPlatform.createPlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller)), throwsUnimplementedError, ); }); } class ImplementsWebViewPlatform implements WebViewPlatform { @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } class MockWebViewPlatformWithMixin extends MockWebViewPlatform with // ignore: prefer_mixin MockPlatformInterfaceMixin {} class ExtendsWebViewPlatform extends WebViewPlatform {} class MockWebViewControllerDelegate extends Mock with // ignore: prefer_mixin MockPlatformInterfaceMixin implements PlatformWebViewController {} ================================================ FILE: packages/webview_flutter/webview_flutter_platform_interface/test/webview_platform_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_platform_interface/test/webview_platform_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:webview_flutter_platform_interface/src/platform_navigation_delegate.dart' as _i3; import 'package:webview_flutter_platform_interface/src/platform_webview_controller.dart' as _i4; import 'package:webview_flutter_platform_interface/src/platform_webview_cookie_manager.dart' as _i2; import 'package:webview_flutter_platform_interface/src/platform_webview_widget.dart' as _i5; import 'package:webview_flutter_platform_interface/src/types/types.dart' as _i7; import 'package:webview_flutter_platform_interface/src/webview_platform.dart' as _i6; // 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 class _FakePlatformWebViewCookieManager_0 extends _i1.SmartFake implements _i2.PlatformWebViewCookieManager { _FakePlatformWebViewCookieManager_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePlatformNavigationDelegate_1 extends _i1.SmartFake implements _i3.PlatformNavigationDelegate { _FakePlatformNavigationDelegate_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePlatformWebViewController_2 extends _i1.SmartFake implements _i4.PlatformWebViewController { _FakePlatformWebViewController_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePlatformWebViewWidget_3 extends _i1.SmartFake implements _i5.PlatformWebViewWidget { _FakePlatformWebViewWidget_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [WebViewPlatform]. /// /// See the documentation for Mockito's code generation for more information. class MockWebViewPlatform extends _i1.Mock implements _i6.WebViewPlatform { MockWebViewPlatform() { _i1.throwOnMissingStub(this); } @override _i2.PlatformWebViewCookieManager createPlatformCookieManager( _i7.PlatformWebViewCookieManagerCreationParams? params) => (super.noSuchMethod( Invocation.method( #createPlatformCookieManager, [params], ), returnValue: _FakePlatformWebViewCookieManager_0( this, Invocation.method( #createPlatformCookieManager, [params], ), ), ) as _i2.PlatformWebViewCookieManager); @override _i3.PlatformNavigationDelegate createPlatformNavigationDelegate( _i7.PlatformNavigationDelegateCreationParams? params) => (super.noSuchMethod( Invocation.method( #createPlatformNavigationDelegate, [params], ), returnValue: _FakePlatformNavigationDelegate_1( this, Invocation.method( #createPlatformNavigationDelegate, [params], ), ), ) as _i3.PlatformNavigationDelegate); @override _i4.PlatformWebViewController createPlatformWebViewController( _i7.PlatformWebViewControllerCreationParams? params) => (super.noSuchMethod( Invocation.method( #createPlatformWebViewController, [params], ), returnValue: _FakePlatformWebViewController_2( this, Invocation.method( #createPlatformWebViewController, [params], ), ), ) as _i4.PlatformWebViewController); @override _i5.PlatformWebViewWidget createPlatformWebViewWidget( _i7.PlatformWebViewWidgetCreationParams? params) => (super.noSuchMethod( Invocation.method( #createPlatformWebViewWidget, [params], ), returnValue: _FakePlatformWebViewWidget_3( this, Invocation.method( #createPlatformWebViewWidget, [params], ), ), ) as _i5.PlatformWebViewWidget); } ================================================ FILE: packages/webview_flutter/webview_flutter_web/AUTHORS ================================================ # 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. Bodhi Mulders ================================================ FILE: packages/webview_flutter/webview_flutter_web/CHANGELOG.md ================================================ ## 0.2.2 * Updates `WebWebViewController.loadRequest` to only set the src of the iFrame when `LoadRequestParams.headers` and `LoadRequestParams.body` are empty and is using the HTTP GET request method. [#118573](https://github.com/flutter/flutter/issues/118573). * Parses the `content-type` header of XHR responses to extract the correct MIME-type and charset. [#118090](https://github.com/flutter/flutter/issues/118090). * Sets `width` and `height` of widget the way the Engine wants, to remove distracting warnings from the development console. * 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 `webview_flutter_platform_interface`. See README for updated usage. * Updates minimum Flutter version to 2.10. ## 0.1.0+4 * Fixes incorrect escaping of some characters when setting the HTML to the iframe element. ## 0.1.0+3 * Minor fixes for new analysis options. ## 0.1.0+2 * Removes unnecessary imports. * Fixes unit tests to run on latest `master` version of Flutter. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 0.1.0+1 * Adds an explanation of registering the implementation in the README. ## 0.1.0 * First web implementation for webview_flutter ================================================ FILE: packages/webview_flutter/webview_flutter_web/LICENSE ================================================ 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. ================================================ FILE: packages/webview_flutter/webview_flutter_web/README.md ================================================ # webview\_flutter\_web This is an implementation of the [`webview_flutter`](https://pub.dev/packages/webview_flutter) plugin for web. It is currently severely limited and doesn't implement most of the available functionality. The following functionality is currently available: - `loadRequest` - `loadHtmlString` (Without `baseUrl`) Nothing else is currently supported. ## Usage This package is not an endorsed implementation of the `webview_flutter` plugin 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`. Once the step above is complete, the APIs from `webview_flutter` listed above can be used as normal on web. ## Tests Tests are contained in the `test` directory. You can run all tests from the root of the package with the following command: ```bash $ flutter test --platform chrome ``` This package uses `package:mockito` in some tests. Mock files can be updated from the root of the package like so: ```bash $ flutter pub run build_runner build --delete-conflicting-outputs ``` ================================================ FILE: packages/webview_flutter/webview_flutter_web/example/.metadata ================================================ # 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: 1e5cb2d87f8542f9fbbd0f22d528823274be0acb channel: master ================================================ FILE: packages/webview_flutter/webview_flutter_web/example/README.md ================================================ # 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. ================================================ FILE: packages/webview_flutter/webview_flutter_web/example/integration_test/legacy/webview_flutter_test.dart ================================================ // Copyright 2013 The Flutter 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 'dart:html' as html; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:webview_flutter_web_example/legacy/web_view.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); // URLs to navigate to in tests. These need to be URLs that we are confident will // always be accessible, and won't do redirection. (E.g., just // 'https://www.google.com/' will sometimes redirect traffic that looks // like it's coming from a bot, which is true of these tests). const String primaryUrl = 'https://flutter.dev/'; const String secondaryUrl = 'https://www.google.com/robots.txt'; testWidgets('initialUrl', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, ), ), ); await controllerCompleter.future; // Assert an iframe has been rendered to the DOM with the correct src attribute. final html.IFrameElement? element = html.document.querySelector('iframe') as html.IFrameElement?; expect(element, isNotNull); expect(element!.src, primaryUrl); }); testWidgets('loadUrl', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, ), ), ); final WebViewController controller = await controllerCompleter.future; await controller.loadUrl(secondaryUrl); // Assert an iframe has been rendered to the DOM with the correct src attribute. final html.IFrameElement? element = html.document.querySelector('iframe') as html.IFrameElement?; expect(element, isNotNull); expect(element!.src, secondaryUrl); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_web/example/integration_test/webview_flutter_test.dart ================================================ // Copyright 2013 The Flutter 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:html' as html; import 'dart:io'; // FIX (dit): Remove these integration tests, or make them run. They currently never fail. // (They won't run because they use `dart:io`. If you remove all `dart:io` bits from // this file, they start failing with `fail()`, for example.) import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'package:webview_flutter_web/webview_flutter_web.dart'; Future main() async { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); final HttpServer server = await HttpServer.bind(InternetAddress.anyIPv4, 0); server.forEach((HttpRequest request) { if (request.uri.path == '/hello.txt') { request.response.writeln('Hello, world.'); } else { fail('unexpected request: ${request.method} ${request.uri}'); } request.response.close(); }); final String prefixUrl = 'http://${server.address.address}:${server.port}'; final String primaryUrl = '$prefixUrl/hello.txt'; testWidgets('loadRequest', (WidgetTester tester) async { final WebWebViewController controller = WebWebViewController(const PlatformWebViewControllerCreationParams()) ..loadRequest( LoadRequestParams(uri: Uri.parse(primaryUrl)), ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Builder(builder: (BuildContext context) { return WebWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }), ), ); // Assert an iframe has been rendered to the DOM with the correct src attribute. final html.IFrameElement? element = html.document.querySelector('iframe') as html.IFrameElement?; expect(element, isNotNull); expect(element!.src, primaryUrl); }); testWidgets('loadHtmlString', (WidgetTester tester) async { final WebWebViewController controller = WebWebViewController(const PlatformWebViewControllerCreationParams()) ..loadHtmlString( 'data:text/html;charset=utf-8,${Uri.encodeFull('test html')}', ); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Builder(builder: (BuildContext context) { return WebWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }), ), ); // Assert an iframe has been rendered to the DOM with the correct src attribute. final html.IFrameElement? element = html.document.querySelector('iframe') as html.IFrameElement?; expect(element, isNotNull); expect( element!.src, 'data:text/html;charset=utf-8,data:text/html;charset=utf-8,test%2520html', ); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_web/example/lib/legacy/web_view.dart ================================================ // Copyright 2013 The Flutter 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/material.dart'; // ignore: implementation_imports import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; // ignore: implementation_imports import 'package:webview_flutter_web/src/webview_flutter_web_legacy.dart'; /// Optional callback invoked when a web view is first created. [controller] is /// the [WebViewController] for the created web view. typedef WebViewCreatedCallback = void Function(WebViewController controller); /// A web view widget for showing html content. /// /// The [WebView] widget wraps around the [WebWebViewPlatform]. /// /// The [WebView] widget is controlled using the [WebViewController] which is /// provided through the `onWebViewCreated` callback. /// /// In this example project it's main purpose is to facilitate integration /// testing of the `webview_flutter_web` package. class WebView extends StatefulWidget { /// Creates a new web view. /// /// The web view can be controlled using a `WebViewController` that is passed to the /// `onWebViewCreated` callback once the web view is created. const WebView({ Key? key, this.onWebViewCreated, this.initialUrl, }) : super(key: key); /// The WebView platform that's used by this WebView. /// /// The default value is [WebWebViewPlatform]. /// This property can be set to use a custom platform implementation for WebViews. /// Setting `platform` doesn't affect [WebView]s that were already created. static WebViewPlatform platform = WebWebViewPlatform(); /// If not null invoked once the web view is created. final WebViewCreatedCallback? onWebViewCreated; /// The initial URL to load. final String? initialUrl; @override State createState() => _WebViewState(); } class _WebViewState extends State { final Completer _controller = Completer(); late final _PlatformCallbacksHandler _platformCallbacksHandler; @override void initState() { super.initState(); _platformCallbacksHandler = _PlatformCallbacksHandler(); } @override void didUpdateWidget(WebView oldWidget) { super.didUpdateWidget(oldWidget); _controller.future.then((WebViewController controller) { controller.updateWidget(widget); }); } @override Widget build(BuildContext context) { return WebView.platform.build( context: context, onWebViewPlatformCreated: (WebViewPlatformController? webViewPlatformController) { final WebViewController controller = WebViewController( widget, webViewPlatformController!, ); _controller.complete(controller); if (widget.onWebViewCreated != null) { widget.onWebViewCreated!(controller); } }, webViewPlatformCallbacksHandler: _platformCallbacksHandler, creationParams: CreationParams( initialUrl: widget.initialUrl, webSettings: _webSettingsFromWidget(widget), ), javascriptChannelRegistry: JavascriptChannelRegistry({}), ); } } class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { _PlatformCallbacksHandler(); @override FutureOr onNavigationRequest( {required String url, required bool isForMainFrame}) { throw UnimplementedError(); } @override void onPageFinished(String url) {} @override void onPageStarted(String url) {} @override void onProgress(int progress) {} @override void onWebResourceError(WebResourceError error) {} } /// Controls a [WebView]. /// /// A [WebViewController] instance can be obtained by setting the [WebView.onWebViewCreated] /// callback for a [WebView] widget. class WebViewController { /// Creates a [WebViewController] which can be used to control the provided /// [WebView] widget. WebViewController( this._widget, this._webViewPlatformController, ) : assert(_webViewPlatformController != null) { _settings = _webSettingsFromWidget(_widget); } final WebViewPlatformController _webViewPlatformController; late WebSettings _settings; WebView _widget; /// Loads the specified URL. /// /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will /// be added as key value pairs of HTTP headers for the request. /// /// `url` must not be null. /// /// Throws an ArgumentError if `url` is not a valid URL string. Future loadUrl( String url, { Map? headers, }) async { assert(url != null); _validateUrlString(url); return _webViewPlatformController.loadUrl(url, headers); } /// Loads a page by making the specified request. Future loadRequest(WebViewRequest request) async { return _webViewPlatformController.loadRequest(request); } /// Accessor to the current URL that the WebView is displaying. /// /// If [WebView.initialUrl] was never specified, returns `null`. /// Note that this operation is asynchronous, and it is possible that the /// current URL changes again by the time this function returns (in other /// words, by the time this future completes, the WebView may be displaying a /// different URL). Future currentUrl() { return _webViewPlatformController.currentUrl(); } /// Checks whether there's a back history item. /// /// Note that this operation is asynchronous, and it is possible that the "canGoBack" state has /// changed by the time the future completed. Future canGoBack() { return _webViewPlatformController.canGoBack(); } /// Checks whether there's a forward history item. /// /// Note that this operation is asynchronous, and it is possible that the "canGoForward" state has /// changed by the time the future completed. Future canGoForward() { return _webViewPlatformController.canGoForward(); } /// Goes back in the history of this WebView. /// /// If there is no back history item this is a no-op. Future goBack() { return _webViewPlatformController.goBack(); } /// Goes forward in the history of this WebView. /// /// If there is no forward history item this is a no-op. Future goForward() { return _webViewPlatformController.goForward(); } /// Reloads the current URL. Future reload() { return _webViewPlatformController.reload(); } /// Clears all caches used by the [WebView]. /// /// The following caches are cleared: /// 1. Browser HTTP Cache. /// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches. /// These are not yet supported in iOS WkWebView. Service workers tend to use this cache. /// 3. Application cache. /// 4. Local Storage. /// /// Note: Calling this method also triggers a reload. Future clearCache() async { await _webViewPlatformController.clearCache(); return reload(); } /// Update the widget managed by the [WebViewController]. Future updateWidget(WebView widget) async { _widget = widget; await _updateSettings(_webSettingsFromWidget(widget)); } Future _updateSettings(WebSettings newSettings) { final WebSettings update = _clearUnchangedWebSettings(_settings, newSettings); _settings = newSettings; return _webViewPlatformController.updateSettings(update); } @visibleForTesting // ignore: public_member_api_docs Future evaluateJavascript(String javascriptString) { if (_settings.javascriptMode == JavascriptMode.disabled) { return Future.error(FlutterError( 'JavaScript mode must be enabled/unrestricted when calling evaluateJavascript.')); } return _webViewPlatformController.evaluateJavascript(javascriptString); } /// Runs the given JavaScript in the context of the current page. /// If you are looking for the result, use [runJavascriptReturningResult] instead. /// The Future completes with an error if a JavaScript error occurred. /// /// When running JavaScript in a [WebView], it is best practice to wait for // the [WebView.onPageFinished] callback. This guarantees all the JavaScript // embedded in the main frame HTML has been loaded. Future runJavascript(String javaScriptString) { if (_settings.javascriptMode == JavascriptMode.disabled) { return Future.error(FlutterError( 'Javascript mode must be enabled/unrestricted when calling runJavascript.')); } return _webViewPlatformController.runJavascript(javaScriptString); } /// Runs the given JavaScript in the context of the current page, and returns the result. /// /// Returns the evaluation result as a JSON formatted string. /// The Future completes with an error if a JavaScript error occurred. /// /// When evaluating JavaScript in a [WebView], it is best practice to wait for /// the [WebView.onPageFinished] callback. This guarantees all the JavaScript /// embedded in the main frame HTML has been loaded. Future runJavascriptReturningResult(String javaScriptString) { if (_settings.javascriptMode == JavascriptMode.disabled) { return Future.error(FlutterError( 'Javascript mode must be enabled/unrestricted when calling runJavascriptReturningResult.')); } return _webViewPlatformController .runJavascriptReturningResult(javaScriptString); } /// Returns the title of the currently loaded page. Future getTitle() { return _webViewPlatformController.getTitle(); } /// Sets the WebView's content scroll position. /// /// The parameters `x` and `y` specify the scroll position in WebView pixels. Future scrollTo(int x, int y) { return _webViewPlatformController.scrollTo(x, y); } /// Move the scrolled position of this view. /// /// The parameters `x` and `y` specify the amount of WebView pixels to scroll by horizontally and vertically respectively. Future scrollBy(int x, int y) { return _webViewPlatformController.scrollBy(x, y); } /// Return the horizontal scroll position, in WebView pixels, of this view. /// /// Scroll position is measured from left. Future getScrollX() { return _webViewPlatformController.getScrollX(); } /// Return the vertical scroll position, in WebView pixels, of this view. /// /// Scroll position is measured from top. Future getScrollY() { return _webViewPlatformController.getScrollY(); } // This method assumes that no fields in `currentValue` are null. WebSettings _clearUnchangedWebSettings( WebSettings currentValue, WebSettings newValue) { assert(currentValue.javascriptMode != null); assert(currentValue.hasNavigationDelegate != null); assert(currentValue.hasProgressTracking != null); assert(currentValue.debuggingEnabled != null); assert(currentValue.userAgent != null); assert(newValue.javascriptMode != null); assert(newValue.hasNavigationDelegate != null); assert(newValue.debuggingEnabled != null); assert(newValue.userAgent != null); assert(newValue.zoomEnabled != null); JavascriptMode? javascriptMode; bool? hasNavigationDelegate; bool? hasProgressTracking; bool? debuggingEnabled; WebSetting userAgent = const WebSetting.absent(); bool? zoomEnabled; if (currentValue.javascriptMode != newValue.javascriptMode) { javascriptMode = newValue.javascriptMode; } if (currentValue.hasNavigationDelegate != newValue.hasNavigationDelegate) { hasNavigationDelegate = newValue.hasNavigationDelegate; } if (currentValue.hasProgressTracking != newValue.hasProgressTracking) { hasProgressTracking = newValue.hasProgressTracking; } if (currentValue.debuggingEnabled != newValue.debuggingEnabled) { debuggingEnabled = newValue.debuggingEnabled; } if (currentValue.userAgent != newValue.userAgent) { userAgent = newValue.userAgent; } if (currentValue.zoomEnabled != newValue.zoomEnabled) { zoomEnabled = newValue.zoomEnabled; } return WebSettings( javascriptMode: javascriptMode, hasNavigationDelegate: hasNavigationDelegate, hasProgressTracking: hasProgressTracking, debuggingEnabled: debuggingEnabled, userAgent: userAgent, zoomEnabled: zoomEnabled, ); } // Throws an ArgumentError if `url` is not a valid URL string. void _validateUrlString(String url) { try { final Uri uri = Uri.parse(url); if (uri.scheme.isEmpty) { throw ArgumentError('Missing scheme in URL string: "$url"'); } } on FormatException catch (e) { throw ArgumentError(e); } } } WebSettings _webSettingsFromWidget(WebView widget) { return WebSettings( javascriptMode: JavascriptMode.unrestricted, hasNavigationDelegate: false, hasProgressTracking: false, debuggingEnabled: false, gestureNavigationEnabled: false, allowsInlineMediaPlayback: true, userAgent: const WebSetting.of(''), zoomEnabled: false, ); } ================================================ FILE: packages/webview_flutter/webview_flutter_web/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'package:webview_flutter_web/webview_flutter_web.dart'; void main() { WebViewPlatform.instance = WebWebViewPlatform(); runApp(const MaterialApp(home: _WebViewExample())); } class _WebViewExample extends StatefulWidget { const _WebViewExample({Key? key}) : super(key: key); @override _WebViewExampleState createState() => _WebViewExampleState(); } class _WebViewExampleState extends State<_WebViewExample> { final PlatformWebViewController _controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), )..loadRequest( LoadRequestParams( uri: Uri.parse('https://flutter.dev'), ), ); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Flutter WebView example'), actions: [ _SampleMenu(_controller), ], ), body: PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: _controller), ).build(context), ); } } enum _MenuOptions { doPostRequest, } class _SampleMenu extends StatelessWidget { const _SampleMenu(this.controller); final PlatformWebViewController controller; @override Widget build(BuildContext context) { return PopupMenuButton<_MenuOptions>( onSelected: (_MenuOptions value) { switch (value) { case _MenuOptions.doPostRequest: _onDoPostRequest(controller); break; } }, itemBuilder: (BuildContext context) => >[ const PopupMenuItem<_MenuOptions>( value: _MenuOptions.doPostRequest, child: Text('Post Request'), ), ], ); } Future _onDoPostRequest(PlatformWebViewController controller) async { final LoadRequestParams params = LoadRequestParams( uri: Uri.parse('https://httpbin.org/post'), method: LoadRequestMethod.post, headers: const { 'foo': 'bar', 'Content-Type': 'text/plain' }, body: Uint8List.fromList('Test Body'.codeUnits), ); await controller.loadRequest(params); } } ================================================ FILE: packages/webview_flutter/webview_flutter_web/example/pubspec.yaml ================================================ name: webview_flutter_web_example description: Demonstrates how to use the webview_flutter_web plugin. publish_to: none environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter flutter_web_plugins: sdk: flutter webview_flutter_platform_interface: ^2.0.0 webview_flutter_web: # When depending on this package from a real application you should use: # webview_flutter_web: ^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: ../ dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true ================================================ FILE: packages/webview_flutter/webview_flutter_web/example/run_test.sh ================================================ #!/usr/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. if pgrep -lf chromedriver > /dev/null; then echo "chromedriver is running." if [ $# -eq 0 ]; then echo "No target specified, running all tests..." find integration_test/ -iname *_test.dart | xargs -n1 -i -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target='{}' else echo "Running test target: $1..." set -x flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target=$1 fi else echo "chromedriver is not running." echo "Please, check the README.md for instructions on how to use run_test.sh" fi ================================================ FILE: packages/webview_flutter/webview_flutter_web/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/webview_flutter/webview_flutter_web/example/web/index.html ================================================ webview_flutter_web Example ================================================ FILE: packages/webview_flutter/webview_flutter_web/example/web/manifest.json ================================================ { "name": "webview_flutter_web Example", "short_name": "example", "start_url": ".", "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", "description": "A new Flutter project.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [ { "src": "icons/Icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" }, { "src": "icons/Icon-maskable-192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { "src": "icons/Icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } ] } ================================================ FILE: packages/webview_flutter/webview_flutter_web/lib/src/content_type.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Class to represent a content-type header value. class ContentType { /// Creates a [ContentType] instance by parsing a "content-type" response [header]. /// /// See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type /// See: https://httpwg.org/specs/rfc9110.html#media.type ContentType.parse(String header) { final Iterable chunks = header.split(';').map((String e) => e.trim().toLowerCase()); for (final String chunk in chunks) { if (!chunk.contains('=')) { _mimeType = chunk; } else { final List bits = chunk.split('=').map((String e) => e.trim()).toList(); assert(bits.length == 2); switch (bits[0]) { case 'charset': _charset = bits[1]; break; case 'boundary': _boundary = bits[1]; break; default: throw StateError('Unable to parse "$chunk" in content-type.'); } } } } String? _mimeType; String? _charset; String? _boundary; /// The MIME-type of the resource or the data. String? get mimeType => _mimeType; /// The character encoding standard. String? get charset => _charset; /// The separation boundary for multipart entities. String? get boundary => _boundary; } ================================================ FILE: packages/webview_flutter/webview_flutter_web/lib/src/http_request_factory.dart ================================================ // Copyright 2013 The Flutter 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:html'; /// Factory class for creating [HttpRequest] instances. class HttpRequestFactory { /// Creates a [HttpRequestFactory]. const HttpRequestFactory(); /// Creates and sends a URL request for the specified [url]. /// /// By default `request` will perform an HTTP GET request, but a different /// method (`POST`, `PUT`, `DELETE`, etc) can be used by specifying the /// [method] parameter. (See also [HttpRequest.postFormData] for `POST` /// requests only. /// /// The Future is completed when the response is available. /// /// If specified, `sendData` will send data in the form of a [ByteBuffer], /// [Blob], [Document], [String], or [FormData] along with the HttpRequest. /// /// If specified, [responseType] sets the desired response format for the /// request. By default it is [String], but can also be 'arraybuffer', 'blob', /// 'document', 'json', or 'text'. See also [HttpRequest.responseType] /// for more information. /// /// The [withCredentials] parameter specified that credentials such as a cookie /// (already) set in the header or /// [authorization headers](http://tools.ietf.org/html/rfc1945#section-10.2) /// should be specified for the request. Details to keep in mind when using /// credentials: /// /// /// Using credentials is only useful for cross-origin requests. /// /// The `Access-Control-Allow-Origin` header of `url` cannot contain a wildcard (///). /// /// The `Access-Control-Allow-Credentials` header of `url` must be set to true. /// /// If `Access-Control-Expose-Headers` has not been set to true, only a subset of all the response headers will be returned when calling [getAllResponseHeaders]. /// /// The following is equivalent to the [getString] sample above: /// /// var name = Uri.encodeQueryComponent('John'); /// var id = Uri.encodeQueryComponent('42'); /// HttpRequest.request('users.json?name=$name&id=$id') /// .then((HttpRequest resp) { /// // Do something with the response. /// }); /// /// Here's an example of submitting an entire form with [FormData]. /// /// var myForm = querySelector('form#myForm'); /// var data = new FormData(myForm); /// HttpRequest.request('/submit', method: 'POST', sendData: data) /// .then((HttpRequest resp) { /// // Do something with the response. /// }); /// /// Note that requests for file:// URIs are only supported by Chrome extensions /// with appropriate permissions in their manifest. Requests to file:// URIs /// will also never fail- the Future will always complete successfully, even /// when the file cannot be found. /// /// See also: [authorization headers](http://en.wikipedia.org/wiki/Basic_access_authentication). Future request(String url, {String? method, bool? withCredentials, String? responseType, String? mimeType, Map? requestHeaders, dynamic sendData, void Function(ProgressEvent e)? onProgress}) { return HttpRequest.request(url, method: method, withCredentials: withCredentials, responseType: responseType, mimeType: mimeType, requestHeaders: requestHeaders, sendData: sendData, onProgress: onProgress); } } ================================================ FILE: packages/webview_flutter/webview_flutter_web/lib/src/shims/dart_ui.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// This file shims dart:ui in web-only scenarios, getting rid of the need to /// suppress analyzer warnings. // TODO(BeMacized): Remove this file once web-only dart:ui APIs, // are exposed from a dedicated place. flutter/flutter#55000 export 'dart_ui_fake.dart' if (dart.library.html) 'dart_ui_real.dart'; ================================================ FILE: packages/webview_flutter/webview_flutter_web/lib/src/shims/dart_ui_fake.dart ================================================ // Copyright 2013 The Flutter 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:html' as html; // Fake interface for the logic that this package needs from (web-only) dart:ui. // This is conditionally exported so the analyzer sees these methods as available. // ignore_for_file: avoid_classes_with_only_static_members // ignore_for_file: camel_case_types /// Shim for web_ui engine.PlatformViewRegistry /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/ui.dart#L62 class platformViewRegistry { /// Shim for registerViewFactory /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/ui.dart#L72 static bool registerViewFactory( String viewTypeId, html.Element Function(int viewId) viewFactory) { return false; } } /// Shim for web_ui engine.AssetManager. /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/src/engine/assets.dart#L12 class webOnlyAssetManager { /// Shim for getAssetUrl. /// https://github.com/flutter/engine/blob/main/lib/web_ui/lib/src/engine/assets.dart#L45 static String getAssetUrl(String asset) => ''; } /// Signature of callbacks that have no arguments and return no data. typedef VoidCallback = void Function(); ================================================ FILE: packages/webview_flutter/webview_flutter_web/lib/src/shims/dart_ui_real.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'dart:ui'; ================================================ FILE: packages/webview_flutter/webview_flutter_web/lib/src/web_webview_controller.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:convert'; import 'dart:html' as html; import 'package:flutter/cupertino.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'content_type.dart'; import 'http_request_factory.dart'; import 'shims/dart_ui.dart' as ui; /// An implementation of [PlatformWebViewControllerCreationParams] using Flutter /// for Web API. @immutable class WebWebViewControllerCreationParams extends PlatformWebViewControllerCreationParams { /// Creates a new [AndroidWebViewControllerCreationParams] instance. WebWebViewControllerCreationParams({ @visibleForTesting this.httpRequestFactory = const HttpRequestFactory(), }) : super(); /// Creates a [WebWebViewControllerCreationParams] instance based on [PlatformWebViewControllerCreationParams]. WebWebViewControllerCreationParams.fromPlatformWebViewControllerCreationParams( // Recommended placeholder to prevent being broken by platform interface. // ignore: avoid_unused_constructor_parameters PlatformWebViewControllerCreationParams params, { @visibleForTesting HttpRequestFactory httpRequestFactory = const HttpRequestFactory(), }) : this(httpRequestFactory: httpRequestFactory); static int _nextIFrameId = 0; /// Handles creating and sending URL requests. final HttpRequestFactory httpRequestFactory; /// The underlying element used as the WebView. @visibleForTesting final html.IFrameElement iFrame = html.IFrameElement() ..id = 'webView${_nextIFrameId++}' ..style.width = '100%' ..style.height = '100%' ..style.border = 'none'; } /// An implementation of [PlatformWebViewController] using Flutter for Web API. class WebWebViewController extends PlatformWebViewController { /// Constructs a [WebWebViewController]. WebWebViewController(PlatformWebViewControllerCreationParams params) : super.implementation(params is WebWebViewControllerCreationParams ? params : WebWebViewControllerCreationParams .fromPlatformWebViewControllerCreationParams(params)); WebWebViewControllerCreationParams get _webWebViewParams => params as WebWebViewControllerCreationParams; @override Future loadHtmlString(String html, {String? baseUrl}) async { // ignore: unsafe_html _webWebViewParams.iFrame.src = Uri.dataFromString( html, mimeType: 'text/html', encoding: utf8, ).toString(); } @override Future loadRequest(LoadRequestParams params) async { if (!params.uri.hasScheme) { throw ArgumentError( 'LoadRequestParams#uri is required to have a scheme.'); } if (params.headers.isEmpty && (params.body == null || params.body!.isEmpty) && params.method == LoadRequestMethod.get) { // ignore: unsafe_html _webWebViewParams.iFrame.src = params.uri.toString(); } else { await _updateIFrameFromXhr(params); } } /// Performs an AJAX request defined by [params]. Future _updateIFrameFromXhr(LoadRequestParams params) async { final html.HttpRequest httpReq = await _webWebViewParams.httpRequestFactory.request( params.uri.toString(), method: params.method.serialize(), requestHeaders: params.headers, sendData: params.body, ); final String header = httpReq.getResponseHeader('content-type') ?? 'text/html'; final ContentType contentType = ContentType.parse(header); final Encoding encoding = Encoding.getByName(contentType.charset) ?? utf8; // ignore: unsafe_html _webWebViewParams.iFrame.src = Uri.dataFromString( httpReq.responseText ?? '', mimeType: contentType.mimeType, encoding: encoding, ).toString(); } } /// An implementation of [PlatformWebViewWidget] using Flutter the for Web API. class WebWebViewWidget extends PlatformWebViewWidget { /// Constructs a [WebWebViewWidget]. WebWebViewWidget(PlatformWebViewWidgetCreationParams params) : super.implementation(params) { final WebWebViewController controller = params.controller as WebWebViewController; ui.platformViewRegistry.registerViewFactory( controller._webWebViewParams.iFrame.id, (int viewId) => controller._webWebViewParams.iFrame, ); } @override Widget build(BuildContext context) { return HtmlElementView( key: params.key, viewType: (params.controller as WebWebViewController) ._webWebViewParams .iFrame .id, ); } } ================================================ FILE: packages/webview_flutter/webview_flutter_web/lib/src/web_webview_platform.dart ================================================ // Copyright 2013 The Flutter 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_web_plugins/flutter_web_plugins.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'web_webview_controller.dart'; /// An implementation of [WebViewPlatform] using Flutter for Web API. class WebWebViewPlatform extends WebViewPlatform { @override PlatformWebViewController createPlatformWebViewController( PlatformWebViewControllerCreationParams params, ) { return WebWebViewController(params); } @override PlatformWebViewWidget createPlatformWebViewWidget( PlatformWebViewWidgetCreationParams params, ) { return WebWebViewWidget(params); } /// Gets called when the plugin is registered. static void registerWith(Registrar registrar) { WebViewPlatform.instance = WebWebViewPlatform(); } } ================================================ FILE: packages/webview_flutter/webview_flutter_web/lib/src/webview_flutter_web_legacy.dart ================================================ // Copyright 2013 The Flutter 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 'dart:convert'; import 'dart:html'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; // ignore: implementation_imports import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; import 'http_request_factory.dart'; import 'shims/dart_ui.dart' as ui; /// Builds an iframe based WebView. /// /// This is used as the default implementation for [WebView.platform] on web. class WebWebViewPlatform implements WebViewPlatform { /// Constructs a new instance of [WebWebViewPlatform]. WebWebViewPlatform() { ui.platformViewRegistry.registerViewFactory( 'webview-iframe', (int viewId) => IFrameElement() ..id = 'webview-$viewId' ..width = '100%' ..height = '100%' ..style.border = 'none'); } @override Widget build({ required BuildContext context, required CreationParams creationParams, required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, required JavascriptChannelRegistry? javascriptChannelRegistry, WebViewPlatformCreatedCallback? onWebViewPlatformCreated, Set>? gestureRecognizers, }) { return HtmlElementView( viewType: 'webview-iframe', onPlatformViewCreated: (int viewId) { if (onWebViewPlatformCreated == null) { return; } final IFrameElement element = document.getElementById('webview-$viewId')! as IFrameElement; if (creationParams.initialUrl != null) { // ignore: unsafe_html element.src = creationParams.initialUrl; } onWebViewPlatformCreated(WebWebViewPlatformController( element, )); }, ); } @override Future clearCookies() async => false; /// Gets called when the plugin is registered. static void registerWith(Registrar registrar) {} } /// Implementation of [WebViewPlatformController] for web. class WebWebViewPlatformController implements WebViewPlatformController { /// Constructs a [WebWebViewPlatformController]. WebWebViewPlatformController(this._element); final IFrameElement _element; HttpRequestFactory _httpRequestFactory = const HttpRequestFactory(); /// Setter for setting the HttpRequestFactory, for testing purposes. @visibleForTesting // ignore: avoid_setters_without_getters set httpRequestFactory(HttpRequestFactory factory) { _httpRequestFactory = factory; } @override Future addJavascriptChannels(Set javascriptChannelNames) { throw UnimplementedError(); } @override Future canGoBack() { throw UnimplementedError(); } @override Future canGoForward() { throw UnimplementedError(); } @override Future clearCache() { throw UnimplementedError(); } @override Future currentUrl() { throw UnimplementedError(); } @override Future evaluateJavascript(String javascript) { throw UnimplementedError(); } @override Future getScrollX() { throw UnimplementedError(); } @override Future getScrollY() { throw UnimplementedError(); } @override Future getTitle() { throw UnimplementedError(); } @override Future goBack() { throw UnimplementedError(); } @override Future goForward() { throw UnimplementedError(); } @override Future loadUrl(String url, Map? headers) async { // ignore: unsafe_html _element.src = url; } @override Future reload() { throw UnimplementedError(); } @override Future removeJavascriptChannels(Set javascriptChannelNames) { throw UnimplementedError(); } @override Future runJavascript(String javascript) { throw UnimplementedError(); } @override Future runJavascriptReturningResult(String javascript) { throw UnimplementedError(); } @override Future scrollBy(int x, int y) { throw UnimplementedError(); } @override Future scrollTo(int x, int y) { throw UnimplementedError(); } @override Future updateSettings(WebSettings setting) { throw UnimplementedError(); } @override Future loadFile(String absoluteFilePath) { throw UnimplementedError(); } @override Future loadHtmlString( String html, { String? baseUrl, }) async { // ignore: unsafe_html _element.src = Uri.dataFromString( html, mimeType: 'text/html', encoding: utf8, ).toString(); } @override Future loadRequest(WebViewRequest request) async { if (!request.uri.hasScheme) { throw ArgumentError('WebViewRequest#uri is required to have a scheme.'); } final HttpRequest httpReq = await _httpRequestFactory.request( request.uri.toString(), method: request.method.serialize(), requestHeaders: request.headers, sendData: request.body); final String contentType = httpReq.getResponseHeader('content-type') ?? 'text/html'; // ignore: unsafe_html _element.src = Uri.dataFromString( httpReq.responseText ?? '', mimeType: contentType, encoding: utf8, ).toString(); } @override Future loadFlutterAsset(String key) { throw UnimplementedError(); } } ================================================ FILE: packages/webview_flutter/webview_flutter_web/lib/webview_flutter_web.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. library webview_flutter_web; export 'src/http_request_factory.dart'; export 'src/web_webview_controller.dart'; export 'src/web_webview_platform.dart'; ================================================ FILE: packages/webview_flutter/webview_flutter_web/pubspec.yaml ================================================ 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.2 environment: sdk: ">=2.14.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: webview_flutter platforms: web: pluginClass: WebWebViewPlatform fileName: webview_flutter_web.dart dependencies: flutter: sdk: flutter flutter_web_plugins: sdk: flutter webview_flutter_platform_interface: ^2.0.0 dev_dependencies: build_runner: ^2.1.5 flutter_driver: sdk: flutter flutter_test: sdk: flutter mockito: ^5.3.2 ================================================ FILE: packages/webview_flutter/webview_flutter_web/test/content_type_test.dart ================================================ // Copyright 2013 The Flutter 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:webview_flutter_web/src/content_type.dart'; void main() { group('ContentType.parse', () { test('basic content-type (lowers case)', () { final ContentType contentType = ContentType.parse('text/pLaIn'); expect(contentType.mimeType, 'text/plain'); expect(contentType.boundary, isNull); expect(contentType.charset, isNull); }); test('with charset', () { final ContentType contentType = ContentType.parse('text/pLaIn; charset=utf-8'); expect(contentType.mimeType, 'text/plain'); expect(contentType.boundary, isNull); expect(contentType.charset, 'utf-8'); }); test('with boundary', () { final ContentType contentType = ContentType.parse('text/pLaIn; boundary=---xyz'); expect(contentType.mimeType, 'text/plain'); expect(contentType.boundary, '---xyz'); expect(contentType.charset, isNull); }); test('with charset and boundary', () { final ContentType contentType = ContentType.parse('text/pLaIn; charset=utf-8; boundary=---xyz'); expect(contentType.mimeType, 'text/plain'); expect(contentType.boundary, '---xyz'); expect(contentType.charset, 'utf-8'); }); test('with boundary and charset', () { final ContentType contentType = ContentType.parse('text/pLaIn; boundary=---xyz; charset=utf-8'); expect(contentType.mimeType, 'text/plain'); expect(contentType.boundary, '---xyz'); expect(contentType.charset, 'utf-8'); }); test('with a bunch of whitespace, boundary and charset', () { final ContentType contentType = ContentType.parse( ' text/pLaIn ; boundary=---xyz; charset=utf-8 '); expect(contentType.mimeType, 'text/plain'); expect(contentType.boundary, '---xyz'); expect(contentType.charset, 'utf-8'); }); test('empty string', () { final ContentType contentType = ContentType.parse(''); expect(contentType.mimeType, ''); expect(contentType.boundary, isNull); expect(contentType.charset, isNull); }); test('unknown parameter (throws)', () { expect(() { ContentType.parse('text/pLaIn; wrong=utf-8'); }, throwsStateError); }); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_web/test/legacy/webview_flutter_web_test.dart ================================================ // Copyright 2013 The Flutter 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:html'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; import 'package:webview_flutter_web/src/http_request_factory.dart'; import 'package:webview_flutter_web/src/webview_flutter_web_legacy.dart'; import 'webview_flutter_web_test.mocks.dart'; @GenerateMocks([ IFrameElement, BuildContext, CreationParams, WebViewPlatformCallbacksHandler, HttpRequestFactory, HttpRequest, ]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('WebWebViewPlatform', () { test('build returns a HtmlElementView', () { // Setup final WebWebViewPlatform platform = WebWebViewPlatform(); // Run final Widget widget = platform.build( context: MockBuildContext(), creationParams: CreationParams(), webViewPlatformCallbacksHandler: MockWebViewPlatformCallbacksHandler(), javascriptChannelRegistry: null, ); // Verify expect(widget, isA()); }); }); group('WebWebViewPlatformController', () { test('loadUrl sets url on iframe src attribute', () { // Setup final MockIFrameElement mockElement = MockIFrameElement(); final WebWebViewPlatformController controller = WebWebViewPlatformController( mockElement, ); // Run controller.loadUrl('test url', null); // Verify verify(mockElement.src = 'test url'); }); group('loadHtmlString', () { test('loadHtmlString loads html into iframe', () { // Setup final MockIFrameElement mockElement = MockIFrameElement(); final WebWebViewPlatformController controller = WebWebViewPlatformController( mockElement, ); // Run controller.loadHtmlString('test html'); // Verify verify(mockElement.src = 'data:text/html;charset=utf-8,${Uri.encodeFull('test html')}'); }); test('loadHtmlString escapes "#" correctly', () { // Setup final MockIFrameElement mockElement = MockIFrameElement(); final WebWebViewPlatformController controller = WebWebViewPlatformController( mockElement, ); // Run controller.loadHtmlString('#'); // Verify verify(mockElement.src = argThat(contains('%23'))); }); }); group('loadRequest', () { test('loadRequest throws ArgumentError on missing scheme', () { // Setup final MockIFrameElement mockElement = MockIFrameElement(); final WebWebViewPlatformController controller = WebWebViewPlatformController( mockElement, ); // Run & Verify expect( () async => controller.loadRequest( WebViewRequest( uri: Uri.parse('flutter.dev'), method: WebViewRequestMethod.get, ), ), throwsA(const TypeMatcher())); }); test('loadRequest makes request and loads response into iframe', () async { // Setup final MockIFrameElement mockElement = MockIFrameElement(); final WebWebViewPlatformController controller = WebWebViewPlatformController( mockElement, ); final MockHttpRequest mockHttpRequest = MockHttpRequest(); when(mockHttpRequest.getResponseHeader('content-type')) .thenReturn('text/plain'); when(mockHttpRequest.responseText).thenReturn('test data'); final MockHttpRequestFactory mockHttpRequestFactory = MockHttpRequestFactory(); when(mockHttpRequestFactory.request( any, method: anyNamed('method'), requestHeaders: anyNamed('requestHeaders'), sendData: anyNamed('sendData'), )).thenAnswer((_) => Future.value(mockHttpRequest)); controller.httpRequestFactory = mockHttpRequestFactory; // Run await controller.loadRequest( WebViewRequest( uri: Uri.parse('https://flutter.dev'), method: WebViewRequestMethod.post, body: Uint8List.fromList('test body'.codeUnits), headers: {'Foo': 'Bar'}), ); // Verify verify(mockHttpRequestFactory.request( 'https://flutter.dev', method: 'post', requestHeaders: {'Foo': 'Bar'}, sendData: Uint8List.fromList('test body'.codeUnits), )); verify(mockElement.src = 'data:;charset=utf-8,${Uri.encodeFull('test data')}'); }); test('loadRequest escapes "#" correctly', () async { // Setup final MockIFrameElement mockElement = MockIFrameElement(); final WebWebViewPlatformController controller = WebWebViewPlatformController( mockElement, ); final MockHttpRequest mockHttpRequest = MockHttpRequest(); when(mockHttpRequest.getResponseHeader('content-type')) .thenReturn('text/html'); when(mockHttpRequest.responseText).thenReturn('#'); final MockHttpRequestFactory mockHttpRequestFactory = MockHttpRequestFactory(); when(mockHttpRequestFactory.request( any, method: anyNamed('method'), requestHeaders: anyNamed('requestHeaders'), sendData: anyNamed('sendData'), )).thenAnswer((_) => Future.value(mockHttpRequest)); controller.httpRequestFactory = mockHttpRequestFactory; // Run await controller.loadRequest( WebViewRequest( uri: Uri.parse('https://flutter.dev'), method: WebViewRequestMethod.post, body: Uint8List.fromList('test body'.codeUnits), headers: {'Foo': 'Bar'}), ); // Verify verify(mockElement.src = argThat(contains('%23'))); }); }); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_web/test/legacy/webview_flutter_web_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_web/test/legacy/webview_flutter_web_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i6; import 'dart:html' as _i2; import 'dart:math' as _i3; import 'package:flutter/foundation.dart' as _i5; import 'package:flutter/src/widgets/notification_listener.dart' as _i7; import 'package:flutter/widgets.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_platform_interface/src/legacy/platform_interface/webview_platform_callbacks_handler.dart' as _i9; import 'package:webview_flutter_platform_interface/src/legacy/types/types.dart' as _i8; import 'package:webview_flutter_web/src/http_request_factory.dart' as _i10; // 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 class _FakeCssClassSet_0 extends _i1.SmartFake implements _i2.CssClassSet { _FakeCssClassSet_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeRectangle_1 extends _i1.SmartFake implements _i3.Rectangle { _FakeRectangle_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeCssRect_2 extends _i1.SmartFake implements _i2.CssRect { _FakeCssRect_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakePoint_3 extends _i1.SmartFake implements _i3.Point { _FakePoint_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeElementEvents_4 extends _i1.SmartFake implements _i2.ElementEvents { _FakeElementEvents_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeCssStyleDeclaration_5 extends _i1.SmartFake implements _i2.CssStyleDeclaration { _FakeCssStyleDeclaration_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeElementStream_6 extends _i1.SmartFake implements _i2.ElementStream { _FakeElementStream_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeElementList_7 extends _i1.SmartFake implements _i2.ElementList { _FakeElementList_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeScrollState_8 extends _i1.SmartFake implements _i2.ScrollState { _FakeScrollState_8( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeAnimation_9 extends _i1.SmartFake implements _i2.Animation { _FakeAnimation_9( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeElement_10 extends _i1.SmartFake implements _i2.Element { _FakeElement_10( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeShadowRoot_11 extends _i1.SmartFake implements _i2.ShadowRoot { _FakeShadowRoot_11( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeDocumentFragment_12 extends _i1.SmartFake implements _i2.DocumentFragment { _FakeDocumentFragment_12( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeNode_13 extends _i1.SmartFake implements _i2.Node { _FakeNode_13( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWidget_14 extends _i1.SmartFake implements _i4.Widget { _FakeWidget_14( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeInheritedWidget_15 extends _i1.SmartFake implements _i4.InheritedWidget { _FakeInheritedWidget_15( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => super.toString(); } class _FakeDiagnosticsNode_16 extends _i1.SmartFake implements _i5.DiagnosticsNode { _FakeDiagnosticsNode_16( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); @override String toString({ _i5.TextTreeConfiguration? parentConfiguration, _i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info, }) => super.toString(); } class _FakeHttpRequest_17 extends _i1.SmartFake implements _i2.HttpRequest { _FakeHttpRequest_17( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeHttpRequestUpload_18 extends _i1.SmartFake implements _i2.HttpRequestUpload { _FakeHttpRequestUpload_18( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeEvents_19 extends _i1.SmartFake implements _i2.Events { _FakeEvents_19( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [IFrameElement]. /// /// See the documentation for Mockito's code generation for more information. class MockIFrameElement extends _i1.Mock implements _i2.IFrameElement { MockIFrameElement() { _i1.throwOnMissingStub(this); } @override set allow(String? value) => super.noSuchMethod( Invocation.setter( #allow, value, ), returnValueForMissingStub: null, ); @override set allowFullscreen(bool? value) => super.noSuchMethod( Invocation.setter( #allowFullscreen, value, ), returnValueForMissingStub: null, ); @override set allowPaymentRequest(bool? value) => super.noSuchMethod( Invocation.setter( #allowPaymentRequest, value, ), returnValueForMissingStub: null, ); @override set csp(String? value) => super.noSuchMethod( Invocation.setter( #csp, value, ), returnValueForMissingStub: null, ); @override set height(String? value) => super.noSuchMethod( Invocation.setter( #height, value, ), returnValueForMissingStub: null, ); @override set name(String? value) => super.noSuchMethod( Invocation.setter( #name, value, ), returnValueForMissingStub: null, ); @override set referrerPolicy(String? value) => super.noSuchMethod( Invocation.setter( #referrerPolicy, value, ), returnValueForMissingStub: null, ); @override set src(String? value) => super.noSuchMethod( Invocation.setter( #src, value, ), returnValueForMissingStub: null, ); @override set srcdoc(String? value) => super.noSuchMethod( Invocation.setter( #srcdoc, value, ), returnValueForMissingStub: null, ); @override set width(String? value) => super.noSuchMethod( Invocation.setter( #width, value, ), returnValueForMissingStub: null, ); @override set nonce(String? value) => super.noSuchMethod( Invocation.setter( #nonce, value, ), returnValueForMissingStub: null, ); @override Map get attributes => (super.noSuchMethod( Invocation.getter(#attributes), returnValue: {}, ) as Map); @override set attributes(Map? value) => super.noSuchMethod( Invocation.setter( #attributes, value, ), returnValueForMissingStub: null, ); @override List<_i2.Element> get children => (super.noSuchMethod( Invocation.getter(#children), returnValue: <_i2.Element>[], ) as List<_i2.Element>); @override set children(List<_i2.Element>? value) => super.noSuchMethod( Invocation.setter( #children, value, ), returnValueForMissingStub: null, ); @override _i2.CssClassSet get classes => (super.noSuchMethod( Invocation.getter(#classes), returnValue: _FakeCssClassSet_0( this, Invocation.getter(#classes), ), ) as _i2.CssClassSet); @override set classes(Iterable? value) => super.noSuchMethod( Invocation.setter( #classes, value, ), returnValueForMissingStub: null, ); @override Map get dataset => (super.noSuchMethod( Invocation.getter(#dataset), returnValue: {}, ) as Map); @override set dataset(Map? value) => super.noSuchMethod( Invocation.setter( #dataset, value, ), returnValueForMissingStub: null, ); @override _i3.Rectangle get client => (super.noSuchMethod( Invocation.getter(#client), returnValue: _FakeRectangle_1( this, Invocation.getter(#client), ), ) as _i3.Rectangle); @override _i3.Rectangle get offset => (super.noSuchMethod( Invocation.getter(#offset), returnValue: _FakeRectangle_1( this, Invocation.getter(#offset), ), ) as _i3.Rectangle); @override String get localName => (super.noSuchMethod( Invocation.getter(#localName), returnValue: '', ) as String); @override _i2.CssRect get contentEdge => (super.noSuchMethod( Invocation.getter(#contentEdge), returnValue: _FakeCssRect_2( this, Invocation.getter(#contentEdge), ), ) as _i2.CssRect); @override _i2.CssRect get paddingEdge => (super.noSuchMethod( Invocation.getter(#paddingEdge), returnValue: _FakeCssRect_2( this, Invocation.getter(#paddingEdge), ), ) as _i2.CssRect); @override _i2.CssRect get borderEdge => (super.noSuchMethod( Invocation.getter(#borderEdge), returnValue: _FakeCssRect_2( this, Invocation.getter(#borderEdge), ), ) as _i2.CssRect); @override _i2.CssRect get marginEdge => (super.noSuchMethod( Invocation.getter(#marginEdge), returnValue: _FakeCssRect_2( this, Invocation.getter(#marginEdge), ), ) as _i2.CssRect); @override _i3.Point get documentOffset => (super.noSuchMethod( Invocation.getter(#documentOffset), returnValue: _FakePoint_3( this, Invocation.getter(#documentOffset), ), ) as _i3.Point); @override set innerHtml(String? html) => super.noSuchMethod( Invocation.setter( #innerHtml, html, ), returnValueForMissingStub: null, ); @override String get innerText => (super.noSuchMethod( Invocation.getter(#innerText), returnValue: '', ) as String); @override set innerText(String? value) => super.noSuchMethod( Invocation.setter( #innerText, value, ), returnValueForMissingStub: null, ); @override _i2.ElementEvents get on => (super.noSuchMethod( Invocation.getter(#on), returnValue: _FakeElementEvents_4( this, Invocation.getter(#on), ), ) as _i2.ElementEvents); @override int get offsetHeight => (super.noSuchMethod( Invocation.getter(#offsetHeight), returnValue: 0, ) as int); @override int get offsetLeft => (super.noSuchMethod( Invocation.getter(#offsetLeft), returnValue: 0, ) as int); @override int get offsetTop => (super.noSuchMethod( Invocation.getter(#offsetTop), returnValue: 0, ) as int); @override int get offsetWidth => (super.noSuchMethod( Invocation.getter(#offsetWidth), returnValue: 0, ) as int); @override int get scrollHeight => (super.noSuchMethod( Invocation.getter(#scrollHeight), returnValue: 0, ) as int); @override int get scrollLeft => (super.noSuchMethod( Invocation.getter(#scrollLeft), returnValue: 0, ) as int); @override set scrollLeft(int? value) => super.noSuchMethod( Invocation.setter( #scrollLeft, value, ), returnValueForMissingStub: null, ); @override int get scrollTop => (super.noSuchMethod( Invocation.getter(#scrollTop), returnValue: 0, ) as int); @override set scrollTop(int? value) => super.noSuchMethod( Invocation.setter( #scrollTop, value, ), returnValueForMissingStub: null, ); @override int get scrollWidth => (super.noSuchMethod( Invocation.getter(#scrollWidth), returnValue: 0, ) as int); @override String get contentEditable => (super.noSuchMethod( Invocation.getter(#contentEditable), returnValue: '', ) as String); @override set contentEditable(String? value) => super.noSuchMethod( Invocation.setter( #contentEditable, value, ), returnValueForMissingStub: null, ); @override set dir(String? value) => super.noSuchMethod( Invocation.setter( #dir, value, ), returnValueForMissingStub: null, ); @override bool get draggable => (super.noSuchMethod( Invocation.getter(#draggable), returnValue: false, ) as bool); @override set draggable(bool? value) => super.noSuchMethod( Invocation.setter( #draggable, value, ), returnValueForMissingStub: null, ); @override bool get hidden => (super.noSuchMethod( Invocation.getter(#hidden), returnValue: false, ) as bool); @override set hidden(bool? value) => super.noSuchMethod( Invocation.setter( #hidden, value, ), returnValueForMissingStub: null, ); @override set inert(bool? value) => super.noSuchMethod( Invocation.setter( #inert, value, ), returnValueForMissingStub: null, ); @override set inputMode(String? value) => super.noSuchMethod( Invocation.setter( #inputMode, value, ), returnValueForMissingStub: null, ); @override set lang(String? value) => super.noSuchMethod( Invocation.setter( #lang, value, ), returnValueForMissingStub: null, ); @override set spellcheck(bool? value) => super.noSuchMethod( Invocation.setter( #spellcheck, value, ), returnValueForMissingStub: null, ); @override _i2.CssStyleDeclaration get style => (super.noSuchMethod( Invocation.getter(#style), returnValue: _FakeCssStyleDeclaration_5( this, Invocation.getter(#style), ), ) as _i2.CssStyleDeclaration); @override set tabIndex(int? value) => super.noSuchMethod( Invocation.setter( #tabIndex, value, ), returnValueForMissingStub: null, ); @override set title(String? value) => super.noSuchMethod( Invocation.setter( #title, value, ), returnValueForMissingStub: null, ); @override set translate(bool? value) => super.noSuchMethod( Invocation.setter( #translate, value, ), returnValueForMissingStub: null, ); @override String get className => (super.noSuchMethod( Invocation.getter(#className), returnValue: '', ) as String); @override set className(String? value) => super.noSuchMethod( Invocation.setter( #className, value, ), returnValueForMissingStub: null, ); @override int get clientHeight => (super.noSuchMethod( Invocation.getter(#clientHeight), returnValue: 0, ) as int); @override int get clientWidth => (super.noSuchMethod( Invocation.getter(#clientWidth), returnValue: 0, ) as int); @override String get id => (super.noSuchMethod( Invocation.getter(#id), returnValue: '', ) as String); @override set id(String? value) => super.noSuchMethod( Invocation.setter( #id, value, ), returnValueForMissingStub: null, ); @override set slot(String? value) => super.noSuchMethod( Invocation.setter( #slot, value, ), returnValueForMissingStub: null, ); @override String get tagName => (super.noSuchMethod( Invocation.getter(#tagName), returnValue: '', ) as String); @override _i2.ElementStream<_i2.Event> get onAbort => (super.noSuchMethod( Invocation.getter(#onAbort), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onAbort), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onBeforeCopy => (super.noSuchMethod( Invocation.getter(#onBeforeCopy), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onBeforeCopy), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onBeforeCut => (super.noSuchMethod( Invocation.getter(#onBeforeCut), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onBeforeCut), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onBeforePaste => (super.noSuchMethod( Invocation.getter(#onBeforePaste), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onBeforePaste), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onBlur => (super.noSuchMethod( Invocation.getter(#onBlur), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onBlur), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onCanPlay => (super.noSuchMethod( Invocation.getter(#onCanPlay), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onCanPlay), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onCanPlayThrough => (super.noSuchMethod( Invocation.getter(#onCanPlayThrough), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onCanPlayThrough), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onChange => (super.noSuchMethod( Invocation.getter(#onChange), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onChange), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.MouseEvent> get onClick => (super.noSuchMethod( Invocation.getter(#onClick), returnValue: _FakeElementStream_6<_i2.MouseEvent>( this, Invocation.getter(#onClick), ), ) as _i2.ElementStream<_i2.MouseEvent>); @override _i2.ElementStream<_i2.MouseEvent> get onContextMenu => (super.noSuchMethod( Invocation.getter(#onContextMenu), returnValue: _FakeElementStream_6<_i2.MouseEvent>( this, Invocation.getter(#onContextMenu), ), ) as _i2.ElementStream<_i2.MouseEvent>); @override _i2.ElementStream<_i2.ClipboardEvent> get onCopy => (super.noSuchMethod( Invocation.getter(#onCopy), returnValue: _FakeElementStream_6<_i2.ClipboardEvent>( this, Invocation.getter(#onCopy), ), ) as _i2.ElementStream<_i2.ClipboardEvent>); @override _i2.ElementStream<_i2.ClipboardEvent> get onCut => (super.noSuchMethod( Invocation.getter(#onCut), returnValue: _FakeElementStream_6<_i2.ClipboardEvent>( this, Invocation.getter(#onCut), ), ) as _i2.ElementStream<_i2.ClipboardEvent>); @override _i2.ElementStream<_i2.Event> get onDoubleClick => (super.noSuchMethod( Invocation.getter(#onDoubleClick), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onDoubleClick), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.MouseEvent> get onDrag => (super.noSuchMethod( Invocation.getter(#onDrag), returnValue: _FakeElementStream_6<_i2.MouseEvent>( this, Invocation.getter(#onDrag), ), ) as _i2.ElementStream<_i2.MouseEvent>); @override _i2.ElementStream<_i2.MouseEvent> get onDragEnd => (super.noSuchMethod( Invocation.getter(#onDragEnd), returnValue: _FakeElementStream_6<_i2.MouseEvent>( this, Invocation.getter(#onDragEnd), ), ) as _i2.ElementStream<_i2.MouseEvent>); @override _i2.ElementStream<_i2.MouseEvent> get onDragEnter => (super.noSuchMethod( Invocation.getter(#onDragEnter), returnValue: _FakeElementStream_6<_i2.MouseEvent>( this, Invocation.getter(#onDragEnter), ), ) as _i2.ElementStream<_i2.MouseEvent>); @override _i2.ElementStream<_i2.MouseEvent> get onDragLeave => (super.noSuchMethod( Invocation.getter(#onDragLeave), returnValue: _FakeElementStream_6<_i2.MouseEvent>( this, Invocation.getter(#onDragLeave), ), ) as _i2.ElementStream<_i2.MouseEvent>); @override _i2.ElementStream<_i2.MouseEvent> get onDragOver => (super.noSuchMethod( Invocation.getter(#onDragOver), returnValue: _FakeElementStream_6<_i2.MouseEvent>( this, Invocation.getter(#onDragOver), ), ) as _i2.ElementStream<_i2.MouseEvent>); @override _i2.ElementStream<_i2.MouseEvent> get onDragStart => (super.noSuchMethod( Invocation.getter(#onDragStart), returnValue: _FakeElementStream_6<_i2.MouseEvent>( this, Invocation.getter(#onDragStart), ), ) as _i2.ElementStream<_i2.MouseEvent>); @override _i2.ElementStream<_i2.MouseEvent> get onDrop => (super.noSuchMethod( Invocation.getter(#onDrop), returnValue: _FakeElementStream_6<_i2.MouseEvent>( this, Invocation.getter(#onDrop), ), ) as _i2.ElementStream<_i2.MouseEvent>); @override _i2.ElementStream<_i2.Event> get onDurationChange => (super.noSuchMethod( Invocation.getter(#onDurationChange), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onDurationChange), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onEmptied => (super.noSuchMethod( Invocation.getter(#onEmptied), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onEmptied), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onEnded => (super.noSuchMethod( Invocation.getter(#onEnded), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onEnded), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onError => (super.noSuchMethod( Invocation.getter(#onError), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onError), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onFocus => (super.noSuchMethod( Invocation.getter(#onFocus), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onFocus), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onInput => (super.noSuchMethod( Invocation.getter(#onInput), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onInput), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onInvalid => (super.noSuchMethod( Invocation.getter(#onInvalid), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onInvalid), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.KeyboardEvent> get onKeyDown => (super.noSuchMethod( Invocation.getter(#onKeyDown), returnValue: _FakeElementStream_6<_i2.KeyboardEvent>( this, Invocation.getter(#onKeyDown), ), ) as _i2.ElementStream<_i2.KeyboardEvent>); @override _i2.ElementStream<_i2.KeyboardEvent> get onKeyPress => (super.noSuchMethod( Invocation.getter(#onKeyPress), returnValue: _FakeElementStream_6<_i2.KeyboardEvent>( this, Invocation.getter(#onKeyPress), ), ) as _i2.ElementStream<_i2.KeyboardEvent>); @override _i2.ElementStream<_i2.KeyboardEvent> get onKeyUp => (super.noSuchMethod( Invocation.getter(#onKeyUp), returnValue: _FakeElementStream_6<_i2.KeyboardEvent>( this, Invocation.getter(#onKeyUp), ), ) as _i2.ElementStream<_i2.KeyboardEvent>); @override _i2.ElementStream<_i2.Event> get onLoad => (super.noSuchMethod( Invocation.getter(#onLoad), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onLoad), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onLoadedData => (super.noSuchMethod( Invocation.getter(#onLoadedData), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onLoadedData), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onLoadedMetadata => (super.noSuchMethod( Invocation.getter(#onLoadedMetadata), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onLoadedMetadata), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.MouseEvent> get onMouseDown => (super.noSuchMethod( Invocation.getter(#onMouseDown), returnValue: _FakeElementStream_6<_i2.MouseEvent>( this, Invocation.getter(#onMouseDown), ), ) as _i2.ElementStream<_i2.MouseEvent>); @override _i2.ElementStream<_i2.MouseEvent> get onMouseEnter => (super.noSuchMethod( Invocation.getter(#onMouseEnter), returnValue: _FakeElementStream_6<_i2.MouseEvent>( this, Invocation.getter(#onMouseEnter), ), ) as _i2.ElementStream<_i2.MouseEvent>); @override _i2.ElementStream<_i2.MouseEvent> get onMouseLeave => (super.noSuchMethod( Invocation.getter(#onMouseLeave), returnValue: _FakeElementStream_6<_i2.MouseEvent>( this, Invocation.getter(#onMouseLeave), ), ) as _i2.ElementStream<_i2.MouseEvent>); @override _i2.ElementStream<_i2.MouseEvent> get onMouseMove => (super.noSuchMethod( Invocation.getter(#onMouseMove), returnValue: _FakeElementStream_6<_i2.MouseEvent>( this, Invocation.getter(#onMouseMove), ), ) as _i2.ElementStream<_i2.MouseEvent>); @override _i2.ElementStream<_i2.MouseEvent> get onMouseOut => (super.noSuchMethod( Invocation.getter(#onMouseOut), returnValue: _FakeElementStream_6<_i2.MouseEvent>( this, Invocation.getter(#onMouseOut), ), ) as _i2.ElementStream<_i2.MouseEvent>); @override _i2.ElementStream<_i2.MouseEvent> get onMouseOver => (super.noSuchMethod( Invocation.getter(#onMouseOver), returnValue: _FakeElementStream_6<_i2.MouseEvent>( this, Invocation.getter(#onMouseOver), ), ) as _i2.ElementStream<_i2.MouseEvent>); @override _i2.ElementStream<_i2.MouseEvent> get onMouseUp => (super.noSuchMethod( Invocation.getter(#onMouseUp), returnValue: _FakeElementStream_6<_i2.MouseEvent>( this, Invocation.getter(#onMouseUp), ), ) as _i2.ElementStream<_i2.MouseEvent>); @override _i2.ElementStream<_i2.WheelEvent> get onMouseWheel => (super.noSuchMethod( Invocation.getter(#onMouseWheel), returnValue: _FakeElementStream_6<_i2.WheelEvent>( this, Invocation.getter(#onMouseWheel), ), ) as _i2.ElementStream<_i2.WheelEvent>); @override _i2.ElementStream<_i2.ClipboardEvent> get onPaste => (super.noSuchMethod( Invocation.getter(#onPaste), returnValue: _FakeElementStream_6<_i2.ClipboardEvent>( this, Invocation.getter(#onPaste), ), ) as _i2.ElementStream<_i2.ClipboardEvent>); @override _i2.ElementStream<_i2.Event> get onPause => (super.noSuchMethod( Invocation.getter(#onPause), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onPause), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onPlay => (super.noSuchMethod( Invocation.getter(#onPlay), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onPlay), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onPlaying => (super.noSuchMethod( Invocation.getter(#onPlaying), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onPlaying), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onRateChange => (super.noSuchMethod( Invocation.getter(#onRateChange), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onRateChange), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onReset => (super.noSuchMethod( Invocation.getter(#onReset), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onReset), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onResize => (super.noSuchMethod( Invocation.getter(#onResize), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onResize), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onScroll => (super.noSuchMethod( Invocation.getter(#onScroll), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onScroll), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onSearch => (super.noSuchMethod( Invocation.getter(#onSearch), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onSearch), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onSeeked => (super.noSuchMethod( Invocation.getter(#onSeeked), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onSeeked), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onSeeking => (super.noSuchMethod( Invocation.getter(#onSeeking), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onSeeking), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onSelect => (super.noSuchMethod( Invocation.getter(#onSelect), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onSelect), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onSelectStart => (super.noSuchMethod( Invocation.getter(#onSelectStart), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onSelectStart), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onStalled => (super.noSuchMethod( Invocation.getter(#onStalled), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onStalled), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onSubmit => (super.noSuchMethod( Invocation.getter(#onSubmit), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onSubmit), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onSuspend => (super.noSuchMethod( Invocation.getter(#onSuspend), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onSuspend), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onTimeUpdate => (super.noSuchMethod( Invocation.getter(#onTimeUpdate), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onTimeUpdate), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.TouchEvent> get onTouchCancel => (super.noSuchMethod( Invocation.getter(#onTouchCancel), returnValue: _FakeElementStream_6<_i2.TouchEvent>( this, Invocation.getter(#onTouchCancel), ), ) as _i2.ElementStream<_i2.TouchEvent>); @override _i2.ElementStream<_i2.TouchEvent> get onTouchEnd => (super.noSuchMethod( Invocation.getter(#onTouchEnd), returnValue: _FakeElementStream_6<_i2.TouchEvent>( this, Invocation.getter(#onTouchEnd), ), ) as _i2.ElementStream<_i2.TouchEvent>); @override _i2.ElementStream<_i2.TouchEvent> get onTouchEnter => (super.noSuchMethod( Invocation.getter(#onTouchEnter), returnValue: _FakeElementStream_6<_i2.TouchEvent>( this, Invocation.getter(#onTouchEnter), ), ) as _i2.ElementStream<_i2.TouchEvent>); @override _i2.ElementStream<_i2.TouchEvent> get onTouchLeave => (super.noSuchMethod( Invocation.getter(#onTouchLeave), returnValue: _FakeElementStream_6<_i2.TouchEvent>( this, Invocation.getter(#onTouchLeave), ), ) as _i2.ElementStream<_i2.TouchEvent>); @override _i2.ElementStream<_i2.TouchEvent> get onTouchMove => (super.noSuchMethod( Invocation.getter(#onTouchMove), returnValue: _FakeElementStream_6<_i2.TouchEvent>( this, Invocation.getter(#onTouchMove), ), ) as _i2.ElementStream<_i2.TouchEvent>); @override _i2.ElementStream<_i2.TouchEvent> get onTouchStart => (super.noSuchMethod( Invocation.getter(#onTouchStart), returnValue: _FakeElementStream_6<_i2.TouchEvent>( this, Invocation.getter(#onTouchStart), ), ) as _i2.ElementStream<_i2.TouchEvent>); @override _i2.ElementStream<_i2.TransitionEvent> get onTransitionEnd => (super.noSuchMethod( Invocation.getter(#onTransitionEnd), returnValue: _FakeElementStream_6<_i2.TransitionEvent>( this, Invocation.getter(#onTransitionEnd), ), ) as _i2.ElementStream<_i2.TransitionEvent>); @override _i2.ElementStream<_i2.Event> get onVolumeChange => (super.noSuchMethod( Invocation.getter(#onVolumeChange), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onVolumeChange), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onWaiting => (super.noSuchMethod( Invocation.getter(#onWaiting), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onWaiting), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onFullscreenChange => (super.noSuchMethod( Invocation.getter(#onFullscreenChange), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onFullscreenChange), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.Event> get onFullscreenError => (super.noSuchMethod( Invocation.getter(#onFullscreenError), returnValue: _FakeElementStream_6<_i2.Event>( this, Invocation.getter(#onFullscreenError), ), ) as _i2.ElementStream<_i2.Event>); @override _i2.ElementStream<_i2.WheelEvent> get onWheel => (super.noSuchMethod( Invocation.getter(#onWheel), returnValue: _FakeElementStream_6<_i2.WheelEvent>( this, Invocation.getter(#onWheel), ), ) as _i2.ElementStream<_i2.WheelEvent>); @override List<_i2.Node> get nodes => (super.noSuchMethod( Invocation.getter(#nodes), returnValue: <_i2.Node>[], ) as List<_i2.Node>); @override set nodes(Iterable<_i2.Node>? value) => super.noSuchMethod( Invocation.setter( #nodes, value, ), returnValueForMissingStub: null, ); @override List<_i2.Node> get childNodes => (super.noSuchMethod( Invocation.getter(#childNodes), returnValue: <_i2.Node>[], ) as List<_i2.Node>); @override int get nodeType => (super.noSuchMethod( Invocation.getter(#nodeType), returnValue: 0, ) as int); @override set text(String? value) => super.noSuchMethod( Invocation.setter( #text, value, ), returnValueForMissingStub: null, ); @override String? getAttribute(String? name) => (super.noSuchMethod(Invocation.method( #getAttribute, [name], )) as String?); @override String? getAttributeNS( String? namespaceURI, String? name, ) => (super.noSuchMethod(Invocation.method( #getAttributeNS, [ namespaceURI, name, ], )) as String?); @override bool hasAttribute(String? name) => (super.noSuchMethod( Invocation.method( #hasAttribute, [name], ), returnValue: false, ) as bool); @override bool hasAttributeNS( String? namespaceURI, String? name, ) => (super.noSuchMethod( Invocation.method( #hasAttributeNS, [ namespaceURI, name, ], ), returnValue: false, ) as bool); @override void removeAttribute(String? name) => super.noSuchMethod( Invocation.method( #removeAttribute, [name], ), returnValueForMissingStub: null, ); @override void removeAttributeNS( String? namespaceURI, String? name, ) => super.noSuchMethod( Invocation.method( #removeAttributeNS, [ namespaceURI, name, ], ), returnValueForMissingStub: null, ); @override void setAttribute( String? name, Object? value, ) => super.noSuchMethod( Invocation.method( #setAttribute, [ name, value, ], ), returnValueForMissingStub: null, ); @override void setAttributeNS( String? namespaceURI, String? name, Object? value, ) => super.noSuchMethod( Invocation.method( #setAttributeNS, [ namespaceURI, name, value, ], ), returnValueForMissingStub: null, ); @override _i2.ElementList querySelectorAll( String? selectors) => (super.noSuchMethod( Invocation.method( #querySelectorAll, [selectors], ), returnValue: _FakeElementList_7( this, Invocation.method( #querySelectorAll, [selectors], ), ), ) as _i2.ElementList); @override _i6.Future<_i2.ScrollState> setApplyScroll(String? nativeScrollBehavior) => (super.noSuchMethod( Invocation.method( #setApplyScroll, [nativeScrollBehavior], ), returnValue: _i6.Future<_i2.ScrollState>.value(_FakeScrollState_8( this, Invocation.method( #setApplyScroll, [nativeScrollBehavior], ), )), ) as _i6.Future<_i2.ScrollState>); @override _i6.Future<_i2.ScrollState> setDistributeScroll( String? nativeScrollBehavior) => (super.noSuchMethod( Invocation.method( #setDistributeScroll, [nativeScrollBehavior], ), returnValue: _i6.Future<_i2.ScrollState>.value(_FakeScrollState_8( this, Invocation.method( #setDistributeScroll, [nativeScrollBehavior], ), )), ) as _i6.Future<_i2.ScrollState>); @override Map getNamespacedAttributes(String? namespace) => (super.noSuchMethod( Invocation.method( #getNamespacedAttributes, [namespace], ), returnValue: {}, ) as Map); @override _i2.CssStyleDeclaration getComputedStyle([String? pseudoElement]) => (super.noSuchMethod( Invocation.method( #getComputedStyle, [pseudoElement], ), returnValue: _FakeCssStyleDeclaration_5( this, Invocation.method( #getComputedStyle, [pseudoElement], ), ), ) as _i2.CssStyleDeclaration); @override void appendText(String? text) => super.noSuchMethod( Invocation.method( #appendText, [text], ), returnValueForMissingStub: null, ); @override void appendHtml( String? text, { _i2.NodeValidator? validator, _i2.NodeTreeSanitizer? treeSanitizer, }) => super.noSuchMethod( Invocation.method( #appendHtml, [text], { #validator: validator, #treeSanitizer: treeSanitizer, }, ), returnValueForMissingStub: null, ); @override void attached() => super.noSuchMethod( Invocation.method( #attached, [], ), returnValueForMissingStub: null, ); @override void detached() => super.noSuchMethod( Invocation.method( #detached, [], ), returnValueForMissingStub: null, ); @override void enteredView() => super.noSuchMethod( Invocation.method( #enteredView, [], ), returnValueForMissingStub: null, ); @override List<_i3.Rectangle> getClientRects() => (super.noSuchMethod( Invocation.method( #getClientRects, [], ), returnValue: <_i3.Rectangle>[], ) as List<_i3.Rectangle>); @override void leftView() => super.noSuchMethod( Invocation.method( #leftView, [], ), returnValueForMissingStub: null, ); @override _i2.Animation animate( Iterable>? frames, [ dynamic timing, ]) => (super.noSuchMethod( Invocation.method( #animate, [ frames, timing, ], ), returnValue: _FakeAnimation_9( this, Invocation.method( #animate, [ frames, timing, ], ), ), ) as _i2.Animation); @override void attributeChanged( String? name, String? oldValue, String? newValue, ) => super.noSuchMethod( Invocation.method( #attributeChanged, [ name, oldValue, newValue, ], ), returnValueForMissingStub: null, ); @override void scrollIntoView([_i2.ScrollAlignment? alignment]) => super.noSuchMethod( Invocation.method( #scrollIntoView, [alignment], ), returnValueForMissingStub: null, ); @override void insertAdjacentText( String? where, String? text, ) => super.noSuchMethod( Invocation.method( #insertAdjacentText, [ where, text, ], ), returnValueForMissingStub: null, ); @override void insertAdjacentHtml( String? where, String? html, { _i2.NodeValidator? validator, _i2.NodeTreeSanitizer? treeSanitizer, }) => super.noSuchMethod( Invocation.method( #insertAdjacentHtml, [ where, html, ], { #validator: validator, #treeSanitizer: treeSanitizer, }, ), returnValueForMissingStub: null, ); @override _i2.Element insertAdjacentElement( String? where, _i2.Element? element, ) => (super.noSuchMethod( Invocation.method( #insertAdjacentElement, [ where, element, ], ), returnValue: _FakeElement_10( this, Invocation.method( #insertAdjacentElement, [ where, element, ], ), ), ) as _i2.Element); @override bool matches(String? selectors) => (super.noSuchMethod( Invocation.method( #matches, [selectors], ), returnValue: false, ) as bool); @override bool matchesWithAncestors(String? selectors) => (super.noSuchMethod( Invocation.method( #matchesWithAncestors, [selectors], ), returnValue: false, ) as bool); @override _i2.ShadowRoot createShadowRoot() => (super.noSuchMethod( Invocation.method( #createShadowRoot, [], ), returnValue: _FakeShadowRoot_11( this, Invocation.method( #createShadowRoot, [], ), ), ) as _i2.ShadowRoot); @override _i3.Point offsetTo(_i2.Element? parent) => (super.noSuchMethod( Invocation.method( #offsetTo, [parent], ), returnValue: _FakePoint_3( this, Invocation.method( #offsetTo, [parent], ), ), ) as _i3.Point); @override _i2.DocumentFragment createFragment( String? html, { _i2.NodeValidator? validator, _i2.NodeTreeSanitizer? treeSanitizer, }) => (super.noSuchMethod( Invocation.method( #createFragment, [html], { #validator: validator, #treeSanitizer: treeSanitizer, }, ), returnValue: _FakeDocumentFragment_12( this, Invocation.method( #createFragment, [html], { #validator: validator, #treeSanitizer: treeSanitizer, }, ), ), ) as _i2.DocumentFragment); @override void setInnerHtml( String? html, { _i2.NodeValidator? validator, _i2.NodeTreeSanitizer? treeSanitizer, }) => super.noSuchMethod( Invocation.method( #setInnerHtml, [html], { #validator: validator, #treeSanitizer: treeSanitizer, }, ), returnValueForMissingStub: null, ); @override _i6.Future requestFullscreen([Map? options]) => (super.noSuchMethod( Invocation.method( #requestFullscreen, [options], ), returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); @override void blur() => super.noSuchMethod( Invocation.method( #blur, [], ), returnValueForMissingStub: null, ); @override void click() => super.noSuchMethod( Invocation.method( #click, [], ), returnValueForMissingStub: null, ); @override void focus() => super.noSuchMethod( Invocation.method( #focus, [], ), returnValueForMissingStub: null, ); @override _i2.ShadowRoot attachShadow(Map? shadowRootInitDict) => (super.noSuchMethod( Invocation.method( #attachShadow, [shadowRootInitDict], ), returnValue: _FakeShadowRoot_11( this, Invocation.method( #attachShadow, [shadowRootInitDict], ), ), ) as _i2.ShadowRoot); @override _i2.Element? closest(String? selectors) => (super.noSuchMethod(Invocation.method( #closest, [selectors], )) as _i2.Element?); @override List<_i2.Animation> getAnimations() => (super.noSuchMethod( Invocation.method( #getAnimations, [], ), returnValue: <_i2.Animation>[], ) as List<_i2.Animation>); @override List getAttributeNames() => (super.noSuchMethod( Invocation.method( #getAttributeNames, [], ), returnValue: [], ) as List); @override _i3.Rectangle getBoundingClientRect() => (super.noSuchMethod( Invocation.method( #getBoundingClientRect, [], ), returnValue: _FakeRectangle_1( this, Invocation.method( #getBoundingClientRect, [], ), ), ) as _i3.Rectangle); @override List<_i2.Node> getDestinationInsertionPoints() => (super.noSuchMethod( Invocation.method( #getDestinationInsertionPoints, [], ), returnValue: <_i2.Node>[], ) as List<_i2.Node>); @override List<_i2.Node> getElementsByClassName(String? classNames) => (super.noSuchMethod( Invocation.method( #getElementsByClassName, [classNames], ), returnValue: <_i2.Node>[], ) as List<_i2.Node>); @override bool hasPointerCapture(int? pointerId) => (super.noSuchMethod( Invocation.method( #hasPointerCapture, [pointerId], ), returnValue: false, ) as bool); @override void releasePointerCapture(int? pointerId) => super.noSuchMethod( Invocation.method( #releasePointerCapture, [pointerId], ), returnValueForMissingStub: null, ); @override void requestPointerLock() => super.noSuchMethod( Invocation.method( #requestPointerLock, [], ), returnValueForMissingStub: null, ); @override void scroll([ dynamic options_OR_x, num? y, ]) => super.noSuchMethod( Invocation.method( #scroll, [ options_OR_x, y, ], ), returnValueForMissingStub: null, ); @override void scrollBy([ dynamic options_OR_x, num? y, ]) => super.noSuchMethod( Invocation.method( #scrollBy, [ options_OR_x, y, ], ), returnValueForMissingStub: null, ); @override void scrollIntoViewIfNeeded([bool? centerIfNeeded]) => super.noSuchMethod( Invocation.method( #scrollIntoViewIfNeeded, [centerIfNeeded], ), returnValueForMissingStub: null, ); @override void scrollTo([ dynamic options_OR_x, num? y, ]) => super.noSuchMethod( Invocation.method( #scrollTo, [ options_OR_x, y, ], ), returnValueForMissingStub: null, ); @override void setPointerCapture(int? pointerId) => super.noSuchMethod( Invocation.method( #setPointerCapture, [pointerId], ), returnValueForMissingStub: null, ); @override void after(Object? nodes) => super.noSuchMethod( Invocation.method( #after, [nodes], ), returnValueForMissingStub: null, ); @override void before(Object? nodes) => super.noSuchMethod( Invocation.method( #before, [nodes], ), returnValueForMissingStub: null, ); @override _i2.Element? querySelector(String? selectors) => (super.noSuchMethod(Invocation.method( #querySelector, [selectors], )) as _i2.Element?); @override void remove() => super.noSuchMethod( Invocation.method( #remove, [], ), returnValueForMissingStub: null, ); @override _i2.Node replaceWith(_i2.Node? otherNode) => (super.noSuchMethod( Invocation.method( #replaceWith, [otherNode], ), returnValue: _FakeNode_13( this, Invocation.method( #replaceWith, [otherNode], ), ), ) as _i2.Node); @override void insertAllBefore( Iterable<_i2.Node>? newNodes, _i2.Node? child, ) => super.noSuchMethod( Invocation.method( #insertAllBefore, [ newNodes, child, ], ), returnValueForMissingStub: null, ); @override _i2.Node append(_i2.Node? node) => (super.noSuchMethod( Invocation.method( #append, [node], ), returnValue: _FakeNode_13( this, Invocation.method( #append, [node], ), ), ) as _i2.Node); @override _i2.Node clone(bool? deep) => (super.noSuchMethod( Invocation.method( #clone, [deep], ), returnValue: _FakeNode_13( this, Invocation.method( #clone, [deep], ), ), ) as _i2.Node); @override bool contains(_i2.Node? other) => (super.noSuchMethod( Invocation.method( #contains, [other], ), returnValue: false, ) as bool); @override _i2.Node getRootNode([Map? options]) => (super.noSuchMethod( Invocation.method( #getRootNode, [options], ), returnValue: _FakeNode_13( this, Invocation.method( #getRootNode, [options], ), ), ) as _i2.Node); @override bool hasChildNodes() => (super.noSuchMethod( Invocation.method( #hasChildNodes, [], ), returnValue: false, ) as bool); @override _i2.Node insertBefore( _i2.Node? node, _i2.Node? child, ) => (super.noSuchMethod( Invocation.method( #insertBefore, [ node, child, ], ), returnValue: _FakeNode_13( this, Invocation.method( #insertBefore, [ node, child, ], ), ), ) as _i2.Node); @override void addEventListener( String? type, _i2.EventListener? listener, [ bool? useCapture, ]) => super.noSuchMethod( Invocation.method( #addEventListener, [ type, listener, useCapture, ], ), returnValueForMissingStub: null, ); @override void removeEventListener( String? type, _i2.EventListener? listener, [ bool? useCapture, ]) => super.noSuchMethod( Invocation.method( #removeEventListener, [ type, listener, useCapture, ], ), returnValueForMissingStub: null, ); @override bool dispatchEvent(_i2.Event? event) => (super.noSuchMethod( Invocation.method( #dispatchEvent, [event], ), returnValue: false, ) as bool); } /// A class which mocks [BuildContext]. /// /// See the documentation for Mockito's code generation for more information. class MockBuildContext extends _i1.Mock implements _i4.BuildContext { MockBuildContext() { _i1.throwOnMissingStub(this); } @override _i4.Widget get widget => (super.noSuchMethod( Invocation.getter(#widget), returnValue: _FakeWidget_14( this, Invocation.getter(#widget), ), ) as _i4.Widget); @override bool get debugDoingBuild => (super.noSuchMethod( Invocation.getter(#debugDoingBuild), returnValue: false, ) as bool); @override _i4.InheritedWidget dependOnInheritedElement( _i4.InheritedElement? ancestor, { Object? aspect, }) => (super.noSuchMethod( Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), returnValue: _FakeInheritedWidget_15( this, Invocation.method( #dependOnInheritedElement, [ancestor], {#aspect: aspect}, ), ), ) as _i4.InheritedWidget); @override void visitAncestorElements(bool Function(_i4.Element)? visitor) => super.noSuchMethod( Invocation.method( #visitAncestorElements, [visitor], ), returnValueForMissingStub: null, ); @override void visitChildElements(_i4.ElementVisitor? visitor) => super.noSuchMethod( Invocation.method( #visitChildElements, [visitor], ), returnValueForMissingStub: null, ); @override void dispatchNotification(_i7.Notification? notification) => super.noSuchMethod( Invocation.method( #dispatchNotification, [notification], ), returnValueForMissingStub: null, ); @override _i5.DiagnosticsNode describeElement( String? name, { _i5.DiagnosticsTreeStyle? style = _i5.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeElement, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_16( this, Invocation.method( #describeElement, [name], {#style: style}, ), ), ) as _i5.DiagnosticsNode); @override _i5.DiagnosticsNode describeWidget( String? name, { _i5.DiagnosticsTreeStyle? style = _i5.DiagnosticsTreeStyle.errorProperty, }) => (super.noSuchMethod( Invocation.method( #describeWidget, [name], {#style: style}, ), returnValue: _FakeDiagnosticsNode_16( this, Invocation.method( #describeWidget, [name], {#style: style}, ), ), ) as _i5.DiagnosticsNode); @override List<_i5.DiagnosticsNode> describeMissingAncestor( {required Type? expectedAncestorType}) => (super.noSuchMethod( Invocation.method( #describeMissingAncestor, [], {#expectedAncestorType: expectedAncestorType}, ), returnValue: <_i5.DiagnosticsNode>[], ) as List<_i5.DiagnosticsNode>); @override _i5.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod( Invocation.method( #describeOwnershipChain, [name], ), returnValue: _FakeDiagnosticsNode_16( this, Invocation.method( #describeOwnershipChain, [name], ), ), ) as _i5.DiagnosticsNode); } /// A class which mocks [CreationParams]. /// /// See the documentation for Mockito's code generation for more information. class MockCreationParams extends _i1.Mock implements _i8.CreationParams { MockCreationParams() { _i1.throwOnMissingStub(this); } @override Set get javascriptChannelNames => (super.noSuchMethod( Invocation.getter(#javascriptChannelNames), returnValue: {}, ) as Set); @override _i8.AutoMediaPlaybackPolicy get autoMediaPlaybackPolicy => (super.noSuchMethod( Invocation.getter(#autoMediaPlaybackPolicy), returnValue: _i8.AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, ) as _i8.AutoMediaPlaybackPolicy); @override List<_i8.WebViewCookie> get cookies => (super.noSuchMethod( Invocation.getter(#cookies), returnValue: <_i8.WebViewCookie>[], ) as List<_i8.WebViewCookie>); } /// A class which mocks [WebViewPlatformCallbacksHandler]. /// /// See the documentation for Mockito's code generation for more information. class MockWebViewPlatformCallbacksHandler extends _i1.Mock implements _i9.WebViewPlatformCallbacksHandler { MockWebViewPlatformCallbacksHandler() { _i1.throwOnMissingStub(this); } @override _i6.FutureOr onNavigationRequest({ required String? url, required bool? isForMainFrame, }) => (super.noSuchMethod( Invocation.method( #onNavigationRequest, [], { #url: url, #isForMainFrame: isForMainFrame, }, ), returnValue: _i6.Future.value(false), ) as _i6.FutureOr); @override void onPageStarted(String? url) => super.noSuchMethod( Invocation.method( #onPageStarted, [url], ), returnValueForMissingStub: null, ); @override void onPageFinished(String? url) => super.noSuchMethod( Invocation.method( #onPageFinished, [url], ), returnValueForMissingStub: null, ); @override void onProgress(int? progress) => super.noSuchMethod( Invocation.method( #onProgress, [progress], ), returnValueForMissingStub: null, ); @override void onWebResourceError(_i8.WebResourceError? error) => super.noSuchMethod( Invocation.method( #onWebResourceError, [error], ), returnValueForMissingStub: null, ); } /// A class which mocks [HttpRequestFactory]. /// /// See the documentation for Mockito's code generation for more information. class MockHttpRequestFactory extends _i1.Mock implements _i10.HttpRequestFactory { MockHttpRequestFactory() { _i1.throwOnMissingStub(this); } @override _i6.Future<_i2.HttpRequest> request( String? url, { String? method, bool? withCredentials, String? responseType, String? mimeType, Map? requestHeaders, dynamic sendData, void Function(_i2.ProgressEvent)? onProgress, }) => (super.noSuchMethod( Invocation.method( #request, [url], { #method: method, #withCredentials: withCredentials, #responseType: responseType, #mimeType: mimeType, #requestHeaders: requestHeaders, #sendData: sendData, #onProgress: onProgress, }, ), returnValue: _i6.Future<_i2.HttpRequest>.value(_FakeHttpRequest_17( this, Invocation.method( #request, [url], { #method: method, #withCredentials: withCredentials, #responseType: responseType, #mimeType: mimeType, #requestHeaders: requestHeaders, #sendData: sendData, #onProgress: onProgress, }, ), )), ) as _i6.Future<_i2.HttpRequest>); } /// A class which mocks [HttpRequest]. /// /// See the documentation for Mockito's code generation for more information. class MockHttpRequest extends _i1.Mock implements _i2.HttpRequest { MockHttpRequest() { _i1.throwOnMissingStub(this); } @override Map get responseHeaders => (super.noSuchMethod( Invocation.getter(#responseHeaders), returnValue: {}, ) as Map); @override int get readyState => (super.noSuchMethod( Invocation.getter(#readyState), returnValue: 0, ) as int); @override String get responseType => (super.noSuchMethod( Invocation.getter(#responseType), returnValue: '', ) as String); @override set responseType(String? value) => super.noSuchMethod( Invocation.setter( #responseType, value, ), returnValueForMissingStub: null, ); @override set timeout(int? value) => super.noSuchMethod( Invocation.setter( #timeout, value, ), returnValueForMissingStub: null, ); @override _i2.HttpRequestUpload get upload => (super.noSuchMethod( Invocation.getter(#upload), returnValue: _FakeHttpRequestUpload_18( this, Invocation.getter(#upload), ), ) as _i2.HttpRequestUpload); @override set withCredentials(bool? value) => super.noSuchMethod( Invocation.setter( #withCredentials, value, ), returnValueForMissingStub: null, ); @override _i6.Stream<_i2.Event> get onReadyStateChange => (super.noSuchMethod( Invocation.getter(#onReadyStateChange), returnValue: _i6.Stream<_i2.Event>.empty(), ) as _i6.Stream<_i2.Event>); @override _i6.Stream<_i2.ProgressEvent> get onAbort => (super.noSuchMethod( Invocation.getter(#onAbort), returnValue: _i6.Stream<_i2.ProgressEvent>.empty(), ) as _i6.Stream<_i2.ProgressEvent>); @override _i6.Stream<_i2.ProgressEvent> get onError => (super.noSuchMethod( Invocation.getter(#onError), returnValue: _i6.Stream<_i2.ProgressEvent>.empty(), ) as _i6.Stream<_i2.ProgressEvent>); @override _i6.Stream<_i2.ProgressEvent> get onLoad => (super.noSuchMethod( Invocation.getter(#onLoad), returnValue: _i6.Stream<_i2.ProgressEvent>.empty(), ) as _i6.Stream<_i2.ProgressEvent>); @override _i6.Stream<_i2.ProgressEvent> get onLoadEnd => (super.noSuchMethod( Invocation.getter(#onLoadEnd), returnValue: _i6.Stream<_i2.ProgressEvent>.empty(), ) as _i6.Stream<_i2.ProgressEvent>); @override _i6.Stream<_i2.ProgressEvent> get onLoadStart => (super.noSuchMethod( Invocation.getter(#onLoadStart), returnValue: _i6.Stream<_i2.ProgressEvent>.empty(), ) as _i6.Stream<_i2.ProgressEvent>); @override _i6.Stream<_i2.ProgressEvent> get onProgress => (super.noSuchMethod( Invocation.getter(#onProgress), returnValue: _i6.Stream<_i2.ProgressEvent>.empty(), ) as _i6.Stream<_i2.ProgressEvent>); @override _i6.Stream<_i2.ProgressEvent> get onTimeout => (super.noSuchMethod( Invocation.getter(#onTimeout), returnValue: _i6.Stream<_i2.ProgressEvent>.empty(), ) as _i6.Stream<_i2.ProgressEvent>); @override _i2.Events get on => (super.noSuchMethod( Invocation.getter(#on), returnValue: _FakeEvents_19( this, Invocation.getter(#on), ), ) as _i2.Events); @override void open( String? method, String? url, { bool? async, String? user, String? password, }) => super.noSuchMethod( Invocation.method( #open, [ method, url, ], { #async: async, #user: user, #password: password, }, ), returnValueForMissingStub: null, ); @override void abort() => super.noSuchMethod( Invocation.method( #abort, [], ), returnValueForMissingStub: null, ); @override String getAllResponseHeaders() => (super.noSuchMethod( Invocation.method( #getAllResponseHeaders, [], ), returnValue: '', ) as String); @override String? getResponseHeader(String? name) => (super.noSuchMethod(Invocation.method( #getResponseHeader, [name], )) as String?); @override void overrideMimeType(String? mime) => super.noSuchMethod( Invocation.method( #overrideMimeType, [mime], ), returnValueForMissingStub: null, ); @override void send([dynamic body_OR_data]) => super.noSuchMethod( Invocation.method( #send, [body_OR_data], ), returnValueForMissingStub: null, ); @override void setRequestHeader( String? name, String? value, ) => super.noSuchMethod( Invocation.method( #setRequestHeader, [ name, value, ], ), returnValueForMissingStub: null, ); @override void addEventListener( String? type, _i2.EventListener? listener, [ bool? useCapture, ]) => super.noSuchMethod( Invocation.method( #addEventListener, [ type, listener, useCapture, ], ), returnValueForMissingStub: null, ); @override void removeEventListener( String? type, _i2.EventListener? listener, [ bool? useCapture, ]) => super.noSuchMethod( Invocation.method( #removeEventListener, [ type, listener, useCapture, ], ), returnValueForMissingStub: null, ); @override bool dispatchEvent(_i2.Event? event) => (super.noSuchMethod( Invocation.method( #dispatchEvent, [event], ), returnValue: false, ) as bool); } ================================================ FILE: packages/webview_flutter/webview_flutter_web/test/web_webview_controller_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:convert'; import 'dart:html'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; 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_web/webview_flutter_web.dart'; import 'web_webview_controller_test.mocks.dart'; @GenerateMocks([], customMocks: >[ MockSpec(onMissingStub: OnMissingStub.returnDefault), MockSpec(onMissingStub: OnMissingStub.returnDefault), ]) void main() { WidgetsFlutterBinding.ensureInitialized(); group('WebWebViewController', () { group('WebWebViewControllerCreationParams', () { test('sets iFrame fields', () { final WebWebViewControllerCreationParams params = WebWebViewControllerCreationParams(); expect(params.iFrame.id, contains('webView')); expect(params.iFrame.style.width, '100%'); expect(params.iFrame.style.height, '100%'); expect(params.iFrame.style.border, 'none'); }); }); group('loadHtmlString', () { test('loadHtmlString loads html into iframe', () async { final WebWebViewController controller = WebWebViewController(WebWebViewControllerCreationParams()); await controller.loadHtmlString('test html'); expect( (controller.params as WebWebViewControllerCreationParams).iFrame.src, 'data:text/html;charset=utf-8,${Uri.encodeFull('test html')}', ); }); test('loadHtmlString escapes "#" correctly', () async { final WebWebViewController controller = WebWebViewController(WebWebViewControllerCreationParams()); await controller.loadHtmlString('#'); expect( (controller.params as WebWebViewControllerCreationParams).iFrame.src, contains('%23'), ); }); }); group('loadRequest', () { test('throws ArgumentError on missing scheme', () async { final WebWebViewController controller = WebWebViewController(WebWebViewControllerCreationParams()); await expectLater( () async => controller.loadRequest( LoadRequestParams(uri: Uri.parse('flutter.dev')), ), throwsA(const TypeMatcher())); }); test('skips XHR for simple GETs (no headers, no data)', () async { final MockHttpRequestFactory mockHttpRequestFactory = MockHttpRequestFactory(); final WebWebViewController controller = WebWebViewController(WebWebViewControllerCreationParams( httpRequestFactory: mockHttpRequestFactory, )); when(mockHttpRequestFactory.request( any, method: anyNamed('method'), requestHeaders: anyNamed('requestHeaders'), sendData: anyNamed('sendData'), )).thenThrow( StateError('The `request` method should not have been called.')); await controller.loadRequest(LoadRequestParams( uri: Uri.parse('https://flutter.dev'), )); expect( (controller.params as WebWebViewControllerCreationParams).iFrame.src, 'https://flutter.dev/', ); }); test('makes request and loads response into iframe', () async { final MockHttpRequestFactory mockHttpRequestFactory = MockHttpRequestFactory(); final WebWebViewController controller = WebWebViewController(WebWebViewControllerCreationParams( httpRequestFactory: mockHttpRequestFactory, )); final MockHttpRequest mockHttpRequest = MockHttpRequest(); when(mockHttpRequest.getResponseHeader('content-type')) .thenReturn('text/plain'); when(mockHttpRequest.responseText).thenReturn('test data'); when(mockHttpRequestFactory.request( any, method: anyNamed('method'), requestHeaders: anyNamed('requestHeaders'), sendData: anyNamed('sendData'), )).thenAnswer((_) => Future.value(mockHttpRequest)); await controller.loadRequest(LoadRequestParams( uri: Uri.parse('https://flutter.dev'), method: LoadRequestMethod.post, body: Uint8List.fromList('test body'.codeUnits), headers: const {'Foo': 'Bar'}, )); verify(mockHttpRequestFactory.request( 'https://flutter.dev', method: 'post', requestHeaders: {'Foo': 'Bar'}, sendData: Uint8List.fromList('test body'.codeUnits), )); expect( (controller.params as WebWebViewControllerCreationParams).iFrame.src, 'data:;charset=utf-8,${Uri.encodeFull('test data')}', ); }); test('parses content-type response header correctly', () async { final MockHttpRequestFactory mockHttpRequestFactory = MockHttpRequestFactory(); final WebWebViewController controller = WebWebViewController(WebWebViewControllerCreationParams( httpRequestFactory: mockHttpRequestFactory, )); final Encoding iso = Encoding.getByName('latin1')!; final MockHttpRequest mockHttpRequest = MockHttpRequest(); when(mockHttpRequest.responseText) .thenReturn(String.fromCharCodes(iso.encode('España'))); when(mockHttpRequest.getResponseHeader('content-type')) .thenReturn('Text/HTmL; charset=latin1'); when(mockHttpRequestFactory.request( any, method: anyNamed('method'), requestHeaders: anyNamed('requestHeaders'), sendData: anyNamed('sendData'), )).thenAnswer((_) => Future.value(mockHttpRequest)); await controller.loadRequest(LoadRequestParams( uri: Uri.parse('https://flutter.dev'), method: LoadRequestMethod.post, )); expect( (controller.params as WebWebViewControllerCreationParams).iFrame.src, 'data:text/html;charset=iso-8859-1,Espa%F1a', ); }); test('escapes "#" correctly', () async { final MockHttpRequestFactory mockHttpRequestFactory = MockHttpRequestFactory(); final WebWebViewController controller = WebWebViewController(WebWebViewControllerCreationParams( httpRequestFactory: mockHttpRequestFactory, )); final MockHttpRequest mockHttpRequest = MockHttpRequest(); when(mockHttpRequest.getResponseHeader('content-type')) .thenReturn('text/html'); when(mockHttpRequest.responseText).thenReturn('#'); when(mockHttpRequestFactory.request( any, method: anyNamed('method'), requestHeaders: anyNamed('requestHeaders'), sendData: anyNamed('sendData'), )).thenAnswer((_) => Future.value(mockHttpRequest)); await controller.loadRequest(LoadRequestParams( uri: Uri.parse('https://flutter.dev'), method: LoadRequestMethod.post, body: Uint8List.fromList('test body'.codeUnits), headers: const {'Foo': 'Bar'}, )); expect( (controller.params as WebWebViewControllerCreationParams).iFrame.src, contains('%23'), ); }); }); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_web/test/web_webview_controller_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_web/test/web_webview_controller_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i3; import 'dart:html' as _i2; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_web/src/http_request_factory.dart' as _i4; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class class _FakeHttpRequestUpload_0 extends _i1.SmartFake implements _i2.HttpRequestUpload { _FakeHttpRequestUpload_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeEvents_1 extends _i1.SmartFake implements _i2.Events { _FakeEvents_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeHttpRequest_2 extends _i1.SmartFake implements _i2.HttpRequest { _FakeHttpRequest_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [HttpRequest]. /// /// See the documentation for Mockito's code generation for more information. class MockHttpRequest extends _i1.Mock implements _i2.HttpRequest { @override Map get responseHeaders => (super.noSuchMethod( Invocation.getter(#responseHeaders), returnValue: {}, returnValueForMissingStub: {}, ) as Map); @override int get readyState => (super.noSuchMethod( Invocation.getter(#readyState), returnValue: 0, returnValueForMissingStub: 0, ) as int); @override String get responseType => (super.noSuchMethod( Invocation.getter(#responseType), returnValue: '', returnValueForMissingStub: '', ) as String); @override set responseType(String? value) => super.noSuchMethod( Invocation.setter( #responseType, value, ), returnValueForMissingStub: null, ); @override set timeout(int? value) => super.noSuchMethod( Invocation.setter( #timeout, value, ), returnValueForMissingStub: null, ); @override _i2.HttpRequestUpload get upload => (super.noSuchMethod( Invocation.getter(#upload), returnValue: _FakeHttpRequestUpload_0( this, Invocation.getter(#upload), ), returnValueForMissingStub: _FakeHttpRequestUpload_0( this, Invocation.getter(#upload), ), ) as _i2.HttpRequestUpload); @override set withCredentials(bool? value) => super.noSuchMethod( Invocation.setter( #withCredentials, value, ), returnValueForMissingStub: null, ); @override _i3.Stream<_i2.Event> get onReadyStateChange => (super.noSuchMethod( Invocation.getter(#onReadyStateChange), returnValue: _i3.Stream<_i2.Event>.empty(), returnValueForMissingStub: _i3.Stream<_i2.Event>.empty(), ) as _i3.Stream<_i2.Event>); @override _i3.Stream<_i2.ProgressEvent> get onAbort => (super.noSuchMethod( Invocation.getter(#onAbort), returnValue: _i3.Stream<_i2.ProgressEvent>.empty(), returnValueForMissingStub: _i3.Stream<_i2.ProgressEvent>.empty(), ) as _i3.Stream<_i2.ProgressEvent>); @override _i3.Stream<_i2.ProgressEvent> get onError => (super.noSuchMethod( Invocation.getter(#onError), returnValue: _i3.Stream<_i2.ProgressEvent>.empty(), returnValueForMissingStub: _i3.Stream<_i2.ProgressEvent>.empty(), ) as _i3.Stream<_i2.ProgressEvent>); @override _i3.Stream<_i2.ProgressEvent> get onLoad => (super.noSuchMethod( Invocation.getter(#onLoad), returnValue: _i3.Stream<_i2.ProgressEvent>.empty(), returnValueForMissingStub: _i3.Stream<_i2.ProgressEvent>.empty(), ) as _i3.Stream<_i2.ProgressEvent>); @override _i3.Stream<_i2.ProgressEvent> get onLoadEnd => (super.noSuchMethod( Invocation.getter(#onLoadEnd), returnValue: _i3.Stream<_i2.ProgressEvent>.empty(), returnValueForMissingStub: _i3.Stream<_i2.ProgressEvent>.empty(), ) as _i3.Stream<_i2.ProgressEvent>); @override _i3.Stream<_i2.ProgressEvent> get onLoadStart => (super.noSuchMethod( Invocation.getter(#onLoadStart), returnValue: _i3.Stream<_i2.ProgressEvent>.empty(), returnValueForMissingStub: _i3.Stream<_i2.ProgressEvent>.empty(), ) as _i3.Stream<_i2.ProgressEvent>); @override _i3.Stream<_i2.ProgressEvent> get onProgress => (super.noSuchMethod( Invocation.getter(#onProgress), returnValue: _i3.Stream<_i2.ProgressEvent>.empty(), returnValueForMissingStub: _i3.Stream<_i2.ProgressEvent>.empty(), ) as _i3.Stream<_i2.ProgressEvent>); @override _i3.Stream<_i2.ProgressEvent> get onTimeout => (super.noSuchMethod( Invocation.getter(#onTimeout), returnValue: _i3.Stream<_i2.ProgressEvent>.empty(), returnValueForMissingStub: _i3.Stream<_i2.ProgressEvent>.empty(), ) as _i3.Stream<_i2.ProgressEvent>); @override _i2.Events get on => (super.noSuchMethod( Invocation.getter(#on), returnValue: _FakeEvents_1( this, Invocation.getter(#on), ), returnValueForMissingStub: _FakeEvents_1( this, Invocation.getter(#on), ), ) as _i2.Events); @override void open( String? method, String? url, { bool? async, String? user, String? password, }) => super.noSuchMethod( Invocation.method( #open, [ method, url, ], { #async: async, #user: user, #password: password, }, ), returnValueForMissingStub: null, ); @override void abort() => super.noSuchMethod( Invocation.method( #abort, [], ), returnValueForMissingStub: null, ); @override String getAllResponseHeaders() => (super.noSuchMethod( Invocation.method( #getAllResponseHeaders, [], ), returnValue: '', returnValueForMissingStub: '', ) as String); @override String? getResponseHeader(String? name) => (super.noSuchMethod( Invocation.method( #getResponseHeader, [name], ), returnValueForMissingStub: null, ) as String?); @override void overrideMimeType(String? mime) => super.noSuchMethod( Invocation.method( #overrideMimeType, [mime], ), returnValueForMissingStub: null, ); @override void send([dynamic body_OR_data]) => super.noSuchMethod( Invocation.method( #send, [body_OR_data], ), returnValueForMissingStub: null, ); @override void setRequestHeader( String? name, String? value, ) => super.noSuchMethod( Invocation.method( #setRequestHeader, [ name, value, ], ), returnValueForMissingStub: null, ); @override void addEventListener( String? type, _i2.EventListener? listener, [ bool? useCapture, ]) => super.noSuchMethod( Invocation.method( #addEventListener, [ type, listener, useCapture, ], ), returnValueForMissingStub: null, ); @override void removeEventListener( String? type, _i2.EventListener? listener, [ bool? useCapture, ]) => super.noSuchMethod( Invocation.method( #removeEventListener, [ type, listener, useCapture, ], ), returnValueForMissingStub: null, ); @override bool dispatchEvent(_i2.Event? event) => (super.noSuchMethod( Invocation.method( #dispatchEvent, [event], ), returnValue: false, returnValueForMissingStub: false, ) as bool); } /// A class which mocks [HttpRequestFactory]. /// /// See the documentation for Mockito's code generation for more information. class MockHttpRequestFactory extends _i1.Mock implements _i4.HttpRequestFactory { @override _i3.Future<_i2.HttpRequest> request( String? url, { String? method, bool? withCredentials, String? responseType, String? mimeType, Map? requestHeaders, dynamic sendData, void Function(_i2.ProgressEvent)? onProgress, }) => (super.noSuchMethod( Invocation.method( #request, [url], { #method: method, #withCredentials: withCredentials, #responseType: responseType, #mimeType: mimeType, #requestHeaders: requestHeaders, #sendData: sendData, #onProgress: onProgress, }, ), returnValue: _i3.Future<_i2.HttpRequest>.value(_FakeHttpRequest_2( this, Invocation.method( #request, [url], { #method: method, #withCredentials: withCredentials, #responseType: responseType, #mimeType: mimeType, #requestHeaders: requestHeaders, #sendData: sendData, #onProgress: onProgress, }, ), )), returnValueForMissingStub: _i3.Future<_i2.HttpRequest>.value(_FakeHttpRequest_2( this, Invocation.method( #request, [url], { #method: method, #withCredentials: withCredentials, #responseType: responseType, #mimeType: mimeType, #requestHeaders: requestHeaders, #sendData: sendData, #onProgress: onProgress, }, ), )), ) as _i3.Future<_i2.HttpRequest>); } ================================================ FILE: packages/webview_flutter/webview_flutter_web/test/web_webview_widget_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'package:webview_flutter_web/webview_flutter_web.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('WebWebViewWidget', () { testWidgets('build returns a HtmlElementView', (WidgetTester tester) async { final WebWebViewController controller = WebWebViewController(WebWebViewControllerCreationParams()); final WebWebViewWidget widget = WebWebViewWidget( PlatformWebViewWidgetCreationParams( key: const Key('keyValue'), controller: controller, ), ); await tester.pumpWidget( Builder(builder: (BuildContext context) => widget.build(context)), ); expect(find.byType(HtmlElementView), findsOneWidget); expect(find.byKey(const Key('keyValue')), findsOneWidget); }); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_web/test/webview_flutter_web_test.dart ================================================ // Copyright 2013 The Flutter 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()); }); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/AUTHORS ================================================ # 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> Maurits van Beusekom Antonino Di Natale Nick Bradshaw ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md ================================================ ## 3.1.0 * Adds support to access native `WKWebView`. ## 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. * Updates README with details on contributing. * Updates pigeon dev dependency to `4.2.13`. ## 3.0.0 * **BREAKING CHANGE** Updates platform implementation to `2.0.0` release of `webview_flutter_platform_interface`. See [webview_flutter](https://pub.dev/packages/webview_flutter/versions/4.0.0) for updated usage. * Updates code for `no_leading_underscores_for_local_identifiers` lint. ## 2.9.5 * Updates imports for `prefer_relative_imports`. ## 2.9.4 * Fixes avoid_redundant_argument_values lint warnings and minor typos. * Fixes typo in an internal method name, from `setCookieForInsances` to `setCookieForInstances`. ## 2.9.3 * Updates `webview_flutter_platform_interface` constraint to the correct minimum version. ## 2.9.2 * Fixes crash when an Objective-C object in `FWFInstanceManager` is released, but the dealloc callback is no longer available. ## 2.9.1 * Fixes regression where the behavior for the `UIScrollView` insets were removed. ## 2.9.0 * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/106316). * Replaces platform implementation with WebKit API built with pigeon. ## 2.8.1 * Ignores unnecessary import warnings in preparation for [upcoming Flutter changes](https://github.com/flutter/flutter/pull/104231). ## 2.8.0 * Raises minimum Dart version to 2.17 and Flutter version to 3.0.0. ## 2.7.5 * Minor fixes for new analysis options. ## 2.7.4 * Removes unnecessary imports. * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors lint warnings. ## 2.7.3 * Removes two occurrences of the compiler warning: "'RequiresUserActionForMediaPlayback' is deprecated: first deprecated in ios 10.0". ## 2.7.2 * Fixes an integration test race condition. * Migrates deprecated `Scaffold.showSnackBar` to `ScaffoldMessenger` in example app. ## 2.7.1 * Fixes header import for cookie manager to be relative only. ## 2.7.0 * Adds implementation of the `loadFlutterAsset` method from the platform interface. ## 2.6.0 * Implements new cookie manager for setting cookies and providing initial cookies. ## 2.5.0 * Adds an option to set the background color of the webview. * Migrates from `analysis_options_legacy.yaml` to `analysis_options.yaml`. * Integration test fixes. * Updates to webview_flutter_platform_interface version 1.5.2. ## 2.4.0 * Implemented new `loadFile` and `loadHtmlString` methods from the platform interface. ## 2.3.0 * Implemented new `loadRequest` method from platform interface. ## 2.2.0 * Implemented new `runJavascript` and `runJavascriptReturningResult` methods in platform interface. ## 2.1.0 * Add `zoomEnabled` functionality. ## 2.0.14 * Update example App so navigation menu loads immediatly but only becomes available when `WebViewController` is available (same behavior as example App in webview_flutter package). ## 2.0.13 * Extract WKWebView implementation from `webview_flutter`. ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/LICENSE ================================================ 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. ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/README.md ================================================ # webview\_flutter\_wkwebview The Apple WKWebView implementation of [`webview_flutter`][1]. ## Usage This package is [endorsed][2], which means you can simply use `webview_flutter` normally. This package will be automatically included in your app when you do. ### External Native API The plugin also provides a native API accessible by the native code of iOS applications or packages. This API follows the convention of breaking changes of the Dart API, which means that any changes to the class that are not backwards compatible will only be made with a major version change of the plugin. Native code other than this external API does not follow breaking change conventions, so app or plugin clients should not use any other native APIs. The API can be accessed by importing the native plugin `webview_flutter_wkwebview`: Objective-C: ```objectivec @import webview_flutter_wkwebview; ``` Then you will have access to the native class `FWFWebViewFlutterWKWebViewExternalAPI`. ## Contributing This package uses [pigeon][3] to generate the communication layer between Flutter and the host platform (iOS). The communication interface is defined in the `pigeons/web_kit.dart` file. After editing the communication interface regenerate the communication layer by running `flutter pub run pigeon --input pigeons/web_kit.dart`. Besides [pigeon][3] this package also uses [mockito][4] to generate mock objects for testing purposes. To generate the mock objects run the following command: ```bash flutter pub run build_runner build --delete-conflicting-outputs ``` If you would like to contribute to the plugin, check out our [contribution guide][5]. [1]: https://pub.dev/packages/webview_flutter [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin [3]: https://pub.dev/packages/pigeon [4]: https://pub.dev/packages/mockito [5]: https://github.com/flutter/plugins/blob/main/CONTRIBUTING.md ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/.metadata ================================================ # 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: 1e5cb2d87f8542f9fbbd0f22d528823274be0acb channel: master ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/README.md ================================================ # 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. ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/assets/www/index.html ================================================ Load file or HTML string example

Local demo page

This is an example page used to demonstrate how to load a local file or HTML string using the Flutter webview plugin.

================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/assets/www/styles/style.css ================================================ h1 { color: blue; } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/legacy/webview_flutter_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This test is run using `flutter drive` by the CI (see /script/tool/README.md // in this repository for details on driving that tooling manually), but can // also be run using `flutter test` directly during development. import 'dart:async'; import 'dart:convert'; import 'dart:io'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/foundation.dart'; 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_platform_interface/src/webview_flutter_platform_interface_legacy.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_example/legacy/navigation_decision.dart'; import 'package:webview_flutter_wkwebview_example/legacy/navigation_request.dart'; import 'package:webview_flutter_wkwebview_example/legacy/web_view.dart'; Future main() async { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); final HttpServer server = await HttpServer.bind(InternetAddress.anyIPv4, 0); server.forEach((HttpRequest request) { if (request.uri.path == '/hello.txt') { request.response.writeln('Hello, world.'); } else if (request.uri.path == '/secondary.txt') { request.response.writeln('How are you today?'); } else if (request.uri.path == '/headers') { request.response.writeln('${request.headers}'); } else if (request.uri.path == '/favicon.ico') { request.response.statusCode = HttpStatus.notFound; } else { fail('unexpected request: ${request.method} ${request.uri}'); } request.response.close(); }); final String prefixUrl = 'http://${server.address.address}:${server.port}'; final String primaryUrl = '$prefixUrl/hello.txt'; final String secondaryUrl = '$prefixUrl/secondary.txt'; final String headersUrl = '$prefixUrl/headers'; testWidgets('initialUrl', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageFinishedCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, onPageFinished: pageFinishedCompleter.complete, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageFinishedCompleter.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); testWidgets( 'withWeakRefenceTo allows encapsulating class to be garbage collected', (WidgetTester tester) async { final Completer gcCompleter = Completer(); final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: gcCompleter.complete, ); ClassWithCallbackClass? instance = ClassWithCallbackClass(); instanceManager.addHostCreatedInstance(instance.callbackClass, 0); instance = null; // Force garbage collection. await IntegrationTestWidgetsFlutterBinding.instance .watchPerformance(() async { await tester.pumpAndSettle(); }); final int gcIdentifier = await gcCompleter.future; expect(gcIdentifier, 0); }, timeout: const Timeout(Duration(seconds: 10))); testWidgets('loadUrl', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final StreamController pageLoads = StreamController(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, onPageFinished: (String url) { pageLoads.add(url); }, ), ), ); final WebViewController controller = await controllerCompleter.future; await controller.loadUrl(secondaryUrl); await expectLater( pageLoads.stream.firstWhere((String url) => url == secondaryUrl), completion(secondaryUrl), ); }); testWidgets('evaluateJavascript', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, ), ), ); final WebViewController controller = await controllerCompleter.future; final String result = await controller.evaluateJavascript('1 + 1'); expect(result, equals('2')); }); testWidgets('loadUrl with headers', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final StreamController pageStarts = StreamController(); final StreamController pageLoads = StreamController(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageStarted: (String url) { pageStarts.add(url); }, onPageFinished: (String url) { pageLoads.add(url); }, ), ), ); final WebViewController controller = await controllerCompleter.future; final Map headers = { 'test_header': 'flutter_test_header' }; await controller.loadUrl(headersUrl, headers: headers); final String? currentUrl = await controller.currentUrl(); expect(currentUrl, headersUrl); await pageStarts.stream.firstWhere((String url) => url == currentUrl); await pageLoads.stream.firstWhere((String url) => url == currentUrl); final String content = await controller .runJavascriptReturningResult('document.documentElement.innerText'); expect(content.contains('flutter_test_header'), isTrue); }); testWidgets('JavascriptChannel', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageStarted = Completer(); final Completer pageLoaded = Completer(); final Completer channelCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), // This is the data URL for: '' initialUrl: 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, javascriptChannels: { JavascriptChannel( name: 'Echo', onMessageReceived: (JavascriptMessage message) { channelCompleter.complete(message.message); }, ), }, onPageStarted: (String url) { pageStarted.complete(null); }, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageStarted.future; await pageLoaded.future; expect(channelCompleter.isCompleted, isFalse); await controller.runJavascript('Echo.postMessage("hello");'); await expectLater(channelCompleter.future, completion('hello')); }); testWidgets('resize webview', (WidgetTester tester) async { final Completer buttonTapResizeCompleter = Completer(); final Completer onPageFinished = Completer(); bool resizeButtonTapped = false; await tester.pumpWidget(ResizableWebView( onResize: (_) { if (resizeButtonTapped) { buttonTapResizeCompleter.complete(); } }, onPageFinished: () => onPageFinished.complete(), )); await onPageFinished.future; resizeButtonTapped = true; await tester.tap(find.byKey(const ValueKey('resizeButton'))); await tester.pumpAndSettle(); expect(buttonTapResizeCompleter.future, completes); }); testWidgets('set custom userAgent', (WidgetTester tester) async { final Completer controllerCompleter1 = Completer(); final GlobalKey globalKey = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: globalKey, initialUrl: 'about:blank', javascriptMode: JavascriptMode.unrestricted, userAgent: 'Custom_User_Agent1', onWebViewCreated: (WebViewController controller) { controllerCompleter1.complete(controller); }, ), ), ); final WebViewController controller1 = await controllerCompleter1.future; final String customUserAgent1 = await _getUserAgent(controller1); expect(customUserAgent1, 'Custom_User_Agent1'); // rebuild the WebView with a different user agent. await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: globalKey, initialUrl: 'about:blank', javascriptMode: JavascriptMode.unrestricted, userAgent: 'Custom_User_Agent2', ), ), ); final String customUserAgent2 = await _getUserAgent(controller1); expect(customUserAgent2, 'Custom_User_Agent2'); }); testWidgets('use default platform userAgent after webView is rebuilt', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final GlobalKey globalKey = GlobalKey(); // Build the webView with no user agent to get the default platform user agent. await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: globalKey, initialUrl: primaryUrl, javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, ), ), ); final WebViewController controller = await controllerCompleter.future; final String defaultPlatformUserAgent = await _getUserAgent(controller); // rebuild the WebView with a custom user agent. await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: globalKey, initialUrl: 'about:blank', javascriptMode: JavascriptMode.unrestricted, userAgent: 'Custom_User_Agent', ), ), ); final String customUserAgent = await _getUserAgent(controller); expect(customUserAgent, 'Custom_User_Agent'); // rebuilds the WebView with no user agent. await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: globalKey, initialUrl: 'about:blank', javascriptMode: JavascriptMode.unrestricted, ), ), ); final String customUserAgent2 = await _getUserAgent(controller); expect(customUserAgent2, defaultPlatformUserAgent); }); group('Video playback policy', () { late String videoTestBase64; setUpAll(() async { final ByteData videoData = await rootBundle.load('assets/sample_video.mp4'); final String base64VideoData = base64Encode(Uint8List.view(videoData.buffer)); final String videoTest = ''' Video auto play '''; videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest)); }); testWidgets('Auto media playback', (WidgetTester tester) async { Completer controllerCompleter = Completer(); Completer pageLoaded = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (String url) { pageLoaded.complete(null); }, initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, ), ), ); WebViewController controller = await controllerCompleter.future; await pageLoaded.future; String isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(false)); controllerCompleter = Completer(); pageLoaded = Completer(); // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); controller = await controllerCompleter.future; await pageLoaded.future; isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(true)); }); testWidgets('Changes to initialMediaPlaybackPolicy are ignored', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); Completer pageLoaded = Completer(); final GlobalKey key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: key, initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (String url) { pageLoaded.complete(null); }, initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; String isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(false)); pageLoaded = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: key, initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); await controller.reload(); await pageLoaded.future; isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(false)); }); testWidgets('Video plays inline when allowsInlineMediaPlayback is true', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageLoaded = Completer(); final Completer videoPlaying = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, javascriptChannels: { JavascriptChannel( name: 'VideoTestTime', onMessageReceived: (JavascriptMessage message) { final double currentTime = double.parse(message.message); // Let it play for at least 1 second to make sure the related video's properties are set. if (currentTime > 1 && !videoPlaying.isCompleted) { videoPlaying.complete(null); } }, ), }, onPageFinished: (String url) { pageLoaded.complete(null); }, initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, allowsInlineMediaPlayback: true, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; // Pump once to trigger the video play. await tester.pump(); // Makes sure we get the correct event that indicates the video is actually playing. await videoPlaying.future; final String fullScreen = await controller.runJavascriptReturningResult('isFullScreen();'); expect(fullScreen, _webviewBool(false)); }); testWidgets( 'Video plays full screen when allowsInlineMediaPlayback is false', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageLoaded = Completer(); final Completer videoPlaying = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( initialUrl: 'data:text/html;charset=utf-8;base64,$videoTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, javascriptChannels: { JavascriptChannel( name: 'VideoTestTime', onMessageReceived: (JavascriptMessage message) { final double currentTime = double.parse(message.message); // Let it play for at least 1 second to make sure the related video's properties are set. if (currentTime > 1 && !videoPlaying.isCompleted) { videoPlaying.complete(null); } }, ), }, onPageFinished: (String url) { pageLoaded.complete(null); }, initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; // Pump once to trigger the video play. await tester.pump(); // Makes sure we get the correct event that indicates the video is actually playing. await videoPlaying.future; final String fullScreen = await controller.runJavascriptReturningResult('isFullScreen();'); expect(fullScreen, _webviewBool(true)); }); }); group('Audio playback policy', () { late String audioTestBase64; setUpAll(() async { final ByteData audioData = await rootBundle.load('assets/sample_audio.ogg'); final String base64AudioData = base64Encode(Uint8List.view(audioData.buffer)); final String audioTest = ''' Audio auto play '''; audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest)); }); testWidgets('Auto media playback', (WidgetTester tester) async { Completer controllerCompleter = Completer(); Completer pageStarted = Completer(); Completer pageLoaded = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageStarted: (String url) { pageStarted.complete(null); }, onPageFinished: (String url) { pageLoaded.complete(null); }, initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, ), ), ); WebViewController controller = await controllerCompleter.future; await pageStarted.future; await pageLoaded.future; String isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(false)); controllerCompleter = Completer(); pageStarted = Completer(); pageLoaded = Completer(); // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageStarted: (String url) { pageStarted.complete(null); }, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); controller = await controllerCompleter.future; await pageStarted.future; await pageLoaded.future; isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(true)); }); testWidgets('Changes to initialMediaPlaybackPolicy are ignored', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); Completer pageStarted = Completer(); Completer pageLoaded = Completer(); final GlobalKey key = GlobalKey(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: key, initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageStarted: (String url) { pageStarted.complete(null); }, onPageFinished: (String url) { pageLoaded.complete(null); }, initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageStarted.future; await pageLoaded.future; String isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(false)); pageStarted = Completer(); pageLoaded = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: key, initialUrl: 'data:text/html;charset=utf-8;base64,$audioTestBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageStarted: (String url) { pageStarted.complete(null); }, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); await controller.reload(); await pageStarted.future; await pageLoaded.future; isPaused = await controller.runJavascriptReturningResult('isPaused();'); expect(isPaused, _webviewBool(false)); }); }); testWidgets('getTitle', (WidgetTester tester) async { const String getTitleTest = ''' Some title '''; final String getTitleTestBase64 = base64Encode(const Utf8Encoder().convert(getTitleTest)); final Completer pageStarted = Completer(); final Completer pageLoaded = Completer(); final Completer controllerCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( initialUrl: 'data:text/html;charset=utf-8;base64,$getTitleTestBase64', javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, onPageStarted: (String url) { pageStarted.complete(null); }, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageStarted.future; await pageLoaded.future; // On at least iOS, it does not appear to be guaranteed that the native // code has the title when the page load completes. Execute some JavaScript // before checking the title to ensure that the page has been fully parsed // and processed. await controller.runJavascript('1;'); final String? title = await controller.getTitle(); expect(title, 'Some title'); }); group('Programmatic Scroll', () { testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { const String scrollTestPage = '''
'''; final String scrollTestPageBase64 = base64Encode(const Utf8Encoder().convert(scrollTestPage)); final Completer pageLoaded = Completer(); final Completer controllerCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( initialUrl: 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); final WebViewController controller = await controllerCompleter.future; await pageLoaded.future; await tester.pumpAndSettle(const Duration(seconds: 3)); int scrollPosX = await controller.getScrollX(); int scrollPosY = await controller.getScrollY(); // Check scrollTo() const int X_SCROLL = 123; const int Y_SCROLL = 321; // Get the initial position; this ensures that scrollTo is actually // changing something, but also gives the native view's scroll position // time to settle. expect(scrollPosX, isNot(X_SCROLL)); expect(scrollPosX, isNot(Y_SCROLL)); await controller.scrollTo(X_SCROLL, Y_SCROLL); scrollPosX = await controller.getScrollX(); scrollPosY = await controller.getScrollY(); expect(scrollPosX, X_SCROLL); expect(scrollPosY, Y_SCROLL); // Check scrollBy() (on top of scrollTo()) await controller.scrollBy(X_SCROLL, Y_SCROLL); scrollPosX = await controller.getScrollX(); scrollPosY = await controller.getScrollY(); expect(scrollPosX, X_SCROLL * 2); expect(scrollPosY, Y_SCROLL * 2); }); }); group('NavigationDelegate', () { const String blankPage = ''; final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' '${base64Encode(const Utf8Encoder().convert(blankPage))}'; testWidgets('can allow requests', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final StreamController pageLoads = StreamController.broadcast(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: blankPageEncoded, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, navigationDelegate: (NavigationRequest request) { return (request.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; }, onPageFinished: (String url) => pageLoads.add(url), ), ), ); await pageLoads.stream.first; // Wait for initial page load. final WebViewController controller = await controllerCompleter.future; await controller.runJavascript('location.href = "$secondaryUrl"'); await pageLoads.stream.first; // Wait for the next page load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); testWidgets('onWebResourceError', (WidgetTester tester) async { final Completer errorCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'https://www.notawebsite..com', onWebResourceError: (WebResourceError error) { errorCompleter.complete(error); }, ), ), ); final WebResourceError error = await errorCompleter.future; expect(error, isNotNull); if (Platform.isIOS) { expect(error.domain, isNotNull); expect(error.failingUrl, isNull); } else if (Platform.isAndroid) { expect(error.errorType, isNotNull); expect(error.failingUrl?.startsWith('https://www.notawebsite..com'), isTrue); } }); testWidgets('onWebResourceError is not called with valid url', (WidgetTester tester) async { final Completer errorCompleter = Completer(); final Completer pageFinishCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', onWebResourceError: (WebResourceError error) { errorCompleter.complete(error); }, onPageFinished: (_) => pageFinishCompleter.complete(), ), ), ); expect(errorCompleter.future, doesNotComplete); await pageFinishCompleter.future; }); testWidgets( 'onWebResourceError only called for main frame', (WidgetTester tester) async { const String iframeTest = ''' WebResourceError test '''; final String iframeTestBase64 = base64Encode(const Utf8Encoder().convert(iframeTest)); final Completer errorCompleter = Completer(); final Completer pageFinishCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: 'data:text/html;charset=utf-8;base64,$iframeTestBase64', onWebResourceError: (WebResourceError error) { errorCompleter.complete(error); }, onPageFinished: (_) => pageFinishCompleter.complete(), ), ), ); expect(errorCompleter.future, doesNotComplete); await pageFinishCompleter.future; }, ); testWidgets('can block requests', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final StreamController pageLoads = StreamController.broadcast(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: blankPageEncoded, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, navigationDelegate: (NavigationRequest request) { return (request.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; }, onPageFinished: (String url) => pageLoads.add(url), ), ), ); await pageLoads.stream.first; // Wait for initial page load. final WebViewController controller = await controllerCompleter.future; 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 .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 Completer controllerCompleter = Completer(); final StreamController pageLoads = StreamController.broadcast(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), initialUrl: blankPageEncoded, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, navigationDelegate: (NavigationRequest request) async { NavigationDecision decision = NavigationDecision.prevent; decision = await Future.delayed( const Duration(milliseconds: 10), () => NavigationDecision.navigate); return decision; }, onPageFinished: (String url) => pageLoads.add(url), ), ), ); await pageLoads.stream.first; // Wait for initial page load. final WebViewController controller = await controllerCompleter.future; await controller.runJavascript('location.href = "$secondaryUrl"'); await pageLoads.stream.first; // Wait for second page to load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); }); testWidgets('launches with gestureNavigationEnabled on iOS', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: SizedBox( width: 400, height: 300, child: WebView( key: GlobalKey(), initialUrl: primaryUrl, gestureNavigationEnabled: true, onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, ), ), ), ); final WebViewController controller = await controllerCompleter.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); testWidgets('target _blank opens in same window', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageLoaded = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (String url) { pageLoaded.complete(null); }, ), ), ); final WebViewController controller = await controllerCompleter.future; await controller.runJavascript('window.open("$primaryUrl", "_blank")'); await pageLoaded.future; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, primaryUrl); }); testWidgets( 'can open new window and go back', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); Completer pageLoaded = Completer(); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( key: GlobalKey(), onWebViewCreated: (WebViewController controller) { controllerCompleter.complete(controller); }, javascriptMode: JavascriptMode.unrestricted, onPageFinished: (String url) { pageLoaded.complete(); }, initialUrl: primaryUrl, ), ), ); final WebViewController controller = await controllerCompleter.future; expect(controller.currentUrl(), completion(primaryUrl)); await pageLoaded.future; pageLoaded = Completer(); await controller.runJavascript('window.open("$secondaryUrl")'); await pageLoaded.future; pageLoaded = Completer(); expect(controller.currentUrl(), completion(secondaryUrl)); expect(controller.canGoBack(), completion(true)); await controller.goBack(); await pageLoaded.future; await expectLater(controller.currentUrl(), completion(primaryUrl)); }, ); } // JavaScript booleans evaluate to different string values on Android and iOS. // This utility method returns the string boolean value of the current platform. String _webviewBool(bool value) { if (defaultTargetPlatform == TargetPlatform.iOS) { return value ? '1' : '0'; } return value ? 'true' : 'false'; } /// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests. Future _getUserAgent(WebViewController controller) async { return controller.runJavascriptReturningResult('navigator.userAgent;'); } class ResizableWebView extends StatefulWidget { const ResizableWebView( {Key? key, required this.onResize, required this.onPageFinished}) : super(key: key); final JavascriptMessageHandler onResize; final VoidCallback onPageFinished; @override State createState() => ResizableWebViewState(); } class ResizableWebViewState extends State { double webViewWidth = 200; double webViewHeight = 200; static const String resizePage = ''' Resize test '''; @override Widget build(BuildContext context) { final String resizeTestBase64 = base64Encode(const Utf8Encoder().convert(resizePage)); return Directionality( textDirection: TextDirection.ltr, child: Column( children: [ SizedBox( width: webViewWidth, height: webViewHeight, child: WebView( initialUrl: 'data:text/html;charset=utf-8;base64,$resizeTestBase64', javascriptChannels: { JavascriptChannel( name: 'Resize', onMessageReceived: widget.onResize, ), }, onPageFinished: (_) => widget.onPageFinished(), javascriptMode: JavascriptMode.unrestricted, ), ), TextButton( key: const Key('resizeButton'), onPressed: () { setState(() { webViewWidth += 100.0; webViewHeight += 100.0; }); }, child: const Text('ResizeButton'), ), ], ), ); } } class CopyableObjectWithCallback with Copyable { CopyableObjectWithCallback(this.callback); final VoidCallback callback; @override CopyableObjectWithCallback copy() { return CopyableObjectWithCallback(callback); } } class ClassWithCallbackClass { ClassWithCallbackClass() { callbackClass = CopyableObjectWithCallback( withWeakRefenceTo( this, (WeakReference weakReference) { return () { // Weak reference to `this` in callback. // ignore: unnecessary_statements weakReference; }; }, ), ); } late final CopyableObjectWithCallback callbackClass; } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // This test is run using `flutter drive` by the CI (see /script/tool/README.md // in this repository for details on driving that tooling manually), but can // also be run using `flutter test` directly during development. import 'dart:async'; import 'dart:convert'; import 'dart:io'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/foundation.dart'; 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_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 { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); final HttpServer server = await HttpServer.bind(InternetAddress.anyIPv4, 0); server.forEach((HttpRequest request) { if (request.uri.path == '/hello.txt') { request.response.writeln('Hello, world.'); } else if (request.uri.path == '/secondary.txt') { request.response.writeln('How are you today?'); } else if (request.uri.path == '/headers') { request.response.writeln('${request.headers}'); } else if (request.uri.path == '/favicon.ico') { request.response.statusCode = HttpStatus.notFound; } else { fail('unexpected request: ${request.method} ${request.uri}'); } request.response.close(); }); final String prefixUrl = 'http://${server.address.address}:${server.port}'; final String primaryUrl = '$prefixUrl/hello.txt'; final String secondaryUrl = '$prefixUrl/secondary.txt'; final String headersUrl = '$prefixUrl/headers'; testWidgets( 'withWeakReferenceTo allows encapsulating class to be garbage collected', (WidgetTester tester) async { final Completer gcCompleter = Completer(); final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: gcCompleter.complete, ); ClassWithCallbackClass? instance = ClassWithCallbackClass(); instanceManager.addHostCreatedInstance(instance.callbackClass, 0); instance = null; // Force garbage collection. await IntegrationTestWidgetsFlutterBinding.instance .watchPerformance(() async { await tester.pumpAndSettle(); }); final int gcIdentifier = await gcCompleter.future; 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(), ) ..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(), ) ..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'), completion(2), ); }); testWidgets('loadRequest with headers', (WidgetTester tester) async { final Map headers = { 'test_header': 'flutter_test_header' }; final StreamController pageLoads = StreamController(); final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), )..setOnPageFinished((String url) => pageLoads.add(url)), ) ..loadRequest( LoadRequestParams( uri: Uri.parse(headersUrl), headers: headers, ), ); 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( 'document.documentElement.innerText', ) as String; expect(content.contains('flutter_test_header'), isTrue); }); testWidgets('JavascriptChannel', (WidgetTester tester) async { final Completer pageFinished = Completer(); final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageFinished.complete()), ); final Completer channelCompleter = Completer(); await controller.addJavaScriptChannel( JavaScriptChannelParams( name: 'Echo', onMessageReceived: (JavaScriptMessage message) { channelCompleter.complete(message.message); }, ), ); controller.loadHtmlString( '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");'); await expectLater(channelCompleter.future, completion('hello')); }); testWidgets('resize webview', (WidgetTester tester) async { final Completer buttonTapResizeCompleter = Completer(); final Completer onPageFinished = Completer(); bool resizeButtonTapped = false; await tester.pumpWidget(ResizableWebView( onResize: () { if (resizeButtonTapped) { buttonTapResizeCompleter.complete(); } }, onPageFinished: () => onPageFinished.complete(), )); await onPageFinished.future; resizeButtonTapped = true; await tester.tap(find.byKey(const ValueKey('resizeButton'))); await tester.pumpAndSettle(); await expectLater(buttonTapResizeCompleter.future, completes); }); testWidgets('set custom userAgent', (WidgetTester tester) async { final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..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'); }); group('Video playback policy', () { late String videoTestBase64; setUpAll(() async { final ByteData videoData = await rootBundle.load('assets/sample_video.mp4'); final String base64VideoData = base64Encode(Uint8List.view(videoData.buffer)); final String videoTest = ''' Video auto play '''; videoTestBase64 = base64Encode(const Utf8Encoder().convert(videoTest)); }); testWidgets('Auto media playback', (WidgetTester tester) async { Completer pageLoaded = Completer(); PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams( mediaTypesRequiringUserAction: const {}, ), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageLoaded.complete()), ) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,$videoTestBase64', ), ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }, )); await pageLoaded.future; bool isPaused = await controller.runJavaScriptReturningResult('isPaused();') as bool; expect(isPaused, false); pageLoaded = Completer(); controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageLoaded.complete()), ) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,$videoTestBase64', ), ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }, )); await pageLoaded.future; isPaused = await controller.runJavaScriptReturningResult('isPaused();') as bool; expect(isPaused, true); }); testWidgets('Video plays inline when allowsInlineMediaPlayback is true', (WidgetTester tester) async { final Completer pageLoaded = Completer(); final Completer videoPlaying = Completer(); final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams( mediaTypesRequiringUserAction: const {}, allowsInlineMediaPlayback: true, ), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageLoaded.complete()), ) ..addJavaScriptChannel( JavaScriptChannelParams( name: 'VideoTestTime', onMessageReceived: (JavaScriptMessage message) { final double currentTime = double.parse(message.message); // Let it play for at least 1 second to make sure the related video's properties are set. if (currentTime > 1 && !videoPlaying.isCompleted) { videoPlaying.complete(null); } }, ), ) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,$videoTestBase64', ), ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }, )); await tester.pumpAndSettle(); await pageLoaded.future; // Makes sure we get the correct event that indicates the video is actually playing. await videoPlaying.future; final bool fullScreen = await controller .runJavaScriptReturningResult('isFullScreen();') as bool; expect(fullScreen, false); }); testWidgets( 'Video plays full screen when allowsInlineMediaPlayback is false', (WidgetTester tester) async { final Completer pageLoaded = Completer(); final Completer videoPlaying = Completer(); final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams( mediaTypesRequiringUserAction: const {}, ), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageLoaded.complete()), ) ..addJavaScriptChannel( JavaScriptChannelParams( name: 'VideoTestTime', onMessageReceived: (JavaScriptMessage message) { final double currentTime = double.parse(message.message); // Let it play for at least 1 second to make sure the related video's properties are set. if (currentTime > 1 && !videoPlaying.isCompleted) { videoPlaying.complete(null); } }, ), ) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,$videoTestBase64', ), ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }, )); await tester.pumpAndSettle(); await pageLoaded.future; // Makes sure we get the correct event that indicates the video is actually playing. await videoPlaying.future; final bool fullScreen = await controller .runJavaScriptReturningResult('isFullScreen();') as bool; expect(fullScreen, true); }); }); group('Audio playback policy', () { late String audioTestBase64; setUpAll(() async { final ByteData audioData = await rootBundle.load('assets/sample_audio.ogg'); final String base64AudioData = base64Encode(Uint8List.view(audioData.buffer)); final String audioTest = ''' Audio auto play '''; audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest)); }); testWidgets('Auto media playback', (WidgetTester tester) async { Completer pageLoaded = Completer(); PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams( mediaTypesRequiringUserAction: const {}, ), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageLoaded.complete()), ) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,$audioTestBase64', ), ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }, )); await pageLoaded.future; bool isPaused = await controller.runJavaScriptReturningResult('isPaused();') as bool; expect(isPaused, false); pageLoaded = Completer(); controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageLoaded.complete()), ) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,$audioTestBase64', ), ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }, )); await pageLoaded.future; isPaused = await controller.runJavaScriptReturningResult('isPaused();') as bool; expect(isPaused, true); }); }); testWidgets('getTitle', (WidgetTester tester) async { const String getTitleTest = ''' Some title '''; final String getTitleTestBase64 = base64Encode(const Utf8Encoder().convert(getTitleTest)); final Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageLoaded.complete()), ) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,$getTitleTestBase64', ), ), ); 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 // code has the title when the page load completes. Execute some JavaScript // before checking the title to ensure that the page has been fully parsed // and processed. await controller.runJavaScript('1;'); final String? title = await controller.getTitle(); expect(title, 'Some title'); }); group('Programmatic Scroll', () { testWidgets('setAndGetScrollPosition', (WidgetTester tester) async { const String scrollTestPage = '''
'''; final String scrollTestPageBase64 = base64Encode(const Utf8Encoder().convert(scrollTestPage)); final Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), )..setOnPageFinished((_) => pageLoaded.complete()), ) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,$scrollTestPageBase64', ), ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }, )); await pageLoaded.future; await tester.pumpAndSettle(const Duration(seconds: 3)); Offset scrollPos = await controller.getScrollPosition(); // Check scrollTo() const int X_SCROLL = 123; const int Y_SCROLL = 321; // Get the initial position; this ensures that scrollTo is actually // changing something, but also gives the native view's scroll position // time to settle. expect(scrollPos.dx, isNot(X_SCROLL)); expect(scrollPos.dy, isNot(Y_SCROLL)); await controller.scrollTo(X_SCROLL, Y_SCROLL); scrollPos = await controller.getScrollPosition(); expect(scrollPos.dx, X_SCROLL); expect(scrollPos.dy, Y_SCROLL); // Check scrollBy() (on top of scrollTo()) await controller.scrollBy(X_SCROLL, Y_SCROLL); scrollPos = await controller.getScrollPosition(); expect(scrollPos.dx, X_SCROLL * 2); expect(scrollPos.dy, Y_SCROLL * 2); }); }); group('NavigationDelegate', () { const String blankPage = ''; final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' '${base64Encode(const Utf8Encoder().convert(blankPage))}'; testWidgets('can allow requests', (WidgetTester tester) async { Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), ) ..setOnPageFinished((_) => pageLoaded.complete()) ..setOnNavigationRequest((NavigationRequest navigationRequest) { return (navigationRequest.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; }), ) ..loadRequest( LoadRequestParams(uri: Uri.parse(blankPageEncoded)), ); 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; final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); testWidgets('onWebResourceError', (WidgetTester tester) async { final Completer errorCompleter = Completer(); final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), )..setOnWebResourceError((WebResourceError error) { errorCompleter.complete(error); }), ) ..loadRequest( 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); expect((error as WebKitWebResourceError).domain, isNotNull); }); testWidgets('onWebResourceError is not called with valid url', (WidgetTester tester) async { final Completer errorCompleter = Completer(); final Completer pageFinishCompleter = Completer(); final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), ) ..setOnPageFinished((_) => pageFinishCompleter.complete()) ..setOnWebResourceError((WebResourceError error) { errorCompleter.complete(error); }), ) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+', ), ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }, )); expect(errorCompleter.future, doesNotComplete); await pageFinishCompleter.future; }); testWidgets( 'onWebResourceError only called for main frame', (WidgetTester tester) async { const String iframeTest = ''' WebResourceError test '''; final String iframeTestBase64 = base64Encode(const Utf8Encoder().convert(iframeTest)); final Completer errorCompleter = Completer(); final Completer pageFinishCompleter = Completer(); final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), ) ..setOnPageFinished((_) => pageFinishCompleter.complete()) ..setOnWebResourceError((WebResourceError error) { errorCompleter.complete(error); }), ) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,$iframeTestBase64', ), ), ); await tester.pumpWidget(Builder( builder: (BuildContext context) { return PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context); }, )); expect(errorCompleter.future, doesNotComplete); await pageFinishCompleter.future; }, ); testWidgets('can block requests', (WidgetTester tester) async { Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), ) ..setOnPageFinished((_) => pageLoaded.complete()) ..setOnNavigationRequest((NavigationRequest navigationRequest) { return (navigationRequest.url.contains('youtube.com')) ? NavigationDecision.prevent : NavigationDecision.navigate; }), ) ..loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded))); 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 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 { Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), ) ..setOnPageFinished((_) => pageLoaded.complete()) ..setOnNavigationRequest( (NavigationRequest navigationRequest) async { NavigationDecision decision = NavigationDecision.prevent; decision = await Future.delayed( const Duration(milliseconds: 10), () => NavigationDecision.navigate); return decision; }), ) ..loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded))); 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; // Wait for second page to load. final String? currentUrl = await controller.currentUrl(); expect(currentUrl, secondaryUrl); }); }); testWidgets('launches with gestureNavigationEnabled on iOS', (WidgetTester tester) async { final WebKitWebViewController controller = WebKitWebViewController( WebKitWebViewControllerCreationParams(), ) ..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); }); testWidgets('target _blank opens in same window', (WidgetTester tester) async { final Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate(WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), )..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); }); testWidgets( 'can open new window and go back', (WidgetTester tester) async { Completer pageLoaded = Completer(); final PlatformWebViewController controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate(WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), )..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(); await controller.runJavaScript('window.open("$secondaryUrl")'); await pageLoaded.future; pageLoaded = Completer(); expect(controller.currentUrl(), completion(secondaryUrl)); expect(controller.canGoBack(), completion(true)); await controller.goBack(); await pageLoaded.future; await expectLater(controller.currentUrl(), completion(primaryUrl)); }, ); } /// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests. Future _getUserAgent(PlatformWebViewController controller) async { return await controller.runJavaScriptReturningResult('navigator.userAgent;') as String; } class ResizableWebView extends StatefulWidget { const ResizableWebView({ Key? key, required this.onResize, required this.onPageFinished, }) : super(key: key); final VoidCallback onResize; final VoidCallback onPageFinished; @override State createState() => ResizableWebViewState(); } class ResizableWebViewState extends State { late final PlatformWebViewController controller = PlatformWebViewController( const PlatformWebViewControllerCreationParams(), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setPlatformNavigationDelegate( WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams(), )..setOnPageFinished((_) => widget.onPageFinished()), ) ..addJavaScriptChannel( JavaScriptChannelParams( name: 'Resize', onMessageReceived: (_) { widget.onResize(); }, ), ) ..loadRequest( LoadRequestParams( uri: Uri.parse( 'data:text/html;charset=utf-8;base64,${base64Encode(const Utf8Encoder().convert(resizePage))}', ), ), ); double webViewWidth = 200; double webViewHeight = 200; static const String resizePage = ''' Resize test '''; @override Widget build(BuildContext context) { return Directionality( textDirection: TextDirection.ltr, child: Column( children: [ SizedBox( width: webViewWidth, height: webViewHeight, child: PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: controller), ).build(context), ), TextButton( key: const Key('resizeButton'), onPressed: () { setState(() { webViewWidth += 100.0; webViewHeight += 100.0; }); }, child: const Text('ResizeButton'), ), ], ), ); } } class CopyableObjectWithCallback with Copyable { CopyableObjectWithCallback(this.callback); final VoidCallback callback; @override CopyableObjectWithCallback copy() { return CopyableObjectWithCallback(callback); } } class ClassWithCallbackClass { ClassWithCallbackClass() { callbackClass = CopyableObjectWithCallback( withWeakRefenceTo( this, (WeakReference weakReference) { return () { // Weak reference to `this` in callback. // ignore: unnecessary_statements weakReference; }; }, ), ); } late final CopyableObjectWithCallback callbackClass; } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 11.0 ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/Flutter/Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/Flutter/Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project # platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def flutter_root generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) unless File.exist?(generated_xcode_build_settings_path) raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" end File.foreach(generated_xcode_build_settings_path) do |line| matches = line.match(/FLUTTER_ROOT\=(.*)/) return matches[1].strip if matches end raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" end require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) flutter_ios_podfile_setup target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths # Matches test_spec dependency. pod 'OCMock', '3.5' end end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner/AppDelegate.h ================================================ // Copyright 2013 The Flutter 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 ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner/AppDelegate.m ================================================ // Copyright 2013 The Flutter 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]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "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" } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # 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. ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName webview_flutter_example CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance CADisableMinimumFrameDurationOnPhone ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner/main.m ================================================ // Copyright 2013 The Flutter 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])); } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 8F4FF949299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */; }; 8FA6A87928062CD000A4B183 /* FWFInstanceManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */; }; 8FB79B5328134C3100C101D3 /* FWFWebViewHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */; }; 8FB79B55281B24F600C101D3 /* FWFDataConvertersTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B54281B24F600C101D3 /* FWFDataConvertersTests.m */; }; 8FB79B672820453400C101D3 /* FWFHTTPCookieStoreHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B662820453400C101D3 /* FWFHTTPCookieStoreHostApiTests.m */; }; 8FB79B6928204E8700C101D3 /* FWFPreferencesHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B6828204E8700C101D3 /* FWFPreferencesHostApiTests.m */; }; 8FB79B6B28204EE500C101D3 /* FWFWebsiteDataStoreHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B6A28204EE500C101D3 /* FWFWebsiteDataStoreHostApiTests.m */; }; 8FB79B6D2820533B00C101D3 /* FWFWebViewConfigurationHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B6C2820533B00C101D3 /* FWFWebViewConfigurationHostApiTests.m */; }; 8FB79B73282096B500C101D3 /* FWFScriptMessageHandlerHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B72282096B500C101D3 /* FWFScriptMessageHandlerHostApiTests.m */; }; 8FB79B7928209D1300C101D3 /* FWFUserContentControllerHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B7828209D1300C101D3 /* FWFUserContentControllerHostApiTests.m */; }; 8FB79B832820A39300C101D3 /* FWFNavigationDelegateHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B822820A39300C101D3 /* FWFNavigationDelegateHostApiTests.m */; }; 8FB79B852820A3A400C101D3 /* FWFUIDelegateHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B842820A3A400C101D3 /* FWFUIDelegateHostApiTests.m */; }; 8FB79B8F2820BAB300C101D3 /* FWFScrollViewHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B8E2820BAB300C101D3 /* FWFScrollViewHostApiTests.m */; }; 8FB79B912820BAC700C101D3 /* FWFUIViewHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B902820BAC700C101D3 /* FWFUIViewHostApiTests.m */; }; 8FB79B972821985200C101D3 /* FWFObjectHostApiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FB79B962821985200C101D3 /* FWFObjectHostApiTests.m */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; D7587C3652F6906210B3AE88 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 17781D9462A1AEA7C99F8E45 /* libPods-RunnerTests.a */; }; DAF0E91266956134538CC667 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 572FFC2B2BA326B420B22679 /* libPods-Runner.a */; }; F7151F77266057800028CB91 /* FLTWebViewUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F76266057800028CB91 /* FLTWebViewUITests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 68BDCAEE23C3F7CB00D9C032 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; F7151F79266057800028CB91 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; proxyType = 1; remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 17781D9462A1AEA7C99F8E45 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 2286ACB87EA8CA27E739AD6C /* 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 = ""; }; 39B2BDAA45DC06EAB8A6C4E7 /* 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 = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 572FFC2B2BA326B420B22679 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 68BDCAE923C3F7CB00D9C032 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 68BDCAED23C3F7CB00D9C032 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FWFWebViewFlutterWKWebViewExternalAPITests.m; sourceTree = ""; }; 8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFInstanceManagerTests.m; sourceTree = ""; }; 8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFWebViewHostApiTests.m; sourceTree = ""; }; 8FB79B54281B24F600C101D3 /* FWFDataConvertersTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFDataConvertersTests.m; sourceTree = ""; }; 8FB79B662820453400C101D3 /* FWFHTTPCookieStoreHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFHTTPCookieStoreHostApiTests.m; sourceTree = ""; }; 8FB79B6828204E8700C101D3 /* FWFPreferencesHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFPreferencesHostApiTests.m; sourceTree = ""; }; 8FB79B6A28204EE500C101D3 /* FWFWebsiteDataStoreHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFWebsiteDataStoreHostApiTests.m; sourceTree = ""; }; 8FB79B6C2820533B00C101D3 /* FWFWebViewConfigurationHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFWebViewConfigurationHostApiTests.m; sourceTree = ""; }; 8FB79B72282096B500C101D3 /* FWFScriptMessageHandlerHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFScriptMessageHandlerHostApiTests.m; sourceTree = ""; }; 8FB79B7828209D1300C101D3 /* FWFUserContentControllerHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFUserContentControllerHostApiTests.m; sourceTree = ""; }; 8FB79B822820A39300C101D3 /* FWFNavigationDelegateHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFNavigationDelegateHostApiTests.m; sourceTree = ""; }; 8FB79B842820A3A400C101D3 /* FWFUIDelegateHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFUIDelegateHostApiTests.m; sourceTree = ""; }; 8FB79B8E2820BAB300C101D3 /* FWFScrollViewHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFScrollViewHostApiTests.m; sourceTree = ""; }; 8FB79B902820BAC700C101D3 /* FWFUIViewHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFUIViewHostApiTests.m; sourceTree = ""; }; 8FB79B962821985200C101D3 /* FWFObjectHostApiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FWFObjectHostApiTests.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B89AA31A64040E4A2F1E0CAF /* 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 = ""; }; F7151F74266057800028CB91 /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F7151F76266057800028CB91 /* FLTWebViewUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLTWebViewUITests.m; sourceTree = ""; }; F7151F78266057800028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F7A1921261392D1CBDAEC2E8 /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 68BDCAE623C3F7CB00D9C032 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( D7587C3652F6906210B3AE88 /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( DAF0E91266956134538CC667 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F71266057800028CB91 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 52FBC2B567345431F81A0A0F /* Frameworks */ = { isa = PBXGroup; children = ( 572FFC2B2BA326B420B22679 /* libPods-Runner.a */, 17781D9462A1AEA7C99F8E45 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; }; 68BDCAEA23C3F7CB00D9C032 /* RunnerTests */ = { isa = PBXGroup; children = ( 68BDCAED23C3F7CB00D9C032 /* Info.plist */, 8F4FF948299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m */, 8FA6A87828062CD000A4B183 /* FWFInstanceManagerTests.m */, 8FB79B5228134C3100C101D3 /* FWFWebViewHostApiTests.m */, 8FB79B54281B24F600C101D3 /* FWFDataConvertersTests.m */, 8FB79B662820453400C101D3 /* FWFHTTPCookieStoreHostApiTests.m */, 8FB79B6828204E8700C101D3 /* FWFPreferencesHostApiTests.m */, 8FB79B6A28204EE500C101D3 /* FWFWebsiteDataStoreHostApiTests.m */, 8FB79B6C2820533B00C101D3 /* FWFWebViewConfigurationHostApiTests.m */, 8FB79B72282096B500C101D3 /* FWFScriptMessageHandlerHostApiTests.m */, 8FB79B7828209D1300C101D3 /* FWFUserContentControllerHostApiTests.m */, 8FB79B822820A39300C101D3 /* FWFNavigationDelegateHostApiTests.m */, 8FB79B842820A3A400C101D3 /* FWFUIDelegateHostApiTests.m */, 8FB79B8E2820BAB300C101D3 /* FWFScrollViewHostApiTests.m */, 8FB79B902820BAC700C101D3 /* FWFUIViewHostApiTests.m */, 8FB79B962821985200C101D3 /* FWFObjectHostApiTests.m */, ); path = RunnerTests; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 68BDCAEA23C3F7CB00D9C032 /* RunnerTests */, F7151F75266057800028CB91 /* RunnerUITests */, 97C146EF1CF9000F007C117D /* Products */, B8AEEA11D6ECBD09750349AE /* Pods */, 52FBC2B567345431F81A0A0F /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, 68BDCAE923C3F7CB00D9C032 /* RunnerTests.xctest */, F7151F74266057800028CB91 /* RunnerUITests.xctest */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; B8AEEA11D6ECBD09750349AE /* Pods */ = { isa = PBXGroup; children = ( F7A1921261392D1CBDAEC2E8 /* Pods-Runner.debug.xcconfig */, B89AA31A64040E4A2F1E0CAF /* Pods-Runner.release.xcconfig */, 39B2BDAA45DC06EAB8A6C4E7 /* Pods-RunnerTests.debug.xcconfig */, 2286ACB87EA8CA27E739AD6C /* Pods-RunnerTests.release.xcconfig */, ); path = Pods; sourceTree = ""; }; F7151F75266057800028CB91 /* RunnerUITests */ = { isa = PBXGroup; children = ( F7151F76266057800028CB91 /* FLTWebViewUITests.m */, F7151F78266057800028CB91 /* Info.plist */, ); path = RunnerUITests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 68BDCAE823C3F7CB00D9C032 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = 68BDCAF223C3F7CB00D9C032 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( AA38EF430495C2FB50F0F114 /* [CP] Check Pods Manifest.lock */, 68BDCAE523C3F7CB00D9C032 /* Sources */, 68BDCAE623C3F7CB00D9C032 /* Frameworks */, 68BDCAE723C3F7CB00D9C032 /* Resources */, ); buildRules = ( ); dependencies = ( 68BDCAEF23C3F7CB00D9C032 /* PBXTargetDependency */, ); name = RunnerTests; productName = webview_flutter_exampleTests; productReference = 68BDCAE923C3F7CB00D9C032 /* RunnerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 6F536C27DD48B395A30EBB65 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; F7151F73266057800028CB91 /* RunnerUITests */ = { isa = PBXNativeTarget; buildConfigurationList = F7151F7B266057800028CB91 /* Build configuration list for PBXNativeTarget "RunnerUITests" */; buildPhases = ( F7151F70266057800028CB91 /* Sources */, F7151F71266057800028CB91 /* Frameworks */, F7151F72266057800028CB91 /* Resources */, ); buildRules = ( ); dependencies = ( F7151F7A266057800028CB91 /* PBXTargetDependency */, ); name = RunnerUITests; productName = RunnerUITests; productReference = F7151F74266057800028CB91 /* RunnerUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { DefaultBuildSystemTypeForWorkspace = Original; LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 68BDCAE823C3F7CB00D9C032 = { DevelopmentTeam = 7624MWN53C; ProvisioningStyle = Automatic; }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; DevelopmentTeam = 7624MWN53C; }; F7151F73266057800028CB91 = { CreatedOnToolsVersion = 12.5; DevelopmentTeam = 7624MWN53C; ProvisioningStyle = Automatic; TestTargetID = 97C146ED1CF9000F007C117D; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, 68BDCAE823C3F7CB00D9C032 /* RunnerTests */, F7151F73266057800028CB91 /* RunnerUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 68BDCAE723C3F7CB00D9C032 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F72266057800028CB91 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n"; }; 6F536C27DD48B395A30EBB65 /* [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", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; 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\n"; }; AA38EF430495C2FB50F0F114 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 68BDCAE523C3F7CB00D9C032 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 8FA6A87928062CD000A4B183 /* FWFInstanceManagerTests.m in Sources */, 8FB79B852820A3A400C101D3 /* FWFUIDelegateHostApiTests.m in Sources */, 8FB79B972821985200C101D3 /* FWFObjectHostApiTests.m in Sources */, 8FB79B672820453400C101D3 /* FWFHTTPCookieStoreHostApiTests.m in Sources */, 8FB79B5328134C3100C101D3 /* FWFWebViewHostApiTests.m in Sources */, 8FB79B73282096B500C101D3 /* FWFScriptMessageHandlerHostApiTests.m in Sources */, 8FB79B7928209D1300C101D3 /* FWFUserContentControllerHostApiTests.m in Sources */, 8F4FF949299ADC2D000A6586 /* FWFWebViewFlutterWKWebViewExternalAPITests.m in Sources */, 8FB79B6B28204EE500C101D3 /* FWFWebsiteDataStoreHostApiTests.m in Sources */, 8FB79B8F2820BAB300C101D3 /* FWFScrollViewHostApiTests.m in Sources */, 8FB79B912820BAC700C101D3 /* FWFUIViewHostApiTests.m in Sources */, 8FB79B55281B24F600C101D3 /* FWFDataConvertersTests.m in Sources */, 8FB79B6D2820533B00C101D3 /* FWFWebViewConfigurationHostApiTests.m in Sources */, 8FB79B832820A39300C101D3 /* FWFNavigationDelegateHostApiTests.m in Sources */, 8FB79B6928204E8700C101D3 /* FWFPreferencesHostApiTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; F7151F70266057800028CB91 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( F7151F77266057800028CB91 /* FLTWebViewUITests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 68BDCAEF23C3F7CB00D9C032 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = 68BDCAEE23C3F7CB00D9C032 /* PBXContainerItemProxy */; }; F7151F7A266057800028CB91 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = F7151F79266057800028CB91 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 68BDCAF023C3F7CB00D9C032 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 39B2BDAA45DC06EAB8A6C4E7 /* 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; }; 68BDCAF123C3F7CB00D9C032 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 2286ACB87EA8CA27E739AD6C /* 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_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"; }; name = Release; }; 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++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; 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; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.webviewFlutterExample; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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 = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.webviewFlutterExample; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; F7151F7C266057800028CB91 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = Runner; }; name = Debug; }; F7151F7D266057800028CB91 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = RunnerUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = Runner; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 68BDCAF223C3F7CB00D9C032 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 68BDCAF023C3F7CB00D9C032 /* Debug */, 68BDCAF123C3F7CB00D9C032 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; F7151F7B266057800028CB91 /* Build configuration list for PBXNativeTarget "RunnerUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( F7151F7C266057800028CB91 /* Debug */, F7151F7D266057800028CB91 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFDataConvertersTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import Flutter; @import XCTest; @import webview_flutter_wkwebview; #import @interface FWFDataConvertersTests : XCTestCase @end @implementation FWFDataConvertersTests - (void)testFWFNSURLRequestFromRequestData { NSURLRequest *request = FWFNSURLRequestFromRequestData([FWFNSUrlRequestData makeWithUrl:@"https://flutter.dev" httpMethod:@"post" httpBody:[FlutterStandardTypedData typedDataWithBytes:[NSData data]] allHttpHeaderFields:@{@"a" : @"header"}]); XCTAssertEqualObjects(request.URL, [NSURL URLWithString:@"https://flutter.dev"]); XCTAssertEqualObjects(request.HTTPMethod, @"POST"); XCTAssertEqualObjects(request.HTTPBody, [NSData data]); XCTAssertEqualObjects(request.allHTTPHeaderFields, @{@"a" : @"header"}); } - (void)testFWFNSURLRequestFromRequestDataDoesNotOverrideDefaultValuesWithNull { NSURLRequest *request = FWFNSURLRequestFromRequestData([FWFNSUrlRequestData makeWithUrl:@"https://flutter.dev" httpMethod:nil httpBody:nil allHttpHeaderFields:@{}]); XCTAssertEqualObjects(request.HTTPMethod, @"GET"); } - (void)testFWFNSHTTPCookieFromCookieData { NSHTTPCookie *cookie = FWFNSHTTPCookieFromCookieData([FWFNSHttpCookieData makeWithPropertyKeys:@[ [FWFNSHttpCookiePropertyKeyEnumData makeWithValue:FWFNSHttpCookiePropertyKeyEnumName] ] propertyValues:@[ @"cookieName" ]]); XCTAssertEqualObjects(cookie, [NSHTTPCookie cookieWithProperties:@{NSHTTPCookieName : @"cookieName"}]); } - (void)testFWFWKUserScriptFromScriptData { WKUserScript *userScript = FWFWKUserScriptFromScriptData([FWFWKUserScriptData makeWithSource:@"mySource" injectionTime:[FWFWKUserScriptInjectionTimeEnumData makeWithValue:FWFWKUserScriptInjectionTimeEnumAtDocumentStart] isMainFrameOnly:@NO]); XCTAssertEqualObjects(userScript.source, @"mySource"); XCTAssertEqual(userScript.injectionTime, WKUserScriptInjectionTimeAtDocumentStart); XCTAssertEqual(userScript.isForMainFrameOnly, NO); } - (void)testFWFWKNavigationActionDataFromNavigationAction { WKNavigationAction *mockNavigationAction = OCMClassMock([WKNavigationAction class]); OCMStub([mockNavigationAction navigationType]).andReturn(WKNavigationTypeReload); NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.flutter.dev/"]]; OCMStub([mockNavigationAction request]).andReturn(request); WKFrameInfo *mockFrameInfo = OCMClassMock([WKFrameInfo class]); OCMStub([mockFrameInfo isMainFrame]).andReturn(YES); OCMStub([mockNavigationAction targetFrame]).andReturn(mockFrameInfo); FWFWKNavigationActionData *data = FWFWKNavigationActionDataFromNavigationAction(mockNavigationAction); XCTAssertNotNil(data); XCTAssertEqual(data.navigationType, FWFWKNavigationTypeReload); } - (void)testFWFNSUrlRequestDataFromNSURLRequest { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://www.flutter.dev/"]]; request.HTTPMethod = @"POST"; request.HTTPBody = [@"aString" dataUsingEncoding:NSUTF8StringEncoding]; request.allHTTPHeaderFields = @{@"a" : @"field"}; FWFNSUrlRequestData *data = FWFNSUrlRequestDataFromNSURLRequest(request); XCTAssertEqualObjects(data.url, @"https://www.flutter.dev/"); XCTAssertEqualObjects(data.httpMethod, @"POST"); XCTAssertEqualObjects(data.httpBody.data, [@"aString" dataUsingEncoding:NSUTF8StringEncoding]); XCTAssertEqualObjects(data.allHttpHeaderFields, @{@"a" : @"field"}); } - (void)testFWFWKFrameInfoDataFromWKFrameInfo { WKFrameInfo *mockFrameInfo = OCMClassMock([WKFrameInfo class]); OCMStub([mockFrameInfo isMainFrame]).andReturn(YES); FWFWKFrameInfoData *targetFrameData = FWFWKFrameInfoDataFromWKFrameInfo(mockFrameInfo); XCTAssertEqualObjects(targetFrameData.isMainFrame, @YES); } - (void)testFWFNSErrorDataFromNSError { NSError *error = [NSError errorWithDomain:@"domain" code:23 userInfo:@{NSLocalizedDescriptionKey : @"description"}]; FWFNSErrorData *data = FWFNSErrorDataFromNSError(error); XCTAssertEqualObjects(data.code, @23); XCTAssertEqualObjects(data.domain, @"domain"); XCTAssertEqualObjects(data.localizedDescription, @"description"); } - (void)testFWFWKScriptMessageDataFromWKScriptMessage { WKScriptMessage *mockScriptMessage = OCMClassMock([WKScriptMessage class]); OCMStub([mockScriptMessage name]).andReturn(@"name"); OCMStub([mockScriptMessage body]).andReturn(@"message"); FWFWKScriptMessageData *data = FWFWKScriptMessageDataFromWKScriptMessage(mockScriptMessage); XCTAssertEqualObjects(data.name, @"name"); XCTAssertEqualObjects(data.body, @"message"); } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFHTTPCookieStoreHostApiTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import Flutter; @import XCTest; @import webview_flutter_wkwebview; #import @interface FWFHTTPCookieStoreHostApiTests : XCTestCase @end @implementation FWFHTTPCookieStoreHostApiTests - (void)testCreateFromWebsiteDataStoreWithIdentifier API_AVAILABLE(ios(11.0)) { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFHTTPCookieStoreHostApiImpl *hostAPI = [[FWFHTTPCookieStoreHostApiImpl alloc] initWithInstanceManager:instanceManager]; WKWebsiteDataStore *mockDataStore = OCMClassMock([WKWebsiteDataStore class]); OCMStub([mockDataStore httpCookieStore]).andReturn(OCMClassMock([WKHTTPCookieStore class])); [instanceManager addDartCreatedInstance:mockDataStore withIdentifier:0]; FlutterError *error; [hostAPI createFromWebsiteDataStoreWithIdentifier:@1 dataStoreIdentifier:@0 error:&error]; WKHTTPCookieStore *cookieStore = (WKHTTPCookieStore *)[instanceManager instanceForIdentifier:1]; XCTAssertTrue([cookieStore isKindOfClass:[WKHTTPCookieStore class]]); XCTAssertNil(error); } - (void)testSetCookie API_AVAILABLE(ios(11.0)) { WKHTTPCookieStore *mockHttpCookieStore = OCMClassMock([WKHTTPCookieStore class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockHttpCookieStore withIdentifier:0]; FWFHTTPCookieStoreHostApiImpl *hostAPI = [[FWFHTTPCookieStoreHostApiImpl alloc] initWithInstanceManager:instanceManager]; FWFNSHttpCookieData *cookieData = [FWFNSHttpCookieData makeWithPropertyKeys:@[ [FWFNSHttpCookiePropertyKeyEnumData makeWithValue:FWFNSHttpCookiePropertyKeyEnumName] ] propertyValues:@[ @"hello" ]]; FlutterError *__block blockError; [hostAPI setCookieForStoreWithIdentifier:@0 cookie:cookieData completion:^(FlutterError *error) { blockError = error; }]; OCMVerify([mockHttpCookieStore setCookie:[NSHTTPCookie cookieWithProperties:@{NSHTTPCookieName : @"hello"}] completionHandler:OCMOCK_ANY]); XCTAssertNil(blockError); } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFInstanceManagerTests.m ================================================ // Copyright 2013 The Flutter 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 webview_flutter_wkwebview; @import webview_flutter_wkwebview.Test; @interface FWFInstanceManagerTests : XCTestCase @end @implementation FWFInstanceManagerTests - (void)testAddDartCreatedInstance { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; NSObject *object = [[NSObject alloc] init]; [instanceManager addDartCreatedInstance:object withIdentifier:0]; XCTAssertEqualObjects([instanceManager instanceForIdentifier:0], object); XCTAssertEqual([instanceManager identifierWithStrongReferenceForInstance:object], 0); } - (void)testAddHostCreatedInstance { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; NSObject *object = [[NSObject alloc] init]; [instanceManager addHostCreatedInstance:object]; long identifier = [instanceManager identifierWithStrongReferenceForInstance:object]; XCTAssertNotEqual(identifier, NSNotFound); XCTAssertEqualObjects([instanceManager instanceForIdentifier:identifier], object); } - (void)testRemoveInstanceWithIdentifier { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; NSObject *object = [[NSObject alloc] init]; [instanceManager addDartCreatedInstance:object withIdentifier:0]; XCTAssertEqualObjects([instanceManager removeInstanceWithIdentifier:0], object); XCTAssertEqual([instanceManager strongInstanceCount], 0); } - (void)testDeallocCallbackIsIgnoredIfNull { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnonnull" // This sets deallocCallback to nil to test that uses are null checked. FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] initWithDeallocCallback:nil]; #pragma clang diagnostic pop [instanceManager addDartCreatedInstance:[[NSObject alloc] init] withIdentifier:0]; // Tests that this doesn't cause a EXC_BAD_ACCESS crash. [instanceManager removeInstanceWithIdentifier:0]; } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFNavigationDelegateHostApiTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import Flutter; @import XCTest; @import webview_flutter_wkwebview; #import @interface FWFNavigationDelegateHostApiTests : XCTestCase @end @implementation FWFNavigationDelegateHostApiTests /** * Creates a partially mocked FWFNavigationDelegate and adds it to instanceManager. * * @param instanceManager Instance manager to add the delegate to. * @param identifier Identifier for the delegate added to the instanceManager. * * @return A mock FWFNavigationDelegate. */ - (id)mockNavigationDelegateWithManager:(FWFInstanceManager *)instanceManager identifier:(long)identifier { FWFNavigationDelegate *navigationDelegate = [[FWFNavigationDelegate alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; [instanceManager addDartCreatedInstance:navigationDelegate withIdentifier:0]; return OCMPartialMock(navigationDelegate); } /** * Creates a mock FWFNavigationDelegateFlutterApiImpl with instanceManager. * * @param instanceManager Instance manager passed to the Flutter API. * * @return A mock FWFNavigationDelegateFlutterApiImpl. */ - (id)mockFlutterApiWithManager:(FWFInstanceManager *)instanceManager { FWFNavigationDelegateFlutterApiImpl *flutterAPI = [[FWFNavigationDelegateFlutterApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; return OCMPartialMock(flutterAPI); } - (void)testCreateWithIdentifier { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFNavigationDelegateHostApiImpl *hostAPI = [[FWFNavigationDelegateHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; [hostAPI createWithIdentifier:@0 error:&error]; FWFNavigationDelegate *navigationDelegate = (FWFNavigationDelegate *)[instanceManager instanceForIdentifier:0]; XCTAssertTrue([navigationDelegate conformsToProtocol:@protocol(WKNavigationDelegate)]); XCTAssertNil(error); } - (void)testDidFinishNavigation { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFNavigationDelegate *mockDelegate = [self mockNavigationDelegateWithManager:instanceManager identifier:0]; FWFNavigationDelegateFlutterApiImpl *mockFlutterAPI = [self mockFlutterApiWithManager:instanceManager]; OCMStub([mockDelegate navigationDelegateAPI]).andReturn(mockFlutterAPI); WKWebView *mockWebView = OCMClassMock([WKWebView class]); OCMStub([mockWebView URL]).andReturn([NSURL URLWithString:@"https://flutter.dev/"]); [instanceManager addDartCreatedInstance:mockWebView withIdentifier:1]; [mockDelegate webView:mockWebView didFinishNavigation:OCMClassMock([WKNavigation class])]; OCMVerify([mockFlutterAPI didFinishNavigationForDelegateWithIdentifier:@0 webViewIdentifier:@1 URL:@"https://flutter.dev/" completion:OCMOCK_ANY]); } - (void)testDidStartProvisionalNavigation { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFNavigationDelegate *mockDelegate = [self mockNavigationDelegateWithManager:instanceManager identifier:0]; FWFNavigationDelegateFlutterApiImpl *mockFlutterAPI = [self mockFlutterApiWithManager:instanceManager]; OCMStub([mockDelegate navigationDelegateAPI]).andReturn(mockFlutterAPI); WKWebView *mockWebView = OCMClassMock([WKWebView class]); OCMStub([mockWebView URL]).andReturn([NSURL URLWithString:@"https://flutter.dev/"]); [instanceManager addDartCreatedInstance:mockWebView withIdentifier:1]; [mockDelegate webView:mockWebView didStartProvisionalNavigation:OCMClassMock([WKNavigation class])]; OCMVerify([mockFlutterAPI didStartProvisionalNavigationForDelegateWithIdentifier:@0 webViewIdentifier:@1 URL:@"https://flutter.dev/" completion:OCMOCK_ANY]); } - (void)testDecidePolicyForNavigationAction { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFNavigationDelegate *mockDelegate = [self mockNavigationDelegateWithManager:instanceManager identifier:0]; FWFNavigationDelegateFlutterApiImpl *mockFlutterAPI = [self mockFlutterApiWithManager:instanceManager]; OCMStub([mockDelegate navigationDelegateAPI]).andReturn(mockFlutterAPI); WKWebView *mockWebView = OCMClassMock([WKWebView class]); [instanceManager addDartCreatedInstance:mockWebView withIdentifier:1]; WKNavigationAction *mockNavigationAction = OCMClassMock([WKNavigationAction class]); OCMStub([mockNavigationAction request]) .andReturn([NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.flutter.dev"]]); WKFrameInfo *mockFrameInfo = OCMClassMock([WKFrameInfo class]); OCMStub([mockFrameInfo isMainFrame]).andReturn(YES); OCMStub([mockNavigationAction targetFrame]).andReturn(mockFrameInfo); OCMStub([mockFlutterAPI decidePolicyForNavigationActionForDelegateWithIdentifier:@0 webViewIdentifier:@1 navigationAction: [OCMArg isKindOfClass:[FWFWKNavigationActionData class]] completion: ([OCMArg invokeBlockWithArgs: [FWFWKNavigationActionPolicyEnumData makeWithValue: FWFWKNavigationActionPolicyEnumCancel], [NSNull null], nil])]); WKNavigationActionPolicy __block callbackPolicy = -1; [mockDelegate webView:mockWebView decidePolicyForNavigationAction:mockNavigationAction decisionHandler:^(WKNavigationActionPolicy policy) { callbackPolicy = policy; }]; XCTAssertEqual(callbackPolicy, WKNavigationActionPolicyCancel); } - (void)testDidFailNavigation { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFNavigationDelegate *mockDelegate = [self mockNavigationDelegateWithManager:instanceManager identifier:0]; FWFNavigationDelegateFlutterApiImpl *mockFlutterAPI = [self mockFlutterApiWithManager:instanceManager]; OCMStub([mockDelegate navigationDelegateAPI]).andReturn(mockFlutterAPI); WKWebView *mockWebView = OCMClassMock([WKWebView class]); [instanceManager addDartCreatedInstance:mockWebView withIdentifier:1]; [mockDelegate webView:mockWebView didFailNavigation:OCMClassMock([WKNavigation class]) withError:[NSError errorWithDomain:@"domain" code:0 userInfo:nil]]; OCMVerify([mockFlutterAPI didFailNavigationForDelegateWithIdentifier:@0 webViewIdentifier:@1 error:[OCMArg isKindOfClass:[FWFNSErrorData class]] completion:OCMOCK_ANY]); } - (void)testDidFailProvisionalNavigation { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFNavigationDelegate *mockDelegate = [self mockNavigationDelegateWithManager:instanceManager identifier:0]; FWFNavigationDelegateFlutterApiImpl *mockFlutterAPI = [self mockFlutterApiWithManager:instanceManager]; OCMStub([mockDelegate navigationDelegateAPI]).andReturn(mockFlutterAPI); WKWebView *mockWebView = OCMClassMock([WKWebView class]); [instanceManager addDartCreatedInstance:mockWebView withIdentifier:1]; [mockDelegate webView:mockWebView didFailProvisionalNavigation:OCMClassMock([WKNavigation class]) withError:[NSError errorWithDomain:@"domain" code:0 userInfo:nil]]; OCMVerify([mockFlutterAPI didFailProvisionalNavigationForDelegateWithIdentifier:@0 webViewIdentifier:@1 error:[OCMArg isKindOfClass:[FWFNSErrorData class]] completion:OCMOCK_ANY]); } - (void)testWebViewWebContentProcessDidTerminate { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFNavigationDelegate *mockDelegate = [self mockNavigationDelegateWithManager:instanceManager identifier:0]; FWFNavigationDelegateFlutterApiImpl *mockFlutterAPI = [self mockFlutterApiWithManager:instanceManager]; OCMStub([mockDelegate navigationDelegateAPI]).andReturn(mockFlutterAPI); WKWebView *mockWebView = OCMClassMock([WKWebView class]); [instanceManager addDartCreatedInstance:mockWebView withIdentifier:1]; [mockDelegate webViewWebContentProcessDidTerminate:mockWebView]; OCMVerify([mockFlutterAPI webViewWebContentProcessDidTerminateForDelegateWithIdentifier:@0 webViewIdentifier:@1 completion:OCMOCK_ANY]); } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFObjectHostApiTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import Flutter; @import XCTest; @import webview_flutter_wkwebview; #import @interface FWFObjectHostApiTests : XCTestCase @end @implementation FWFObjectHostApiTests /** * Creates a partially mocked FWFObject and adds it to instanceManager. * * @param instanceManager Instance manager to add the delegate to. * @param identifier Identifier for the delegate added to the instanceManager. * * @return A mock FWFObject. */ - (id)mockObjectWithManager:(FWFInstanceManager *)instanceManager identifier:(long)identifier { FWFObject *object = [[FWFObject alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; [instanceManager addDartCreatedInstance:object withIdentifier:0]; return OCMPartialMock(object); } /** * Creates a mock FWFObjectFlutterApiImpl with instanceManager. * * @param instanceManager Instance manager passed to the Flutter API. * * @return A mock FWFObjectFlutterApiImpl. */ - (id)mockFlutterApiWithManager:(FWFInstanceManager *)instanceManager { FWFObjectFlutterApiImpl *flutterAPI = [[FWFObjectFlutterApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; return OCMPartialMock(flutterAPI); } - (void)testAddObserver { NSObject *mockObject = OCMClassMock([NSObject class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockObject withIdentifier:0]; FWFObjectHostApiImpl *hostAPI = [[FWFObjectHostApiImpl alloc] initWithInstanceManager:instanceManager]; NSObject *observerObject = [[NSObject alloc] init]; [instanceManager addDartCreatedInstance:observerObject withIdentifier:1]; FlutterError *error; [hostAPI addObserverForObjectWithIdentifier:@0 observerIdentifier:@1 keyPath:@"myKey" options:@[ [FWFNSKeyValueObservingOptionsEnumData makeWithValue:FWFNSKeyValueObservingOptionsEnumOldValue], [FWFNSKeyValueObservingOptionsEnumData makeWithValue:FWFNSKeyValueObservingOptionsEnumNewValue] ] error:&error]; OCMVerify([mockObject addObserver:observerObject forKeyPath:@"myKey" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:nil]); XCTAssertNil(error); } - (void)testRemoveObserver { NSObject *mockObject = OCMClassMock([NSObject class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockObject withIdentifier:0]; FWFObjectHostApiImpl *hostAPI = [[FWFObjectHostApiImpl alloc] initWithInstanceManager:instanceManager]; NSObject *observerObject = [[NSObject alloc] init]; [instanceManager addDartCreatedInstance:observerObject withIdentifier:1]; FlutterError *error; [hostAPI removeObserverForObjectWithIdentifier:@0 observerIdentifier:@1 keyPath:@"myKey" error:&error]; OCMVerify([mockObject removeObserver:observerObject forKeyPath:@"myKey"]); XCTAssertNil(error); } - (void)testDispose { NSObject *object = [[NSObject alloc] init]; FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:object withIdentifier:0]; FWFObjectHostApiImpl *hostAPI = [[FWFObjectHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; [hostAPI disposeObjectWithIdentifier:@0 error:&error]; // Only the strong reference is removed, so the weak reference will remain until object is set to // nil. object = nil; XCTAssertFalse([instanceManager containsInstance:object]); XCTAssertNil(error); } - (void)testObserveValueForKeyPath { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFObject *mockObject = [self mockObjectWithManager:instanceManager identifier:0]; FWFObjectFlutterApiImpl *mockFlutterAPI = [self mockFlutterApiWithManager:instanceManager]; OCMStub([mockObject objectApi]).andReturn(mockFlutterAPI); NSObject *object = [[NSObject alloc] init]; [instanceManager addDartCreatedInstance:object withIdentifier:1]; [mockObject observeValueForKeyPath:@"keyPath" ofObject:object change:@{NSKeyValueChangeOldKey : @"key"} context:nil]; OCMVerify([mockFlutterAPI observeValueForObjectWithIdentifier:@0 keyPath:@"keyPath" objectIdentifier:@1 changeKeys:[OCMArg checkWithBlock:^BOOL( NSArray *value) { return value[0].value == FWFNSKeyValueChangeKeyEnumOldValue; }] changeValues:[OCMArg checkWithBlock:^BOOL(id value) { return [@"key" isEqual:value[0]]; }] completion:OCMOCK_ANY]); } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFPreferencesHostApiTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import Flutter; @import XCTest; @import webview_flutter_wkwebview; #import @interface FWFPreferencesHostApiTests : XCTestCase @end @implementation FWFPreferencesHostApiTests - (void)testCreateFromWebViewConfigurationWithIdentifier { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFPreferencesHostApiImpl *hostAPI = [[FWFPreferencesHostApiImpl alloc] initWithInstanceManager:instanceManager]; [instanceManager addDartCreatedInstance:[[WKWebViewConfiguration alloc] init] withIdentifier:0]; FlutterError *error; [hostAPI createFromWebViewConfigurationWithIdentifier:@1 configurationIdentifier:@0 error:&error]; WKPreferences *preferences = (WKPreferences *)[instanceManager instanceForIdentifier:1]; XCTAssertTrue([preferences isKindOfClass:[WKPreferences class]]); XCTAssertNil(error); } - (void)testSetJavaScriptEnabled { WKPreferences *mockPreferences = OCMClassMock([WKPreferences class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockPreferences withIdentifier:0]; FWFPreferencesHostApiImpl *hostAPI = [[FWFPreferencesHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; [hostAPI setJavaScriptEnabledForPreferencesWithIdentifier:@0 isEnabled:@YES error:&error]; OCMVerify([mockPreferences setJavaScriptEnabled:YES]); XCTAssertNil(error); } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFScriptMessageHandlerHostApiTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import Flutter; @import XCTest; @import webview_flutter_wkwebview; #import @interface FWFScriptMessageHandlerHostApiTests : XCTestCase @end @implementation FWFScriptMessageHandlerHostApiTests /** * Creates a partially mocked FWFScriptMessageHandler and adds it to instanceManager. * * @param instanceManager Instance manager to add the delegate to. * @param identifier Identifier for the delegate added to the instanceManager. * * @return A mock FWFScriptMessageHandler. */ - (id)mockHandlerWithManager:(FWFInstanceManager *)instanceManager identifier:(long)identifier { FWFScriptMessageHandler *handler = [[FWFScriptMessageHandler alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; [instanceManager addDartCreatedInstance:handler withIdentifier:0]; return OCMPartialMock(handler); } /** * Creates a mock FWFScriptMessageHandlerFlutterApiImpl with instanceManager. * * @param instanceManager Instance manager passed to the Flutter API. * * @return A mock FWFScriptMessageHandlerFlutterApiImpl. */ - (id)mockFlutterApiWithManager:(FWFInstanceManager *)instanceManager { FWFScriptMessageHandlerFlutterApiImpl *flutterAPI = [[FWFScriptMessageHandlerFlutterApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; return OCMPartialMock(flutterAPI); } - (void)testCreateWithIdentifier { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFScriptMessageHandlerHostApiImpl *hostAPI = [[FWFScriptMessageHandlerHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; [hostAPI createWithIdentifier:@0 error:&error]; FWFScriptMessageHandler *scriptMessageHandler = (FWFScriptMessageHandler *)[instanceManager instanceForIdentifier:0]; XCTAssertTrue([scriptMessageHandler conformsToProtocol:@protocol(WKScriptMessageHandler)]); XCTAssertNil(error); } - (void)testDidReceiveScriptMessageForHandler { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFScriptMessageHandler *mockHandler = [self mockHandlerWithManager:instanceManager identifier:0]; FWFScriptMessageHandlerFlutterApiImpl *mockFlutterAPI = [self mockFlutterApiWithManager:instanceManager]; OCMStub([mockHandler scriptMessageHandlerAPI]).andReturn(mockFlutterAPI); WKUserContentController *userContentController = [[WKUserContentController alloc] init]; [instanceManager addDartCreatedInstance:userContentController withIdentifier:1]; WKScriptMessage *mockScriptMessage = OCMClassMock([WKScriptMessage class]); OCMStub([mockScriptMessage name]).andReturn(@"name"); OCMStub([mockScriptMessage body]).andReturn(@"message"); [mockHandler userContentController:userContentController didReceiveScriptMessage:mockScriptMessage]; OCMVerify([mockFlutterAPI didReceiveScriptMessageForHandlerWithIdentifier:@0 userContentControllerIdentifier:@1 message:[OCMArg isKindOfClass:[FWFWKScriptMessageData class]] completion:OCMOCK_ANY]); } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFScrollViewHostApiTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import Flutter; @import XCTest; @import webview_flutter_wkwebview; #import @interface FWFScrollViewHostApiTests : XCTestCase @end @implementation FWFScrollViewHostApiTests - (void)testGetContentOffset { UIScrollView *mockScrollView = OCMClassMock([UIScrollView class]); OCMStub([mockScrollView contentOffset]).andReturn(CGPointMake(1.0, 2.0)); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockScrollView withIdentifier:0]; FWFScrollViewHostApiImpl *hostAPI = [[FWFScrollViewHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; NSArray *expectedValue = @[ @1.0, @2.0 ]; XCTAssertEqualObjects([hostAPI contentOffsetForScrollViewWithIdentifier:@0 error:&error], expectedValue); XCTAssertNil(error); } - (void)testScrollBy { UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; scrollView.contentOffset = CGPointMake(1, 2); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:scrollView withIdentifier:0]; FWFScrollViewHostApiImpl *hostAPI = [[FWFScrollViewHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; [hostAPI scrollByForScrollViewWithIdentifier:@0 x:@1 y:@2 error:&error]; XCTAssertEqual(scrollView.contentOffset.x, 2); XCTAssertEqual(scrollView.contentOffset.y, 4); XCTAssertNil(error); } - (void)testSetContentOffset { UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)]; FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:scrollView withIdentifier:0]; FWFScrollViewHostApiImpl *hostAPI = [[FWFScrollViewHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; [hostAPI setContentOffsetForScrollViewWithIdentifier:@0 toX:@1 y:@2 error:&error]; XCTAssertEqual(scrollView.contentOffset.x, 1); XCTAssertEqual(scrollView.contentOffset.y, 2); XCTAssertNil(error); } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFUIDelegateHostApiTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import Flutter; @import XCTest; @import webview_flutter_wkwebview; #import @interface FWFUIDelegateHostApiTests : XCTestCase @end @implementation FWFUIDelegateHostApiTests /** * Creates a partially mocked FWFUIDelegate and adds it to instanceManager. * * @param instanceManager Instance manager to add the delegate to. * @param identifier Identifier for the delegate added to the instanceManager. * * @return A mock FWFUIDelegate. */ - (id)mockDelegateWithManager:(FWFInstanceManager *)instanceManager identifier:(long)identifier { FWFUIDelegate *delegate = [[FWFUIDelegate alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; [instanceManager addDartCreatedInstance:delegate withIdentifier:0]; return OCMPartialMock(delegate); } /** * Creates a mock FWFUIDelegateFlutterApiImpl with instanceManager. * * @param instanceManager Instance manager passed to the Flutter API. * * @return A mock FWFUIDelegateFlutterApiImpl. */ - (id)mockFlutterApiWithManager:(FWFInstanceManager *)instanceManager { FWFUIDelegateFlutterApiImpl *flutterAPI = [[FWFUIDelegateFlutterApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; return OCMPartialMock(flutterAPI); } - (void)testCreateWithIdentifier { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFUIDelegateHostApiImpl *hostAPI = [[FWFUIDelegateHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; [hostAPI createWithIdentifier:@0 error:&error]; FWFUIDelegate *delegate = (FWFUIDelegate *)[instanceManager instanceForIdentifier:0]; XCTAssertTrue([delegate conformsToProtocol:@protocol(WKUIDelegate)]); XCTAssertNil(error); } - (void)testOnCreateWebViewForDelegateWithIdentifier { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFUIDelegate *mockDelegate = [self mockDelegateWithManager:instanceManager identifier:0]; FWFUIDelegateFlutterApiImpl *mockFlutterAPI = [self mockFlutterApiWithManager:instanceManager]; OCMStub([mockDelegate UIDelegateAPI]).andReturn(mockFlutterAPI); WKWebView *mockWebView = OCMClassMock([WKWebView class]); [instanceManager addDartCreatedInstance:mockWebView withIdentifier:1]; WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; id mockConfigurationFlutterApi = OCMPartialMock(mockFlutterAPI.webViewConfigurationFlutterApi); NSNumber *__block configurationIdentifier; OCMStub([mockConfigurationFlutterApi createWithIdentifier:[OCMArg checkWithBlock:^BOOL(id value) { configurationIdentifier = value; return YES; }] completion:OCMOCK_ANY]); WKNavigationAction *mockNavigationAction = OCMClassMock([WKNavigationAction class]); OCMStub([mockNavigationAction request]) .andReturn([NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.flutter.dev"]]); WKFrameInfo *mockFrameInfo = OCMClassMock([WKFrameInfo class]); OCMStub([mockFrameInfo isMainFrame]).andReturn(YES); OCMStub([mockNavigationAction targetFrame]).andReturn(mockFrameInfo); [mockDelegate webView:mockWebView createWebViewWithConfiguration:configuration forNavigationAction:mockNavigationAction windowFeatures:OCMClassMock([WKWindowFeatures class])]; OCMVerify([mockFlutterAPI onCreateWebViewForDelegateWithIdentifier:@0 webViewIdentifier:@1 configurationIdentifier:configurationIdentifier navigationAction:[OCMArg isKindOfClass:[FWFWKNavigationActionData class]] completion:OCMOCK_ANY]); } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFUIViewHostApiTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import Flutter; @import XCTest; @import webview_flutter_wkwebview; #import @interface FWFUIViewHostApiTests : XCTestCase @end @implementation FWFUIViewHostApiTests - (void)testSetBackgroundColor { UIView *mockUIView = OCMClassMock([UIView class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockUIView withIdentifier:0]; FWFUIViewHostApiImpl *hostAPI = [[FWFUIViewHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; [hostAPI setBackgroundColorForViewWithIdentifier:@0 toValue:@123 error:&error]; OCMVerify([mockUIView setBackgroundColor:[UIColor colorWithRed:(123 >> 16 & 0xff) / 255.0 green:(123 >> 8 & 0xff) / 255.0 blue:(123 & 0xff) / 255.0 alpha:(123 >> 24 & 0xff) / 255.0]]); XCTAssertNil(error); } - (void)testSetOpaque { UIView *mockUIView = OCMClassMock([UIView class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockUIView withIdentifier:0]; FWFUIViewHostApiImpl *hostAPI = [[FWFUIViewHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; [hostAPI setOpaqueForViewWithIdentifier:@0 isOpaque:@YES error:&error]; OCMVerify([mockUIView setOpaque:YES]); XCTAssertNil(error); } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFUserContentControllerHostApiTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import Flutter; @import XCTest; @import webview_flutter_wkwebview; #import @interface FWFUserContentControllerHostApiTests : XCTestCase @end @implementation FWFUserContentControllerHostApiTests - (void)testCreateFromWebViewConfigurationWithIdentifier { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFUserContentControllerHostApiImpl *hostAPI = [[FWFUserContentControllerHostApiImpl alloc] initWithInstanceManager:instanceManager]; [instanceManager addDartCreatedInstance:[[WKWebViewConfiguration alloc] init] withIdentifier:0]; FlutterError *error; [hostAPI createFromWebViewConfigurationWithIdentifier:@1 configurationIdentifier:@0 error:&error]; WKUserContentController *userContentController = (WKUserContentController *)[instanceManager instanceForIdentifier:1]; XCTAssertTrue([userContentController isKindOfClass:[WKUserContentController class]]); XCTAssertNil(error); } - (void)testAddScriptMessageHandler { WKUserContentController *mockUserContentController = OCMClassMock([WKUserContentController class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockUserContentController withIdentifier:0]; FWFUserContentControllerHostApiImpl *hostAPI = [[FWFUserContentControllerHostApiImpl alloc] initWithInstanceManager:instanceManager]; id mockMessageHandler = OCMProtocolMock(@protocol(WKScriptMessageHandler)); [instanceManager addDartCreatedInstance:mockMessageHandler withIdentifier:1]; FlutterError *error; [hostAPI addScriptMessageHandlerForControllerWithIdentifier:@0 handlerIdentifier:@1 ofName:@"apple" error:&error]; OCMVerify([mockUserContentController addScriptMessageHandler:mockMessageHandler name:@"apple"]); XCTAssertNil(error); } - (void)testRemoveScriptMessageHandler { WKUserContentController *mockUserContentController = OCMClassMock([WKUserContentController class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockUserContentController withIdentifier:0]; FWFUserContentControllerHostApiImpl *hostAPI = [[FWFUserContentControllerHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; [hostAPI removeScriptMessageHandlerForControllerWithIdentifier:@0 name:@"apple" error:&error]; OCMVerify([mockUserContentController removeScriptMessageHandlerForName:@"apple"]); XCTAssertNil(error); } - (void)testRemoveAllScriptMessageHandlers API_AVAILABLE(ios(14.0)) { WKUserContentController *mockUserContentController = OCMClassMock([WKUserContentController class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockUserContentController withIdentifier:0]; FWFUserContentControllerHostApiImpl *hostAPI = [[FWFUserContentControllerHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; [hostAPI removeAllScriptMessageHandlersForControllerWithIdentifier:@0 error:&error]; OCMVerify([mockUserContentController removeAllScriptMessageHandlers]); XCTAssertNil(error); } - (void)testAddUserScript { WKUserContentController *mockUserContentController = OCMClassMock([WKUserContentController class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockUserContentController withIdentifier:0]; FWFUserContentControllerHostApiImpl *hostAPI = [[FWFUserContentControllerHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; [hostAPI addUserScriptForControllerWithIdentifier:@0 userScript: [FWFWKUserScriptData makeWithSource:@"runAScript" injectionTime: [FWFWKUserScriptInjectionTimeEnumData makeWithValue: FWFWKUserScriptInjectionTimeEnumAtDocumentEnd] isMainFrameOnly:@YES] error:&error]; OCMVerify([mockUserContentController addUserScript:[OCMArg isKindOfClass:[WKUserScript class]]]); XCTAssertNil(error); } - (void)testRemoveAllUserScripts { WKUserContentController *mockUserContentController = OCMClassMock([WKUserContentController class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockUserContentController withIdentifier:0]; FWFUserContentControllerHostApiImpl *hostAPI = [[FWFUserContentControllerHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; [hostAPI removeAllUserScriptsForControllerWithIdentifier:@0 error:&error]; OCMVerify([mockUserContentController removeAllUserScripts]); XCTAssertNil(error); } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewConfigurationHostApiTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import Flutter; @import XCTest; @import webview_flutter_wkwebview; #import @interface FWFWebViewConfigurationHostApiTests : XCTestCase @end @implementation FWFWebViewConfigurationHostApiTests - (void)testCreateWithIdentifier { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFWebViewConfigurationHostApiImpl *hostAPI = [[FWFWebViewConfigurationHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; [hostAPI createWithIdentifier:@0 error:&error]; WKWebViewConfiguration *configuration = (WKWebViewConfiguration *)[instanceManager instanceForIdentifier:0]; XCTAssertTrue([configuration isKindOfClass:[WKWebViewConfiguration class]]); XCTAssertNil(error); } - (void)testCreateFromWebViewWithIdentifier { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFWebViewConfigurationHostApiImpl *hostAPI = [[FWFWebViewConfigurationHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; WKWebView *mockWebView = OCMClassMock([WKWebView class]); OCMStub([mockWebView configuration]).andReturn(OCMClassMock([WKWebViewConfiguration class])); [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FlutterError *error; [hostAPI createFromWebViewWithIdentifier:@1 webViewIdentifier:@0 error:&error]; WKWebViewConfiguration *configuration = (WKWebViewConfiguration *)[instanceManager instanceForIdentifier:1]; XCTAssertTrue([configuration isKindOfClass:[WKWebViewConfiguration class]]); XCTAssertNil(error); } - (void)testSetAllowsInlineMediaPlayback { WKWebViewConfiguration *mockWebViewConfiguration = OCMClassMock([WKWebViewConfiguration class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebViewConfiguration withIdentifier:0]; FWFWebViewConfigurationHostApiImpl *hostAPI = [[FWFWebViewConfigurationHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; [hostAPI setAllowsInlineMediaPlaybackForConfigurationWithIdentifier:@0 isAllowed:@NO error:&error]; OCMVerify([mockWebViewConfiguration setAllowsInlineMediaPlayback:NO]); XCTAssertNil(error); } - (void)testSetMediaTypesRequiringUserActionForPlayback { WKWebViewConfiguration *mockWebViewConfiguration = OCMClassMock([WKWebViewConfiguration class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebViewConfiguration withIdentifier:0]; FWFWebViewConfigurationHostApiImpl *hostAPI = [[FWFWebViewConfigurationHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; [hostAPI setMediaTypesRequiresUserActionForConfigurationWithIdentifier:@0 forTypes:@[ [FWFWKAudiovisualMediaTypeEnumData makeWithValue: FWFWKAudiovisualMediaTypeEnumAudio], [FWFWKAudiovisualMediaTypeEnumData makeWithValue: FWFWKAudiovisualMediaTypeEnumVideo] ] error:&error]; OCMVerify([mockWebViewConfiguration setMediaTypesRequiringUserActionForPlayback:(WKAudiovisualMediaTypeAudio | WKAudiovisualMediaTypeVideo)]); XCTAssertNil(error); } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewFlutterWKWebViewExternalAPITests.m ================================================ // Copyright 2013 The Flutter 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 webview_flutter_wkwebview; @interface FWFWebViewFlutterWKWebViewExternalAPITests : XCTestCase @end @implementation FWFWebViewFlutterWKWebViewExternalAPITests - (void)testWebViewForIdentifier { WKWebView *webView = [[WKWebView alloc] init]; FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:webView withIdentifier:0]; id mockPluginRegistry = OCMProtocolMock(@protocol(FlutterPluginRegistry)); OCMStub([mockPluginRegistry valuePublishedByPlugin:@"FLTWebViewFlutterPlugin"]) .andReturn(instanceManager); XCTAssertEqualObjects( [FWFWebViewFlutterWKWebViewExternalAPI webViewForIdentifier:0 withPluginRegistry:mockPluginRegistry], webView); } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebViewHostApiTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import Flutter; @import XCTest; @import webview_flutter_wkwebview; #import static bool feq(CGFloat a, CGFloat b) { return fabs(b - a) < FLT_EPSILON; } @interface FWFWebViewHostApiTests : XCTestCase @end @implementation FWFWebViewHostApiTests - (void)testCreateWithIdentifier { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; [instanceManager addDartCreatedInstance:[[WKWebViewConfiguration alloc] init] withIdentifier:0]; FlutterError *error; [hostAPI createWithIdentifier:@1 configurationIdentifier:@0 error:&error]; WKWebView *webView = (WKWebView *)[instanceManager instanceForIdentifier:1]; XCTAssertTrue([webView isKindOfClass:[WKWebView class]]); XCTAssertNil(error); } - (void)testLoadRequest { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; FWFNSUrlRequestData *requestData = [FWFNSUrlRequestData makeWithUrl:@"https://www.flutter.dev" httpMethod:@"get" httpBody:nil allHttpHeaderFields:@{@"a" : @"header"}]; [hostAPI loadRequestForWebViewWithIdentifier:@0 request:requestData error:&error]; NSURL *url = [NSURL URLWithString:@"https://www.flutter.dev"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"get"; request.allHTTPHeaderFields = @{@"a" : @"header"}; OCMVerify([mockWebView loadRequest:request]); XCTAssertNil(error); } - (void)testLoadRequestWithInvalidUrl { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); OCMReject([mockWebView loadRequest:OCMOCK_ANY]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; FWFNSUrlRequestData *requestData = [FWFNSUrlRequestData makeWithUrl:@"%invalidUrl%" httpMethod:nil httpBody:nil allHttpHeaderFields:@{}]; [hostAPI loadRequestForWebViewWithIdentifier:@0 request:requestData error:&error]; XCTAssertNotNil(error); XCTAssertEqualObjects(error.code, @"FWFURLRequestParsingError"); XCTAssertEqualObjects(error.message, @"Failed instantiating an NSURLRequest."); XCTAssertEqualObjects(error.details, @"URL was: '%invalidUrl%'"); } - (void)testSetCustomUserAgent { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; [hostAPI setUserAgentForWebViewWithIdentifier:@0 userAgent:@"userA" error:&error]; OCMVerify([mockWebView setCustomUserAgent:@"userA"]); XCTAssertNil(error); } - (void)testURL { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); OCMStub([mockWebView URL]).andReturn([NSURL URLWithString:@"https://www.flutter.dev/"]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; XCTAssertEqualObjects([hostAPI URLForWebViewWithIdentifier:@0 error:&error], @"https://www.flutter.dev/"); XCTAssertNil(error); } - (void)testCanGoBack { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); OCMStub([mockWebView canGoBack]).andReturn(YES); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; XCTAssertEqualObjects([hostAPI canGoBackForWebViewWithIdentifier:@0 error:&error], @YES); XCTAssertNil(error); } - (void)testSetUIDelegate { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; id mockDelegate = OCMProtocolMock(@protocol(WKUIDelegate)); [instanceManager addDartCreatedInstance:mockDelegate withIdentifier:1]; FlutterError *error; [hostAPI setUIDelegateForWebViewWithIdentifier:@0 delegateIdentifier:@1 error:&error]; OCMVerify([mockWebView setUIDelegate:mockDelegate]); XCTAssertNil(error); } - (void)testSetNavigationDelegate { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; id mockDelegate = OCMProtocolMock(@protocol(WKNavigationDelegate)); [instanceManager addDartCreatedInstance:mockDelegate withIdentifier:1]; FlutterError *error; [hostAPI setNavigationDelegateForWebViewWithIdentifier:@0 delegateIdentifier:@1 error:&error]; OCMVerify([mockWebView setNavigationDelegate:mockDelegate]); XCTAssertNil(error); } - (void)testEstimatedProgress { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); OCMStub([mockWebView estimatedProgress]).andReturn(34.0); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; XCTAssertEqualObjects([hostAPI estimatedProgressForWebViewWithIdentifier:@0 error:&error], @34.0); XCTAssertNil(error); } - (void)testloadHTMLString { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; [hostAPI loadHTMLForWebViewWithIdentifier:@0 HTMLString:@"myString" baseURL:@"myBaseUrl" error:&error]; OCMVerify([mockWebView loadHTMLString:@"myString" baseURL:[NSURL URLWithString:@"myBaseUrl"]]); XCTAssertNil(error); } - (void)testLoadFileURL { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; [hostAPI loadFileForWebViewWithIdentifier:@0 fileURL:@"myFolder/apple.txt" readAccessURL:@"myFolder" error:&error]; XCTAssertNil(error); OCMVerify([mockWebView loadFileURL:[NSURL fileURLWithPath:@"myFolder/apple.txt" isDirectory:NO] allowingReadAccessToURL:[NSURL fileURLWithPath:@"myFolder/" isDirectory:YES] ]); } - (void)testLoadFlutterAsset { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFAssetManager *mockAssetManager = OCMClassMock([FWFAssetManager class]); OCMStub([mockAssetManager lookupKeyForAsset:@"assets/index.html"]) .andReturn(@"myFolder/assets/index.html"); NSBundle *mockBundle = OCMClassMock([NSBundle class]); OCMStub([mockBundle URLForResource:@"myFolder/assets/index" withExtension:@"html"]) .andReturn([NSURL URLWithString:@"webview_flutter/myFolder/assets/index.html"]); FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager bundle:mockBundle assetManager:mockAssetManager]; FlutterError *error; [hostAPI loadAssetForWebViewWithIdentifier:@0 assetKey:@"assets/index.html" error:&error]; XCTAssertNil(error); OCMVerify([mockWebView loadFileURL:[NSURL URLWithString:@"webview_flutter/myFolder/assets/index.html"] allowingReadAccessToURL:[NSURL URLWithString:@"webview_flutter/myFolder/assets/"]]); } - (void)testCanGoForward { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); OCMStub([mockWebView canGoForward]).andReturn(NO); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; XCTAssertEqualObjects([hostAPI canGoForwardForWebViewWithIdentifier:@0 error:&error], @NO); XCTAssertNil(error); } - (void)testGoBack { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; [hostAPI goBackForWebViewWithIdentifier:@0 error:&error]; OCMVerify([mockWebView goBack]); XCTAssertNil(error); } - (void)testGoForward { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; [hostAPI goForwardForWebViewWithIdentifier:@0 error:&error]; OCMVerify([mockWebView goForward]); XCTAssertNil(error); } - (void)testReload { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; [hostAPI reloadWebViewWithIdentifier:@0 error:&error]; OCMVerify([mockWebView reload]); XCTAssertNil(error); } - (void)testTitle { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); OCMStub([mockWebView title]).andReturn(@"myTitle"); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; XCTAssertEqualObjects([hostAPI titleForWebViewWithIdentifier:@0 error:&error], @"myTitle"); XCTAssertNil(error); } - (void)testSetAllowsBackForwardNavigationGestures { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; FlutterError *error; [hostAPI setAllowsBackForwardForWebViewWithIdentifier:@0 isAllowed:@YES error:&error]; OCMVerify([mockWebView setAllowsBackForwardNavigationGestures:YES]); XCTAssertNil(error); } - (void)testEvaluateJavaScript { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); OCMStub([mockWebView evaluateJavaScript:@"runJavaScript" completionHandler:([OCMArg invokeBlockWithArgs:@"result", [NSNull null], nil])]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; NSString __block *returnValue; FlutterError __block *returnError; [hostAPI evaluateJavaScriptForWebViewWithIdentifier:@0 javaScriptString:@"runJavaScript" completion:^(id result, FlutterError *error) { returnValue = result; returnError = error; }]; XCTAssertEqualObjects(returnValue, @"result"); XCTAssertNil(returnError); } - (void)testEvaluateJavaScriptReturnsNSErrorData { FWFWebView *mockWebView = OCMClassMock([FWFWebView class]); OCMStub([mockWebView evaluateJavaScript:@"runJavaScript" completionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], [NSError errorWithDomain:@"errorDomain" code:0 userInfo:@{ NSLocalizedDescriptionKey : @"description" }], nil])]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebView withIdentifier:0]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; NSString __block *returnValue; FlutterError __block *returnError; [hostAPI evaluateJavaScriptForWebViewWithIdentifier:@0 javaScriptString:@"runJavaScript" completion:^(id result, FlutterError *error) { returnValue = result; returnError = error; }]; XCTAssertNil(returnValue); FWFNSErrorData *errorData = returnError.details; XCTAssertTrue([errorData isKindOfClass:[FWFNSErrorData class]]); XCTAssertEqualObjects(errorData.code, @0); XCTAssertEqualObjects(errorData.domain, @"errorDomain"); XCTAssertEqualObjects(errorData.localizedDescription, @"description"); } - (void)testWebViewContentInsetBehaviorShouldBeNeverOnIOS11 API_AVAILABLE(ios(11.0)) { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; [instanceManager addDartCreatedInstance:[[WKWebViewConfiguration alloc] init] withIdentifier:0]; FlutterError *error; [hostAPI createWithIdentifier:@1 configurationIdentifier:@0 error:&error]; FWFWebView *webView = (FWFWebView *)[instanceManager instanceForIdentifier:1]; XCTAssertEqual(webView.scrollView.contentInsetAdjustmentBehavior, UIScrollViewContentInsetAdjustmentNever); } - (void)testScrollViewsAutomaticallyAdjustsScrollIndicatorInsetsShouldbeNoOnIOS13 API_AVAILABLE( ios(13.0)) { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFWebViewHostApiImpl *hostAPI = [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:OCMProtocolMock(@protocol(FlutterBinaryMessenger)) instanceManager:instanceManager]; [instanceManager addDartCreatedInstance:[[WKWebViewConfiguration alloc] init] withIdentifier:0]; FlutterError *error; [hostAPI createWithIdentifier:@1 configurationIdentifier:@0 error:&error]; FWFWebView *webView = (FWFWebView *)[instanceManager instanceForIdentifier:1]; XCTAssertFalse(webView.scrollView.automaticallyAdjustsScrollIndicatorInsets); } - (void)testContentInsetsSumAlwaysZeroAfterSetFrame { FWFWebView *webView = [[FWFWebView alloc] initWithFrame:CGRectMake(0, 0, 300, 400) configuration:[[WKWebViewConfiguration alloc] init]]; webView.scrollView.contentInset = UIEdgeInsetsMake(0, 0, 300, 0); XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(webView.scrollView.contentInset, UIEdgeInsetsZero)); webView.frame = CGRectMake(0, 0, 300, 200); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(webView.scrollView.contentInset, UIEdgeInsetsZero)); XCTAssertTrue(CGRectEqualToRect(webView.frame, CGRectMake(0, 0, 300, 200))); if (@available(iOS 11, *)) { // After iOS 11, we need to make sure the contentInset compensates the adjustedContentInset. UIScrollView *partialMockScrollView = OCMPartialMock(webView.scrollView); UIEdgeInsets insetToAdjust = UIEdgeInsetsMake(0, 0, 300, 0); OCMStub(partialMockScrollView.adjustedContentInset).andReturn(insetToAdjust); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(webView.scrollView.contentInset, UIEdgeInsetsZero)); webView.frame = CGRectMake(0, 0, 300, 100); XCTAssertTrue(feq(webView.scrollView.contentInset.bottom, -insetToAdjust.bottom)); XCTAssertTrue(CGRectEqualToRect(webView.frame, CGRectMake(0, 0, 300, 100))); } } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/FWFWebsiteDataStoreHostApiTests.m ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @import Flutter; @import XCTest; @import webview_flutter_wkwebview; #import @interface FWFWebsiteDataStoreHostApiTests : XCTestCase @end @implementation FWFWebsiteDataStoreHostApiTests - (void)testCreateFromWebViewConfigurationWithIdentifier { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFWebsiteDataStoreHostApiImpl *hostAPI = [[FWFWebsiteDataStoreHostApiImpl alloc] initWithInstanceManager:instanceManager]; [instanceManager addDartCreatedInstance:[[WKWebViewConfiguration alloc] init] withIdentifier:0]; FlutterError *error; [hostAPI createFromWebViewConfigurationWithIdentifier:@1 configurationIdentifier:@0 error:&error]; WKWebsiteDataStore *dataStore = (WKWebsiteDataStore *)[instanceManager instanceForIdentifier:1]; XCTAssertTrue([dataStore isKindOfClass:[WKWebsiteDataStore class]]); XCTAssertNil(error); } - (void)testCreateDefaultDataStoreWithIdentifier { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; FWFWebsiteDataStoreHostApiImpl *hostAPI = [[FWFWebsiteDataStoreHostApiImpl alloc] initWithInstanceManager:instanceManager]; FlutterError *error; [hostAPI createDefaultDataStoreWithIdentifier:@0 error:&error]; WKWebsiteDataStore *dataStore = (WKWebsiteDataStore *)[instanceManager instanceForIdentifier:0]; XCTAssertEqualObjects(dataStore, [WKWebsiteDataStore defaultDataStore]); XCTAssertNil(error); } - (void)testRemoveDataOfTypes { WKWebsiteDataStore *mockWebsiteDataStore = OCMClassMock([WKWebsiteDataStore class]); WKWebsiteDataRecord *mockDataRecord = OCMClassMock([WKWebsiteDataRecord class]); OCMStub([mockWebsiteDataStore fetchDataRecordsOfTypes:[NSSet setWithObject:WKWebsiteDataTypeLocalStorage] completionHandler:([OCMArg invokeBlockWithArgs:@[ mockDataRecord ], nil])]); OCMStub([mockWebsiteDataStore removeDataOfTypes:[NSSet setWithObject:WKWebsiteDataTypeLocalStorage] modifiedSince:[NSDate dateWithTimeIntervalSince1970:45.0] completionHandler:([OCMArg invokeBlock])]); FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] init]; [instanceManager addDartCreatedInstance:mockWebsiteDataStore withIdentifier:0]; FWFWebsiteDataStoreHostApiImpl *hostAPI = [[FWFWebsiteDataStoreHostApiImpl alloc] initWithInstanceManager:instanceManager]; NSNumber __block *returnValue; FlutterError *__block blockError; [hostAPI removeDataFromDataStoreWithIdentifier:@0 ofTypes:@[ [FWFWKWebsiteDataTypeEnumData makeWithValue:FWFWKWebsiteDataTypeEnumLocalStorage] ] modifiedSince:@45.0 completion:^(NSNumber *result, FlutterError *error) { returnValue = result; blockError = error; }]; XCTAssertEqualObjects(returnValue, @YES); // Asserts whether the NSNumber will be deserialized by the standard codec as a boolean. XCTAssertEqual(CFGetTypeID((__bridge CFTypeRef)(returnValue)), CFBooleanGetTypeID()); XCTAssertNil(blockError); } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerTests/Info.plist ================================================ 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 ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerUITests/FLTWebViewUITests.m ================================================ // Copyright 2013 The Flutter 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; @import os.log; static UIColor *getPixelColorInImage(CGImageRef image, size_t x, size_t y) { CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(image)); const UInt8 *data = CFDataGetBytePtr(pixelData); size_t bytesPerRow = CGImageGetBytesPerRow(image); size_t pixelInfo = (bytesPerRow * y) + (x * 4); // 4 bytes per pixel UInt8 red = data[pixelInfo + 0]; UInt8 green = data[pixelInfo + 1]; UInt8 blue = data[pixelInfo + 2]; UInt8 alpha = data[pixelInfo + 3]; CFRelease(pixelData); return [UIColor colorWithRed:red / 255.0f green:green / 255.0f blue:blue / 255.0f alpha:alpha / 255.0f]; } @interface FLTWebViewUITests : XCTestCase @property(nonatomic, strong) XCUIApplication *app; @end @implementation FLTWebViewUITests - (void)setUp { self.continueAfterFailure = NO; self.app = [[XCUIApplication alloc] init]; [self.app launch]; } - (void)testTransparentBackground { XCUIApplication *app = self.app; XCUIElement *menu = app.buttons[@"Show menu"]; if (![menu waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find menu"); } [menu tap]; XCUIElement *transparentBackground = app.buttons[@"Transparent background example"]; if (![transparentBackground waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find Transparent background example"); } [transparentBackground tap]; XCUIElement *transparentBackgroundLoaded = app.webViews.staticTexts[@"Transparent background test"]; if (![transparentBackgroundLoaded waitForExistenceWithTimeout:30.0]) { os_log_error(OS_LOG_DEFAULT, "%@", app.debugDescription); XCTFail(@"Failed due to not able to find Transparent background test"); } XCUIScreenshot *screenshot = [[XCUIScreen mainScreen] screenshot]; UIImage *screenshotImage = screenshot.image; CGImageRef screenshotCGImage = screenshotImage.CGImage; UIColor *centerLeftColor = getPixelColorInImage(screenshotCGImage, 0, CGImageGetHeight(screenshotCGImage) / 2); UIColor *centerColor = getPixelColorInImage(screenshotCGImage, CGImageGetWidth(screenshotCGImage) / 2, CGImageGetHeight(screenshotCGImage) / 2); CGColorSpaceRef centerLeftColorSpace = CGColorGetColorSpace(centerLeftColor.CGColor); // Flutter Colors.green color : 0xFF4CAF50 -> rgba(76, 175, 80, 1) // https://github.com/flutter/flutter/blob/f4abaa0735eba4dfd8f33f73363911d63931fe03/packages/flutter/lib/src/material/colors.dart#L1208 // The background color of the webview is : rgba(0, 0, 0, 0.5) // The expected color is : rgba(38, 87, 40, 1) CGFloat flutterGreenColorComponents[] = {38.0f / 255.0f, 87.0f / 255.0f, 40.0f / 255.0f, 1.0f}; CGColorRef flutterGreenColor = CGColorCreate(centerLeftColorSpace, flutterGreenColorComponents); CGFloat redColorComponents[] = {1.0f, 0.0f, 0.0f, 1.0f}; CGColorRef redColor = CGColorCreate(centerLeftColorSpace, redColorComponents); CGColorSpaceRelease(centerLeftColorSpace); XCTAssertTrue(CGColorEqualToColor(flutterGreenColor, centerLeftColor.CGColor)); XCTAssertTrue(CGColorEqualToColor(redColor, centerColor.CGColor)); } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/ios/RunnerUITests/Info.plist ================================================ 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 ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/lib/legacy/navigation_decision.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// A decision on how to handle a navigation request. enum NavigationDecision { /// Prevent the navigation from taking place. prevent, /// Allow the navigation to take place. navigate, } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/lib/legacy/navigation_request.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Information about a navigation action that is about to be executed. class NavigationRequest { NavigationRequest._({required this.url, required this.isForMainFrame}); /// The URL that will be loaded if the navigation is executed. final String url; /// Whether the navigation request is to be loaded as the main frame. final bool isForMainFrame; @override String toString() { return 'NavigationRequest(url: $url, isForMainFrame: $isForMainFrame)'; } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/lib/legacy/web_view.dart ================================================ // Copyright 2013 The Flutter 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 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; // ignore: implementation_imports import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; // ignore: implementation_imports import 'package:webview_flutter_wkwebview/src/webview_flutter_wkwebview_legacy.dart'; import 'navigation_decision.dart'; import 'navigation_request.dart'; /// Optional callback invoked when a web view is first created. [controller] is /// the [WebViewController] for the created web view. typedef WebViewCreatedCallback = void Function(WebViewController controller); /// Decides how to handle a specific navigation request. /// /// The returned [NavigationDecision] determines how the navigation described by /// `navigation` should be handled. /// /// See also: [WebView.navigationDelegate]. typedef NavigationDelegate = FutureOr Function( NavigationRequest navigation); /// Signature for when a [WebView] has started loading a page. typedef PageStartedCallback = void Function(String url); /// Signature for when a [WebView] has finished loading a page. typedef PageFinishedCallback = void Function(String url); /// Signature for when a [WebView] is loading a page. typedef PageLoadingCallback = void Function(int progress); /// Signature for when a [WebView] has failed to load a resource. typedef WebResourceErrorCallback = void Function(WebResourceError error); /// A web view widget for showing html content. /// /// There is a known issue that on iOS 13.4 and 13.5, other flutter widgets covering /// the `WebView` is not able to block the `WebView` from receiving touch events. /// See https://github.com/flutter/flutter/issues/53490. class WebView extends StatefulWidget { /// Creates a new web view. /// /// The web view can be controlled using a `WebViewController` that is passed to the /// `onWebViewCreated` callback once the web view is created. /// /// The `javascriptMode` and `autoMediaPlaybackPolicy` parameters must not be null. const WebView({ Key? key, this.onWebViewCreated, this.initialUrl, this.initialCookies = const [], this.javascriptMode = JavascriptMode.disabled, this.javascriptChannels, this.navigationDelegate, this.gestureRecognizers, this.onPageStarted, this.onPageFinished, this.onProgress, this.onWebResourceError, this.debuggingEnabled = false, this.gestureNavigationEnabled = false, this.userAgent, this.zoomEnabled = true, this.initialMediaPlaybackPolicy = AutoMediaPlaybackPolicy.require_user_action_for_all_media_types, this.allowsInlineMediaPlayback = false, this.backgroundColor, }) : assert(javascriptMode != null), assert(initialMediaPlaybackPolicy != null), assert(allowsInlineMediaPlayback != null), super(key: key); /// The WebView platform that's used by this WebView. static final WebViewPlatform platform = CupertinoWebView(); /// If not null invoked once the web view is created. final WebViewCreatedCallback? onWebViewCreated; /// Which gestures should be consumed by the web view. /// /// It is possible for other gesture recognizers to be competing with the web view on pointer /// events, e.g if the web view is inside a [ListView] the [ListView] will want to handle /// vertical drags. The web view will claim gestures that are recognized by any of the /// recognizers on this list. /// /// When this set is empty or null, the web view will only handle pointer events for gestures that /// were not claimed by any other gesture recognizer. final Set>? gestureRecognizers; /// The initial URL to load. final String? initialUrl; /// The initial cookies to set. final List initialCookies; /// Whether JavaScript execution is enabled. final JavascriptMode javascriptMode; /// The set of [JavascriptChannel]s available to JavaScript code running in the web view. /// /// For each [JavascriptChannel] in the set, a channel object is made available for the /// JavaScript code in a window property named [JavascriptChannel.name]. /// The JavaScript code can then call `postMessage` on that object to send a message that will be /// passed to [JavascriptChannel.onMessageReceived]. /// /// For example for the following JavascriptChannel: /// /// ```dart /// JavascriptChannel(name: 'Print', onMessageReceived: (JavascriptMessage message) { print(message.message); }); /// ``` /// /// JavaScript code can call: /// /// ```javascript /// Print.postMessage('Hello'); /// ``` /// /// To asynchronously invoke the message handler which will print the message to standard output. /// /// Adding a new JavaScript channel only takes affect after the next page is loaded. /// /// Set values must not be null. A [JavascriptChannel.name] cannot be the same for multiple /// channels in the list. /// /// A null value is equivalent to an empty set. final Set? javascriptChannels; /// A delegate function that decides how to handle navigation actions. /// /// When a navigation is initiated by the WebView (e.g when a user clicks a link) /// this delegate is called and has to decide how to proceed with the navigation. /// /// See [NavigationDecision] for possible decisions the delegate can take. /// /// When null all navigation actions are allowed. /// /// Caveats on Android: /// /// * Navigation actions targeted to the main frame can be intercepted, /// navigation actions targeted to subframes are allowed regardless of the value /// returned by this delegate. /// * Setting a navigationDelegate makes the WebView treat all navigations as if they were /// triggered by a user gesture, this disables some of Chromium's security mechanisms. /// A navigationDelegate should only be set when loading trusted content. /// * On Android WebView versions earlier than 67(most devices running at least Android L+ should have /// a later version): /// * When a navigationDelegate is set pages with frames are not properly handled by the /// webview, and frames will be opened in the main frame. /// * When a navigationDelegate is set HTTP requests do not include the HTTP referer header. final NavigationDelegate? navigationDelegate; /// Controls whether inline playback of HTML5 videos is allowed on iOS. /// /// This field is ignored on Android because Android allows it by default. /// /// By default `allowsInlineMediaPlayback` is false. final bool allowsInlineMediaPlayback; /// Invoked when a page starts loading. final PageStartedCallback? onPageStarted; /// Invoked when a page has finished loading. /// /// This is invoked only for the main frame. /// /// When [onPageFinished] is invoked on Android, the page being rendered may /// not be updated yet. /// /// When invoked on iOS or Android, any JavaScript code that is embedded /// directly in the HTML has been loaded and code injected with /// [WebViewController.evaluateJavascript] can assume this. final PageFinishedCallback? onPageFinished; /// Invoked when a page is loading. final PageLoadingCallback? onProgress; /// Invoked when a web resource has failed to load. /// /// This callback is only called for the main page. final WebResourceErrorCallback? onWebResourceError; /// Controls whether WebView debugging is enabled. /// /// Setting this to true enables [WebView debugging on Android](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/). /// /// WebView debugging is enabled by default in dev builds on iOS. /// /// To debug WebViews on iOS: /// - Enable developer options (Open Safari, go to Preferences -> Advanced and make sure "Show Develop Menu in Menubar" is on.) /// - From the Menu-bar (of Safari) select Develop -> iPhone Simulator -> /// /// By default `debuggingEnabled` is false. final bool debuggingEnabled; /// A Boolean value indicating whether horizontal swipe gestures will trigger back-forward list navigations. /// /// This only works on iOS. /// /// By default `gestureNavigationEnabled` is false. final bool gestureNavigationEnabled; /// The value used for the HTTP User-Agent: request header. /// /// When null the platform's webview default is used for the User-Agent header. /// /// When the [WebView] is rebuilt with a different `userAgent`, the page reloads and the request uses the new User Agent. /// /// When [WebViewController.goBack] is called after changing `userAgent` the previous `userAgent` value is used until the page is reloaded. /// /// This field is ignored on iOS versions prior to 9 as the platform does not support a custom /// user agent. /// /// By default `userAgent` is null. final String? userAgent; /// A Boolean value indicating whether the WebView should support zooming using its on-screen zoom controls and gestures. /// /// By default 'zoomEnabled' is true final bool zoomEnabled; /// Which restrictions apply on automatic media playback. /// /// This initial value is applied to the platform's webview upon creation. Any following /// changes to this parameter are ignored (as long as the state of the [WebView] is preserved). /// /// The default policy is [AutoMediaPlaybackPolicy.require_user_action_for_all_media_types]. final AutoMediaPlaybackPolicy initialMediaPlaybackPolicy; /// The background color of the [WebView]. /// /// When `null` the platform's webview default background color is used. By /// default [backgroundColor] is `null`. final Color? backgroundColor; @override State createState() => _WebViewState(); } class _WebViewState extends State { final Completer _controller = Completer(); late final JavascriptChannelRegistry _javascriptChannelRegistry; late final _PlatformCallbacksHandler _platformCallbacksHandler; @override void initState() { super.initState(); _platformCallbacksHandler = _PlatformCallbacksHandler(widget); _javascriptChannelRegistry = JavascriptChannelRegistry(widget.javascriptChannels); } @override void didUpdateWidget(WebView oldWidget) { super.didUpdateWidget(oldWidget); _controller.future.then((WebViewController controller) { controller._updateWidget(widget); }); } @override Widget build(BuildContext context) { return WebView.platform.build( context: context, onWebViewPlatformCreated: (WebViewPlatformController? webViewPlatformController) { final WebViewController controller = WebViewController._( widget, webViewPlatformController!, _javascriptChannelRegistry, ); _controller.complete(controller); if (widget.onWebViewCreated != null) { widget.onWebViewCreated!(controller); } }, webViewPlatformCallbacksHandler: _platformCallbacksHandler, creationParams: CreationParams( initialUrl: widget.initialUrl, webSettings: _webSettingsFromWidget(widget), javascriptChannelNames: _javascriptChannelRegistry.channels.keys.toSet(), autoMediaPlaybackPolicy: widget.initialMediaPlaybackPolicy, userAgent: widget.userAgent, cookies: widget.initialCookies, backgroundColor: widget.backgroundColor, ), javascriptChannelRegistry: _javascriptChannelRegistry, ); } } /// Controls a [WebView]. /// /// A [WebViewController] instance can be obtained by setting the [WebView.onWebViewCreated] /// callback for a [WebView] widget. class WebViewController { WebViewController._( this._widget, this._webViewPlatformController, this._javascriptChannelRegistry, ) : assert(_webViewPlatformController != null) { _settings = _webSettingsFromWidget(_widget); } final JavascriptChannelRegistry _javascriptChannelRegistry; final WebViewPlatformController _webViewPlatformController; late WebSettings _settings; WebView _widget; /// Loads the Flutter asset specified in the pubspec.yaml file. /// /// Throws an ArgumentError if [key] is not part of the specified assets /// in the pubspec.yaml file. Future loadFlutterAsset(String key) { return _webViewPlatformController.loadFlutterAsset(key); } /// Loads the file located on the specified [absoluteFilePath]. /// /// The [absoluteFilePath] parameter should contain the absolute path to the /// file as it is stored on the device. For example: /// `/Users/username/Documents/www/index.html`. /// /// Throws an ArgumentError if the [absoluteFilePath] does not exist. Future loadFile( String absoluteFilePath, ) { assert(absoluteFilePath.isNotEmpty); return _webViewPlatformController.loadFile(absoluteFilePath); } /// Loads the supplied HTML string. /// /// The [baseUrl] parameter is used when resolving relative URLs within the /// HTML string. Future loadHtmlString( String html, { String? baseUrl, }) { assert(html.isNotEmpty); return _webViewPlatformController.loadHtmlString( html, baseUrl: baseUrl, ); } /// Loads the specified URL. /// /// If `headers` is not null and the URL is an HTTP URL, the key value paris in `headers` will /// be added as key value pairs of HTTP headers for the request. /// /// `url` must not be null. /// /// Throws an ArgumentError if `url` is not a valid URL string. Future loadUrl( String url, { Map? headers, }) async { assert(url != null); _validateUrlString(url); return _webViewPlatformController.loadUrl(url, headers); } /// Loads a page by making the specified request. Future loadRequest(WebViewRequest request) async { return _webViewPlatformController.loadRequest(request); } /// Accessor to the current URL that the WebView is displaying. /// /// If [WebView.initialUrl] was never specified, returns `null`. /// Note that this operation is asynchronous, and it is possible that the /// current URL changes again by the time this function returns (in other /// words, by the time this future completes, the WebView may be displaying a /// different URL). Future currentUrl() { return _webViewPlatformController.currentUrl(); } /// Checks whether there's a back history item. /// /// Note that this operation is asynchronous, and it is possible that the "canGoBack" state has /// changed by the time the future completed. Future canGoBack() { return _webViewPlatformController.canGoBack(); } /// Checks whether there's a forward history item. /// /// Note that this operation is asynchronous, and it is possible that the "canGoForward" state has /// changed by the time the future completed. Future canGoForward() { return _webViewPlatformController.canGoForward(); } /// Goes back in the history of this WebView. /// /// If there is no back history item this is a no-op. Future goBack() { return _webViewPlatformController.goBack(); } /// Goes forward in the history of this WebView. /// /// If there is no forward history item this is a no-op. Future goForward() { return _webViewPlatformController.goForward(); } /// Reloads the current URL. Future reload() { return _webViewPlatformController.reload(); } /// Clears all caches used by the [WebView]. /// /// The following caches are cleared: /// 1. Browser HTTP Cache. /// 2. [Cache API](https://developers.google.com/web/fundamentals/instant-and-offline/web-storage/cache-api) caches. /// These are not yet supported in iOS WkWebView. Service workers tend to use this cache. /// 3. Application cache. /// 4. Local Storage. /// /// Note: Calling this method also triggers a reload. Future clearCache() async { await _webViewPlatformController.clearCache(); return reload(); } Future _updateWidget(WebView widget) async { _widget = widget; await _updateSettings(_webSettingsFromWidget(widget)); await _updateJavascriptChannels( _javascriptChannelRegistry.channels.values.toSet()); } Future _updateSettings(WebSettings newSettings) { final WebSettings update = _clearUnchangedWebSettings(_settings, newSettings); _settings = newSettings; return _webViewPlatformController.updateSettings(update); } Future _updateJavascriptChannels( Set? newChannels) async { final Set currentChannels = _javascriptChannelRegistry.channels.keys.toSet(); final Set newChannelNames = _extractChannelNames(newChannels); final Set channelsToAdd = newChannelNames.difference(currentChannels); final Set channelsToRemove = currentChannels.difference(newChannelNames); if (channelsToRemove.isNotEmpty) { await _webViewPlatformController .removeJavascriptChannels(channelsToRemove); } if (channelsToAdd.isNotEmpty) { await _webViewPlatformController.addJavascriptChannels(channelsToAdd); } _javascriptChannelRegistry.updateJavascriptChannelsFromSet(newChannels); } @visibleForTesting // ignore: public_member_api_docs Future evaluateJavascript(String javascriptString) { if (_settings.javascriptMode == JavascriptMode.disabled) { return Future.error(FlutterError( 'JavaScript mode must be enabled/unrestricted when calling evaluateJavascript.')); } return _webViewPlatformController.evaluateJavascript(javascriptString); } /// Runs the given JavaScript in the context of the current page. /// If you are looking for the result, use [runJavascriptReturningResult] instead. /// The Future completes with an error if a JavaScript error occurred. /// /// When running JavaScript in a [WebView], it is best practice to wait for /// the [WebView.onPageFinished] callback. This guarantees all the JavaScript /// embedded in the main frame HTML has been loaded. Future runJavascript(String javaScriptString) { if (_settings.javascriptMode == JavascriptMode.disabled) { return Future.error(FlutterError( 'Javascript mode must be enabled/unrestricted when calling runJavascript.')); } return _webViewPlatformController.runJavascript(javaScriptString); } /// Runs the given JavaScript in the context of the current page, and returns the result. /// /// Depending on the value type the return value would be one of: /// - For primitive JavaScript types: the value string formatted (e.g JavaScript 100 returns '100'). /// - For JavaScript arrays of supported types: a string formatted NSArray(e.g '(1,2,3), note that the string for NSArray is formatted and might contain newlines and extra spaces.'). /// /// The Future completes with an error if a JavaScript error occurred, or if the /// type the given expression evaluates to is unsupported. Unsupported values include /// certain non primitive types, as well as `undefined` or `null` on iOS 14+. /// /// When evaluating JavaScript in a [WebView], it is best practice to wait for /// the [WebView.onPageFinished] callback. This guarantees all the JavaScript /// embedded in the main frame HTML has been loaded. Future runJavascriptReturningResult(String javaScriptString) { if (_settings.javascriptMode == JavascriptMode.disabled) { return Future.error(FlutterError( 'Javascript mode must be enabled/unrestricted when calling runJavascriptReturningResult.')); } return _webViewPlatformController .runJavascriptReturningResult(javaScriptString); } /// Returns the title of the currently loaded page. Future getTitle() { return _webViewPlatformController.getTitle(); } /// Sets the WebView's content scroll position. /// /// The parameters `x` and `y` specify the scroll position in WebView pixels. Future scrollTo(int x, int y) { return _webViewPlatformController.scrollTo(x, y); } /// Move the scrolled position of this view. /// /// The parameters `x` and `y` specify the amount of WebView pixels to scroll by horizontally and vertically respectively. Future scrollBy(int x, int y) { return _webViewPlatformController.scrollBy(x, y); } /// Return the horizontal scroll position, in WebView pixels, of this view. /// /// Scroll position is measured from left. Future getScrollX() { return _webViewPlatformController.getScrollX(); } /// Return the vertical scroll position, in WebView pixels, of this view. /// /// Scroll position is measured from top. Future getScrollY() { return _webViewPlatformController.getScrollY(); } // This method assumes that no fields in `currentValue` are null. WebSettings _clearUnchangedWebSettings( WebSettings currentValue, WebSettings newValue) { assert(currentValue.javascriptMode != null); assert(currentValue.hasNavigationDelegate != null); assert(currentValue.hasProgressTracking != null); assert(currentValue.debuggingEnabled != null); assert(currentValue.userAgent != null); assert(newValue.javascriptMode != null); assert(newValue.hasNavigationDelegate != null); assert(newValue.debuggingEnabled != null); assert(newValue.userAgent != null); JavascriptMode? javascriptMode; bool? hasNavigationDelegate; bool? hasProgressTracking; bool? debuggingEnabled; WebSetting userAgent = const WebSetting.absent(); if (currentValue.javascriptMode != newValue.javascriptMode) { javascriptMode = newValue.javascriptMode; } if (currentValue.hasNavigationDelegate != newValue.hasNavigationDelegate) { hasNavigationDelegate = newValue.hasNavigationDelegate; } if (currentValue.hasProgressTracking != newValue.hasProgressTracking) { hasProgressTracking = newValue.hasProgressTracking; } if (currentValue.debuggingEnabled != newValue.debuggingEnabled) { debuggingEnabled = newValue.debuggingEnabled; } if (currentValue.userAgent != newValue.userAgent) { userAgent = newValue.userAgent; } return WebSettings( javascriptMode: javascriptMode, hasNavigationDelegate: hasNavigationDelegate, hasProgressTracking: hasProgressTracking, debuggingEnabled: debuggingEnabled, userAgent: userAgent, ); } Set _extractChannelNames(Set? channels) { final Set channelNames = channels == null ? {} : channels.map((JavascriptChannel channel) => channel.name).toSet(); return channelNames; } // Throws an ArgumentError if `url` is not a valid URL string. void _validateUrlString(String url) { try { final Uri uri = Uri.parse(url); if (uri.scheme.isEmpty) { throw ArgumentError('Missing scheme in URL string: "$url"'); } } on FormatException catch (e) { throw ArgumentError(e); } } } WebSettings _webSettingsFromWidget(WebView widget) { return WebSettings( javascriptMode: widget.javascriptMode, hasNavigationDelegate: widget.navigationDelegate != null, hasProgressTracking: widget.onProgress != null, debuggingEnabled: widget.debuggingEnabled, gestureNavigationEnabled: widget.gestureNavigationEnabled, allowsInlineMediaPlayback: widget.allowsInlineMediaPlayback, userAgent: WebSetting.of(widget.userAgent), zoomEnabled: widget.zoomEnabled, ); } class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { _PlatformCallbacksHandler(this._webView); final WebView _webView; @override FutureOr onNavigationRequest({ required String url, required bool isForMainFrame, }) async { if (url.startsWith('https://www.youtube.com/')) { debugPrint('blocking navigation to $url'); return false; } debugPrint('allowing navigation to $url'); return true; } @override void onPageStarted(String url) { if (_webView.onPageStarted != null) { _webView.onPageStarted!(url); } } @override void onPageFinished(String url) { if (_webView.onPageFinished != null) { _webView.onPageFinished!(url); } } @override void onProgress(int progress) { if (_webView.onProgress != null) { _webView.onProgress!(progress); } } @override void onWebResourceError(WebResourceError error) { if (_webView.onWebResourceError != null) { _webView.onWebResourceError!(error); } } } /// App-facing cookie manager that exposes the correct platform implementation. class WebViewCookieManager extends WebViewCookieManagerPlatform { WebViewCookieManager._(); /// Returns an instance of the cookie manager for the current platform. static WebViewCookieManagerPlatform get instance { if (WebViewCookieManagerPlatform.instance == null) { if (Platform.isIOS) { WebViewCookieManagerPlatform.instance = WKWebViewCookieManager(); } else { throw AssertionError( 'This platform is currently unsupported for webview_flutter_wkwebview.'); } } return WebViewCookieManagerPlatform.instance!; } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart ================================================ // Copyright 2013 The Flutter 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 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; void main() { runApp(const MaterialApp(home: WebViewExample())); } const String kNavigationExamplePage = ''' Navigation Delegate Example

The navigation delegate is set to block navigation to the youtube website.

'''; const String kLocalExamplePage = ''' Load file or HTML string example

Local demo page

This is an example page used to demonstrate how to load a local file or HTML string using the Flutter webview plugin.

'''; // NOTE: This is used by the transparency test in `example/ios/RunnerUITests/FLTWebViewUITests.m`. const String kTransparentBackgroundPage = ''' Transparent background test

Transparent background test

'''; class WebViewExample extends StatefulWidget { const WebViewExample({Key? key, this.cookieManager}) : super(key: key); final PlatformWebViewCookieManager? cookieManager; @override State createState() => _WebViewExampleState(); } class _WebViewExampleState extends State { late final PlatformWebViewController _controller; @override void initState() { super.initState(); _controller = PlatformWebViewController( WebKitWebViewControllerCreationParams(allowsInlineMediaPlayback: true), ) ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setBackgroundColor(const Color(0x80000000)) ..setPlatformNavigationDelegate( PlatformNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ) ..setOnProgress((int progress) { debugPrint('WebView is loading (progress : $progress%)'); }) ..setOnPageStarted((String url) { debugPrint('Page started loading: $url'); }) ..setOnPageFinished((String url) { debugPrint('Page finished loading: $url'); }) ..setOnWebResourceError((WebResourceError error) { debugPrint(''' Page resource error: code: ${error.errorCode} description: ${error.description} errorType: ${error.errorType} isForMainFrame: ${error.isForMainFrame} '''); }) ..setOnNavigationRequest((NavigationRequest request) { if (request.url.startsWith('https://www.youtube.com/')) { debugPrint('blocking navigation to ${request.url}'); return NavigationDecision.prevent; } debugPrint('allowing navigation to ${request.url}'); return NavigationDecision.navigate; }), ) ..addJavaScriptChannel(JavaScriptChannelParams( name: 'Toaster', onMessageReceived: (JavaScriptMessage message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message.message)), ); }, )) ..loadRequest(LoadRequestParams( uri: Uri.parse('https://flutter.dev'), )); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFF4CAF50), appBar: AppBar( title: const Text('Flutter WebView example'), // This drop down menu demonstrates that Flutter widgets can be shown over the web view. actions: [ NavigationControls(webViewController: _controller), SampleMenu( webViewController: _controller, cookieManager: widget.cookieManager, ), ], ), body: PlatformWebViewWidget( PlatformWebViewWidgetCreationParams(controller: _controller), ).build(context), floatingActionButton: favoriteButton(), ); } Widget favoriteButton() { return FloatingActionButton( onPressed: () async { final String? url = await _controller.currentUrl(); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Favorited $url')), ); } }, child: const Icon(Icons.favorite), ); } } enum MenuOptions { showUserAgent, listCookies, clearCookies, addToCache, listCache, clearCache, navigationDelegate, doPostRequest, loadLocalFile, loadFlutterAsset, loadHtmlString, transparentBackground, setCookie, } class SampleMenu extends StatelessWidget { SampleMenu({ Key? key, required this.webViewController, PlatformWebViewCookieManager? cookieManager, }) : cookieManager = cookieManager ?? PlatformWebViewCookieManager( const PlatformWebViewCookieManagerCreationParams(), ), super(key: key); final PlatformWebViewController webViewController; late final PlatformWebViewCookieManager cookieManager; @override Widget build(BuildContext context) { return PopupMenuButton( key: const ValueKey('ShowPopupMenu'), onSelected: (MenuOptions value) { switch (value) { case MenuOptions.showUserAgent: _onShowUserAgent(); break; case MenuOptions.listCookies: _onListCookies(context); break; case MenuOptions.clearCookies: _onClearCookies(context); break; case MenuOptions.addToCache: _onAddToCache(context); break; case MenuOptions.listCache: _onListCache(); break; case MenuOptions.clearCache: _onClearCache(context); break; case MenuOptions.navigationDelegate: _onNavigationDelegateExample(); break; case MenuOptions.doPostRequest: _onDoPostRequest(); break; case MenuOptions.loadLocalFile: _onLoadLocalFileExample(); break; case MenuOptions.loadFlutterAsset: _onLoadFlutterAssetExample(); break; case MenuOptions.loadHtmlString: _onLoadHtmlStringExample(); break; case MenuOptions.transparentBackground: _onTransparentBackground(); break; case MenuOptions.setCookie: _onSetCookie(); break; } }, itemBuilder: (BuildContext context) => >[ const PopupMenuItem( value: MenuOptions.showUserAgent, child: Text('Show user agent'), ), const PopupMenuItem( value: MenuOptions.listCookies, child: Text('List cookies'), ), const PopupMenuItem( value: MenuOptions.clearCookies, child: Text('Clear cookies'), ), const PopupMenuItem( value: MenuOptions.addToCache, child: Text('Add to cache'), ), const PopupMenuItem( value: MenuOptions.listCache, child: Text('List cache'), ), const PopupMenuItem( value: MenuOptions.clearCache, child: Text('Clear cache'), ), const PopupMenuItem( value: MenuOptions.navigationDelegate, child: Text('Navigation Delegate example'), ), const PopupMenuItem( value: MenuOptions.doPostRequest, child: Text('Post Request'), ), const PopupMenuItem( value: MenuOptions.loadHtmlString, child: Text('Load HTML string'), ), const PopupMenuItem( value: MenuOptions.loadLocalFile, child: Text('Load local file'), ), const PopupMenuItem( value: MenuOptions.loadFlutterAsset, child: Text('Load Flutter Asset'), ), const PopupMenuItem( value: MenuOptions.setCookie, child: Text('Set cookie'), ), const PopupMenuItem( key: ValueKey('ShowTransparentBackgroundExample'), value: MenuOptions.transparentBackground, child: Text('Transparent background example'), ), ], ); } Future _onShowUserAgent() { // Send a message with the user agent string to the Toaster JavaScript channel we registered // with the WebView. return webViewController.runJavaScript( 'Toaster.postMessage("User Agent: " + navigator.userAgent);', ); } Future _onListCookies(BuildContext context) async { final String cookies = await webViewController .runJavaScriptReturningResult('document.cookie') as String; 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";', ); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Added a test entry to cache.'), )); } } Future _onListCache() { return webViewController.runJavaScript('caches.keys()' // ignore: missing_whitespace_between_adjacent_strings '.then((cacheKeys) => JSON.stringify({"cacheKeys" : cacheKeys, "localStorage" : localStorage}))' '.then((caches) => Toaster.postMessage(caches))'); } Future _onClearCache(BuildContext context) async { await webViewController.clearCache(); await webViewController.clearLocalStorage(); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Cache cleared.'), )); } } Future _onClearCookies(BuildContext context) async { final bool hadCookies = await cookieManager.clearCookies(); String message = 'There were cookies. Now, they are gone!'; if (!hadCookies) { message = 'There are no cookies.'; } if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(message), )); } } Future _onNavigationDelegateExample() { final String contentBase64 = base64Encode( const Utf8Encoder().convert(kNavigationExamplePage), ); return webViewController.loadRequest( LoadRequestParams( uri: Uri.parse('data:text/html;base64,$contentBase64'), ), ); } Future _onSetCookie() async { await cookieManager.setCookie( const WebViewCookie( name: 'foo', value: 'bar', domain: 'httpbin.org', path: '/anything', ), ); await webViewController.loadRequest(LoadRequestParams( uri: Uri.parse('https://httpbin.org/anything'), )); } Future _onDoPostRequest() { return webViewController.loadRequest(LoadRequestParams( uri: Uri.parse('https://httpbin.org/post'), method: LoadRequestMethod.post, headers: const { 'foo': 'bar', 'Content-Type': 'text/plain', }, body: Uint8List.fromList('Test Body'.codeUnits), )); } Future _onLoadLocalFileExample() async { final String pathToIndex = await _prepareLocalFile(); await webViewController.loadFile(pathToIndex); } Future _onLoadFlutterAssetExample() { return webViewController.loadFlutterAsset('assets/www/index.html'); } Future _onLoadHtmlStringExample() { return webViewController.loadHtmlString(kLocalExamplePage); } Future _onTransparentBackground() { return webViewController.loadHtmlString(kTransparentBackgroundPage); } Widget _getCookieList(String cookies) { if (cookies == null || cookies == '""') { return Container(); } final List cookieList = cookies.split(';'); final Iterable cookieWidgets = cookieList.map((String cookie) => Text(cookie)); return Column( mainAxisAlignment: MainAxisAlignment.end, mainAxisSize: MainAxisSize.min, children: cookieWidgets.toList(), ); } static Future _prepareLocalFile() async { final String tmpDir = (await getTemporaryDirectory()).path; final File indexFile = File( {tmpDir, 'www', 'index.html'}.join(Platform.pathSeparator)); await indexFile.create(recursive: true); await indexFile.writeAsString(kLocalExamplePage); return indexFile.path; } } class NavigationControls extends StatelessWidget { const NavigationControls({Key? key, required this.webViewController}) : super(key: key); final PlatformWebViewController webViewController; @override Widget build(BuildContext context) { return Row( children: [ IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () async { if (await webViewController.canGoBack()) { await webViewController.goBack(); } else { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('No back history item')), ); } } }, ), IconButton( icon: const Icon(Icons.arrow_forward_ios), onPressed: () async { if (await webViewController.canGoForward()) { await webViewController.goForward(); } else { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('No forward history item')), ); } } }, ), IconButton( icon: const Icon(Icons.replay), onPressed: () => webViewController.reload(), ), ], ); } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml ================================================ name: webview_flutter_wkwebview_example description: Demonstrates how to use the webview_flutter_wkwebview plugin. publish_to: none environment: sdk: ">=2.12.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter path_provider: ^2.0.6 webview_flutter_platform_interface: ^2.0.0 webview_flutter_wkwebview: # When depending on this package from a real application you should use: # webview_flutter: ^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: ../ dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter integration_test: sdk: flutter flutter: uses-material-design: true assets: - assets/sample_audio.ogg - assets/sample_video.mp4 - assets/www/index.html - assets/www/styles/style.css ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/example/test_driver/integration_test.dart ================================================ // Copyright 2013 The Flutter 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(); ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Assets/.gitkeep ================================================ ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import #import NS_ASSUME_NONNULL_BEGIN @interface FLTWebViewFlutterPlugin : NSObject @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FLTWebViewFlutterPlugin.m ================================================ // Copyright 2013 The Flutter 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 "FLTWebViewFlutterPlugin.h" #import "FWFGeneratedWebKitApis.h" #import "FWFHTTPCookieStoreHostApi.h" #import "FWFInstanceManager.h" #import "FWFNavigationDelegateHostApi.h" #import "FWFObjectHostApi.h" #import "FWFPreferencesHostApi.h" #import "FWFScriptMessageHandlerHostApi.h" #import "FWFScrollViewHostApi.h" #import "FWFUIDelegateHostApi.h" #import "FWFUIViewHostApi.h" #import "FWFUserContentControllerHostApi.h" #import "FWFWebViewConfigurationHostApi.h" #import "FWFWebViewHostApi.h" #import "FWFWebsiteDataStoreHostApi.h" @interface FWFWebViewFactory : NSObject @property(nonatomic, weak) FWFInstanceManager *instanceManager; - (instancetype)initWithManager:(FWFInstanceManager *)manager; @end @implementation FWFWebViewFactory - (instancetype)initWithManager:(FWFInstanceManager *)manager { self = [self init]; if (self) { _instanceManager = manager; } return self; } - (NSObject *)createArgsCodec { return [FlutterStandardMessageCodec sharedInstance]; } - (NSObject *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args { NSNumber *identifier = (NSNumber *)args; FWFWebView *webView = (FWFWebView *)[self.instanceManager instanceForIdentifier:identifier.longValue]; webView.frame = frame; return webView; } @end @implementation FLTWebViewFlutterPlugin + (void)registerWithRegistrar:(NSObject *)registrar { FWFInstanceManager *instanceManager = [[FWFInstanceManager alloc] initWithDeallocCallback:^(long identifier) { FWFObjectFlutterApiImpl *objectApi = [[FWFObjectFlutterApiImpl alloc] initWithBinaryMessenger:registrar.messenger instanceManager:[[FWFInstanceManager alloc] init]]; dispatch_async(dispatch_get_main_queue(), ^{ [objectApi disposeObjectWithIdentifier:@(identifier) completion:^(NSError *error) { NSAssert(!error, @"%@", error); }]; }); }]; FWFWKHttpCookieStoreHostApiSetup( registrar.messenger, [[FWFHTTPCookieStoreHostApiImpl alloc] initWithInstanceManager:instanceManager]); FWFWKNavigationDelegateHostApiSetup( registrar.messenger, [[FWFNavigationDelegateHostApiImpl alloc] initWithBinaryMessenger:registrar.messenger instanceManager:instanceManager]); FWFNSObjectHostApiSetup(registrar.messenger, [[FWFObjectHostApiImpl alloc] initWithInstanceManager:instanceManager]); FWFWKPreferencesHostApiSetup(registrar.messenger, [[FWFPreferencesHostApiImpl alloc] initWithInstanceManager:instanceManager]); FWFWKScriptMessageHandlerHostApiSetup( registrar.messenger, [[FWFScriptMessageHandlerHostApiImpl alloc] initWithBinaryMessenger:registrar.messenger instanceManager:instanceManager]); FWFUIScrollViewHostApiSetup(registrar.messenger, [[FWFScrollViewHostApiImpl alloc] initWithInstanceManager:instanceManager]); FWFWKUIDelegateHostApiSetup(registrar.messenger, [[FWFUIDelegateHostApiImpl alloc] initWithBinaryMessenger:registrar.messenger instanceManager:instanceManager]); FWFUIViewHostApiSetup(registrar.messenger, [[FWFUIViewHostApiImpl alloc] initWithInstanceManager:instanceManager]); FWFWKUserContentControllerHostApiSetup( registrar.messenger, [[FWFUserContentControllerHostApiImpl alloc] initWithInstanceManager:instanceManager]); FWFWKWebsiteDataStoreHostApiSetup( registrar.messenger, [[FWFWebsiteDataStoreHostApiImpl alloc] initWithInstanceManager:instanceManager]); FWFWKWebViewConfigurationHostApiSetup( registrar.messenger, [[FWFWebViewConfigurationHostApiImpl alloc] initWithBinaryMessenger:registrar.messenger instanceManager:instanceManager]); FWFWKWebViewHostApiSetup(registrar.messenger, [[FWFWebViewHostApiImpl alloc] initWithBinaryMessenger:registrar.messenger instanceManager:instanceManager]); FWFWebViewFactory *webviewFactory = [[FWFWebViewFactory alloc] initWithManager:instanceManager]; [registrar registerViewFactory:webviewFactory withId:@"plugins.flutter.io/webview"]; // InstanceManager is published so that a strong reference is maintained. [registrar publish:instanceManager]; } - (void)detachFromEngineForRegistrar:(NSObject *)registrar { [registrar publish:[NSNull null]]; } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.h ================================================ // Copyright 2013 The Flutter 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 "FWFGeneratedWebKitApis.h" #import NS_ASSUME_NONNULL_BEGIN /** * Converts an FWFNSUrlRequestData to an NSURLRequest. * * @param data The data object containing information to create an NSURLRequest. * * @return An NSURLRequest or nil if data could not be converted. */ extern NSURLRequest *_Nullable FWFNSURLRequestFromRequestData(FWFNSUrlRequestData *data); /** * Converts an FWFNSHttpCookieData to an NSHTTPCookie. * * @param data The data object containing information to create an NSHTTPCookie. * * @return An NSHTTPCookie or nil if data could not be converted. */ extern NSHTTPCookie *_Nullable FWFNSHTTPCookieFromCookieData(FWFNSHttpCookieData *data); /** * Converts an FWFNSKeyValueObservingOptionsEnumData to an NSKeyValueObservingOptions. * * @param data The data object containing information to create an NSKeyValueObservingOptions. * * @return An NSKeyValueObservingOptions or -1 if data could not be converted. */ extern NSKeyValueObservingOptions FWFNSKeyValueObservingOptionsFromEnumData( FWFNSKeyValueObservingOptionsEnumData *data); /** * Converts an FWFNSHTTPCookiePropertyKeyEnumData to an NSHTTPCookiePropertyKey. * * @param data The data object containing information to create an NSHTTPCookiePropertyKey. * * @return An NSHttpCookiePropertyKey or nil if data could not be converted. */ extern NSHTTPCookiePropertyKey _Nullable FWFNSHTTPCookiePropertyKeyFromEnumData( FWFNSHttpCookiePropertyKeyEnumData *data); /** * Converts a WKUserScriptData to a WKUserScript. * * @param data The data object containing information to create a WKUserScript. * * @return A WKUserScript or nil if data could not be converted. */ extern WKUserScript *FWFWKUserScriptFromScriptData(FWFWKUserScriptData *data); /** * Converts an FWFWKUserScriptInjectionTimeEnumData to a WKUserScriptInjectionTime. * * @param data The data object containing information to create a WKUserScriptInjectionTime. * * @return A WKUserScriptInjectionTime or -1 if data could not be converted. */ extern WKUserScriptInjectionTime FWFWKUserScriptInjectionTimeFromEnumData( FWFWKUserScriptInjectionTimeEnumData *data); /** * Converts an FWFWKAudiovisualMediaTypeEnumData to a WKAudiovisualMediaTypes. * * @param data The data object containing information to create a WKAudiovisualMediaTypes. * * @return A WKAudiovisualMediaType or -1 if data could not be converted. */ API_AVAILABLE(ios(10.0)) extern WKAudiovisualMediaTypes FWFWKAudiovisualMediaTypeFromEnumData( FWFWKAudiovisualMediaTypeEnumData *data); /** * Converts an FWFWKWebsiteDataTypeEnumData to a WKWebsiteDataType. * * @param data The data object containing information to create a WKWebsiteDataType. * * @return A WKWebsiteDataType or nil if data could not be converted. */ extern NSString *_Nullable FWFWKWebsiteDataTypeFromEnumData(FWFWKWebsiteDataTypeEnumData *data); /** * Converts a WKNavigationAction to an FWFWKNavigationActionData. * * @param action The object containing information to create a WKNavigationActionData. * * @return A FWFWKNavigationActionData. */ extern FWFWKNavigationActionData *FWFWKNavigationActionDataFromNavigationAction( WKNavigationAction *action); /** * Converts a NSURLRequest to an FWFNSUrlRequestData. * * @param request The object containing information to create a WKNavigationActionData. * * @return A FWFNSUrlRequestData. */ extern FWFNSUrlRequestData *FWFNSUrlRequestDataFromNSURLRequest(NSURLRequest *request); /** * Converts a WKFrameInfo to an FWFWKFrameInfoData. * * @param info The object containing information to create a FWFWKFrameInfoData. * * @return A FWFWKFrameInfoData. */ extern FWFWKFrameInfoData *FWFWKFrameInfoDataFromWKFrameInfo(WKFrameInfo *info); /** * Converts an FWFWKNavigationActionPolicyEnumData to a WKNavigationActionPolicy. * * @param data The data object containing information to create a WKNavigationActionPolicy. * * @return A WKNavigationActionPolicy or -1 if data could not be converted. */ extern WKNavigationActionPolicy FWFWKNavigationActionPolicyFromEnumData( FWFWKNavigationActionPolicyEnumData *data); /** * Converts a NSError to an FWFNSErrorData. * * @param error The object containing information to create a FWFNSErrorData. * * @return A FWFNSErrorData. */ extern FWFNSErrorData *FWFNSErrorDataFromNSError(NSError *error); /** * Converts an NSKeyValueChangeKey to a FWFNSKeyValueChangeKeyEnumData. * * @param key The data object containing information to create a FWFNSKeyValueChangeKeyEnumData. * * @return A FWFNSKeyValueChangeKeyEnumData or nil if data could not be converted. */ extern FWFNSKeyValueChangeKeyEnumData *FWFNSKeyValueChangeKeyEnumDataFromNSKeyValueChangeKey( NSKeyValueChangeKey key); /** * Converts a WKScriptMessage to an FWFWKScriptMessageData. * * @param message The object containing information to create a FWFWKScriptMessageData. * * @return A FWFWKScriptMessageData. */ extern FWFWKScriptMessageData *FWFWKScriptMessageDataFromWKScriptMessage(WKScriptMessage *message); /** * Converts a WKNavigationType to an FWFWKNavigationType. * * @param type The object containing information to create a FWFWKNavigationType * * @return A FWFWKNavigationType. */ extern FWFWKNavigationType FWFWKNavigationTypeFromWKNavigationType(WKNavigationType type); NS_ASSUME_NONNULL_END ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.m ================================================ // Copyright 2013 The Flutter 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 "FWFDataConverters.h" #import NSURLRequest *_Nullable FWFNSURLRequestFromRequestData(FWFNSUrlRequestData *data) { NSURL *url = [NSURL URLWithString:data.url]; if (!url) { return nil; } NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; if (!request) { return nil; } if (data.httpMethod) { [request setHTTPMethod:data.httpMethod]; } if (data.httpBody) { [request setHTTPBody:data.httpBody.data]; } [request setAllHTTPHeaderFields:data.allHttpHeaderFields]; return request; } extern NSHTTPCookie *_Nullable FWFNSHTTPCookieFromCookieData(FWFNSHttpCookieData *data) { NSMutableDictionary *properties = [NSMutableDictionary dictionary]; for (int i = 0; i < data.propertyKeys.count; i++) { NSHTTPCookiePropertyKey cookieKey = FWFNSHTTPCookiePropertyKeyFromEnumData(data.propertyKeys[i]); if (!cookieKey) { // Some keys aren't supported on all versions, so this ignores keys // that require a higher version or are unsupported. continue; } [properties setObject:data.propertyValues[i] forKey:cookieKey]; } return [NSHTTPCookie cookieWithProperties:properties]; } NSKeyValueObservingOptions FWFNSKeyValueObservingOptionsFromEnumData( FWFNSKeyValueObservingOptionsEnumData *data) { switch (data.value) { case FWFNSKeyValueObservingOptionsEnumNewValue: return NSKeyValueObservingOptionNew; case FWFNSKeyValueObservingOptionsEnumOldValue: return NSKeyValueObservingOptionOld; case FWFNSKeyValueObservingOptionsEnumInitialValue: return NSKeyValueObservingOptionInitial; case FWFNSKeyValueObservingOptionsEnumPriorNotification: return NSKeyValueObservingOptionPrior; } return -1; } NSHTTPCookiePropertyKey _Nullable FWFNSHTTPCookiePropertyKeyFromEnumData( FWFNSHttpCookiePropertyKeyEnumData *data) { switch (data.value) { case FWFNSHttpCookiePropertyKeyEnumComment: return NSHTTPCookieComment; case FWFNSHttpCookiePropertyKeyEnumCommentUrl: return NSHTTPCookieCommentURL; case FWFNSHttpCookiePropertyKeyEnumDiscard: return NSHTTPCookieDiscard; case FWFNSHttpCookiePropertyKeyEnumDomain: return NSHTTPCookieDomain; case FWFNSHttpCookiePropertyKeyEnumExpires: return NSHTTPCookieExpires; case FWFNSHttpCookiePropertyKeyEnumMaximumAge: return NSHTTPCookieMaximumAge; case FWFNSHttpCookiePropertyKeyEnumName: return NSHTTPCookieName; case FWFNSHttpCookiePropertyKeyEnumOriginUrl: return NSHTTPCookieOriginURL; case FWFNSHttpCookiePropertyKeyEnumPath: return NSHTTPCookiePath; case FWFNSHttpCookiePropertyKeyEnumPort: return NSHTTPCookiePort; case FWFNSHttpCookiePropertyKeyEnumSameSitePolicy: if (@available(iOS 13.0, *)) { return NSHTTPCookieSameSitePolicy; } else { return nil; } case FWFNSHttpCookiePropertyKeyEnumSecure: return NSHTTPCookieSecure; case FWFNSHttpCookiePropertyKeyEnumValue: return NSHTTPCookieValue; case FWFNSHttpCookiePropertyKeyEnumVersion: return NSHTTPCookieVersion; } return nil; } extern WKUserScript *FWFWKUserScriptFromScriptData(FWFWKUserScriptData *data) { return [[WKUserScript alloc] initWithSource:data.source injectionTime:FWFWKUserScriptInjectionTimeFromEnumData(data.injectionTime) forMainFrameOnly:data.isMainFrameOnly.boolValue]; } WKUserScriptInjectionTime FWFWKUserScriptInjectionTimeFromEnumData( FWFWKUserScriptInjectionTimeEnumData *data) { switch (data.value) { case FWFWKUserScriptInjectionTimeEnumAtDocumentStart: return WKUserScriptInjectionTimeAtDocumentStart; case FWFWKUserScriptInjectionTimeEnumAtDocumentEnd: return WKUserScriptInjectionTimeAtDocumentEnd; } return -1; } API_AVAILABLE(ios(10.0)) WKAudiovisualMediaTypes FWFWKAudiovisualMediaTypeFromEnumData( FWFWKAudiovisualMediaTypeEnumData *data) { switch (data.value) { case FWFWKAudiovisualMediaTypeEnumNone: return WKAudiovisualMediaTypeNone; case FWFWKAudiovisualMediaTypeEnumAudio: return WKAudiovisualMediaTypeAudio; case FWFWKAudiovisualMediaTypeEnumVideo: return WKAudiovisualMediaTypeVideo; case FWFWKAudiovisualMediaTypeEnumAll: return WKAudiovisualMediaTypeAll; } return -1; } NSString *_Nullable FWFWKWebsiteDataTypeFromEnumData(FWFWKWebsiteDataTypeEnumData *data) { switch (data.value) { case FWFWKWebsiteDataTypeEnumCookies: return WKWebsiteDataTypeCookies; case FWFWKWebsiteDataTypeEnumMemoryCache: return WKWebsiteDataTypeMemoryCache; case FWFWKWebsiteDataTypeEnumDiskCache: return WKWebsiteDataTypeDiskCache; case FWFWKWebsiteDataTypeEnumOfflineWebApplicationCache: return WKWebsiteDataTypeOfflineWebApplicationCache; case FWFWKWebsiteDataTypeEnumLocalStorage: return WKWebsiteDataTypeLocalStorage; case FWFWKWebsiteDataTypeEnumSessionStorage: return WKWebsiteDataTypeSessionStorage; case FWFWKWebsiteDataTypeEnumWebSQLDatabases: return WKWebsiteDataTypeWebSQLDatabases; case FWFWKWebsiteDataTypeEnumIndexedDBDatabases: return WKWebsiteDataTypeIndexedDBDatabases; } return nil; } FWFWKNavigationActionData *FWFWKNavigationActionDataFromNavigationAction( WKNavigationAction *action) { return [FWFWKNavigationActionData makeWithRequest:FWFNSUrlRequestDataFromNSURLRequest(action.request) targetFrame:FWFWKFrameInfoDataFromWKFrameInfo(action.targetFrame) navigationType:FWFWKNavigationTypeFromWKNavigationType(action.navigationType)]; } FWFNSUrlRequestData *FWFNSUrlRequestDataFromNSURLRequest(NSURLRequest *request) { return [FWFNSUrlRequestData makeWithUrl:request.URL.absoluteString httpMethod:request.HTTPMethod httpBody:request.HTTPBody ? [FlutterStandardTypedData typedDataWithBytes:request.HTTPBody] : nil allHttpHeaderFields:request.allHTTPHeaderFields ? request.allHTTPHeaderFields : @{}]; } FWFWKFrameInfoData *FWFWKFrameInfoDataFromWKFrameInfo(WKFrameInfo *info) { return [FWFWKFrameInfoData makeWithIsMainFrame:@(info.isMainFrame)]; } WKNavigationActionPolicy FWFWKNavigationActionPolicyFromEnumData( FWFWKNavigationActionPolicyEnumData *data) { switch (data.value) { case FWFWKNavigationActionPolicyEnumAllow: return WKNavigationActionPolicyAllow; case FWFWKNavigationActionPolicyEnumCancel: return WKNavigationActionPolicyCancel; } return -1; } FWFNSErrorData *FWFNSErrorDataFromNSError(NSError *error) { return [FWFNSErrorData makeWithCode:@(error.code) domain:error.domain localizedDescription:error.localizedDescription]; } FWFNSKeyValueChangeKeyEnumData *FWFNSKeyValueChangeKeyEnumDataFromNSKeyValueChangeKey( NSKeyValueChangeKey key) { if ([key isEqualToString:NSKeyValueChangeIndexesKey]) { return [FWFNSKeyValueChangeKeyEnumData makeWithValue:FWFNSKeyValueChangeKeyEnumIndexes]; } else if ([key isEqualToString:NSKeyValueChangeKindKey]) { return [FWFNSKeyValueChangeKeyEnumData makeWithValue:FWFNSKeyValueChangeKeyEnumKind]; } else if ([key isEqualToString:NSKeyValueChangeNewKey]) { return [FWFNSKeyValueChangeKeyEnumData makeWithValue:FWFNSKeyValueChangeKeyEnumNewValue]; } else if ([key isEqualToString:NSKeyValueChangeNotificationIsPriorKey]) { return [FWFNSKeyValueChangeKeyEnumData makeWithValue:FWFNSKeyValueChangeKeyEnumNotificationIsPrior]; } else if ([key isEqualToString:NSKeyValueChangeOldKey]) { return [FWFNSKeyValueChangeKeyEnumData makeWithValue:FWFNSKeyValueChangeKeyEnumOldValue]; } return nil; } FWFWKScriptMessageData *FWFWKScriptMessageDataFromWKScriptMessage(WKScriptMessage *message) { return [FWFWKScriptMessageData makeWithName:message.name body:message.body]; } FWFWKNavigationType FWFWKNavigationTypeFromWKNavigationType(WKNavigationType type) { switch (type) { case WKNavigationTypeLinkActivated: return FWFWKNavigationTypeLinkActivated; case WKNavigationTypeFormSubmitted: return FWFWKNavigationTypeFormResubmitted; case WKNavigationTypeBackForward: return FWFWKNavigationTypeBackForward; case WKNavigationTypeReload: return FWFWKNavigationTypeReload; case WKNavigationTypeFormResubmitted: return FWFWKNavigationTypeFormResubmitted; case WKNavigationTypeOther: return FWFWKNavigationTypeOther; } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h ================================================ // Copyright 2013 The Flutter 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.13), do not edit directly. // See also: https://pub.dev/packages/pigeon #import @protocol FlutterBinaryMessenger; @protocol FlutterMessageCodec; @class FlutterError; @class FlutterStandardTypedData; NS_ASSUME_NONNULL_BEGIN /// Mirror of NSKeyValueObservingOptions. /// /// See /// https://developer.apple.com/documentation/foundation/nskeyvalueobservingoptions?language=objc. typedef NS_ENUM(NSUInteger, FWFNSKeyValueObservingOptionsEnum) { FWFNSKeyValueObservingOptionsEnumNewValue = 0, FWFNSKeyValueObservingOptionsEnumOldValue = 1, FWFNSKeyValueObservingOptionsEnumInitialValue = 2, FWFNSKeyValueObservingOptionsEnumPriorNotification = 3, }; /// Mirror of NSKeyValueChange. /// /// See https://developer.apple.com/documentation/foundation/nskeyvaluechange?language=objc. typedef NS_ENUM(NSUInteger, FWFNSKeyValueChangeEnum) { FWFNSKeyValueChangeEnumSetting = 0, FWFNSKeyValueChangeEnumInsertion = 1, FWFNSKeyValueChangeEnumRemoval = 2, FWFNSKeyValueChangeEnumReplacement = 3, }; /// Mirror of NSKeyValueChangeKey. /// /// See https://developer.apple.com/documentation/foundation/nskeyvaluechangekey?language=objc. typedef NS_ENUM(NSUInteger, FWFNSKeyValueChangeKeyEnum) { FWFNSKeyValueChangeKeyEnumIndexes = 0, FWFNSKeyValueChangeKeyEnumKind = 1, FWFNSKeyValueChangeKeyEnumNewValue = 2, FWFNSKeyValueChangeKeyEnumNotificationIsPrior = 3, FWFNSKeyValueChangeKeyEnumOldValue = 4, }; /// Mirror of WKUserScriptInjectionTime. /// /// See https://developer.apple.com/documentation/webkit/wkuserscriptinjectiontime?language=objc. typedef NS_ENUM(NSUInteger, FWFWKUserScriptInjectionTimeEnum) { FWFWKUserScriptInjectionTimeEnumAtDocumentStart = 0, FWFWKUserScriptInjectionTimeEnumAtDocumentEnd = 1, }; /// Mirror of WKAudiovisualMediaTypes. /// /// See /// [WKAudiovisualMediaTypes](https://developer.apple.com/documentation/webkit/wkaudiovisualmediatypes?language=objc). typedef NS_ENUM(NSUInteger, FWFWKAudiovisualMediaTypeEnum) { FWFWKAudiovisualMediaTypeEnumNone = 0, FWFWKAudiovisualMediaTypeEnumAudio = 1, FWFWKAudiovisualMediaTypeEnumVideo = 2, FWFWKAudiovisualMediaTypeEnumAll = 3, }; /// Mirror of WKWebsiteDataTypes. /// /// See /// https://developer.apple.com/documentation/webkit/wkwebsitedatarecord/data_store_record_types?language=objc. typedef NS_ENUM(NSUInteger, FWFWKWebsiteDataTypeEnum) { FWFWKWebsiteDataTypeEnumCookies = 0, FWFWKWebsiteDataTypeEnumMemoryCache = 1, FWFWKWebsiteDataTypeEnumDiskCache = 2, FWFWKWebsiteDataTypeEnumOfflineWebApplicationCache = 3, FWFWKWebsiteDataTypeEnumLocalStorage = 4, FWFWKWebsiteDataTypeEnumSessionStorage = 5, FWFWKWebsiteDataTypeEnumWebSQLDatabases = 6, FWFWKWebsiteDataTypeEnumIndexedDBDatabases = 7, }; /// Mirror of WKNavigationActionPolicy. /// /// See https://developer.apple.com/documentation/webkit/wknavigationactionpolicy?language=objc. typedef NS_ENUM(NSUInteger, FWFWKNavigationActionPolicyEnum) { FWFWKNavigationActionPolicyEnumAllow = 0, FWFWKNavigationActionPolicyEnumCancel = 1, }; /// Mirror of NSHTTPCookiePropertyKey. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiepropertykey. typedef NS_ENUM(NSUInteger, FWFNSHttpCookiePropertyKeyEnum) { FWFNSHttpCookiePropertyKeyEnumComment = 0, FWFNSHttpCookiePropertyKeyEnumCommentUrl = 1, FWFNSHttpCookiePropertyKeyEnumDiscard = 2, FWFNSHttpCookiePropertyKeyEnumDomain = 3, FWFNSHttpCookiePropertyKeyEnumExpires = 4, FWFNSHttpCookiePropertyKeyEnumMaximumAge = 5, FWFNSHttpCookiePropertyKeyEnumName = 6, FWFNSHttpCookiePropertyKeyEnumOriginUrl = 7, FWFNSHttpCookiePropertyKeyEnumPath = 8, FWFNSHttpCookiePropertyKeyEnumPort = 9, FWFNSHttpCookiePropertyKeyEnumSameSitePolicy = 10, FWFNSHttpCookiePropertyKeyEnumSecure = 11, FWFNSHttpCookiePropertyKeyEnumValue = 12, FWFNSHttpCookiePropertyKeyEnumVersion = 13, }; /// An object that contains information about an action that causes navigation /// to occur. /// /// Wraps /// [WKNavigationType](https://developer.apple.com/documentation/webkit/wknavigationaction?language=objc). typedef NS_ENUM(NSUInteger, FWFWKNavigationType) { /// A link activation. /// /// See /// https://developer.apple.com/documentation/webkit/wknavigationtype/wknavigationtypelinkactivated?language=objc. FWFWKNavigationTypeLinkActivated = 0, /// A request to submit a form. /// /// See /// https://developer.apple.com/documentation/webkit/wknavigationtype/wknavigationtypeformsubmitted?language=objc. FWFWKNavigationTypeSubmitted = 1, /// A request for the frame’s next or previous item. /// /// See /// https://developer.apple.com/documentation/webkit/wknavigationtype/wknavigationtypebackforward?language=objc. FWFWKNavigationTypeBackForward = 2, /// A request to reload the webpage. /// /// See /// https://developer.apple.com/documentation/webkit/wknavigationtype/wknavigationtypereload?language=objc. FWFWKNavigationTypeReload = 3, /// A request to resubmit a form. /// /// See /// https://developer.apple.com/documentation/webkit/wknavigationtype/wknavigationtypeformresubmitted?language=objc. FWFWKNavigationTypeFormResubmitted = 4, /// A navigation request that originates for some other reason. /// /// See /// https://developer.apple.com/documentation/webkit/wknavigationtype/wknavigationtypeother?language=objc. FWFWKNavigationTypeOther = 5, }; @class FWFNSKeyValueObservingOptionsEnumData; @class FWFNSKeyValueChangeKeyEnumData; @class FWFWKUserScriptInjectionTimeEnumData; @class FWFWKAudiovisualMediaTypeEnumData; @class FWFWKWebsiteDataTypeEnumData; @class FWFWKNavigationActionPolicyEnumData; @class FWFNSHttpCookiePropertyKeyEnumData; @class FWFNSUrlRequestData; @class FWFWKUserScriptData; @class FWFWKNavigationActionData; @class FWFWKFrameInfoData; @class FWFNSErrorData; @class FWFWKScriptMessageData; @class FWFNSHttpCookieData; @interface FWFNSKeyValueObservingOptionsEnumData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithValue:(FWFNSKeyValueObservingOptionsEnum)value; @property(nonatomic, assign) FWFNSKeyValueObservingOptionsEnum value; @end @interface FWFNSKeyValueChangeKeyEnumData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithValue:(FWFNSKeyValueChangeKeyEnum)value; @property(nonatomic, assign) FWFNSKeyValueChangeKeyEnum value; @end @interface FWFWKUserScriptInjectionTimeEnumData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithValue:(FWFWKUserScriptInjectionTimeEnum)value; @property(nonatomic, assign) FWFWKUserScriptInjectionTimeEnum value; @end @interface FWFWKAudiovisualMediaTypeEnumData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithValue:(FWFWKAudiovisualMediaTypeEnum)value; @property(nonatomic, assign) FWFWKAudiovisualMediaTypeEnum value; @end @interface FWFWKWebsiteDataTypeEnumData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithValue:(FWFWKWebsiteDataTypeEnum)value; @property(nonatomic, assign) FWFWKWebsiteDataTypeEnum value; @end @interface FWFWKNavigationActionPolicyEnumData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithValue:(FWFWKNavigationActionPolicyEnum)value; @property(nonatomic, assign) FWFWKNavigationActionPolicyEnum value; @end @interface FWFNSHttpCookiePropertyKeyEnumData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithValue:(FWFNSHttpCookiePropertyKeyEnum)value; @property(nonatomic, assign) FWFNSHttpCookiePropertyKeyEnum value; @end /// Mirror of NSURLRequest. /// /// See https://developer.apple.com/documentation/foundation/nsurlrequest?language=objc. @interface FWFNSUrlRequestData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithUrl:(NSString *)url httpMethod:(nullable NSString *)httpMethod httpBody:(nullable FlutterStandardTypedData *)httpBody allHttpHeaderFields:(NSDictionary *)allHttpHeaderFields; @property(nonatomic, copy) NSString *url; @property(nonatomic, copy, nullable) NSString *httpMethod; @property(nonatomic, strong, nullable) FlutterStandardTypedData *httpBody; @property(nonatomic, strong) NSDictionary *allHttpHeaderFields; @end /// Mirror of WKUserScript. /// /// See https://developer.apple.com/documentation/webkit/wkuserscript?language=objc. @interface FWFWKUserScriptData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithSource:(NSString *)source injectionTime:(nullable FWFWKUserScriptInjectionTimeEnumData *)injectionTime isMainFrameOnly:(NSNumber *)isMainFrameOnly; @property(nonatomic, copy) NSString *source; @property(nonatomic, strong, nullable) FWFWKUserScriptInjectionTimeEnumData *injectionTime; @property(nonatomic, strong) NSNumber *isMainFrameOnly; @end /// Mirror of WKNavigationAction. /// /// See https://developer.apple.com/documentation/webkit/wknavigationaction. @interface FWFWKNavigationActionData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithRequest:(FWFNSUrlRequestData *)request targetFrame:(FWFWKFrameInfoData *)targetFrame navigationType:(FWFWKNavigationType)navigationType; @property(nonatomic, strong) FWFNSUrlRequestData *request; @property(nonatomic, strong) FWFWKFrameInfoData *targetFrame; @property(nonatomic, assign) FWFWKNavigationType navigationType; @end /// Mirror of WKFrameInfo. /// /// See https://developer.apple.com/documentation/webkit/wkframeinfo?language=objc. @interface FWFWKFrameInfoData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithIsMainFrame:(NSNumber *)isMainFrame; @property(nonatomic, strong) NSNumber *isMainFrame; @end /// Mirror of NSError. /// /// See https://developer.apple.com/documentation/foundation/nserror?language=objc. @interface FWFNSErrorData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithCode:(NSNumber *)code domain:(NSString *)domain localizedDescription:(NSString *)localizedDescription; @property(nonatomic, strong) NSNumber *code; @property(nonatomic, copy) NSString *domain; @property(nonatomic, copy) NSString *localizedDescription; @end /// Mirror of WKScriptMessage. /// /// See https://developer.apple.com/documentation/webkit/wkscriptmessage?language=objc. @interface FWFWKScriptMessageData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithName:(NSString *)name body:(id)body; @property(nonatomic, copy) NSString *name; @property(nonatomic, strong) id body; @end /// Mirror of NSHttpCookieData. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookie?language=objc. @interface FWFNSHttpCookieData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithPropertyKeys:(NSArray *)propertyKeys propertyValues:(NSArray *)propertyValues; @property(nonatomic, strong) NSArray *propertyKeys; @property(nonatomic, strong) NSArray *propertyValues; @end /// The codec used by FWFWKWebsiteDataStoreHostApi. NSObject *FWFWKWebsiteDataStoreHostApiGetCodec(void); /// Mirror of WKWebsiteDataStore. /// /// See https://developer.apple.com/documentation/webkit/wkwebsitedatastore?language=objc. @protocol FWFWKWebsiteDataStoreHostApi - (void)createFromWebViewConfigurationWithIdentifier:(NSNumber *)identifier configurationIdentifier:(NSNumber *)configurationIdentifier error:(FlutterError *_Nullable *_Nonnull)error; - (void)createDefaultDataStoreWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; - (void)removeDataFromDataStoreWithIdentifier:(NSNumber *)identifier ofTypes:(NSArray *)dataTypes modifiedSince:(NSNumber *)modificationTimeInSecondsSinceEpoch completion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; @end extern void FWFWKWebsiteDataStoreHostApiSetup( id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFUIViewHostApi. NSObject *FWFUIViewHostApiGetCodec(void); /// Mirror of UIView. /// /// See https://developer.apple.com/documentation/uikit/uiview?language=objc. @protocol FWFUIViewHostApi - (void)setBackgroundColorForViewWithIdentifier:(NSNumber *)identifier toValue:(nullable NSNumber *)value error:(FlutterError *_Nullable *_Nonnull)error; - (void)setOpaqueForViewWithIdentifier:(NSNumber *)identifier isOpaque:(NSNumber *)opaque error:(FlutterError *_Nullable *_Nonnull)error; @end extern void FWFUIViewHostApiSetup(id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFUIScrollViewHostApi. NSObject *FWFUIScrollViewHostApiGetCodec(void); /// Mirror of UIScrollView. /// /// See https://developer.apple.com/documentation/uikit/uiscrollview?language=objc. @protocol FWFUIScrollViewHostApi - (void)createFromWebViewWithIdentifier:(NSNumber *)identifier webViewIdentifier:(NSNumber *)webViewIdentifier error:(FlutterError *_Nullable *_Nonnull)error; /// @return `nil` only when `error != nil`. - (nullable NSArray *) contentOffsetForScrollViewWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; - (void)scrollByForScrollViewWithIdentifier:(NSNumber *)identifier x:(NSNumber *)x y:(NSNumber *)y error:(FlutterError *_Nullable *_Nonnull)error; - (void)setContentOffsetForScrollViewWithIdentifier:(NSNumber *)identifier toX:(NSNumber *)x y:(NSNumber *)y error:(FlutterError *_Nullable *_Nonnull)error; @end extern void FWFUIScrollViewHostApiSetup(id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFWKWebViewConfigurationHostApi. NSObject *FWFWKWebViewConfigurationHostApiGetCodec(void); /// Mirror of WKWebViewConfiguration. /// /// See https://developer.apple.com/documentation/webkit/wkwebviewconfiguration?language=objc. @protocol FWFWKWebViewConfigurationHostApi - (void)createWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; - (void)createFromWebViewWithIdentifier:(NSNumber *)identifier webViewIdentifier:(NSNumber *)webViewIdentifier error:(FlutterError *_Nullable *_Nonnull)error; - (void)setAllowsInlineMediaPlaybackForConfigurationWithIdentifier:(NSNumber *)identifier isAllowed:(NSNumber *)allow error: (FlutterError *_Nullable *_Nonnull) error; - (void) setMediaTypesRequiresUserActionForConfigurationWithIdentifier:(NSNumber *)identifier forTypes: (NSArray< FWFWKAudiovisualMediaTypeEnumData *> *)types error: (FlutterError *_Nullable *_Nonnull) error; @end extern void FWFWKWebViewConfigurationHostApiSetup( id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFWKWebViewConfigurationFlutterApi. NSObject *FWFWKWebViewConfigurationFlutterApiGetCodec(void); /// Handles callbacks from an WKWebViewConfiguration instance. /// /// See https://developer.apple.com/documentation/webkit/wkwebviewconfiguration?language=objc. @interface FWFWKWebViewConfigurationFlutterApi : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; - (void)createWithIdentifier:(NSNumber *)identifier completion:(void (^)(NSError *_Nullable))completion; @end /// The codec used by FWFWKUserContentControllerHostApi. NSObject *FWFWKUserContentControllerHostApiGetCodec(void); /// Mirror of WKUserContentController. /// /// See https://developer.apple.com/documentation/webkit/wkusercontentcontroller?language=objc. @protocol FWFWKUserContentControllerHostApi - (void)createFromWebViewConfigurationWithIdentifier:(NSNumber *)identifier configurationIdentifier:(NSNumber *)configurationIdentifier error:(FlutterError *_Nullable *_Nonnull)error; - (void)addScriptMessageHandlerForControllerWithIdentifier:(NSNumber *)identifier handlerIdentifier:(NSNumber *)handlerIdentifier ofName:(NSString *)name error:(FlutterError *_Nullable *_Nonnull)error; - (void)removeScriptMessageHandlerForControllerWithIdentifier:(NSNumber *)identifier name:(NSString *)name error:(FlutterError *_Nullable *_Nonnull) error; - (void)removeAllScriptMessageHandlersForControllerWithIdentifier:(NSNumber *)identifier error: (FlutterError *_Nullable *_Nonnull) error; - (void)addUserScriptForControllerWithIdentifier:(NSNumber *)identifier userScript:(FWFWKUserScriptData *)userScript error:(FlutterError *_Nullable *_Nonnull)error; - (void)removeAllUserScriptsForControllerWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; @end extern void FWFWKUserContentControllerHostApiSetup( id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFWKPreferencesHostApi. NSObject *FWFWKPreferencesHostApiGetCodec(void); /// Mirror of WKUserPreferences. /// /// See https://developer.apple.com/documentation/webkit/wkpreferences?language=objc. @protocol FWFWKPreferencesHostApi - (void)createFromWebViewConfigurationWithIdentifier:(NSNumber *)identifier configurationIdentifier:(NSNumber *)configurationIdentifier error:(FlutterError *_Nullable *_Nonnull)error; - (void)setJavaScriptEnabledForPreferencesWithIdentifier:(NSNumber *)identifier isEnabled:(NSNumber *)enabled error:(FlutterError *_Nullable *_Nonnull)error; @end extern void FWFWKPreferencesHostApiSetup(id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFWKScriptMessageHandlerHostApi. NSObject *FWFWKScriptMessageHandlerHostApiGetCodec(void); /// Mirror of WKScriptMessageHandler. /// /// See https://developer.apple.com/documentation/webkit/wkscriptmessagehandler?language=objc. @protocol FWFWKScriptMessageHandlerHostApi - (void)createWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; @end extern void FWFWKScriptMessageHandlerHostApiSetup( id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFWKScriptMessageHandlerFlutterApi. NSObject *FWFWKScriptMessageHandlerFlutterApiGetCodec(void); /// Handles callbacks from an WKScriptMessageHandler instance. /// /// See https://developer.apple.com/documentation/webkit/wkscriptmessagehandler?language=objc. @interface FWFWKScriptMessageHandlerFlutterApi : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; - (void)didReceiveScriptMessageForHandlerWithIdentifier:(NSNumber *)identifier userContentControllerIdentifier:(NSNumber *)userContentControllerIdentifier message:(FWFWKScriptMessageData *)message completion:(void (^)(NSError *_Nullable))completion; @end /// The codec used by FWFWKNavigationDelegateHostApi. NSObject *FWFWKNavigationDelegateHostApiGetCodec(void); /// Mirror of WKNavigationDelegate. /// /// See https://developer.apple.com/documentation/webkit/wknavigationdelegate?language=objc. @protocol FWFWKNavigationDelegateHostApi - (void)createWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; @end extern void FWFWKNavigationDelegateHostApiSetup( id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFWKNavigationDelegateFlutterApi. NSObject *FWFWKNavigationDelegateFlutterApiGetCodec(void); /// Handles callbacks from an WKNavigationDelegate instance. /// /// See https://developer.apple.com/documentation/webkit/wknavigationdelegate?language=objc. @interface FWFWKNavigationDelegateFlutterApi : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; - (void)didFinishNavigationForDelegateWithIdentifier:(NSNumber *)identifier webViewIdentifier:(NSNumber *)webViewIdentifier URL:(nullable NSString *)url completion:(void (^)(NSError *_Nullable))completion; - (void)didStartProvisionalNavigationForDelegateWithIdentifier:(NSNumber *)identifier webViewIdentifier:(NSNumber *)webViewIdentifier URL:(nullable NSString *)url completion: (void (^)(NSError *_Nullable))completion; - (void) decidePolicyForNavigationActionForDelegateWithIdentifier:(NSNumber *)identifier webViewIdentifier:(NSNumber *)webViewIdentifier navigationAction: (FWFWKNavigationActionData *)navigationAction completion: (void (^)(FWFWKNavigationActionPolicyEnumData *_Nullable, NSError *_Nullable))completion; - (void)didFailNavigationForDelegateWithIdentifier:(NSNumber *)identifier webViewIdentifier:(NSNumber *)webViewIdentifier error:(FWFNSErrorData *)error completion:(void (^)(NSError *_Nullable))completion; - (void)didFailProvisionalNavigationForDelegateWithIdentifier:(NSNumber *)identifier webViewIdentifier:(NSNumber *)webViewIdentifier error:(FWFNSErrorData *)error completion: (void (^)(NSError *_Nullable))completion; - (void)webViewWebContentProcessDidTerminateForDelegateWithIdentifier:(NSNumber *)identifier webViewIdentifier:(NSNumber *)webViewIdentifier completion:(void (^)(NSError *_Nullable)) completion; @end /// The codec used by FWFNSObjectHostApi. NSObject *FWFNSObjectHostApiGetCodec(void); /// Mirror of NSObject. /// /// See https://developer.apple.com/documentation/objectivec/nsobject. @protocol FWFNSObjectHostApi - (void)disposeObjectWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; - (void)addObserverForObjectWithIdentifier:(NSNumber *)identifier observerIdentifier:(NSNumber *)observerIdentifier keyPath:(NSString *)keyPath options: (NSArray *)options error:(FlutterError *_Nullable *_Nonnull)error; - (void)removeObserverForObjectWithIdentifier:(NSNumber *)identifier observerIdentifier:(NSNumber *)observerIdentifier keyPath:(NSString *)keyPath error:(FlutterError *_Nullable *_Nonnull)error; @end extern void FWFNSObjectHostApiSetup(id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFNSObjectFlutterApi. NSObject *FWFNSObjectFlutterApiGetCodec(void); /// Handles callbacks from an NSObject instance. /// /// See https://developer.apple.com/documentation/objectivec/nsobject. @interface FWFNSObjectFlutterApi : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; - (void)observeValueForObjectWithIdentifier:(NSNumber *)identifier keyPath:(NSString *)keyPath objectIdentifier:(NSNumber *)objectIdentifier changeKeys:(NSArray *)changeKeys changeValues:(NSArray *)changeValues completion:(void (^)(NSError *_Nullable))completion; - (void)disposeObjectWithIdentifier:(NSNumber *)identifier completion:(void (^)(NSError *_Nullable))completion; @end /// The codec used by FWFWKWebViewHostApi. NSObject *FWFWKWebViewHostApiGetCodec(void); /// Mirror of WKWebView. /// /// See https://developer.apple.com/documentation/webkit/wkwebview?language=objc. @protocol FWFWKWebViewHostApi - (void)createWithIdentifier:(NSNumber *)identifier configurationIdentifier:(NSNumber *)configurationIdentifier error:(FlutterError *_Nullable *_Nonnull)error; - (void)setUIDelegateForWebViewWithIdentifier:(NSNumber *)identifier delegateIdentifier:(nullable NSNumber *)uiDelegateIdentifier error:(FlutterError *_Nullable *_Nonnull)error; - (void)setNavigationDelegateForWebViewWithIdentifier:(NSNumber *)identifier delegateIdentifier: (nullable NSNumber *)navigationDelegateIdentifier error:(FlutterError *_Nullable *_Nonnull)error; - (nullable NSString *)URLForWebViewWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; /// @return `nil` only when `error != nil`. - (nullable NSNumber *)estimatedProgressForWebViewWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull) error; - (void)loadRequestForWebViewWithIdentifier:(NSNumber *)identifier request:(FWFNSUrlRequestData *)request error:(FlutterError *_Nullable *_Nonnull)error; - (void)loadHTMLForWebViewWithIdentifier:(NSNumber *)identifier HTMLString:(NSString *)string baseURL:(nullable NSString *)baseUrl error:(FlutterError *_Nullable *_Nonnull)error; - (void)loadFileForWebViewWithIdentifier:(NSNumber *)identifier fileURL:(NSString *)url readAccessURL:(NSString *)readAccessUrl error:(FlutterError *_Nullable *_Nonnull)error; - (void)loadAssetForWebViewWithIdentifier:(NSNumber *)identifier assetKey:(NSString *)key error:(FlutterError *_Nullable *_Nonnull)error; /// @return `nil` only when `error != nil`. - (nullable NSNumber *)canGoBackForWebViewWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; /// @return `nil` only when `error != nil`. - (nullable NSNumber *)canGoForwardForWebViewWithIdentifier:(NSNumber *)identifier error: (FlutterError *_Nullable *_Nonnull)error; - (void)goBackForWebViewWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; - (void)goForwardForWebViewWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; - (void)reloadWebViewWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; - (nullable NSString *)titleForWebViewWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; - (void)setAllowsBackForwardForWebViewWithIdentifier:(NSNumber *)identifier isAllowed:(NSNumber *)allow error:(FlutterError *_Nullable *_Nonnull)error; - (void)setUserAgentForWebViewWithIdentifier:(NSNumber *)identifier userAgent:(nullable NSString *)userAgent error:(FlutterError *_Nullable *_Nonnull)error; - (void)evaluateJavaScriptForWebViewWithIdentifier:(NSNumber *)identifier javaScriptString:(NSString *)javaScriptString completion:(void (^)(id _Nullable, FlutterError *_Nullable))completion; @end extern void FWFWKWebViewHostApiSetup(id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFWKUIDelegateHostApi. NSObject *FWFWKUIDelegateHostApiGetCodec(void); /// Mirror of WKUIDelegate. /// /// See https://developer.apple.com/documentation/webkit/wkuidelegate?language=objc. @protocol FWFWKUIDelegateHostApi - (void)createWithIdentifier:(NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error; @end extern void FWFWKUIDelegateHostApiSetup(id binaryMessenger, NSObject *_Nullable api); /// The codec used by FWFWKUIDelegateFlutterApi. NSObject *FWFWKUIDelegateFlutterApiGetCodec(void); /// Handles callbacks from an WKUIDelegate instance. /// /// See https://developer.apple.com/documentation/webkit/wkuidelegate?language=objc. @interface FWFWKUIDelegateFlutterApi : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger; - (void)onCreateWebViewForDelegateWithIdentifier:(NSNumber *)identifier webViewIdentifier:(NSNumber *)webViewIdentifier configurationIdentifier:(NSNumber *)configurationIdentifier navigationAction:(FWFWKNavigationActionData *)navigationAction completion:(void (^)(NSError *_Nullable))completion; @end /// The codec used by FWFWKHttpCookieStoreHostApi. NSObject *FWFWKHttpCookieStoreHostApiGetCodec(void); /// Mirror of WKHttpCookieStore. /// /// See https://developer.apple.com/documentation/webkit/wkhttpcookiestore?language=objc. @protocol FWFWKHttpCookieStoreHostApi - (void)createFromWebsiteDataStoreWithIdentifier:(NSNumber *)identifier dataStoreIdentifier:(NSNumber *)websiteDataStoreIdentifier error:(FlutterError *_Nullable *_Nonnull)error; - (void)setCookieForStoreWithIdentifier:(NSNumber *)identifier cookie:(FWFNSHttpCookieData *)cookie completion:(void (^)(FlutterError *_Nullable))completion; @end extern void FWFWKHttpCookieStoreHostApiSetup(id binaryMessenger, NSObject *_Nullable api); NS_ASSUME_NONNULL_END ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m ================================================ // Copyright 2013 The Flutter 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.13), do not edit directly. // See also: https://pub.dev/packages/pigeon #import "FWFGeneratedWebKitApis.h" #import #if !__has_feature(objc_arc) #error File requires ARC to be enabled. #endif static NSArray *wrapResult(id result, FlutterError *error) { if (error) { return @[ error.code ?: [NSNull null], error.message ?: [NSNull null], error.details ?: [NSNull null] ]; } return @[ result ?: [NSNull null] ]; } static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { id result = array[key]; return (result == [NSNull null]) ? nil : result; } @interface FWFNSKeyValueObservingOptionsEnumData () + (FWFNSKeyValueObservingOptionsEnumData *)fromList:(NSArray *)list; + (nullable FWFNSKeyValueObservingOptionsEnumData *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end @interface FWFNSKeyValueChangeKeyEnumData () + (FWFNSKeyValueChangeKeyEnumData *)fromList:(NSArray *)list; + (nullable FWFNSKeyValueChangeKeyEnumData *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end @interface FWFWKUserScriptInjectionTimeEnumData () + (FWFWKUserScriptInjectionTimeEnumData *)fromList:(NSArray *)list; + (nullable FWFWKUserScriptInjectionTimeEnumData *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end @interface FWFWKAudiovisualMediaTypeEnumData () + (FWFWKAudiovisualMediaTypeEnumData *)fromList:(NSArray *)list; + (nullable FWFWKAudiovisualMediaTypeEnumData *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end @interface FWFWKWebsiteDataTypeEnumData () + (FWFWKWebsiteDataTypeEnumData *)fromList:(NSArray *)list; + (nullable FWFWKWebsiteDataTypeEnumData *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end @interface FWFWKNavigationActionPolicyEnumData () + (FWFWKNavigationActionPolicyEnumData *)fromList:(NSArray *)list; + (nullable FWFWKNavigationActionPolicyEnumData *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end @interface FWFNSHttpCookiePropertyKeyEnumData () + (FWFNSHttpCookiePropertyKeyEnumData *)fromList:(NSArray *)list; + (nullable FWFNSHttpCookiePropertyKeyEnumData *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end @interface FWFNSUrlRequestData () + (FWFNSUrlRequestData *)fromList:(NSArray *)list; + (nullable FWFNSUrlRequestData *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end @interface FWFWKUserScriptData () + (FWFWKUserScriptData *)fromList:(NSArray *)list; + (nullable FWFWKUserScriptData *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end @interface FWFWKNavigationActionData () + (FWFWKNavigationActionData *)fromList:(NSArray *)list; + (nullable FWFWKNavigationActionData *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end @interface FWFWKFrameInfoData () + (FWFWKFrameInfoData *)fromList:(NSArray *)list; + (nullable FWFWKFrameInfoData *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end @interface FWFNSErrorData () + (FWFNSErrorData *)fromList:(NSArray *)list; + (nullable FWFNSErrorData *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end @interface FWFWKScriptMessageData () + (FWFWKScriptMessageData *)fromList:(NSArray *)list; + (nullable FWFWKScriptMessageData *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end @interface FWFNSHttpCookieData () + (FWFNSHttpCookieData *)fromList:(NSArray *)list; + (nullable FWFNSHttpCookieData *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end @implementation FWFNSKeyValueObservingOptionsEnumData + (instancetype)makeWithValue:(FWFNSKeyValueObservingOptionsEnum)value { FWFNSKeyValueObservingOptionsEnumData *pigeonResult = [[FWFNSKeyValueObservingOptionsEnumData alloc] init]; pigeonResult.value = value; return pigeonResult; } + (FWFNSKeyValueObservingOptionsEnumData *)fromList:(NSArray *)list { FWFNSKeyValueObservingOptionsEnumData *pigeonResult = [[FWFNSKeyValueObservingOptionsEnumData alloc] init]; pigeonResult.value = [GetNullableObjectAtIndex(list, 0) integerValue]; return pigeonResult; } + (nullable FWFNSKeyValueObservingOptionsEnumData *)nullableFromList:(NSArray *)list { return (list) ? [FWFNSKeyValueObservingOptionsEnumData fromList:list] : nil; } - (NSArray *)toList { return @[ @(self.value), ]; } @end @implementation FWFNSKeyValueChangeKeyEnumData + (instancetype)makeWithValue:(FWFNSKeyValueChangeKeyEnum)value { FWFNSKeyValueChangeKeyEnumData *pigeonResult = [[FWFNSKeyValueChangeKeyEnumData alloc] init]; pigeonResult.value = value; return pigeonResult; } + (FWFNSKeyValueChangeKeyEnumData *)fromList:(NSArray *)list { FWFNSKeyValueChangeKeyEnumData *pigeonResult = [[FWFNSKeyValueChangeKeyEnumData alloc] init]; pigeonResult.value = [GetNullableObjectAtIndex(list, 0) integerValue]; return pigeonResult; } + (nullable FWFNSKeyValueChangeKeyEnumData *)nullableFromList:(NSArray *)list { return (list) ? [FWFNSKeyValueChangeKeyEnumData fromList:list] : nil; } - (NSArray *)toList { return @[ @(self.value), ]; } @end @implementation FWFWKUserScriptInjectionTimeEnumData + (instancetype)makeWithValue:(FWFWKUserScriptInjectionTimeEnum)value { FWFWKUserScriptInjectionTimeEnumData *pigeonResult = [[FWFWKUserScriptInjectionTimeEnumData alloc] init]; pigeonResult.value = value; return pigeonResult; } + (FWFWKUserScriptInjectionTimeEnumData *)fromList:(NSArray *)list { FWFWKUserScriptInjectionTimeEnumData *pigeonResult = [[FWFWKUserScriptInjectionTimeEnumData alloc] init]; pigeonResult.value = [GetNullableObjectAtIndex(list, 0) integerValue]; return pigeonResult; } + (nullable FWFWKUserScriptInjectionTimeEnumData *)nullableFromList:(NSArray *)list { return (list) ? [FWFWKUserScriptInjectionTimeEnumData fromList:list] : nil; } - (NSArray *)toList { return @[ @(self.value), ]; } @end @implementation FWFWKAudiovisualMediaTypeEnumData + (instancetype)makeWithValue:(FWFWKAudiovisualMediaTypeEnum)value { FWFWKAudiovisualMediaTypeEnumData *pigeonResult = [[FWFWKAudiovisualMediaTypeEnumData alloc] init]; pigeonResult.value = value; return pigeonResult; } + (FWFWKAudiovisualMediaTypeEnumData *)fromList:(NSArray *)list { FWFWKAudiovisualMediaTypeEnumData *pigeonResult = [[FWFWKAudiovisualMediaTypeEnumData alloc] init]; pigeonResult.value = [GetNullableObjectAtIndex(list, 0) integerValue]; return pigeonResult; } + (nullable FWFWKAudiovisualMediaTypeEnumData *)nullableFromList:(NSArray *)list { return (list) ? [FWFWKAudiovisualMediaTypeEnumData fromList:list] : nil; } - (NSArray *)toList { return @[ @(self.value), ]; } @end @implementation FWFWKWebsiteDataTypeEnumData + (instancetype)makeWithValue:(FWFWKWebsiteDataTypeEnum)value { FWFWKWebsiteDataTypeEnumData *pigeonResult = [[FWFWKWebsiteDataTypeEnumData alloc] init]; pigeonResult.value = value; return pigeonResult; } + (FWFWKWebsiteDataTypeEnumData *)fromList:(NSArray *)list { FWFWKWebsiteDataTypeEnumData *pigeonResult = [[FWFWKWebsiteDataTypeEnumData alloc] init]; pigeonResult.value = [GetNullableObjectAtIndex(list, 0) integerValue]; return pigeonResult; } + (nullable FWFWKWebsiteDataTypeEnumData *)nullableFromList:(NSArray *)list { return (list) ? [FWFWKWebsiteDataTypeEnumData fromList:list] : nil; } - (NSArray *)toList { return @[ @(self.value), ]; } @end @implementation FWFWKNavigationActionPolicyEnumData + (instancetype)makeWithValue:(FWFWKNavigationActionPolicyEnum)value { FWFWKNavigationActionPolicyEnumData *pigeonResult = [[FWFWKNavigationActionPolicyEnumData alloc] init]; pigeonResult.value = value; return pigeonResult; } + (FWFWKNavigationActionPolicyEnumData *)fromList:(NSArray *)list { FWFWKNavigationActionPolicyEnumData *pigeonResult = [[FWFWKNavigationActionPolicyEnumData alloc] init]; pigeonResult.value = [GetNullableObjectAtIndex(list, 0) integerValue]; return pigeonResult; } + (nullable FWFWKNavigationActionPolicyEnumData *)nullableFromList:(NSArray *)list { return (list) ? [FWFWKNavigationActionPolicyEnumData fromList:list] : nil; } - (NSArray *)toList { return @[ @(self.value), ]; } @end @implementation FWFNSHttpCookiePropertyKeyEnumData + (instancetype)makeWithValue:(FWFNSHttpCookiePropertyKeyEnum)value { FWFNSHttpCookiePropertyKeyEnumData *pigeonResult = [[FWFNSHttpCookiePropertyKeyEnumData alloc] init]; pigeonResult.value = value; return pigeonResult; } + (FWFNSHttpCookiePropertyKeyEnumData *)fromList:(NSArray *)list { FWFNSHttpCookiePropertyKeyEnumData *pigeonResult = [[FWFNSHttpCookiePropertyKeyEnumData alloc] init]; pigeonResult.value = [GetNullableObjectAtIndex(list, 0) integerValue]; return pigeonResult; } + (nullable FWFNSHttpCookiePropertyKeyEnumData *)nullableFromList:(NSArray *)list { return (list) ? [FWFNSHttpCookiePropertyKeyEnumData fromList:list] : nil; } - (NSArray *)toList { return @[ @(self.value), ]; } @end @implementation FWFNSUrlRequestData + (instancetype)makeWithUrl:(NSString *)url httpMethod:(nullable NSString *)httpMethod httpBody:(nullable FlutterStandardTypedData *)httpBody allHttpHeaderFields:(NSDictionary *)allHttpHeaderFields { FWFNSUrlRequestData *pigeonResult = [[FWFNSUrlRequestData alloc] init]; pigeonResult.url = url; pigeonResult.httpMethod = httpMethod; pigeonResult.httpBody = httpBody; pigeonResult.allHttpHeaderFields = allHttpHeaderFields; return pigeonResult; } + (FWFNSUrlRequestData *)fromList:(NSArray *)list { FWFNSUrlRequestData *pigeonResult = [[FWFNSUrlRequestData alloc] init]; pigeonResult.url = GetNullableObjectAtIndex(list, 0); NSAssert(pigeonResult.url != nil, @""); pigeonResult.httpMethod = GetNullableObjectAtIndex(list, 1); pigeonResult.httpBody = GetNullableObjectAtIndex(list, 2); pigeonResult.allHttpHeaderFields = GetNullableObjectAtIndex(list, 3); NSAssert(pigeonResult.allHttpHeaderFields != nil, @""); return pigeonResult; } + (nullable FWFNSUrlRequestData *)nullableFromList:(NSArray *)list { return (list) ? [FWFNSUrlRequestData fromList:list] : nil; } - (NSArray *)toList { return @[ (self.url ?: [NSNull null]), (self.httpMethod ?: [NSNull null]), (self.httpBody ?: [NSNull null]), (self.allHttpHeaderFields ?: [NSNull null]), ]; } @end @implementation FWFWKUserScriptData + (instancetype)makeWithSource:(NSString *)source injectionTime:(nullable FWFWKUserScriptInjectionTimeEnumData *)injectionTime isMainFrameOnly:(NSNumber *)isMainFrameOnly { FWFWKUserScriptData *pigeonResult = [[FWFWKUserScriptData alloc] init]; pigeonResult.source = source; pigeonResult.injectionTime = injectionTime; pigeonResult.isMainFrameOnly = isMainFrameOnly; return pigeonResult; } + (FWFWKUserScriptData *)fromList:(NSArray *)list { FWFWKUserScriptData *pigeonResult = [[FWFWKUserScriptData alloc] init]; pigeonResult.source = GetNullableObjectAtIndex(list, 0); NSAssert(pigeonResult.source != nil, @""); pigeonResult.injectionTime = [FWFWKUserScriptInjectionTimeEnumData nullableFromList:(GetNullableObjectAtIndex(list, 1))]; pigeonResult.isMainFrameOnly = GetNullableObjectAtIndex(list, 2); NSAssert(pigeonResult.isMainFrameOnly != nil, @""); return pigeonResult; } + (nullable FWFWKUserScriptData *)nullableFromList:(NSArray *)list { return (list) ? [FWFWKUserScriptData fromList:list] : nil; } - (NSArray *)toList { return @[ (self.source ?: [NSNull null]), (self.injectionTime ? [self.injectionTime toList] : [NSNull null]), (self.isMainFrameOnly ?: [NSNull null]), ]; } @end @implementation FWFWKNavigationActionData + (instancetype)makeWithRequest:(FWFNSUrlRequestData *)request targetFrame:(FWFWKFrameInfoData *)targetFrame navigationType:(FWFWKNavigationType)navigationType { FWFWKNavigationActionData *pigeonResult = [[FWFWKNavigationActionData alloc] init]; pigeonResult.request = request; pigeonResult.targetFrame = targetFrame; pigeonResult.navigationType = navigationType; return pigeonResult; } + (FWFWKNavigationActionData *)fromList:(NSArray *)list { FWFWKNavigationActionData *pigeonResult = [[FWFWKNavigationActionData alloc] init]; pigeonResult.request = [FWFNSUrlRequestData nullableFromList:(GetNullableObjectAtIndex(list, 0))]; NSAssert(pigeonResult.request != nil, @""); pigeonResult.targetFrame = [FWFWKFrameInfoData nullableFromList:(GetNullableObjectAtIndex(list, 1))]; NSAssert(pigeonResult.targetFrame != nil, @""); pigeonResult.navigationType = [GetNullableObjectAtIndex(list, 2) integerValue]; return pigeonResult; } + (nullable FWFWKNavigationActionData *)nullableFromList:(NSArray *)list { return (list) ? [FWFWKNavigationActionData fromList:list] : nil; } - (NSArray *)toList { return @[ (self.request ? [self.request toList] : [NSNull null]), (self.targetFrame ? [self.targetFrame toList] : [NSNull null]), @(self.navigationType), ]; } @end @implementation FWFWKFrameInfoData + (instancetype)makeWithIsMainFrame:(NSNumber *)isMainFrame { FWFWKFrameInfoData *pigeonResult = [[FWFWKFrameInfoData alloc] init]; pigeonResult.isMainFrame = isMainFrame; return pigeonResult; } + (FWFWKFrameInfoData *)fromList:(NSArray *)list { FWFWKFrameInfoData *pigeonResult = [[FWFWKFrameInfoData alloc] init]; pigeonResult.isMainFrame = GetNullableObjectAtIndex(list, 0); NSAssert(pigeonResult.isMainFrame != nil, @""); return pigeonResult; } + (nullable FWFWKFrameInfoData *)nullableFromList:(NSArray *)list { return (list) ? [FWFWKFrameInfoData fromList:list] : nil; } - (NSArray *)toList { return @[ (self.isMainFrame ?: [NSNull null]), ]; } @end @implementation FWFNSErrorData + (instancetype)makeWithCode:(NSNumber *)code domain:(NSString *)domain localizedDescription:(NSString *)localizedDescription { FWFNSErrorData *pigeonResult = [[FWFNSErrorData alloc] init]; pigeonResult.code = code; pigeonResult.domain = domain; pigeonResult.localizedDescription = localizedDescription; return pigeonResult; } + (FWFNSErrorData *)fromList:(NSArray *)list { FWFNSErrorData *pigeonResult = [[FWFNSErrorData alloc] init]; pigeonResult.code = GetNullableObjectAtIndex(list, 0); NSAssert(pigeonResult.code != nil, @""); pigeonResult.domain = GetNullableObjectAtIndex(list, 1); NSAssert(pigeonResult.domain != nil, @""); pigeonResult.localizedDescription = GetNullableObjectAtIndex(list, 2); NSAssert(pigeonResult.localizedDescription != nil, @""); return pigeonResult; } + (nullable FWFNSErrorData *)nullableFromList:(NSArray *)list { return (list) ? [FWFNSErrorData fromList:list] : nil; } - (NSArray *)toList { return @[ (self.code ?: [NSNull null]), (self.domain ?: [NSNull null]), (self.localizedDescription ?: [NSNull null]), ]; } @end @implementation FWFWKScriptMessageData + (instancetype)makeWithName:(NSString *)name body:(id)body { FWFWKScriptMessageData *pigeonResult = [[FWFWKScriptMessageData alloc] init]; pigeonResult.name = name; pigeonResult.body = body; return pigeonResult; } + (FWFWKScriptMessageData *)fromList:(NSArray *)list { FWFWKScriptMessageData *pigeonResult = [[FWFWKScriptMessageData alloc] init]; pigeonResult.name = GetNullableObjectAtIndex(list, 0); NSAssert(pigeonResult.name != nil, @""); pigeonResult.body = GetNullableObjectAtIndex(list, 1); return pigeonResult; } + (nullable FWFWKScriptMessageData *)nullableFromList:(NSArray *)list { return (list) ? [FWFWKScriptMessageData fromList:list] : nil; } - (NSArray *)toList { return @[ (self.name ?: [NSNull null]), (self.body ?: [NSNull null]), ]; } @end @implementation FWFNSHttpCookieData + (instancetype)makeWithPropertyKeys:(NSArray *)propertyKeys propertyValues:(NSArray *)propertyValues { FWFNSHttpCookieData *pigeonResult = [[FWFNSHttpCookieData alloc] init]; pigeonResult.propertyKeys = propertyKeys; pigeonResult.propertyValues = propertyValues; return pigeonResult; } + (FWFNSHttpCookieData *)fromList:(NSArray *)list { FWFNSHttpCookieData *pigeonResult = [[FWFNSHttpCookieData alloc] init]; pigeonResult.propertyKeys = GetNullableObjectAtIndex(list, 0); NSAssert(pigeonResult.propertyKeys != nil, @""); pigeonResult.propertyValues = GetNullableObjectAtIndex(list, 1); NSAssert(pigeonResult.propertyValues != nil, @""); return pigeonResult; } + (nullable FWFNSHttpCookieData *)nullableFromList:(NSArray *)list { return (list) ? [FWFNSHttpCookieData fromList:list] : nil; } - (NSArray *)toList { return @[ (self.propertyKeys ?: [NSNull null]), (self.propertyValues ?: [NSNull null]), ]; } @end @interface FWFWKWebsiteDataStoreHostApiCodecReader : FlutterStandardReader @end @implementation FWFWKWebsiteDataStoreHostApiCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { case 128: return [FWFWKWebsiteDataTypeEnumData fromList:[self readValue]]; default: return [super readValueOfType:type]; } } @end @interface FWFWKWebsiteDataStoreHostApiCodecWriter : FlutterStandardWriter @end @implementation FWFWKWebsiteDataStoreHostApiCodecWriter - (void)writeValue:(id)value { if ([value isKindOfClass:[FWFWKWebsiteDataTypeEnumData class]]) { [self writeByte:128]; [self writeValue:[value toList]]; } else { [super writeValue:value]; } } @end @interface FWFWKWebsiteDataStoreHostApiCodecReaderWriter : FlutterStandardReaderWriter @end @implementation FWFWKWebsiteDataStoreHostApiCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { return [[FWFWKWebsiteDataStoreHostApiCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { return [[FWFWKWebsiteDataStoreHostApiCodecReader alloc] initWithData:data]; } @end NSObject *FWFWKWebsiteDataStoreHostApiGetCodec() { static FlutterStandardMessageCodec *sSharedObject = nil; static dispatch_once_t sPred = 0; dispatch_once(&sPred, ^{ FWFWKWebsiteDataStoreHostApiCodecReaderWriter *readerWriter = [[FWFWKWebsiteDataStoreHostApiCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } void FWFWKWebsiteDataStoreHostApiSetup(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName: @"dev.flutter.pigeon.WKWebsiteDataStoreHostApi.createFromWebViewConfiguration" binaryMessenger:binaryMessenger codec:FWFWKWebsiteDataStoreHostApiGetCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(createFromWebViewConfigurationWithIdentifier: configurationIdentifier:error:)], @"FWFWKWebsiteDataStoreHostApi api (%@) doesn't respond to " @"@selector(createFromWebViewConfigurationWithIdentifier:configurationIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_configurationIdentifier = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api createFromWebViewConfigurationWithIdentifier:arg_identifier configurationIdentifier:arg_configurationIdentifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebsiteDataStoreHostApi.createDefaultDataStore" binaryMessenger:binaryMessenger codec:FWFWKWebsiteDataStoreHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(createDefaultDataStoreWithIdentifier:error:)], @"FWFWKWebsiteDataStoreHostApi api (%@) doesn't respond to " @"@selector(createDefaultDataStoreWithIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api createDefaultDataStoreWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebsiteDataStoreHostApi.removeDataOfTypes" binaryMessenger:binaryMessenger codec:FWFWKWebsiteDataStoreHostApiGetCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector (removeDataFromDataStoreWithIdentifier:ofTypes:modifiedSince:completion:)], @"FWFWKWebsiteDataStoreHostApi api (%@) doesn't respond to " @"@selector(removeDataFromDataStoreWithIdentifier:ofTypes:modifiedSince:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSArray *arg_dataTypes = GetNullableObjectAtIndex(args, 1); NSNumber *arg_modificationTimeInSecondsSinceEpoch = GetNullableObjectAtIndex(args, 2); [api removeDataFromDataStoreWithIdentifier:arg_identifier ofTypes:arg_dataTypes modifiedSince:arg_modificationTimeInSecondsSinceEpoch completion:^(NSNumber *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; }]; } else { [channel setMessageHandler:nil]; } } } NSObject *FWFUIViewHostApiGetCodec() { static FlutterStandardMessageCodec *sSharedObject = nil; sSharedObject = [FlutterStandardMessageCodec sharedInstance]; return sSharedObject; } void FWFUIViewHostApiSetup(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.UIViewHostApi.setBackgroundColor" binaryMessenger:binaryMessenger codec:FWFUIViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(setBackgroundColorForViewWithIdentifier: toValue:error:)], @"FWFUIViewHostApi api (%@) doesn't respond to " @"@selector(setBackgroundColorForViewWithIdentifier:toValue:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_value = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api setBackgroundColorForViewWithIdentifier:arg_identifier toValue:arg_value error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.UIViewHostApi.setOpaque" binaryMessenger:binaryMessenger codec:FWFUIViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(setOpaqueForViewWithIdentifier:isOpaque:error:)], @"FWFUIViewHostApi api (%@) doesn't respond to " @"@selector(setOpaqueForViewWithIdentifier:isOpaque:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_opaque = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api setOpaqueForViewWithIdentifier:arg_identifier isOpaque:arg_opaque error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } } NSObject *FWFUIScrollViewHostApiGetCodec() { static FlutterStandardMessageCodec *sSharedObject = nil; sSharedObject = [FlutterStandardMessageCodec sharedInstance]; return sSharedObject; } void FWFUIScrollViewHostApiSetup(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.UIScrollViewHostApi.createFromWebView" binaryMessenger:binaryMessenger codec:FWFUIScrollViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(createFromWebViewWithIdentifier: webViewIdentifier:error:)], @"FWFUIScrollViewHostApi api (%@) doesn't respond to " @"@selector(createFromWebViewWithIdentifier:webViewIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_webViewIdentifier = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api createFromWebViewWithIdentifier:arg_identifier webViewIdentifier:arg_webViewIdentifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.UIScrollViewHostApi.getContentOffset" binaryMessenger:binaryMessenger codec:FWFUIScrollViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(contentOffsetForScrollViewWithIdentifier:error:)], @"FWFUIScrollViewHostApi api (%@) doesn't respond to " @"@selector(contentOffsetForScrollViewWithIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSArray *output = [api contentOffsetForScrollViewWithIdentifier:arg_identifier error:&error]; callback(wrapResult(output, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.UIScrollViewHostApi.scrollBy" binaryMessenger:binaryMessenger codec:FWFUIScrollViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(scrollByForScrollViewWithIdentifier:x:y:error:)], @"FWFUIScrollViewHostApi api (%@) doesn't respond to " @"@selector(scrollByForScrollViewWithIdentifier:x:y:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_x = GetNullableObjectAtIndex(args, 1); NSNumber *arg_y = GetNullableObjectAtIndex(args, 2); FlutterError *error; [api scrollByForScrollViewWithIdentifier:arg_identifier x:arg_x y:arg_y error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.UIScrollViewHostApi.setContentOffset" binaryMessenger:binaryMessenger codec:FWFUIScrollViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (setContentOffsetForScrollViewWithIdentifier:toX:y:error:)], @"FWFUIScrollViewHostApi api (%@) doesn't respond to " @"@selector(setContentOffsetForScrollViewWithIdentifier:toX:y:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_x = GetNullableObjectAtIndex(args, 1); NSNumber *arg_y = GetNullableObjectAtIndex(args, 2); FlutterError *error; [api setContentOffsetForScrollViewWithIdentifier:arg_identifier toX:arg_x y:arg_y error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } } @interface FWFWKWebViewConfigurationHostApiCodecReader : FlutterStandardReader @end @implementation FWFWKWebViewConfigurationHostApiCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { case 128: return [FWFWKAudiovisualMediaTypeEnumData fromList:[self readValue]]; default: return [super readValueOfType:type]; } } @end @interface FWFWKWebViewConfigurationHostApiCodecWriter : FlutterStandardWriter @end @implementation FWFWKWebViewConfigurationHostApiCodecWriter - (void)writeValue:(id)value { if ([value isKindOfClass:[FWFWKAudiovisualMediaTypeEnumData class]]) { [self writeByte:128]; [self writeValue:[value toList]]; } else { [super writeValue:value]; } } @end @interface FWFWKWebViewConfigurationHostApiCodecReaderWriter : FlutterStandardReaderWriter @end @implementation FWFWKWebViewConfigurationHostApiCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { return [[FWFWKWebViewConfigurationHostApiCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { return [[FWFWKWebViewConfigurationHostApiCodecReader alloc] initWithData:data]; } @end NSObject *FWFWKWebViewConfigurationHostApiGetCodec() { static FlutterStandardMessageCodec *sSharedObject = nil; static dispatch_once_t sPred = 0; dispatch_once(&sPred, ^{ FWFWKWebViewConfigurationHostApiCodecReaderWriter *readerWriter = [[FWFWKWebViewConfigurationHostApiCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } void FWFWKWebViewConfigurationHostApiSetup(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewConfigurationHostApi.create" binaryMessenger:binaryMessenger codec:FWFWKWebViewConfigurationHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(createWithIdentifier:error:)], @"FWFWKWebViewConfigurationHostApi api (%@) doesn't respond to " @"@selector(createWithIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api createWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewConfigurationHostApi.createFromWebView" binaryMessenger:binaryMessenger codec:FWFWKWebViewConfigurationHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(createFromWebViewWithIdentifier: webViewIdentifier:error:)], @"FWFWKWebViewConfigurationHostApi api (%@) doesn't respond to " @"@selector(createFromWebViewWithIdentifier:webViewIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_webViewIdentifier = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api createFromWebViewWithIdentifier:arg_identifier webViewIdentifier:arg_webViewIdentifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName: @"dev.flutter.pigeon.WKWebViewConfigurationHostApi.setAllowsInlineMediaPlayback" binaryMessenger:binaryMessenger codec:FWFWKWebViewConfigurationHostApiGetCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector (setAllowsInlineMediaPlaybackForConfigurationWithIdentifier:isAllowed:error:)], @"FWFWKWebViewConfigurationHostApi api (%@) doesn't respond to " @"@selector(setAllowsInlineMediaPlaybackForConfigurationWithIdentifier:isAllowed:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_allow = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api setAllowsInlineMediaPlaybackForConfigurationWithIdentifier:arg_identifier isAllowed:arg_allow error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewConfigurationHostApi." @"setMediaTypesRequiringUserActionForPlayback" binaryMessenger:binaryMessenger codec:FWFWKWebViewConfigurationHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (setMediaTypesRequiresUserActionForConfigurationWithIdentifier: forTypes:error:)], @"FWFWKWebViewConfigurationHostApi api (%@) doesn't respond to " @"@selector(setMediaTypesRequiresUserActionForConfigurationWithIdentifier:forTypes:" @"error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSArray *arg_types = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api setMediaTypesRequiresUserActionForConfigurationWithIdentifier:arg_identifier forTypes:arg_types error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } } NSObject *FWFWKWebViewConfigurationFlutterApiGetCodec() { static FlutterStandardMessageCodec *sSharedObject = nil; sSharedObject = [FlutterStandardMessageCodec sharedInstance]; return sSharedObject; } @interface FWFWKWebViewConfigurationFlutterApi () @property(nonatomic, strong) NSObject *binaryMessenger; @end @implementation FWFWKWebViewConfigurationFlutterApi - (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger { self = [super init]; if (self) { _binaryMessenger = binaryMessenger; } return self; } - (void)createWithIdentifier:(NSNumber *)arg_identifier completion:(void (^)(NSError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:@"dev.flutter.pigeon.WKWebViewConfigurationFlutterApi.create" binaryMessenger:self.binaryMessenger codec:FWFWKWebViewConfigurationFlutterApiGetCodec()]; [channel sendMessage:@[ arg_identifier ?: [NSNull null] ] reply:^(id reply) { completion(nil); }]; } @end @interface FWFWKUserContentControllerHostApiCodecReader : FlutterStandardReader @end @implementation FWFWKUserContentControllerHostApiCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { case 128: return [FWFWKUserScriptData fromList:[self readValue]]; case 129: return [FWFWKUserScriptInjectionTimeEnumData fromList:[self readValue]]; default: return [super readValueOfType:type]; } } @end @interface FWFWKUserContentControllerHostApiCodecWriter : FlutterStandardWriter @end @implementation FWFWKUserContentControllerHostApiCodecWriter - (void)writeValue:(id)value { if ([value isKindOfClass:[FWFWKUserScriptData class]]) { [self writeByte:128]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKUserScriptInjectionTimeEnumData class]]) { [self writeByte:129]; [self writeValue:[value toList]]; } else { [super writeValue:value]; } } @end @interface FWFWKUserContentControllerHostApiCodecReaderWriter : FlutterStandardReaderWriter @end @implementation FWFWKUserContentControllerHostApiCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { return [[FWFWKUserContentControllerHostApiCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { return [[FWFWKUserContentControllerHostApiCodecReader alloc] initWithData:data]; } @end NSObject *FWFWKUserContentControllerHostApiGetCodec() { static FlutterStandardMessageCodec *sSharedObject = nil; static dispatch_once_t sPred = 0; dispatch_once(&sPred, ^{ FWFWKUserContentControllerHostApiCodecReaderWriter *readerWriter = [[FWFWKUserContentControllerHostApiCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } void FWFWKUserContentControllerHostApiSetup(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName: @"dev.flutter.pigeon.WKUserContentControllerHostApi.createFromWebViewConfiguration" binaryMessenger:binaryMessenger codec:FWFWKUserContentControllerHostApiGetCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(createFromWebViewConfigurationWithIdentifier: configurationIdentifier:error:)], @"FWFWKUserContentControllerHostApi api (%@) doesn't respond to " @"@selector(createFromWebViewConfigurationWithIdentifier:configurationIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_configurationIdentifier = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api createFromWebViewConfigurationWithIdentifier:arg_identifier configurationIdentifier:arg_configurationIdentifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKUserContentControllerHostApi.addScriptMessageHandler" binaryMessenger:binaryMessenger codec:FWFWKUserContentControllerHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (addScriptMessageHandlerForControllerWithIdentifier: handlerIdentifier:ofName:error:)], @"FWFWKUserContentControllerHostApi api (%@) doesn't respond to " @"@selector(addScriptMessageHandlerForControllerWithIdentifier:handlerIdentifier:" @"ofName:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_handlerIdentifier = GetNullableObjectAtIndex(args, 1); NSString *arg_name = GetNullableObjectAtIndex(args, 2); FlutterError *error; [api addScriptMessageHandlerForControllerWithIdentifier:arg_identifier handlerIdentifier:arg_handlerIdentifier ofName:arg_name error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName: @"dev.flutter.pigeon.WKUserContentControllerHostApi.removeScriptMessageHandler" binaryMessenger:binaryMessenger codec:FWFWKUserContentControllerHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (removeScriptMessageHandlerForControllerWithIdentifier:name:error:)], @"FWFWKUserContentControllerHostApi api (%@) doesn't respond to " @"@selector(removeScriptMessageHandlerForControllerWithIdentifier:name:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSString *arg_name = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api removeScriptMessageHandlerForControllerWithIdentifier:arg_identifier name:arg_name error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName: @"dev.flutter.pigeon.WKUserContentControllerHostApi.removeAllScriptMessageHandlers" binaryMessenger:binaryMessenger codec:FWFWKUserContentControllerHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (removeAllScriptMessageHandlersForControllerWithIdentifier:error:)], @"FWFWKUserContentControllerHostApi api (%@) doesn't respond to " @"@selector(removeAllScriptMessageHandlersForControllerWithIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api removeAllScriptMessageHandlersForControllerWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKUserContentControllerHostApi.addUserScript" binaryMessenger:binaryMessenger codec:FWFWKUserContentControllerHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(addUserScriptForControllerWithIdentifier: userScript:error:)], @"FWFWKUserContentControllerHostApi api (%@) doesn't respond to " @"@selector(addUserScriptForControllerWithIdentifier:userScript:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FWFWKUserScriptData *arg_userScript = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api addUserScriptForControllerWithIdentifier:arg_identifier userScript:arg_userScript error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKUserContentControllerHostApi.removeAllUserScripts" binaryMessenger:binaryMessenger codec:FWFWKUserContentControllerHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (removeAllUserScriptsForControllerWithIdentifier:error:)], @"FWFWKUserContentControllerHostApi api (%@) doesn't respond to " @"@selector(removeAllUserScriptsForControllerWithIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api removeAllUserScriptsForControllerWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } } NSObject *FWFWKPreferencesHostApiGetCodec() { static FlutterStandardMessageCodec *sSharedObject = nil; sSharedObject = [FlutterStandardMessageCodec sharedInstance]; return sSharedObject; } void FWFWKPreferencesHostApiSetup(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKPreferencesHostApi.createFromWebViewConfiguration" binaryMessenger:binaryMessenger codec:FWFWKPreferencesHostApiGetCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(createFromWebViewConfigurationWithIdentifier: configurationIdentifier:error:)], @"FWFWKPreferencesHostApi api (%@) doesn't respond to " @"@selector(createFromWebViewConfigurationWithIdentifier:configurationIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_configurationIdentifier = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api createFromWebViewConfigurationWithIdentifier:arg_identifier configurationIdentifier:arg_configurationIdentifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKPreferencesHostApi.setJavaScriptEnabled" binaryMessenger:binaryMessenger codec:FWFWKPreferencesHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (setJavaScriptEnabledForPreferencesWithIdentifier:isEnabled:error:)], @"FWFWKPreferencesHostApi api (%@) doesn't respond to " @"@selector(setJavaScriptEnabledForPreferencesWithIdentifier:isEnabled:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_enabled = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api setJavaScriptEnabledForPreferencesWithIdentifier:arg_identifier isEnabled:arg_enabled error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } } NSObject *FWFWKScriptMessageHandlerHostApiGetCodec() { static FlutterStandardMessageCodec *sSharedObject = nil; sSharedObject = [FlutterStandardMessageCodec sharedInstance]; return sSharedObject; } void FWFWKScriptMessageHandlerHostApiSetup(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKScriptMessageHandlerHostApi.create" binaryMessenger:binaryMessenger codec:FWFWKScriptMessageHandlerHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(createWithIdentifier:error:)], @"FWFWKScriptMessageHandlerHostApi api (%@) doesn't respond to " @"@selector(createWithIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api createWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } } @interface FWFWKScriptMessageHandlerFlutterApiCodecReader : FlutterStandardReader @end @implementation FWFWKScriptMessageHandlerFlutterApiCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { case 128: return [FWFWKScriptMessageData fromList:[self readValue]]; default: return [super readValueOfType:type]; } } @end @interface FWFWKScriptMessageHandlerFlutterApiCodecWriter : FlutterStandardWriter @end @implementation FWFWKScriptMessageHandlerFlutterApiCodecWriter - (void)writeValue:(id)value { if ([value isKindOfClass:[FWFWKScriptMessageData class]]) { [self writeByte:128]; [self writeValue:[value toList]]; } else { [super writeValue:value]; } } @end @interface FWFWKScriptMessageHandlerFlutterApiCodecReaderWriter : FlutterStandardReaderWriter @end @implementation FWFWKScriptMessageHandlerFlutterApiCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { return [[FWFWKScriptMessageHandlerFlutterApiCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { return [[FWFWKScriptMessageHandlerFlutterApiCodecReader alloc] initWithData:data]; } @end NSObject *FWFWKScriptMessageHandlerFlutterApiGetCodec() { static FlutterStandardMessageCodec *sSharedObject = nil; static dispatch_once_t sPred = 0; dispatch_once(&sPred, ^{ FWFWKScriptMessageHandlerFlutterApiCodecReaderWriter *readerWriter = [[FWFWKScriptMessageHandlerFlutterApiCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } @interface FWFWKScriptMessageHandlerFlutterApi () @property(nonatomic, strong) NSObject *binaryMessenger; @end @implementation FWFWKScriptMessageHandlerFlutterApi - (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger { self = [super init]; if (self) { _binaryMessenger = binaryMessenger; } return self; } - (void)didReceiveScriptMessageForHandlerWithIdentifier:(NSNumber *)arg_identifier userContentControllerIdentifier: (NSNumber *)arg_userContentControllerIdentifier message:(FWFWKScriptMessageData *)arg_message completion:(void (^)(NSError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName: @"dev.flutter.pigeon.WKScriptMessageHandlerFlutterApi.didReceiveScriptMessage" binaryMessenger:self.binaryMessenger codec:FWFWKScriptMessageHandlerFlutterApiGetCodec()]; [channel sendMessage:@[ arg_identifier ?: [NSNull null], arg_userContentControllerIdentifier ?: [NSNull null], arg_message ?: [NSNull null] ] reply:^(id reply) { completion(nil); }]; } @end NSObject *FWFWKNavigationDelegateHostApiGetCodec() { static FlutterStandardMessageCodec *sSharedObject = nil; sSharedObject = [FlutterStandardMessageCodec sharedInstance]; return sSharedObject; } void FWFWKNavigationDelegateHostApiSetup(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKNavigationDelegateHostApi.create" binaryMessenger:binaryMessenger codec:FWFWKNavigationDelegateHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(createWithIdentifier:error:)], @"FWFWKNavigationDelegateHostApi api (%@) doesn't respond to " @"@selector(createWithIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api createWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } } @interface FWFWKNavigationDelegateFlutterApiCodecReader : FlutterStandardReader @end @implementation FWFWKNavigationDelegateFlutterApiCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { case 128: return [FWFNSErrorData fromList:[self readValue]]; case 129: return [FWFNSUrlRequestData fromList:[self readValue]]; case 130: return [FWFWKFrameInfoData fromList:[self readValue]]; case 131: return [FWFWKNavigationActionData fromList:[self readValue]]; case 132: return [FWFWKNavigationActionPolicyEnumData fromList:[self readValue]]; default: return [super readValueOfType:type]; } } @end @interface FWFWKNavigationDelegateFlutterApiCodecWriter : FlutterStandardWriter @end @implementation FWFWKNavigationDelegateFlutterApiCodecWriter - (void)writeValue:(id)value { if ([value isKindOfClass:[FWFNSErrorData class]]) { [self writeByte:128]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) { [self writeByte:129]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) { [self writeByte:130]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) { [self writeByte:131]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) { [self writeByte:132]; [self writeValue:[value toList]]; } else { [super writeValue:value]; } } @end @interface FWFWKNavigationDelegateFlutterApiCodecReaderWriter : FlutterStandardReaderWriter @end @implementation FWFWKNavigationDelegateFlutterApiCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { return [[FWFWKNavigationDelegateFlutterApiCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { return [[FWFWKNavigationDelegateFlutterApiCodecReader alloc] initWithData:data]; } @end NSObject *FWFWKNavigationDelegateFlutterApiGetCodec() { static FlutterStandardMessageCodec *sSharedObject = nil; static dispatch_once_t sPred = 0; dispatch_once(&sPred, ^{ FWFWKNavigationDelegateFlutterApiCodecReaderWriter *readerWriter = [[FWFWKNavigationDelegateFlutterApiCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } @interface FWFWKNavigationDelegateFlutterApi () @property(nonatomic, strong) NSObject *binaryMessenger; @end @implementation FWFWKNavigationDelegateFlutterApi - (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger { self = [super init]; if (self) { _binaryMessenger = binaryMessenger; } return self; } - (void)didFinishNavigationForDelegateWithIdentifier:(NSNumber *)arg_identifier webViewIdentifier:(NSNumber *)arg_webViewIdentifier URL:(nullable NSString *)arg_url completion:(void (^)(NSError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName: @"dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didFinishNavigation" binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; [channel sendMessage:@[ arg_identifier ?: [NSNull null], arg_webViewIdentifier ?: [NSNull null], arg_url ?: [NSNull null] ] reply:^(id reply) { completion(nil); }]; } - (void)didStartProvisionalNavigationForDelegateWithIdentifier:(NSNumber *)arg_identifier webViewIdentifier:(NSNumber *)arg_webViewIdentifier URL:(nullable NSString *)arg_url completion: (void (^)(NSError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName: @"dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didStartProvisionalNavigation" binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; [channel sendMessage:@[ arg_identifier ?: [NSNull null], arg_webViewIdentifier ?: [NSNull null], arg_url ?: [NSNull null] ] reply:^(id reply) { completion(nil); }]; } - (void) decidePolicyForNavigationActionForDelegateWithIdentifier:(NSNumber *)arg_identifier webViewIdentifier:(NSNumber *)arg_webViewIdentifier navigationAction: (FWFWKNavigationActionData *)arg_navigationAction completion: (void (^)(FWFWKNavigationActionPolicyEnumData *_Nullable, NSError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName: @"dev.flutter.pigeon.WKNavigationDelegateFlutterApi.decidePolicyForNavigationAction" binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; [channel sendMessage:@[ arg_identifier ?: [NSNull null], arg_webViewIdentifier ?: [NSNull null], arg_navigationAction ?: [NSNull null] ] reply:^(id reply) { FWFWKNavigationActionPolicyEnumData *output = reply; completion(output, nil); }]; } - (void)didFailNavigationForDelegateWithIdentifier:(NSNumber *)arg_identifier webViewIdentifier:(NSNumber *)arg_webViewIdentifier error:(FWFNSErrorData *)arg_error completion:(void (^)(NSError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:@"dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didFailNavigation" binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; [channel sendMessage:@[ arg_identifier ?: [NSNull null], arg_webViewIdentifier ?: [NSNull null], arg_error ?: [NSNull null] ] reply:^(id reply) { completion(nil); }]; } - (void)didFailProvisionalNavigationForDelegateWithIdentifier:(NSNumber *)arg_identifier webViewIdentifier:(NSNumber *)arg_webViewIdentifier error:(FWFNSErrorData *)arg_error completion: (void (^)(NSError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName: @"dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didFailProvisionalNavigation" binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; [channel sendMessage:@[ arg_identifier ?: [NSNull null], arg_webViewIdentifier ?: [NSNull null], arg_error ?: [NSNull null] ] reply:^(id reply) { completion(nil); }]; } - (void)webViewWebContentProcessDidTerminateForDelegateWithIdentifier:(NSNumber *)arg_identifier webViewIdentifier: (NSNumber *)arg_webViewIdentifier completion:(void (^)(NSError *_Nullable)) completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName: @"dev.flutter.pigeon.WKNavigationDelegateFlutterApi.webViewWebContentProcessDidTerminate" binaryMessenger:self.binaryMessenger codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; [channel sendMessage:@[ arg_identifier ?: [NSNull null], arg_webViewIdentifier ?: [NSNull null] ] reply:^(id reply) { completion(nil); }]; } @end @interface FWFNSObjectHostApiCodecReader : FlutterStandardReader @end @implementation FWFNSObjectHostApiCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { case 128: return [FWFNSKeyValueObservingOptionsEnumData fromList:[self readValue]]; default: return [super readValueOfType:type]; } } @end @interface FWFNSObjectHostApiCodecWriter : FlutterStandardWriter @end @implementation FWFNSObjectHostApiCodecWriter - (void)writeValue:(id)value { if ([value isKindOfClass:[FWFNSKeyValueObservingOptionsEnumData class]]) { [self writeByte:128]; [self writeValue:[value toList]]; } else { [super writeValue:value]; } } @end @interface FWFNSObjectHostApiCodecReaderWriter : FlutterStandardReaderWriter @end @implementation FWFNSObjectHostApiCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { return [[FWFNSObjectHostApiCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { return [[FWFNSObjectHostApiCodecReader alloc] initWithData:data]; } @end NSObject *FWFNSObjectHostApiGetCodec() { static FlutterStandardMessageCodec *sSharedObject = nil; static dispatch_once_t sPred = 0; dispatch_once(&sPred, ^{ FWFNSObjectHostApiCodecReaderWriter *readerWriter = [[FWFNSObjectHostApiCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } void FWFNSObjectHostApiSetup(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.NSObjectHostApi.dispose" binaryMessenger:binaryMessenger codec:FWFNSObjectHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(disposeObjectWithIdentifier:error:)], @"FWFNSObjectHostApi api (%@) doesn't respond to " @"@selector(disposeObjectWithIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api disposeObjectWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.NSObjectHostApi.addObserver" binaryMessenger:binaryMessenger codec:FWFNSObjectHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (addObserverForObjectWithIdentifier: observerIdentifier:keyPath:options:error:)], @"FWFNSObjectHostApi api (%@) doesn't respond to " @"@selector(addObserverForObjectWithIdentifier:observerIdentifier:keyPath:options:" @"error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_observerIdentifier = GetNullableObjectAtIndex(args, 1); NSString *arg_keyPath = GetNullableObjectAtIndex(args, 2); NSArray *arg_options = GetNullableObjectAtIndex(args, 3); FlutterError *error; [api addObserverForObjectWithIdentifier:arg_identifier observerIdentifier:arg_observerIdentifier keyPath:arg_keyPath options:arg_options error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.NSObjectHostApi.removeObserver" binaryMessenger:binaryMessenger codec:FWFNSObjectHostApiGetCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(removeObserverForObjectWithIdentifier: observerIdentifier:keyPath:error:)], @"FWFNSObjectHostApi api (%@) doesn't respond to " @"@selector(removeObserverForObjectWithIdentifier:observerIdentifier:keyPath:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_observerIdentifier = GetNullableObjectAtIndex(args, 1); NSString *arg_keyPath = GetNullableObjectAtIndex(args, 2); FlutterError *error; [api removeObserverForObjectWithIdentifier:arg_identifier observerIdentifier:arg_observerIdentifier keyPath:arg_keyPath error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } } @interface FWFNSObjectFlutterApiCodecReader : FlutterStandardReader @end @implementation FWFNSObjectFlutterApiCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { case 128: return [FWFNSErrorData fromList:[self readValue]]; case 129: return [FWFNSHttpCookieData fromList:[self readValue]]; case 130: return [FWFNSHttpCookiePropertyKeyEnumData fromList:[self readValue]]; case 131: return [FWFNSKeyValueChangeKeyEnumData fromList:[self readValue]]; case 132: return [FWFNSKeyValueObservingOptionsEnumData fromList:[self readValue]]; case 133: return [FWFNSUrlRequestData fromList:[self readValue]]; case 134: return [FWFWKAudiovisualMediaTypeEnumData fromList:[self readValue]]; case 135: return [FWFWKFrameInfoData fromList:[self readValue]]; case 136: return [FWFWKNavigationActionData fromList:[self readValue]]; case 137: return [FWFWKNavigationActionPolicyEnumData fromList:[self readValue]]; case 138: return [FWFWKScriptMessageData fromList:[self readValue]]; case 139: return [FWFWKUserScriptData fromList:[self readValue]]; case 140: return [FWFWKUserScriptInjectionTimeEnumData fromList:[self readValue]]; case 141: return [FWFWKWebsiteDataTypeEnumData fromList:[self readValue]]; default: return [super readValueOfType:type]; } } @end @interface FWFNSObjectFlutterApiCodecWriter : FlutterStandardWriter @end @implementation FWFNSObjectFlutterApiCodecWriter - (void)writeValue:(id)value { if ([value isKindOfClass:[FWFNSErrorData class]]) { [self writeByte:128]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFNSHttpCookieData class]]) { [self writeByte:129]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFNSHttpCookiePropertyKeyEnumData class]]) { [self writeByte:130]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFNSKeyValueChangeKeyEnumData class]]) { [self writeByte:131]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFNSKeyValueObservingOptionsEnumData class]]) { [self writeByte:132]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) { [self writeByte:133]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKAudiovisualMediaTypeEnumData class]]) { [self writeByte:134]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) { [self writeByte:135]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) { [self writeByte:136]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) { [self writeByte:137]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKScriptMessageData class]]) { [self writeByte:138]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKUserScriptData class]]) { [self writeByte:139]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKUserScriptInjectionTimeEnumData class]]) { [self writeByte:140]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKWebsiteDataTypeEnumData class]]) { [self writeByte:141]; [self writeValue:[value toList]]; } else { [super writeValue:value]; } } @end @interface FWFNSObjectFlutterApiCodecReaderWriter : FlutterStandardReaderWriter @end @implementation FWFNSObjectFlutterApiCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { return [[FWFNSObjectFlutterApiCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { return [[FWFNSObjectFlutterApiCodecReader alloc] initWithData:data]; } @end NSObject *FWFNSObjectFlutterApiGetCodec() { static FlutterStandardMessageCodec *sSharedObject = nil; static dispatch_once_t sPred = 0; dispatch_once(&sPred, ^{ FWFNSObjectFlutterApiCodecReaderWriter *readerWriter = [[FWFNSObjectFlutterApiCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } @interface FWFNSObjectFlutterApi () @property(nonatomic, strong) NSObject *binaryMessenger; @end @implementation FWFNSObjectFlutterApi - (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger { self = [super init]; if (self) { _binaryMessenger = binaryMessenger; } return self; } - (void)observeValueForObjectWithIdentifier:(NSNumber *)arg_identifier keyPath:(NSString *)arg_keyPath objectIdentifier:(NSNumber *)arg_objectIdentifier changeKeys: (NSArray *)arg_changeKeys changeValues:(NSArray *)arg_changeValues completion:(void (^)(NSError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:@"dev.flutter.pigeon.NSObjectFlutterApi.observeValue" binaryMessenger:self.binaryMessenger codec:FWFNSObjectFlutterApiGetCodec()]; [channel sendMessage:@[ arg_identifier ?: [NSNull null], arg_keyPath ?: [NSNull null], arg_objectIdentifier ?: [NSNull null], arg_changeKeys ?: [NSNull null], arg_changeValues ?: [NSNull null] ] reply:^(id reply) { completion(nil); }]; } - (void)disposeObjectWithIdentifier:(NSNumber *)arg_identifier completion:(void (^)(NSError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:@"dev.flutter.pigeon.NSObjectFlutterApi.dispose" binaryMessenger:self.binaryMessenger codec:FWFNSObjectFlutterApiGetCodec()]; [channel sendMessage:@[ arg_identifier ?: [NSNull null] ] reply:^(id reply) { completion(nil); }]; } @end @interface FWFWKWebViewHostApiCodecReader : FlutterStandardReader @end @implementation FWFWKWebViewHostApiCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { case 128: return [FWFNSErrorData fromList:[self readValue]]; case 129: return [FWFNSHttpCookieData fromList:[self readValue]]; case 130: return [FWFNSHttpCookiePropertyKeyEnumData fromList:[self readValue]]; case 131: return [FWFNSKeyValueChangeKeyEnumData fromList:[self readValue]]; case 132: return [FWFNSKeyValueObservingOptionsEnumData fromList:[self readValue]]; case 133: return [FWFNSUrlRequestData fromList:[self readValue]]; case 134: return [FWFWKAudiovisualMediaTypeEnumData fromList:[self readValue]]; case 135: return [FWFWKFrameInfoData fromList:[self readValue]]; case 136: return [FWFWKNavigationActionData fromList:[self readValue]]; case 137: return [FWFWKNavigationActionPolicyEnumData fromList:[self readValue]]; case 138: return [FWFWKScriptMessageData fromList:[self readValue]]; case 139: return [FWFWKUserScriptData fromList:[self readValue]]; case 140: return [FWFWKUserScriptInjectionTimeEnumData fromList:[self readValue]]; case 141: return [FWFWKWebsiteDataTypeEnumData fromList:[self readValue]]; default: return [super readValueOfType:type]; } } @end @interface FWFWKWebViewHostApiCodecWriter : FlutterStandardWriter @end @implementation FWFWKWebViewHostApiCodecWriter - (void)writeValue:(id)value { if ([value isKindOfClass:[FWFNSErrorData class]]) { [self writeByte:128]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFNSHttpCookieData class]]) { [self writeByte:129]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFNSHttpCookiePropertyKeyEnumData class]]) { [self writeByte:130]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFNSKeyValueChangeKeyEnumData class]]) { [self writeByte:131]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFNSKeyValueObservingOptionsEnumData class]]) { [self writeByte:132]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) { [self writeByte:133]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKAudiovisualMediaTypeEnumData class]]) { [self writeByte:134]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) { [self writeByte:135]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) { [self writeByte:136]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) { [self writeByte:137]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKScriptMessageData class]]) { [self writeByte:138]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKUserScriptData class]]) { [self writeByte:139]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKUserScriptInjectionTimeEnumData class]]) { [self writeByte:140]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKWebsiteDataTypeEnumData class]]) { [self writeByte:141]; [self writeValue:[value toList]]; } else { [super writeValue:value]; } } @end @interface FWFWKWebViewHostApiCodecReaderWriter : FlutterStandardReaderWriter @end @implementation FWFWKWebViewHostApiCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { return [[FWFWKWebViewHostApiCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { return [[FWFWKWebViewHostApiCodecReader alloc] initWithData:data]; } @end NSObject *FWFWKWebViewHostApiGetCodec() { static FlutterStandardMessageCodec *sSharedObject = nil; static dispatch_once_t sPred = 0; dispatch_once(&sPred, ^{ FWFWKWebViewHostApiCodecReaderWriter *readerWriter = [[FWFWKWebViewHostApiCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } void FWFWKWebViewHostApiSetup(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewHostApi.create" binaryMessenger:binaryMessenger codec:FWFWKWebViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(createWithIdentifier: configurationIdentifier:error:)], @"FWFWKWebViewHostApi api (%@) doesn't respond to " @"@selector(createWithIdentifier:configurationIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_configurationIdentifier = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api createWithIdentifier:arg_identifier configurationIdentifier:arg_configurationIdentifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewHostApi.setUIDelegate" binaryMessenger:binaryMessenger codec:FWFWKWebViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(setUIDelegateForWebViewWithIdentifier: delegateIdentifier:error:)], @"FWFWKWebViewHostApi api (%@) doesn't respond to " @"@selector(setUIDelegateForWebViewWithIdentifier:delegateIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_uiDelegateIdentifier = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api setUIDelegateForWebViewWithIdentifier:arg_identifier delegateIdentifier:arg_uiDelegateIdentifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewHostApi.setNavigationDelegate" binaryMessenger:binaryMessenger codec:FWFWKWebViewHostApiGetCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector(setNavigationDelegateForWebViewWithIdentifier: delegateIdentifier:error:)], @"FWFWKWebViewHostApi api (%@) doesn't respond to " @"@selector(setNavigationDelegateForWebViewWithIdentifier:delegateIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_navigationDelegateIdentifier = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api setNavigationDelegateForWebViewWithIdentifier:arg_identifier delegateIdentifier:arg_navigationDelegateIdentifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewHostApi.getUrl" binaryMessenger:binaryMessenger codec:FWFWKWebViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(URLForWebViewWithIdentifier:error:)], @"FWFWKWebViewHostApi api (%@) doesn't respond to " @"@selector(URLForWebViewWithIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSString *output = [api URLForWebViewWithIdentifier:arg_identifier error:&error]; callback(wrapResult(output, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewHostApi.getEstimatedProgress" binaryMessenger:binaryMessenger codec:FWFWKWebViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(estimatedProgressForWebViewWithIdentifier: error:)], @"FWFWKWebViewHostApi api (%@) doesn't respond to " @"@selector(estimatedProgressForWebViewWithIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSNumber *output = [api estimatedProgressForWebViewWithIdentifier:arg_identifier error:&error]; callback(wrapResult(output, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewHostApi.loadRequest" binaryMessenger:binaryMessenger codec:FWFWKWebViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(loadRequestForWebViewWithIdentifier: request:error:)], @"FWFWKWebViewHostApi api (%@) doesn't respond to " @"@selector(loadRequestForWebViewWithIdentifier:request:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FWFNSUrlRequestData *arg_request = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api loadRequestForWebViewWithIdentifier:arg_identifier request:arg_request error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewHostApi.loadHtmlString" binaryMessenger:binaryMessenger codec:FWFWKWebViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(loadHTMLForWebViewWithIdentifier: HTMLString:baseURL:error:)], @"FWFWKWebViewHostApi api (%@) doesn't respond to " @"@selector(loadHTMLForWebViewWithIdentifier:HTMLString:baseURL:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSString *arg_string = GetNullableObjectAtIndex(args, 1); NSString *arg_baseUrl = GetNullableObjectAtIndex(args, 2); FlutterError *error; [api loadHTMLForWebViewWithIdentifier:arg_identifier HTMLString:arg_string baseURL:arg_baseUrl error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewHostApi.loadFileUrl" binaryMessenger:binaryMessenger codec:FWFWKWebViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (loadFileForWebViewWithIdentifier:fileURL:readAccessURL:error:)], @"FWFWKWebViewHostApi api (%@) doesn't respond to " @"@selector(loadFileForWebViewWithIdentifier:fileURL:readAccessURL:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSString *arg_url = GetNullableObjectAtIndex(args, 1); NSString *arg_readAccessUrl = GetNullableObjectAtIndex(args, 2); FlutterError *error; [api loadFileForWebViewWithIdentifier:arg_identifier fileURL:arg_url readAccessURL:arg_readAccessUrl error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewHostApi.loadFlutterAsset" binaryMessenger:binaryMessenger codec:FWFWKWebViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(loadAssetForWebViewWithIdentifier: assetKey:error:)], @"FWFWKWebViewHostApi api (%@) doesn't respond to " @"@selector(loadAssetForWebViewWithIdentifier:assetKey:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSString *arg_key = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api loadAssetForWebViewWithIdentifier:arg_identifier assetKey:arg_key error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewHostApi.canGoBack" binaryMessenger:binaryMessenger codec:FWFWKWebViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(canGoBackForWebViewWithIdentifier:error:)], @"FWFWKWebViewHostApi api (%@) doesn't respond to " @"@selector(canGoBackForWebViewWithIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSNumber *output = [api canGoBackForWebViewWithIdentifier:arg_identifier error:&error]; callback(wrapResult(output, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewHostApi.canGoForward" binaryMessenger:binaryMessenger codec:FWFWKWebViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(canGoForwardForWebViewWithIdentifier:error:)], @"FWFWKWebViewHostApi api (%@) doesn't respond to " @"@selector(canGoForwardForWebViewWithIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSNumber *output = [api canGoForwardForWebViewWithIdentifier:arg_identifier error:&error]; callback(wrapResult(output, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewHostApi.goBack" binaryMessenger:binaryMessenger codec:FWFWKWebViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(goBackForWebViewWithIdentifier:error:)], @"FWFWKWebViewHostApi api (%@) doesn't respond to " @"@selector(goBackForWebViewWithIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api goBackForWebViewWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewHostApi.goForward" binaryMessenger:binaryMessenger codec:FWFWKWebViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(goForwardForWebViewWithIdentifier:error:)], @"FWFWKWebViewHostApi api (%@) doesn't respond to " @"@selector(goForwardForWebViewWithIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api goForwardForWebViewWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewHostApi.reload" binaryMessenger:binaryMessenger codec:FWFWKWebViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(reloadWebViewWithIdentifier:error:)], @"FWFWKWebViewHostApi api (%@) doesn't respond to " @"@selector(reloadWebViewWithIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api reloadWebViewWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewHostApi.getTitle" binaryMessenger:binaryMessenger codec:FWFWKWebViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(titleForWebViewWithIdentifier:error:)], @"FWFWKWebViewHostApi api (%@) doesn't respond to " @"@selector(titleForWebViewWithIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FlutterError *error; NSString *output = [api titleForWebViewWithIdentifier:arg_identifier error:&error]; callback(wrapResult(output, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName: @"dev.flutter.pigeon.WKWebViewHostApi.setAllowsBackForwardNavigationGestures" binaryMessenger:binaryMessenger codec:FWFWKWebViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector (setAllowsBackForwardForWebViewWithIdentifier:isAllowed:error:)], @"FWFWKWebViewHostApi api (%@) doesn't respond to " @"@selector(setAllowsBackForwardForWebViewWithIdentifier:isAllowed:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_allow = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api setAllowsBackForwardForWebViewWithIdentifier:arg_identifier isAllowed:arg_allow error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewHostApi.setCustomUserAgent" binaryMessenger:binaryMessenger codec:FWFWKWebViewHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(setUserAgentForWebViewWithIdentifier: userAgent:error:)], @"FWFWKWebViewHostApi api (%@) doesn't respond to " @"@selector(setUserAgentForWebViewWithIdentifier:userAgent:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSString *arg_userAgent = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api setUserAgentForWebViewWithIdentifier:arg_identifier userAgent:arg_userAgent error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKWebViewHostApi.evaluateJavaScript" binaryMessenger:binaryMessenger codec:FWFWKWebViewHostApiGetCodec()]; if (api) { NSCAssert( [api respondsToSelector:@selector (evaluateJavaScriptForWebViewWithIdentifier:javaScriptString:completion:)], @"FWFWKWebViewHostApi api (%@) doesn't respond to " @"@selector(evaluateJavaScriptForWebViewWithIdentifier:javaScriptString:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSString *arg_javaScriptString = GetNullableObjectAtIndex(args, 1); [api evaluateJavaScriptForWebViewWithIdentifier:arg_identifier javaScriptString:arg_javaScriptString completion:^(id _Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; }]; } else { [channel setMessageHandler:nil]; } } } NSObject *FWFWKUIDelegateHostApiGetCodec() { static FlutterStandardMessageCodec *sSharedObject = nil; sSharedObject = [FlutterStandardMessageCodec sharedInstance]; return sSharedObject; } void FWFWKUIDelegateHostApiSetup(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKUIDelegateHostApi.create" binaryMessenger:binaryMessenger codec:FWFWKUIDelegateHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(createWithIdentifier:error:)], @"FWFWKUIDelegateHostApi api (%@) doesn't respond to " @"@selector(createWithIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FlutterError *error; [api createWithIdentifier:arg_identifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } } @interface FWFWKUIDelegateFlutterApiCodecReader : FlutterStandardReader @end @implementation FWFWKUIDelegateFlutterApiCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { case 128: return [FWFNSUrlRequestData fromList:[self readValue]]; case 129: return [FWFWKFrameInfoData fromList:[self readValue]]; case 130: return [FWFWKNavigationActionData fromList:[self readValue]]; default: return [super readValueOfType:type]; } } @end @interface FWFWKUIDelegateFlutterApiCodecWriter : FlutterStandardWriter @end @implementation FWFWKUIDelegateFlutterApiCodecWriter - (void)writeValue:(id)value { if ([value isKindOfClass:[FWFNSUrlRequestData class]]) { [self writeByte:128]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) { [self writeByte:129]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) { [self writeByte:130]; [self writeValue:[value toList]]; } else { [super writeValue:value]; } } @end @interface FWFWKUIDelegateFlutterApiCodecReaderWriter : FlutterStandardReaderWriter @end @implementation FWFWKUIDelegateFlutterApiCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { return [[FWFWKUIDelegateFlutterApiCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { return [[FWFWKUIDelegateFlutterApiCodecReader alloc] initWithData:data]; } @end NSObject *FWFWKUIDelegateFlutterApiGetCodec() { static FlutterStandardMessageCodec *sSharedObject = nil; static dispatch_once_t sPred = 0; dispatch_once(&sPred, ^{ FWFWKUIDelegateFlutterApiCodecReaderWriter *readerWriter = [[FWFWKUIDelegateFlutterApiCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } @interface FWFWKUIDelegateFlutterApi () @property(nonatomic, strong) NSObject *binaryMessenger; @end @implementation FWFWKUIDelegateFlutterApi - (instancetype)initWithBinaryMessenger:(NSObject *)binaryMessenger { self = [super init]; if (self) { _binaryMessenger = binaryMessenger; } return self; } - (void)onCreateWebViewForDelegateWithIdentifier:(NSNumber *)arg_identifier webViewIdentifier:(NSNumber *)arg_webViewIdentifier configurationIdentifier:(NSNumber *)arg_configurationIdentifier navigationAction:(FWFWKNavigationActionData *)arg_navigationAction completion:(void (^)(NSError *_Nullable))completion { FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel messageChannelWithName:@"dev.flutter.pigeon.WKUIDelegateFlutterApi.onCreateWebView" binaryMessenger:self.binaryMessenger codec:FWFWKUIDelegateFlutterApiGetCodec()]; [channel sendMessage:@[ arg_identifier ?: [NSNull null], arg_webViewIdentifier ?: [NSNull null], arg_configurationIdentifier ?: [NSNull null], arg_navigationAction ?: [NSNull null] ] reply:^(id reply) { completion(nil); }]; } @end @interface FWFWKHttpCookieStoreHostApiCodecReader : FlutterStandardReader @end @implementation FWFWKHttpCookieStoreHostApiCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { case 128: return [FWFNSHttpCookieData fromList:[self readValue]]; case 129: return [FWFNSHttpCookiePropertyKeyEnumData fromList:[self readValue]]; default: return [super readValueOfType:type]; } } @end @interface FWFWKHttpCookieStoreHostApiCodecWriter : FlutterStandardWriter @end @implementation FWFWKHttpCookieStoreHostApiCodecWriter - (void)writeValue:(id)value { if ([value isKindOfClass:[FWFNSHttpCookieData class]]) { [self writeByte:128]; [self writeValue:[value toList]]; } else if ([value isKindOfClass:[FWFNSHttpCookiePropertyKeyEnumData class]]) { [self writeByte:129]; [self writeValue:[value toList]]; } else { [super writeValue:value]; } } @end @interface FWFWKHttpCookieStoreHostApiCodecReaderWriter : FlutterStandardReaderWriter @end @implementation FWFWKHttpCookieStoreHostApiCodecReaderWriter - (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { return [[FWFWKHttpCookieStoreHostApiCodecWriter alloc] initWithData:data]; } - (FlutterStandardReader *)readerWithData:(NSData *)data { return [[FWFWKHttpCookieStoreHostApiCodecReader alloc] initWithData:data]; } @end NSObject *FWFWKHttpCookieStoreHostApiGetCodec() { static FlutterStandardMessageCodec *sSharedObject = nil; static dispatch_once_t sPred = 0; dispatch_once(&sPred, ^{ FWFWKHttpCookieStoreHostApiCodecReaderWriter *readerWriter = [[FWFWKHttpCookieStoreHostApiCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } void FWFWKHttpCookieStoreHostApiSetup(id binaryMessenger, NSObject *api) { { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKHttpCookieStoreHostApi.createFromWebsiteDataStore" binaryMessenger:binaryMessenger codec:FWFWKHttpCookieStoreHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(createFromWebsiteDataStoreWithIdentifier: dataStoreIdentifier:error:)], @"FWFWKHttpCookieStoreHostApi api (%@) doesn't respond to " @"@selector(createFromWebsiteDataStoreWithIdentifier:dataStoreIdentifier:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); NSNumber *arg_websiteDataStoreIdentifier = GetNullableObjectAtIndex(args, 1); FlutterError *error; [api createFromWebsiteDataStoreWithIdentifier:arg_identifier dataStoreIdentifier:arg_websiteDataStoreIdentifier error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:@"dev.flutter.pigeon.WKHttpCookieStoreHostApi.setCookie" binaryMessenger:binaryMessenger codec:FWFWKHttpCookieStoreHostApiGetCodec()]; if (api) { NSCAssert([api respondsToSelector:@selector(setCookieForStoreWithIdentifier: cookie:completion:)], @"FWFWKHttpCookieStoreHostApi api (%@) doesn't respond to " @"@selector(setCookieForStoreWithIdentifier:cookie:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSNumber *arg_identifier = GetNullableObjectAtIndex(args, 0); FWFNSHttpCookieData *arg_cookie = GetNullableObjectAtIndex(args, 1); [api setCookieForStoreWithIdentifier:arg_identifier cookie:arg_cookie completion:^(FlutterError *_Nullable error) { callback(wrapResult(nil, error)); }]; }]; } else { [channel setMessageHandler:nil]; } } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFHTTPCookieStoreHostApi.h ================================================ // Copyright 2013 The Flutter 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 "FWFGeneratedWebKitApis.h" #import "FWFInstanceManager.h" NS_ASSUME_NONNULL_BEGIN /** * Host api implementation for WKHTTPCookieStore. * * Handles creating WKHTTPCookieStore that intercommunicate with a paired Dart object. */ @interface FWFHTTPCookieStoreHostApiImpl : NSObject - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFHTTPCookieStoreHostApi.m ================================================ // Copyright 2013 The Flutter 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 "FWFHTTPCookieStoreHostApi.h" #import "FWFDataConverters.h" #import "FWFWebsiteDataStoreHostApi.h" @interface FWFHTTPCookieStoreHostApiImpl () // InstanceManager must be weak to prevent a circular reference with the object it stores. @property(nonatomic, weak) FWFInstanceManager *instanceManager; @end @implementation FWFHTTPCookieStoreHostApiImpl - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager { self = [self init]; if (self) { _instanceManager = instanceManager; } return self; } - (WKHTTPCookieStore *)HTTPCookieStoreForIdentifier:(NSNumber *)identifier API_AVAILABLE(ios(11.0)) { return (WKHTTPCookieStore *)[self.instanceManager instanceForIdentifier:identifier.longValue]; } - (void)createFromWebsiteDataStoreWithIdentifier:(nonnull NSNumber *)identifier dataStoreIdentifier:(nonnull NSNumber *)websiteDataStoreIdentifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull) error { if (@available(iOS 11.0, *)) { WKWebsiteDataStore *dataStore = (WKWebsiteDataStore *)[self.instanceManager instanceForIdentifier:websiteDataStoreIdentifier.longValue]; [self.instanceManager addDartCreatedInstance:dataStore.httpCookieStore withIdentifier:identifier.longValue]; } else { *error = [FlutterError errorWithCode:@"FWFUnsupportedVersionError" message:@"WKWebsiteDataStore.httpCookieStore is only supported on versions 11+." details:nil]; } } - (void)setCookieForStoreWithIdentifier:(nonnull NSNumber *)identifier cookie:(nonnull FWFNSHttpCookieData *)cookie completion:(nonnull void (^)(FlutterError *_Nullable))completion { NSHTTPCookie *nsCookie = FWFNSHTTPCookieFromCookieData(cookie); if (@available(iOS 11.0, *)) { [[self HTTPCookieStoreForIdentifier:identifier] setCookie:nsCookie completionHandler:^{ completion(nil); }]; } else { completion([FlutterError errorWithCode:@"FWFUnsupportedVersionError" message:@"setCookie is only supported on versions 11+." details:nil]); } } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import NS_ASSUME_NONNULL_BEGIN typedef void (^FWFOnDeallocCallback)(long identifier); /** * Maintains instances used to communicate with the corresponding objects in Dart. * * When an instance is added with an identifier, either can be used to retrieve the other. * * Added instances are added as a weak reference and a strong reference. When the strong reference * is removed with `removeStrongReferenceWithIdentifier:` and the weak reference is deallocated, * the `deallocCallback` is made with the instance's identifier. However, if the strong reference is * removed and then the identifier is retrieved with the intention to pass the identifier to Dart * (e.g. calling `identifierForInstance:identifierWillBePassedToFlutter:` with * `identifierWillBePassedToFlutter` set to YES), the strong reference to the instance is recreated. * The strong reference will then need to be removed manually again. * * Accessing and inserting to an InstanceManager is thread safe. */ @interface FWFInstanceManager : NSObject @property(readonly) FWFOnDeallocCallback deallocCallback; - (instancetype)initWithDeallocCallback:(FWFOnDeallocCallback)callback; // TODO(bparrishMines): Pairs should not be able to be overwritten and this feature // should be replaced with a call to clear the manager in the event of a hot restart. /** * Adds a new instance that was instantiated from Dart. * * If an instance or identifier has already been added, it will be replaced by the new values. The * Dart InstanceManager is considered the source of truth and has the capability to overwrite stored * pairs in response to hot restarts. * * @param instance The instance to be stored. * @param instanceIdentifier The identifier to be paired with instance. This value must be >= 0. */ - (void)addDartCreatedInstance:(NSObject *)instance withIdentifier:(long)instanceIdentifier; /** * Adds a new instance that was instantiated from the host platform. * * @param instance The instance to be stored. * @return The unique identifier stored with instance. */ - (long)addHostCreatedInstance:(nonnull NSObject *)instance; /** * Removes `instanceIdentifier` and its associated strongly referenced instance, if present, from * the manager. * * @param instanceIdentifier The identifier paired to an instance. * * @return The removed instance if the manager contains the given instanceIdentifier, otherwise * nil. */ - (nullable NSObject *)removeInstanceWithIdentifier:(long)instanceIdentifier; /** * Retrieves the instance associated with identifier. * * @param instanceIdentifier The identifier paired to an instance. * * @return The instance associated with `instanceIdentifier` if the manager contains the value, * otherwise nil. */ - (nullable NSObject *)instanceForIdentifier:(long)instanceIdentifier; /** * Retrieves the identifier paired with an instance. * * If the manager contains `instance`, as a strong or weak reference, the strong reference to * `instance` will be recreated and will need to be removed again with * `removeInstanceWithIdentifier:`. * * This method also expects the Dart `InstanceManager` to have, or recreate, a weak reference to the * instance the identifier is associated with once it receives it. * * @param instance An instance that may be stored in the manager. * * @return The identifier associated with `instance` if the manager contains the value, otherwise * NSNotFound. */ - (long)identifierWithStrongReferenceForInstance:(nonnull NSObject *)instance; /** * Returns whether this manager contains the given `instance`. * * @return Whether this manager contains the given `instance`. */ - (BOOL)containsInstance:(nonnull NSObject *)instance; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager.m ================================================ // Copyright 2013 The Flutter 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 "FWFInstanceManager.h" #import "FWFInstanceManager_Test.h" #import // Attaches to an object to receive a callback when the object is deallocated. @interface FWFFinalizer : NSObject @end // Attaches to an object to receive a callback when the object is deallocated. @implementation FWFFinalizer { long _identifier; // Callbacks are no longer made once FWFInstanceManager is inaccessible. FWFOnDeallocCallback __weak _callback; } - (instancetype)initWithIdentifier:(long)identifier callback:(FWFOnDeallocCallback)callback { self = [self init]; if (self) { _identifier = identifier; _callback = callback; } return self; } + (void)attachToInstance:(NSObject *)instance withIdentifier:(long)identifier callback:(FWFOnDeallocCallback)callback { FWFFinalizer *finalizer = [[FWFFinalizer alloc] initWithIdentifier:identifier callback:callback]; objc_setAssociatedObject(instance, _cmd, finalizer, OBJC_ASSOCIATION_RETAIN); } + (void)detachFromInstance:(NSObject *)instance { objc_setAssociatedObject(instance, @selector(attachToInstance:withIdentifier:callback:), nil, OBJC_ASSOCIATION_ASSIGN); } - (void)dealloc { if (_callback) { _callback(_identifier); } } @end @interface FWFInstanceManager () @property dispatch_queue_t lockQueue; @property NSMapTable *identifiers; @property NSMapTable *weakInstances; @property NSMapTable *strongInstances; @property long nextIdentifier; @end @implementation FWFInstanceManager // Identifiers are locked to a specific range to avoid collisions with objects // created simultaneously from Dart. // Host uses identifiers >= 2^16 and Dart is expected to use values n where, // 0 <= n < 2^16. static long const FWFMinHostCreatedIdentifier = 65536; - (instancetype)init { self = [super init]; if (self) { _deallocCallback = _deallocCallback ? _deallocCallback : ^(long identifier) { }; _lockQueue = dispatch_queue_create("FWFInstanceManager", DISPATCH_QUEUE_SERIAL); _identifiers = [NSMapTable weakToStrongObjectsMapTable]; _weakInstances = [NSMapTable strongToWeakObjectsMapTable]; _strongInstances = [NSMapTable strongToStrongObjectsMapTable]; _nextIdentifier = FWFMinHostCreatedIdentifier; } return self; } - (instancetype)initWithDeallocCallback:(FWFOnDeallocCallback)callback { self = [self init]; if (self) { _deallocCallback = callback; } return self; } - (void)addDartCreatedInstance:(NSObject *)instance withIdentifier:(long)instanceIdentifier { NSParameterAssert(instance); NSParameterAssert(instanceIdentifier >= 0); dispatch_async(_lockQueue, ^{ [self addInstance:instance withIdentifier:instanceIdentifier]; }); } - (long)addHostCreatedInstance:(nonnull NSObject *)instance { NSParameterAssert(instance); long __block identifier = -1; dispatch_sync(_lockQueue, ^{ identifier = self.nextIdentifier++; [self addInstance:instance withIdentifier:identifier]; }); return identifier; } - (nullable NSObject *)removeInstanceWithIdentifier:(long)instanceIdentifier { NSObject *__block instance = nil; dispatch_sync(_lockQueue, ^{ instance = [self.strongInstances objectForKey:@(instanceIdentifier)]; if (instance) { [self.strongInstances removeObjectForKey:@(instanceIdentifier)]; } }); return instance; } - (nullable NSObject *)instanceForIdentifier:(long)instanceIdentifier { NSObject *__block instance = nil; dispatch_sync(_lockQueue, ^{ instance = [self.weakInstances objectForKey:@(instanceIdentifier)]; }); return instance; } - (void)addInstance:(nonnull NSObject *)instance withIdentifier:(long)instanceIdentifier { [self.identifiers setObject:@(instanceIdentifier) forKey:instance]; [self.weakInstances setObject:instance forKey:@(instanceIdentifier)]; [self.strongInstances setObject:instance forKey:@(instanceIdentifier)]; [FWFFinalizer attachToInstance:instance withIdentifier:instanceIdentifier callback:self.deallocCallback]; } - (long)identifierWithStrongReferenceForInstance:(nonnull NSObject *)instance { NSNumber *__block identifierNumber = nil; dispatch_sync(_lockQueue, ^{ identifierNumber = [self.identifiers objectForKey:instance]; if (identifierNumber) { [self.strongInstances setObject:instance forKey:identifierNumber]; } }); return identifierNumber ? identifierNumber.longValue : NSNotFound; } - (BOOL)containsInstance:(nonnull NSObject *)instance { BOOL __block containsInstance; dispatch_sync(_lockQueue, ^{ containsInstance = [self.identifiers objectForKey:instance]; }); return containsInstance; } - (NSUInteger)strongInstanceCount { NSUInteger __block count = -1; dispatch_sync(_lockQueue, ^{ count = self.strongInstances.count; }); return count; } - (NSUInteger)weakInstanceCount { NSUInteger __block count = -1; dispatch_sync(_lockQueue, ^{ count = self.weakInstances.count; }); return count; } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFInstanceManager_Test.h ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #import NS_ASSUME_NONNULL_BEGIN @interface FWFInstanceManager () /** * The number of instances stored as a strong reference. * * Added for debugging purposes. */ - (NSUInteger)strongInstanceCount; /** * The number of instances stored as a weak reference. * * Added for debugging purposes. NSMapTables that store keys or objects as weak reference will be * reclaimed nondeterministically. */ - (NSUInteger)weakInstanceCount; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.h ================================================ // Copyright 2013 The Flutter 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 #import "FWFGeneratedWebKitApis.h" #import "FWFInstanceManager.h" #import "FWFObjectHostApi.h" NS_ASSUME_NONNULL_BEGIN /** * Flutter api implementation for WKNavigationDelegate. * * Handles making callbacks to Dart for a WKNavigationDelegate. */ @interface FWFNavigationDelegateFlutterApiImpl : FWFWKNavigationDelegateFlutterApi - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @end /** * Implementation of WKNavigationDelegate for FWFNavigationDelegateHostApiImpl. */ @interface FWFNavigationDelegate : FWFObject @property(readonly, nonnull, nonatomic) FWFNavigationDelegateFlutterApiImpl *navigationDelegateAPI; - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @end /** * Host api implementation for WKNavigationDelegate. * * Handles creating WKNavigationDelegate that intercommunicate with a paired Dart object. */ @interface FWFNavigationDelegateHostApiImpl : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.m ================================================ // Copyright 2013 The Flutter 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 "FWFNavigationDelegateHostApi.h" #import "FWFDataConverters.h" #import "FWFWebViewConfigurationHostApi.h" @interface FWFNavigationDelegateFlutterApiImpl () // InstanceManager must be weak to prevent a circular reference with the object it stores. @property(nonatomic, weak) FWFInstanceManager *instanceManager; @end @implementation FWFNavigationDelegateFlutterApiImpl - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager { self = [self initWithBinaryMessenger:binaryMessenger]; if (self) { _instanceManager = instanceManager; } return self; } - (long)identifierForDelegate:(FWFNavigationDelegate *)instance { return [self.instanceManager identifierWithStrongReferenceForInstance:instance]; } - (void)didFinishNavigationForDelegate:(FWFNavigationDelegate *)instance webView:(WKWebView *)webView URL:(NSString *)URL completion:(void (^)(NSError *_Nullable))completion { NSNumber *webViewIdentifier = @([self.instanceManager identifierWithStrongReferenceForInstance:webView]); [self didFinishNavigationForDelegateWithIdentifier:@([self identifierForDelegate:instance]) webViewIdentifier:webViewIdentifier URL:URL completion:completion]; } - (void)didStartProvisionalNavigationForDelegate:(FWFNavigationDelegate *)instance webView:(WKWebView *)webView URL:(NSString *)URL completion:(void (^)(NSError *_Nullable))completion { NSNumber *webViewIdentifier = @([self.instanceManager identifierWithStrongReferenceForInstance:webView]); [self didStartProvisionalNavigationForDelegateWithIdentifier:@([self identifierForDelegate:instance]) webViewIdentifier:webViewIdentifier URL:URL completion:completion]; } - (void) decidePolicyForNavigationActionForDelegate:(FWFNavigationDelegate *)instance webView:(WKWebView *)webView navigationAction:(WKNavigationAction *)navigationAction completion: (void (^)(FWFWKNavigationActionPolicyEnumData *_Nullable, NSError *_Nullable))completion { NSNumber *webViewIdentifier = @([self.instanceManager identifierWithStrongReferenceForInstance:webView]); FWFWKNavigationActionData *navigationActionData = FWFWKNavigationActionDataFromNavigationAction(navigationAction); [self decidePolicyForNavigationActionForDelegateWithIdentifier:@([self identifierForDelegate:instance]) webViewIdentifier:webViewIdentifier navigationAction:navigationActionData completion:completion]; } - (void)didFailNavigationForDelegate:(FWFNavigationDelegate *)instance webView:(WKWebView *)webView error:(NSError *)error completion:(void (^)(NSError *_Nullable))completion { NSNumber *webViewIdentifier = @([self.instanceManager identifierWithStrongReferenceForInstance:webView]); [self didFailNavigationForDelegateWithIdentifier:@([self identifierForDelegate:instance]) webViewIdentifier:webViewIdentifier error:FWFNSErrorDataFromNSError(error) completion:completion]; } - (void)didFailProvisionalNavigationForDelegate:(FWFNavigationDelegate *)instance webView:(WKWebView *)webView error:(NSError *)error completion:(void (^)(NSError *_Nullable))completion { NSNumber *webViewIdentifier = @([self.instanceManager identifierWithStrongReferenceForInstance:webView]); [self didFailProvisionalNavigationForDelegateWithIdentifier:@([self identifierForDelegate:instance]) webViewIdentifier:webViewIdentifier error:FWFNSErrorDataFromNSError(error) completion:completion]; } - (void)webViewWebContentProcessDidTerminateForDelegate:(FWFNavigationDelegate *)instance webView:(WKWebView *)webView completion:(void (^)(NSError *_Nullable))completion { NSNumber *webViewIdentifier = @([self.instanceManager identifierWithStrongReferenceForInstance:webView]); [self webViewWebContentProcessDidTerminateForDelegateWithIdentifier: @([self identifierForDelegate:instance]) webViewIdentifier:webViewIdentifier completion:completion]; } @end @implementation FWFNavigationDelegate - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager { self = [super initWithBinaryMessenger:binaryMessenger instanceManager:instanceManager]; if (self) { _navigationDelegateAPI = [[FWFNavigationDelegateFlutterApiImpl alloc] initWithBinaryMessenger:binaryMessenger instanceManager:instanceManager]; } return self; } - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { [self.navigationDelegateAPI didFinishNavigationForDelegate:self webView:webView URL:webView.URL.absoluteString completion:^(NSError *error) { NSAssert(!error, @"%@", error); }]; } - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation { [self.navigationDelegateAPI didStartProvisionalNavigationForDelegate:self webView:webView URL:webView.URL.absoluteString completion:^(NSError *error) { NSAssert(!error, @"%@", error); }]; } - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { [self.navigationDelegateAPI decidePolicyForNavigationActionForDelegate:self webView:webView navigationAction:navigationAction completion:^(FWFWKNavigationActionPolicyEnumData *policy, NSError *error) { NSAssert(!error, @"%@", error); decisionHandler( FWFWKNavigationActionPolicyFromEnumData(policy)); }]; } - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error { [self.navigationDelegateAPI didFailNavigationForDelegate:self webView:webView error:error completion:^(NSError *error) { NSAssert(!error, @"%@", error); }]; } - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error { [self.navigationDelegateAPI didFailProvisionalNavigationForDelegate:self webView:webView error:error completion:^(NSError *error) { NSAssert(!error, @"%@", error); }]; } - (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView { [self.navigationDelegateAPI webViewWebContentProcessDidTerminateForDelegate:self webView:webView completion:^(NSError *error) { NSAssert(!error, @"%@", error); }]; } @end @interface FWFNavigationDelegateHostApiImpl () // BinaryMessenger must be weak to prevent a circular reference with the host API it // references. @property(nonatomic, weak) id binaryMessenger; // InstanceManager must be weak to prevent a circular reference with the object it stores. @property(nonatomic, weak) FWFInstanceManager *instanceManager; @end @implementation FWFNavigationDelegateHostApiImpl - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager { self = [self init]; if (self) { _binaryMessenger = binaryMessenger; _instanceManager = instanceManager; } return self; } - (FWFNavigationDelegate *)navigationDelegateForIdentifier:(NSNumber *)identifier { return (FWFNavigationDelegate *)[self.instanceManager instanceForIdentifier:identifier.longValue]; } - (void)createWithIdentifier:(nonnull NSNumber *)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { FWFNavigationDelegate *navigationDelegate = [[FWFNavigationDelegate alloc] initWithBinaryMessenger:self.binaryMessenger instanceManager:self.instanceManager]; [self.instanceManager addDartCreatedInstance:navigationDelegate withIdentifier:identifier.longValue]; } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFObjectHostApi.h ================================================ // Copyright 2013 The Flutter 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 "FWFGeneratedWebKitApis.h" #import "FWFInstanceManager.h" NS_ASSUME_NONNULL_BEGIN /** * Flutter api implementation for NSObject. * * Handles making callbacks to Dart for an NSObject. */ @interface FWFObjectFlutterApiImpl : FWFNSObjectFlutterApi - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; - (void)observeValueForObject:(NSObject *)instance keyPath:(NSString *)keyPath object:(NSObject *)object change:(NSDictionary *)change completion:(void (^)(NSError *_Nullable))completion; @end /** * Implementation of NSObject for FWFObjectHostApiImpl. */ @interface FWFObject : NSObject @property(readonly, nonnull, nonatomic) FWFObjectFlutterApiImpl *objectApi; - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @end /** * Host api implementation for NSObject. * * Handles creating NSObject that intercommunicate with a paired Dart object. */ @interface FWFObjectHostApiImpl : NSObject - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFObjectHostApi.m ================================================ // Copyright 2013 The Flutter 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 "FWFObjectHostApi.h" #import "FWFDataConverters.h" @interface FWFObjectFlutterApiImpl () // InstanceManager must be weak to prevent a circular reference with the object it stores. @property(nonatomic, weak) FWFInstanceManager *instanceManager; @end @implementation FWFObjectFlutterApiImpl - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager { self = [self initWithBinaryMessenger:binaryMessenger]; if (self) { _instanceManager = instanceManager; } return self; } - (long)identifierForObject:(NSObject *)instance { return [self.instanceManager identifierWithStrongReferenceForInstance:instance]; } - (void)observeValueForObject:(NSObject *)instance keyPath:(NSString *)keyPath object:(NSObject *)object change:(NSDictionary *)change completion:(void (^)(NSError *_Nullable))completion { NSMutableArray *changeKeys = [NSMutableArray array]; NSMutableArray *changeValues = [NSMutableArray array]; [change enumerateKeysAndObjectsUsingBlock:^(NSKeyValueChangeKey key, id value, BOOL *stop) { [changeKeys addObject:FWFNSKeyValueChangeKeyEnumDataFromNSKeyValueChangeKey(key)]; [changeValues addObject:value]; }]; NSNumber *objectIdentifier = @([self.instanceManager identifierWithStrongReferenceForInstance:object]); [self observeValueForObjectWithIdentifier:@([self identifierForObject:instance]) keyPath:keyPath objectIdentifier:objectIdentifier changeKeys:changeKeys changeValues:changeValues completion:completion]; } @end @implementation FWFObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager { self = [self init]; if (self) { _objectApi = [[FWFObjectFlutterApiImpl alloc] initWithBinaryMessenger:binaryMessenger instanceManager:instanceManager]; } return self; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { [self.objectApi observeValueForObject:self keyPath:keyPath object:object change:change completion:^(NSError *error) { NSAssert(!error, @"%@", error); }]; } @end @interface FWFObjectHostApiImpl () // InstanceManager must be weak to prevent a circular reference with the object it stores. @property(nonatomic, weak) FWFInstanceManager *instanceManager; @end @implementation FWFObjectHostApiImpl - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager { self = [self init]; if (self) { _instanceManager = instanceManager; } return self; } - (NSObject *)objectForIdentifier:(NSNumber *)identifier { return (NSObject *)[self.instanceManager instanceForIdentifier:identifier.longValue]; } - (void)addObserverForObjectWithIdentifier:(nonnull NSNumber *)identifier observerIdentifier:(nonnull NSNumber *)observer keyPath:(nonnull NSString *)keyPath options: (nonnull NSArray *) options error:(FlutterError *_Nullable *_Nonnull)error { NSKeyValueObservingOptions optionsInt = 0; for (FWFNSKeyValueObservingOptionsEnumData *data in options) { optionsInt |= FWFNSKeyValueObservingOptionsFromEnumData(data); } [[self objectForIdentifier:identifier] addObserver:[self objectForIdentifier:observer] forKeyPath:keyPath options:optionsInt context:nil]; } - (void)removeObserverForObjectWithIdentifier:(nonnull NSNumber *)identifier observerIdentifier:(nonnull NSNumber *)observer keyPath:(nonnull NSString *)keyPath error:(FlutterError *_Nullable *_Nonnull)error { [[self objectForIdentifier:identifier] removeObserver:[self objectForIdentifier:observer] forKeyPath:keyPath]; } - (void)disposeObjectWithIdentifier:(nonnull NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error { [self.instanceManager removeInstanceWithIdentifier:identifier.longValue]; } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFPreferencesHostApi.h ================================================ // Copyright 2013 The Flutter 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 "FWFGeneratedWebKitApis.h" #import "FWFInstanceManager.h" NS_ASSUME_NONNULL_BEGIN /** * Host api implementation for WKPreferences. * * Handles creating WKPreferences that intercommunicate with a paired Dart object. */ @interface FWFPreferencesHostApiImpl : NSObject - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFPreferencesHostApi.m ================================================ // Copyright 2013 The Flutter 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 "FWFPreferencesHostApi.h" #import "FWFWebViewConfigurationHostApi.h" @interface FWFPreferencesHostApiImpl () // InstanceManager must be weak to prevent a circular reference with the object it stores. @property(nonatomic, weak) FWFInstanceManager *instanceManager; @end @implementation FWFPreferencesHostApiImpl - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager { self = [self init]; if (self) { _instanceManager = instanceManager; } return self; } - (WKPreferences *)preferencesForIdentifier:(NSNumber *)identifier { return (WKPreferences *)[self.instanceManager instanceForIdentifier:identifier.longValue]; } - (void)createWithIdentifier:(nonnull NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error { WKPreferences *preferences = [[WKPreferences alloc] init]; [self.instanceManager addDartCreatedInstance:preferences withIdentifier:identifier.longValue]; } - (void)createFromWebViewConfigurationWithIdentifier:(nonnull NSNumber *)identifier configurationIdentifier:(nonnull NSNumber *)configurationIdentifier error:(FlutterError *_Nullable *_Nonnull)error { WKWebViewConfiguration *configuration = (WKWebViewConfiguration *)[self.instanceManager instanceForIdentifier:configurationIdentifier.longValue]; [self.instanceManager addDartCreatedInstance:configuration.preferences withIdentifier:identifier.longValue]; } - (void)setJavaScriptEnabledForPreferencesWithIdentifier:(nonnull NSNumber *)identifier isEnabled:(nonnull NSNumber *)enabled error:(FlutterError *_Nullable *_Nonnull)error { [[self preferencesForIdentifier:identifier] setJavaScriptEnabled:enabled.boolValue]; } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScriptMessageHandlerHostApi.h ================================================ // Copyright 2013 The Flutter 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 "FWFGeneratedWebKitApis.h" #import "FWFInstanceManager.h" #import "FWFObjectHostApi.h" NS_ASSUME_NONNULL_BEGIN /** * Flutter api implementation for WKScriptMessageHandler. * * Handles making callbacks to Dart for a WKScriptMessageHandler. */ @interface FWFScriptMessageHandlerFlutterApiImpl : FWFWKScriptMessageHandlerFlutterApi - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @end /** * Implementation of WKScriptMessageHandler for FWFScriptMessageHandlerHostApiImpl. */ @interface FWFScriptMessageHandler : FWFObject @property(readonly, nonnull, nonatomic) FWFScriptMessageHandlerFlutterApiImpl *scriptMessageHandlerAPI; - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @end /** * Host api implementation for WKScriptMessageHandler. * * Handles creating WKScriptMessageHandler that intercommunicate with a paired Dart object. */ @interface FWFScriptMessageHandlerHostApiImpl : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScriptMessageHandlerHostApi.m ================================================ // Copyright 2013 The Flutter 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 "FWFScriptMessageHandlerHostApi.h" #import "FWFDataConverters.h" @interface FWFScriptMessageHandlerFlutterApiImpl () // InstanceManager must be weak to prevent a circular reference with the object it stores. @property(nonatomic, weak) FWFInstanceManager *instanceManager; @end @implementation FWFScriptMessageHandlerFlutterApiImpl - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager { self = [self initWithBinaryMessenger:binaryMessenger]; if (self) { _instanceManager = instanceManager; } return self; } - (long)identifierForHandler:(FWFScriptMessageHandler *)instance { return [self.instanceManager identifierWithStrongReferenceForInstance:instance]; } - (void)didReceiveScriptMessageForHandler:(FWFScriptMessageHandler *)instance userContentController:(WKUserContentController *)userContentController message:(WKScriptMessage *)message completion:(void (^)(NSError *_Nullable))completion { NSNumber *userContentControllerIdentifier = @([self.instanceManager identifierWithStrongReferenceForInstance:userContentController]); FWFWKScriptMessageData *messageData = FWFWKScriptMessageDataFromWKScriptMessage(message); [self didReceiveScriptMessageForHandlerWithIdentifier:@([self identifierForHandler:instance]) userContentControllerIdentifier:userContentControllerIdentifier message:messageData completion:completion]; } @end @implementation FWFScriptMessageHandler - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager { self = [super initWithBinaryMessenger:binaryMessenger instanceManager:instanceManager]; if (self) { _scriptMessageHandlerAPI = [[FWFScriptMessageHandlerFlutterApiImpl alloc] initWithBinaryMessenger:binaryMessenger instanceManager:instanceManager]; } return self; } - (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message { [self.scriptMessageHandlerAPI didReceiveScriptMessageForHandler:self userContentController:userContentController message:message completion:^(NSError *error) { NSAssert(!error, @"%@", error); }]; } @end @interface FWFScriptMessageHandlerHostApiImpl () // BinaryMessenger must be weak to prevent a circular reference with the host API it // references. @property(nonatomic, weak) id binaryMessenger; // InstanceManager must be weak to prevent a circular reference with the object it stores. @property(nonatomic, weak) FWFInstanceManager *instanceManager; @end @implementation FWFScriptMessageHandlerHostApiImpl - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager { self = [self init]; if (self) { _binaryMessenger = binaryMessenger; _instanceManager = instanceManager; } return self; } - (FWFScriptMessageHandler *)scriptMessageHandlerForIdentifier:(NSNumber *)identifier { return (FWFScriptMessageHandler *)[self.instanceManager instanceForIdentifier:identifier.longValue]; } - (void)createWithIdentifier:(nonnull NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error { FWFScriptMessageHandler *scriptMessageHandler = [[FWFScriptMessageHandler alloc] initWithBinaryMessenger:self.binaryMessenger instanceManager:self.instanceManager]; [self.instanceManager addDartCreatedInstance:scriptMessageHandler withIdentifier:identifier.longValue]; } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScrollViewHostApi.h ================================================ // Copyright 2013 The Flutter 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 "FWFGeneratedWebKitApis.h" #import "FWFInstanceManager.h" NS_ASSUME_NONNULL_BEGIN /** * Host api implementation for UIScrollView. * * Handles creating UIScrollView that intercommunicate with a paired Dart object. */ @interface FWFScrollViewHostApiImpl : NSObject - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFScrollViewHostApi.m ================================================ // Copyright 2013 The Flutter 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 "FWFScrollViewHostApi.h" #import "FWFWebViewHostApi.h" @interface FWFScrollViewHostApiImpl () // InstanceManager must be weak to prevent a circular reference with the object it stores. @property(nonatomic, weak) FWFInstanceManager *instanceManager; @end @implementation FWFScrollViewHostApiImpl - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager { self = [self init]; if (self) { _instanceManager = instanceManager; } return self; } - (UIScrollView *)scrollViewForIdentifier:(NSNumber *)identifier { return (UIScrollView *)[self.instanceManager instanceForIdentifier:identifier.longValue]; } - (void)createFromWebViewWithIdentifier:(nonnull NSNumber *)identifier webViewIdentifier:(nonnull NSNumber *)webViewIdentifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { WKWebView *webView = (WKWebView *)[self.instanceManager instanceForIdentifier:webViewIdentifier.longValue]; [self.instanceManager addDartCreatedInstance:webView.scrollView withIdentifier:identifier.longValue]; } - (NSArray *) contentOffsetForScrollViewWithIdentifier:(nonnull NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error { CGPoint point = [[self scrollViewForIdentifier:identifier] contentOffset]; return @[ @(point.x), @(point.y) ]; } - (void)scrollByForScrollViewWithIdentifier:(nonnull NSNumber *)identifier x:(nonnull NSNumber *)x y:(nonnull NSNumber *)y error:(FlutterError *_Nullable *_Nonnull)error { UIScrollView *scrollView = [self scrollViewForIdentifier:identifier]; CGPoint contentOffset = scrollView.contentOffset; [scrollView setContentOffset:CGPointMake(contentOffset.x + x.doubleValue, contentOffset.y + y.doubleValue)]; } - (void)setContentOffsetForScrollViewWithIdentifier:(nonnull NSNumber *)identifier toX:(nonnull NSNumber *)x y:(nonnull NSNumber *)y error:(FlutterError *_Nullable *_Nonnull)error { [[self scrollViewForIdentifier:identifier] setContentOffset:CGPointMake(x.doubleValue, y.doubleValue)]; } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIDelegateHostApi.h ================================================ // Copyright 2013 The Flutter 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 "FWFGeneratedWebKitApis.h" #import "FWFInstanceManager.h" #import "FWFObjectHostApi.h" #import "FWFWebViewConfigurationHostApi.h" NS_ASSUME_NONNULL_BEGIN /** * Flutter api implementation for WKUIDelegate. * * Handles making callbacks to Dart for a WKUIDelegate. */ @interface FWFUIDelegateFlutterApiImpl : FWFWKUIDelegateFlutterApi @property(readonly, nonatomic) FWFWebViewConfigurationFlutterApiImpl *webViewConfigurationFlutterApi; - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @end /** * Implementation of WKUIDelegate for FWFUIDelegateHostApiImpl. */ @interface FWFUIDelegate : FWFObject @property(readonly, nonnull, nonatomic) FWFUIDelegateFlutterApiImpl *UIDelegateAPI; - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @end /** * Host api implementation for WKUIDelegate. * * Handles creating WKUIDelegate that intercommunicate with a paired Dart object. */ @interface FWFUIDelegateHostApiImpl : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIDelegateHostApi.m ================================================ // Copyright 2013 The Flutter 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 "FWFUIDelegateHostApi.h" #import "FWFDataConverters.h" @interface FWFUIDelegateFlutterApiImpl () // BinaryMessenger must be weak to prevent a circular reference with the host API it // references. @property(nonatomic, weak) id binaryMessenger; // InstanceManager must be weak to prevent a circular reference with the object it stores. @property(nonatomic, weak) FWFInstanceManager *instanceManager; @end @implementation FWFUIDelegateFlutterApiImpl - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager { self = [self initWithBinaryMessenger:binaryMessenger]; if (self) { _binaryMessenger = binaryMessenger; _instanceManager = instanceManager; _webViewConfigurationFlutterApi = [[FWFWebViewConfigurationFlutterApiImpl alloc] initWithBinaryMessenger:binaryMessenger instanceManager:instanceManager]; } return self; } - (long)identifierForDelegate:(FWFUIDelegate *)instance { return [self.instanceManager identifierWithStrongReferenceForInstance:instance]; } - (void)onCreateWebViewForDelegate:(FWFUIDelegate *)instance webView:(WKWebView *)webView configuration:(WKWebViewConfiguration *)configuration navigationAction:(WKNavigationAction *)navigationAction completion:(void (^)(NSError *_Nullable))completion { if (![self.instanceManager containsInstance:configuration]) { [self.webViewConfigurationFlutterApi createWithConfiguration:configuration completion:^(NSError *error) { NSAssert(!error, @"%@", error); }]; } NSNumber *configurationIdentifier = @([self.instanceManager identifierWithStrongReferenceForInstance:configuration]); FWFWKNavigationActionData *navigationActionData = FWFWKNavigationActionDataFromNavigationAction(navigationAction); [self onCreateWebViewForDelegateWithIdentifier:@([self identifierForDelegate:instance]) webViewIdentifier: @([self.instanceManager identifierWithStrongReferenceForInstance:webView]) configurationIdentifier:configurationIdentifier navigationAction:navigationActionData completion:completion]; } @end @implementation FWFUIDelegate - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager { self = [super initWithBinaryMessenger:binaryMessenger instanceManager:instanceManager]; if (self) { _UIDelegateAPI = [[FWFUIDelegateFlutterApiImpl alloc] initWithBinaryMessenger:binaryMessenger instanceManager:instanceManager]; } return self; } - (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures { [self.UIDelegateAPI onCreateWebViewForDelegate:self webView:webView configuration:configuration navigationAction:navigationAction completion:^(NSError *error) { NSAssert(!error, @"%@", error); }]; return nil; } @end @interface FWFUIDelegateHostApiImpl () // BinaryMessenger must be weak to prevent a circular reference with the host API it // references. @property(nonatomic, weak) id binaryMessenger; // InstanceManager must be weak to prevent a circular reference with the object it stores. @property(nonatomic, weak) FWFInstanceManager *instanceManager; @end @implementation FWFUIDelegateHostApiImpl - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager { self = [self init]; if (self) { _binaryMessenger = binaryMessenger; _instanceManager = instanceManager; } return self; } - (FWFUIDelegate *)delegateForIdentifier:(NSNumber *)identifier { return (FWFUIDelegate *)[self.instanceManager instanceForIdentifier:identifier.longValue]; } - (void)createWithIdentifier:(nonnull NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error { FWFUIDelegate *uIDelegate = [[FWFUIDelegate alloc] initWithBinaryMessenger:self.binaryMessenger instanceManager:self.instanceManager]; [self.instanceManager addDartCreatedInstance:uIDelegate withIdentifier:identifier.longValue]; } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIViewHostApi.h ================================================ // Copyright 2013 The Flutter 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 "FWFGeneratedWebKitApis.h" #import "FWFInstanceManager.h" NS_ASSUME_NONNULL_BEGIN /** * Host api implementation for UIView. * * Handles creating UIView that intercommunicate with a paired Dart object. */ @interface FWFUIViewHostApiImpl : NSObject - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIViewHostApi.m ================================================ // Copyright 2013 The Flutter 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 "FWFUIViewHostApi.h" @interface FWFUIViewHostApiImpl () // InstanceManager must be weak to prevent a circular reference with the object it stores. @property(nonatomic, weak) FWFInstanceManager *instanceManager; @end @implementation FWFUIViewHostApiImpl - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager { self = [self init]; if (self) { _instanceManager = instanceManager; } return self; } - (UIView *)viewForIdentifier:(NSNumber *)identifier { return (UIView *)[self.instanceManager instanceForIdentifier:identifier.longValue]; } - (void)setBackgroundColorForViewWithIdentifier:(nonnull NSNumber *)identifier toValue:(nullable NSNumber *)color error:(FlutterError *_Nullable *_Nonnull)error { if (color == nil) { [[self viewForIdentifier:identifier] setBackgroundColor:nil]; } int colorInt = color.intValue; UIColor *colorObject = [UIColor colorWithRed:(colorInt >> 16 & 0xff) / 255.0 green:(colorInt >> 8 & 0xff) / 255.0 blue:(colorInt & 0xff) / 255.0 alpha:(colorInt >> 24 & 0xff) / 255.0]; [[self viewForIdentifier:identifier] setBackgroundColor:colorObject]; } - (void)setOpaqueForViewWithIdentifier:(nonnull NSNumber *)identifier isOpaque:(nonnull NSNumber *)opaque error:(FlutterError *_Nullable *_Nonnull)error { [[self viewForIdentifier:identifier] setOpaque:opaque.boolValue]; } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUserContentControllerHostApi.h ================================================ // Copyright 2013 The Flutter 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 "FWFGeneratedWebKitApis.h" #import "FWFInstanceManager.h" NS_ASSUME_NONNULL_BEGIN /** * Host api implementation for WKUserContentController. * * Handles creating WKUserContentController that intercommunicate with a paired Dart object. */ @interface FWFUserContentControllerHostApiImpl : NSObject - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUserContentControllerHostApi.m ================================================ // Copyright 2013 The Flutter 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 "FWFUserContentControllerHostApi.h" #import "FWFDataConverters.h" #import "FWFWebViewConfigurationHostApi.h" @interface FWFUserContentControllerHostApiImpl () // InstanceManager must be weak to prevent a circular reference with the object it stores. @property(nonatomic, weak) FWFInstanceManager *instanceManager; @end @implementation FWFUserContentControllerHostApiImpl - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager { self = [self init]; if (self) { _instanceManager = instanceManager; } return self; } - (WKUserContentController *)userContentControllerForIdentifier:(NSNumber *)identifier { return (WKUserContentController *)[self.instanceManager instanceForIdentifier:identifier.longValue]; } - (void)createFromWebViewConfigurationWithIdentifier:(nonnull NSNumber *)identifier configurationIdentifier:(nonnull NSNumber *)configurationIdentifier error:(FlutterError *_Nullable *_Nonnull)error { WKWebViewConfiguration *configuration = (WKWebViewConfiguration *)[self.instanceManager instanceForIdentifier:configurationIdentifier.longValue]; [self.instanceManager addDartCreatedInstance:configuration.userContentController withIdentifier:identifier.longValue]; } - (void)addScriptMessageHandlerForControllerWithIdentifier:(nonnull NSNumber *)identifier handlerIdentifier:(nonnull NSNumber *)handler ofName:(nonnull NSString *)name error: (FlutterError *_Nullable *_Nonnull)error { [[self userContentControllerForIdentifier:identifier] addScriptMessageHandler:(id)[self.instanceManager instanceForIdentifier:handler.longValue] name:name]; } - (void)removeScriptMessageHandlerForControllerWithIdentifier:(nonnull NSNumber *)identifier name:(nonnull NSString *)name error:(FlutterError *_Nullable *_Nonnull) error { [[self userContentControllerForIdentifier:identifier] removeScriptMessageHandlerForName:name]; } - (void)removeAllScriptMessageHandlersForControllerWithIdentifier:(nonnull NSNumber *)identifier error: (FlutterError *_Nullable *_Nonnull) error { if (@available(iOS 14.0, *)) { [[self userContentControllerForIdentifier:identifier] removeAllScriptMessageHandlers]; } else { *error = [FlutterError errorWithCode:@"FWFUnsupportedVersionError" message:@"removeAllScriptMessageHandlers is only supported on versions 14+." details:nil]; } } - (void)addUserScriptForControllerWithIdentifier:(nonnull NSNumber *)identifier userScript:(nonnull FWFWKUserScriptData *)userScript error:(FlutterError *_Nullable *_Nonnull)error { [[self userContentControllerForIdentifier:identifier] addUserScript:FWFWKUserScriptFromScriptData(userScript)]; } - (void)removeAllUserScriptsForControllerWithIdentifier:(nonnull NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error { [[self userContentControllerForIdentifier:identifier] removeAllUserScripts]; } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewConfigurationHostApi.h ================================================ // Copyright 2013 The Flutter 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 "FWFGeneratedWebKitApis.h" #import "FWFInstanceManager.h" #import "FWFObjectHostApi.h" NS_ASSUME_NONNULL_BEGIN /** * Flutter api implementation for WKWebViewConfiguration. * * Handles making callbacks to Dart for a WKWebViewConfiguration. */ @interface FWFWebViewConfigurationFlutterApiImpl : FWFWKWebViewConfigurationFlutterApi - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; - (void)createWithConfiguration:(WKWebViewConfiguration *)configuration completion:(void (^)(NSError *_Nullable))completion; @end /** * Implementation of WKWebViewConfiguration for FWFWebViewConfigurationHostApiImpl. */ @interface FWFWebViewConfiguration : WKWebViewConfiguration @property(readonly, nonnull, nonatomic) FWFObjectFlutterApiImpl *objectApi; - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @end /** * Host api implementation for WKWebViewConfiguration. * * Handles creating WKWebViewConfiguration that intercommunicate with a paired Dart object. */ @interface FWFWebViewConfigurationHostApiImpl : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewConfigurationHostApi.m ================================================ // Copyright 2013 The Flutter 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 "FWFWebViewConfigurationHostApi.h" #import "FWFDataConverters.h" #import "FWFWebViewConfigurationHostApi.h" @interface FWFWebViewConfigurationFlutterApiImpl () // InstanceManager must be weak to prevent a circular reference with the object it stores. @property(nonatomic, weak) FWFInstanceManager *instanceManager; @end @implementation FWFWebViewConfigurationFlutterApiImpl - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager { self = [self initWithBinaryMessenger:binaryMessenger]; if (self) { _instanceManager = instanceManager; } return self; } - (void)createWithConfiguration:(WKWebViewConfiguration *)configuration completion:(void (^)(NSError *_Nullable))completion { long identifier = [self.instanceManager addHostCreatedInstance:configuration]; [self createWithIdentifier:@(identifier) completion:completion]; } @end @implementation FWFWebViewConfiguration - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager { self = [self init]; if (self) { _objectApi = [[FWFObjectFlutterApiImpl alloc] initWithBinaryMessenger:binaryMessenger instanceManager:instanceManager]; } return self; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { [self.objectApi observeValueForObject:self keyPath:keyPath object:object change:change completion:^(NSError *error) { NSAssert(!error, @"%@", error); }]; } @end @interface FWFWebViewConfigurationHostApiImpl () // BinaryMessenger must be weak to prevent a circular reference with the host API it // references. @property(nonatomic, weak) id binaryMessenger; // InstanceManager must be weak to prevent a circular reference with the object it stores. @property(nonatomic, weak) FWFInstanceManager *instanceManager; @end @implementation FWFWebViewConfigurationHostApiImpl - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager { self = [self init]; if (self) { _binaryMessenger = binaryMessenger; _instanceManager = instanceManager; } return self; } - (WKWebViewConfiguration *)webViewConfigurationForIdentifier:(NSNumber *)identifier { return (WKWebViewConfiguration *)[self.instanceManager instanceForIdentifier:identifier.longValue]; } - (void)createWithIdentifier:(nonnull NSNumber *)identifier error:(FlutterError *_Nullable *_Nonnull)error { FWFWebViewConfiguration *webViewConfiguration = [[FWFWebViewConfiguration alloc] initWithBinaryMessenger:self.binaryMessenger instanceManager:self.instanceManager]; [self.instanceManager addDartCreatedInstance:webViewConfiguration withIdentifier:identifier.longValue]; } - (void)createFromWebViewWithIdentifier:(nonnull NSNumber *)identifier webViewIdentifier:(nonnull NSNumber *)webViewIdentifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { WKWebView *webView = (WKWebView *)[self.instanceManager instanceForIdentifier:webViewIdentifier.longValue]; [self.instanceManager addDartCreatedInstance:webView.configuration withIdentifier:identifier.longValue]; } - (void)setAllowsInlineMediaPlaybackForConfigurationWithIdentifier:(nonnull NSNumber *)identifier isAllowed:(nonnull NSNumber *)allow error: (FlutterError *_Nullable *_Nonnull) error { [[self webViewConfigurationForIdentifier:identifier] setAllowsInlineMediaPlayback:allow.boolValue]; } - (void) setMediaTypesRequiresUserActionForConfigurationWithIdentifier:(nonnull NSNumber *)identifier forTypes: (nonnull NSArray< FWFWKAudiovisualMediaTypeEnumData *> *)types error: (FlutterError *_Nullable *_Nonnull) error { NSAssert(types.count, @"Types must not be empty."); WKWebViewConfiguration *configuration = (WKWebViewConfiguration *)[self webViewConfigurationForIdentifier:identifier]; if (@available(iOS 10.0, *)) { WKAudiovisualMediaTypes typesInt = 0; for (FWFWKAudiovisualMediaTypeEnumData *data in types) { typesInt |= FWFWKAudiovisualMediaTypeFromEnumData(data); } [configuration setMediaTypesRequiringUserActionForPlayback:typesInt]; } else { for (FWFWKAudiovisualMediaTypeEnumData *data in types) { switch (data.value) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" case FWFWKAudiovisualMediaTypeEnumNone: configuration.requiresUserActionForMediaPlayback = false; break; case FWFWKAudiovisualMediaTypeEnumAudio: case FWFWKAudiovisualMediaTypeEnumVideo: case FWFWKAudiovisualMediaTypeEnumAll: configuration.requiresUserActionForMediaPlayback = true; break; #pragma clang diagnostic pop } } } } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.h ================================================ // Copyright 2013 The Flutter 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 NS_ASSUME_NONNULL_BEGIN /** * App and package facing native API provided by the `webview_flutter_wkwebview` plugin. * * This class follows the convention of breaking changes of the Dart API, which means that any * changes to the class that are not backwards compatible will only be made with a major version * change of the plugin. Native code other than this external API does not follow breaking change * conventions, so app or plugin clients should not use any other native APIs. */ @interface FWFWebViewFlutterWKWebViewExternalAPI : NSObject /** * Retrieves the `WKWebView` that is associated with `identifier`. * * See the Dart method `WebKitWebViewController.webViewIdentifier` to get the identifier of an * underlying `WKWebView`. * * @param identifier The associated identifier of the `WebView`. * @param registry The plugin registry the `FLTWebViewFlutterPlugin` should belong to. If * the registry doesn't contain an attached instance of `FLTWebViewFlutterPlugin`, * this method returns nil. * @return The `WKWebView` associated with `identifier` or nil if a `WKWebView` instance associated * with `identifier` could not be found. */ + (nullable WKWebView *)webViewForIdentifier:(long)identifier withPluginRegistry:(id)registry; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewFlutterWKWebViewExternalAPI.m ================================================ // Copyright 2013 The Flutter 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 "FWFWebViewFlutterWKWebViewExternalAPI.h" #import "FWFInstanceManager.h" @implementation FWFWebViewFlutterWKWebViewExternalAPI + (nullable WKWebView *)webViewForIdentifier:(long)identifier withPluginRegistry:(id)registry { FWFInstanceManager *instanceManager = (FWFInstanceManager *)[registry valuePublishedByPlugin:@"FLTWebViewFlutterPlugin"]; id instance = [instanceManager instanceForIdentifier:identifier]; if ([instance isKindOfClass:[WKWebView class]]) { return instance; } return nil; } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewHostApi.h ================================================ // Copyright 2013 The Flutter 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 "FWFGeneratedWebKitApis.h" #import "FWFInstanceManager.h" #import "FWFObjectHostApi.h" NS_ASSUME_NONNULL_BEGIN /** * A set of Flutter and Dart assets used by a `FlutterEngine` to initialize execution. * * Default implementation delegates methods to FlutterDartProject. */ @interface FWFAssetManager : NSObject - (NSString *)lookupKeyForAsset:(NSString *)asset; @end /** * Implementation of WKWebView that can be used as a FlutterPlatformView. */ @interface FWFWebView : WKWebView @property(readonly, nonnull, nonatomic) FWFObjectFlutterApiImpl *objectApi; - (instancetype)initWithFrame:(CGRect)frame configuration:(nonnull WKWebViewConfiguration *)configuration binaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; @end /** * Host api implementation for WKWebView. * * Handles creating WKWebViews that intercommunicate with a paired Dart object. */ @interface FWFWebViewHostApiImpl : NSObject - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager; - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager bundle:(NSBundle *)bundle assetManager:(FWFAssetManager *)assetManager; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebViewHostApi.m ================================================ // Copyright 2013 The Flutter 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 "FWFWebViewHostApi.h" #import "FWFDataConverters.h" @implementation FWFAssetManager - (NSString *)lookupKeyForAsset:(NSString *)asset { return [FlutterDartProject lookupKeyForAsset:asset]; } @end @implementation FWFWebView - (instancetype)initWithFrame:(CGRect)frame configuration:(nonnull WKWebViewConfiguration *)configuration binaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager { self = [self initWithFrame:frame configuration:configuration]; if (self) { _objectApi = [[FWFObjectFlutterApiImpl alloc] initWithBinaryMessenger:binaryMessenger instanceManager:instanceManager]; if (@available(iOS 11.0, *)) { self.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; if (@available(iOS 13.0, *)) { self.scrollView.automaticallyAdjustsScrollIndicatorInsets = NO; } } } return self; } - (void)setFrame:(CGRect)frame { [super setFrame:frame]; // Prevents the contentInsets from being adjusted by iOS and gives control to Flutter. self.scrollView.contentInset = UIEdgeInsetsZero; if (@available(iOS 11, *)) { // Above iOS 11, adjust contentInset to compensate the adjustedContentInset so the sum will // always be 0. if (UIEdgeInsetsEqualToEdgeInsets(self.scrollView.adjustedContentInset, UIEdgeInsetsZero)) { return; } UIEdgeInsets insetToAdjust = self.scrollView.adjustedContentInset; self.scrollView.contentInset = UIEdgeInsetsMake(-insetToAdjust.top, -insetToAdjust.left, -insetToAdjust.bottom, -insetToAdjust.right); } } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { [self.objectApi observeValueForObject:self keyPath:keyPath object:object change:change completion:^(NSError *error) { NSAssert(!error, @"%@", error); }]; } - (nonnull UIView *)view { return self; } @end @interface FWFWebViewHostApiImpl () // BinaryMessenger must be weak to prevent a circular reference with the host API it // references. @property(nonatomic, weak) id binaryMessenger; // InstanceManager must be weak to prevent a circular reference with the object it stores. @property(nonatomic, weak) FWFInstanceManager *instanceManager; @property NSBundle *bundle; @property FWFAssetManager *assetManager; @end @implementation FWFWebViewHostApiImpl - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager { return [self initWithBinaryMessenger:binaryMessenger instanceManager:instanceManager bundle:[NSBundle mainBundle] assetManager:[[FWFAssetManager alloc] init]]; } - (instancetype)initWithBinaryMessenger:(id)binaryMessenger instanceManager:(FWFInstanceManager *)instanceManager bundle:(NSBundle *)bundle assetManager:(FWFAssetManager *)assetManager { self = [self init]; if (self) { _binaryMessenger = binaryMessenger; _instanceManager = instanceManager; _bundle = bundle; _assetManager = assetManager; } return self; } - (FWFWebView *)webViewForIdentifier:(NSNumber *)identifier { return (FWFWebView *)[self.instanceManager instanceForIdentifier:identifier.longValue]; } + (nonnull FlutterError *)errorForURLString:(nonnull NSString *)string { NSString *errorDetails = [NSString stringWithFormat:@"Initializing NSURL with the supplied " @"'%@' path resulted in a nil value.", string]; return [FlutterError errorWithCode:@"FWFURLParsingError" message:@"Failed parsing file path." details:errorDetails]; } - (void)createWithIdentifier:(nonnull NSNumber *)identifier configurationIdentifier:(nonnull NSNumber *)configurationIdentifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { WKWebViewConfiguration *configuration = (WKWebViewConfiguration *)[self.instanceManager instanceForIdentifier:configurationIdentifier.longValue]; FWFWebView *webView = [[FWFWebView alloc] initWithFrame:CGRectMake(0, 0, 0, 0) configuration:configuration binaryMessenger:self.binaryMessenger instanceManager:self.instanceManager]; [self.instanceManager addDartCreatedInstance:webView withIdentifier:identifier.longValue]; } - (void)loadRequestForWebViewWithIdentifier:(nonnull NSNumber *)identifier request:(nonnull FWFNSUrlRequestData *)request error: (FlutterError *_Nullable __autoreleasing *_Nonnull)error { NSURLRequest *urlRequest = FWFNSURLRequestFromRequestData(request); if (!urlRequest) { *error = [FlutterError errorWithCode:@"FWFURLRequestParsingError" message:@"Failed instantiating an NSURLRequest." details:[NSString stringWithFormat:@"URL was: '%@'", request.url]]; return; } [[self webViewForIdentifier:identifier] loadRequest:urlRequest]; } - (void)setUserAgentForWebViewWithIdentifier:(nonnull NSNumber *)identifier userAgent:(nullable NSString *)userAgent error:(FlutterError *_Nullable __autoreleasing *_Nonnull) error { [[self webViewForIdentifier:identifier] setCustomUserAgent:userAgent]; } - (nullable NSNumber *) canGoBackForWebViewWithIdentifier:(nonnull NSNumber *)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { return @([self webViewForIdentifier:identifier].canGoBack); } - (nullable NSString *) URLForWebViewWithIdentifier:(nonnull NSNumber *)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { return [self webViewForIdentifier:identifier].URL.absoluteString; } - (nullable NSNumber *) canGoForwardForWebViewWithIdentifier:(nonnull NSNumber *)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { return @([[self webViewForIdentifier:identifier] canGoForward]); } - (nullable NSNumber *) estimatedProgressForWebViewWithIdentifier:(nonnull NSNumber *)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull) error { return @([[self webViewForIdentifier:identifier] estimatedProgress]); } - (void)evaluateJavaScriptForWebViewWithIdentifier:(nonnull NSNumber *)identifier javaScriptString:(nonnull NSString *)javaScriptString completion: (nonnull void (^)(id _Nullable, FlutterError *_Nullable))completion { [[self webViewForIdentifier:identifier] evaluateJavaScript:javaScriptString completionHandler:^(id _Nullable result, NSError *_Nullable error) { id returnValue = nil; FlutterError *flutterError = nil; if (!error) { if (!result || [result isKindOfClass:[NSString class]] || [result isKindOfClass:[NSNumber class]]) { returnValue = result; } else if (![result isKindOfClass:[NSNull class]]) { NSString *className = NSStringFromClass([result class]); NSLog(@"Return type of evaluateJavaScript is not directly supported: %@. Returned " @"description of value.", className); returnValue = [result description]; } } else { flutterError = [FlutterError errorWithCode:@"FWFEvaluateJavaScriptError" message:@"Failed evaluating JavaScript." details:FWFNSErrorDataFromNSError(error)]; } completion(returnValue, flutterError); }]; } - (void)goBackForWebViewWithIdentifier:(nonnull NSNumber *)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { [[self webViewForIdentifier:identifier] goBack]; } - (void)goForwardForWebViewWithIdentifier:(nonnull NSNumber *)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { [[self webViewForIdentifier:identifier] goForward]; } - (void)loadAssetForWebViewWithIdentifier:(nonnull NSNumber *)identifier assetKey:(nonnull NSString *)key error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { NSString *assetFilePath = [self.assetManager lookupKeyForAsset:key]; NSURL *url = [self.bundle URLForResource:[assetFilePath stringByDeletingPathExtension] withExtension:assetFilePath.pathExtension]; if (!url) { *error = [FWFWebViewHostApiImpl errorForURLString:assetFilePath]; } else { [[self webViewForIdentifier:identifier] loadFileURL:url allowingReadAccessToURL:[url URLByDeletingLastPathComponent]]; } } - (void)loadFileForWebViewWithIdentifier:(nonnull NSNumber *)identifier fileURL:(nonnull NSString *)url readAccessURL:(nonnull NSString *)readAccessUrl error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { NSURL *fileURL = [NSURL fileURLWithPath:url isDirectory:NO]; NSURL *readAccessNSURL = [NSURL fileURLWithPath:readAccessUrl isDirectory:YES]; if (!fileURL) { *error = [FWFWebViewHostApiImpl errorForURLString:url]; } else if (!readAccessNSURL) { *error = [FWFWebViewHostApiImpl errorForURLString:readAccessUrl]; } else { [[self webViewForIdentifier:identifier] loadFileURL:fileURL allowingReadAccessToURL:readAccessNSURL]; } } - (void)loadHTMLForWebViewWithIdentifier:(nonnull NSNumber *)identifier HTMLString:(nonnull NSString *)string baseURL:(nullable NSString *)baseUrl error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { [[self webViewForIdentifier:identifier] loadHTMLString:string baseURL:[NSURL URLWithString:baseUrl]]; } - (void)reloadWebViewWithIdentifier:(nonnull NSNumber *)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { [[self webViewForIdentifier:identifier] reload]; } - (void) setAllowsBackForwardForWebViewWithIdentifier:(nonnull NSNumber *)identifier isAllowed:(nonnull NSNumber *)allow error:(FlutterError *_Nullable __autoreleasing *_Nonnull) error { [[self webViewForIdentifier:identifier] setAllowsBackForwardNavigationGestures:allow.boolValue]; } - (void) setNavigationDelegateForWebViewWithIdentifier:(nonnull NSNumber *)identifier delegateIdentifier:(nullable NSNumber *)navigationDelegateIdentifier error: (FlutterError *_Nullable __autoreleasing *_Nonnull) error { id navigationDelegate = (id)[self.instanceManager instanceForIdentifier:navigationDelegateIdentifier.longValue]; [[self webViewForIdentifier:identifier] setNavigationDelegate:navigationDelegate]; } - (void)setUIDelegateForWebViewWithIdentifier:(nonnull NSNumber *)identifier delegateIdentifier:(nullable NSNumber *)uiDelegateIdentifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull) error { id navigationDelegate = (id)[self.instanceManager instanceForIdentifier:uiDelegateIdentifier.longValue]; [[self webViewForIdentifier:identifier] setUIDelegate:navigationDelegate]; } - (nullable NSString *) titleForWebViewWithIdentifier:(nonnull NSNumber *)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { return [[self webViewForIdentifier:identifier] title]; } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebsiteDataStoreHostApi.h ================================================ // Copyright 2013 The Flutter 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 "FWFGeneratedWebKitApis.h" #import "FWFInstanceManager.h" NS_ASSUME_NONNULL_BEGIN /** * Host api implementation for WKWebsiteDataStore. * * Handles creating WKWebsiteDataStore that intercommunicate with a paired Dart object. */ @interface FWFWebsiteDataStoreHostApiImpl : NSObject - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager; @end NS_ASSUME_NONNULL_END ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFWebsiteDataStoreHostApi.m ================================================ // Copyright 2013 The Flutter 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 "FWFWebsiteDataStoreHostApi.h" #import "FWFDataConverters.h" #import "FWFWebViewConfigurationHostApi.h" @interface FWFWebsiteDataStoreHostApiImpl () // InstanceManager must be weak to prevent a circular reference with the object it stores. @property(nonatomic, weak) FWFInstanceManager *instanceManager; @end @implementation FWFWebsiteDataStoreHostApiImpl - (instancetype)initWithInstanceManager:(FWFInstanceManager *)instanceManager { self = [self init]; if (self) { _instanceManager = instanceManager; } return self; } - (WKWebsiteDataStore *)websiteDataStoreForIdentifier:(NSNumber *)identifier { return (WKWebsiteDataStore *)[self.instanceManager instanceForIdentifier:identifier.longValue]; } - (void)createFromWebViewConfigurationWithIdentifier:(nonnull NSNumber *)identifier configurationIdentifier:(nonnull NSNumber *)configurationIdentifier error:(FlutterError *_Nullable *_Nonnull)error { WKWebViewConfiguration *configuration = (WKWebViewConfiguration *)[self.instanceManager instanceForIdentifier:configurationIdentifier.longValue]; [self.instanceManager addDartCreatedInstance:configuration.websiteDataStore withIdentifier:identifier.longValue]; } - (void)createDefaultDataStoreWithIdentifier:(nonnull NSNumber *)identifier error:(FlutterError *_Nullable __autoreleasing *_Nonnull) error { [self.instanceManager addDartCreatedInstance:[WKWebsiteDataStore defaultDataStore] withIdentifier:identifier.longValue]; } - (void) removeDataFromDataStoreWithIdentifier:(nonnull NSNumber *)identifier ofTypes: (nonnull NSArray *)dataTypes modifiedSince:(nonnull NSNumber *)modificationTimeInSecondsSinceEpoch completion:(nonnull void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion { NSMutableSet *stringDataTypes = [NSMutableSet set]; for (FWFWKWebsiteDataTypeEnumData *type in dataTypes) { [stringDataTypes addObject:FWFWKWebsiteDataTypeFromEnumData(type)]; } WKWebsiteDataStore *dataStore = [self websiteDataStoreForIdentifier:identifier]; [dataStore fetchDataRecordsOfTypes:stringDataTypes completionHandler:^(NSArray *records) { [dataStore removeDataOfTypes:stringDataTypes modifiedSince:[NSDate dateWithTimeIntervalSince1970: modificationTimeInSecondsSinceEpoch.doubleValue] completionHandler:^{ completion([NSNumber numberWithBool:(records.count > 0)], nil); }]; }]; } @end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FlutterWebView.modulemap ================================================ framework module webview_flutter_wkwebview { umbrella header "webview-umbrella.h" export * module * { export * } explicit module Test { header "FWFInstanceManager_Test.h" } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/webview-umbrella.h ================================================ // Copyright 2013 The Flutter 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 #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/ios/webview_flutter_wkwebview.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| s.name = 'webview_flutter_wkwebview' s.version = '0.0.1' s.summary = 'A WebView Plugin for Flutter.' s.description = <<-DESC A Flutter plugin that provides a WebView widget. 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/webview_flutter/webview_flutter_wkwebview' } s.documentation_url = 'https://pub.dev/packages/webview_flutter' s.source_files = 'Classes/**/*.{h,m}' s.public_header_files = 'Classes/**/*.h' s.module_map = 'Classes/FlutterWebView.modulemap' s.dependency 'Flutter' s.platform = :ios, '9.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } end ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/instance_manager.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; /// An immutable object that can provide functional copies of itself. /// /// All implementers are expected to be immutable as defined by the annotation. @immutable mixin Copyable { /// Instantiates and returns a functionally identical object to oneself. /// /// Outside of tests, this method should only ever be called by /// [InstanceManager]. /// /// Subclasses should always override their parent's implementation of this /// method. @protected Copyable copy(); } /// Maintains instances used to communicate with the native objects they /// represent. /// /// Added instances are stored as weak references and their copies are stored /// as strong references to maintain access to their variables and callback /// methods. Both are stored with the same identifier. /// /// When a weak referenced instance becomes inaccessible, /// [onWeakReferenceRemoved] is called with its associated identifier. /// /// If an instance is retrieved and has the possibility to be used, /// (e.g. calling [getInstanceWithWeakReference]) a copy of the strong reference /// is added as a weak reference with the same identifier. This prevents a /// scenario where the weak referenced instance was released and then later /// returned by the host platform. class InstanceManager { /// Constructs an [InstanceManager]. InstanceManager({required void Function(int) onWeakReferenceRemoved}) { this.onWeakReferenceRemoved = (int identifier) { _weakInstances.remove(identifier); onWeakReferenceRemoved(identifier); }; _finalizer = Finalizer(this.onWeakReferenceRemoved); } // Identifiers are locked to a specific range to avoid collisions with objects // created simultaneously by the host platform. // Host uses identifiers >= 2^16 and Dart is expected to use values n where, // 0 <= n < 2^16. static const int _maxDartCreatedIdentifier = 65536; // Expando is used because it doesn't prevent its keys from becoming // inaccessible. This allows the manager to efficiently retrieve an identifier // of an instance without holding a strong reference to that instance. // // It also doesn't use `==` to search for identifiers, which would lead to an // infinite loop when comparing an object to its copy. (i.e. which was caused // by calling instanceManager.getIdentifier() inside of `==` while this was a // HashMap). final Expando _identifiers = Expando(); final Map> _weakInstances = >{}; final Map _strongInstances = {}; late final Finalizer _finalizer; int _nextIdentifier = 0; /// Called when a weak referenced instance is removed by [removeWeakReference] /// or becomes inaccessible. late final void Function(int) onWeakReferenceRemoved; /// Adds a new instance that was instantiated by Dart. /// /// In other words, Dart wants to add a new instance that will represent /// an object that will be instantiated on the host platform. /// /// Throws assertion error if the instance has already been added. /// /// Returns the randomly generated id of the [instance] added. int addDartCreatedInstance(Copyable instance) { assert(getIdentifier(instance) == null); final int identifier = _nextUniqueIdentifier(); _addInstanceWithIdentifier(instance, identifier); return identifier; } /// Removes the instance, if present, and call [onWeakReferenceRemoved] with /// its identifier. /// /// Returns the identifier associated with the removed instance. Otherwise, /// `null` if the instance was not found in this manager. /// /// This does not remove the the strong referenced instance associated with /// [instance]. This can be done with [remove]. int? removeWeakReference(Copyable instance) { final int? identifier = getIdentifier(instance); if (identifier == null) { return null; } _identifiers[instance] = null; _finalizer.detach(instance); onWeakReferenceRemoved(identifier); return identifier; } /// Removes [identifier] and its associated strongly referenced instance, if /// present, from the manager. /// /// Returns the strong referenced instance associated with [identifier] before /// it was removed. Returns `null` if [identifier] was not associated with /// any strong reference. /// /// This does not remove the the weak referenced instance associtated with /// [identifier]. This can be done with [removeWeakReference]. T? remove(int identifier) { return _strongInstances.remove(identifier) as T?; } /// Retrieves the instance associated with identifier. /// /// The value returned is chosen from the following order: /// /// 1. A weakly referenced instance associated with identifier. /// 2. If the only instance associated with identifier is a strongly /// referenced instance, a copy of the instance is added as a weak reference /// with the same identifier. Returning the newly created copy. /// 3. If no instance is associated with identifier, returns null. /// /// This method also expects the host `InstanceManager` to have a strong /// reference to the instance the identifier is associated with. T? getInstanceWithWeakReference(int identifier) { final Copyable? weakInstance = _weakInstances[identifier]?.target; if (weakInstance == null) { final Copyable? strongInstance = _strongInstances[identifier]; if (strongInstance != null) { final Copyable copy = strongInstance.copy(); _identifiers[copy] = identifier; _weakInstances[identifier] = WeakReference(copy); _finalizer.attach(copy, identifier, detach: copy); return copy as T; } return strongInstance as T?; } return weakInstance as T; } /// Retrieves the identifier associated with instance. int? getIdentifier(Copyable instance) { return _identifiers[instance]; } /// Adds a new instance that was instantiated by the host platform. /// /// In other words, the host platform wants to add a new instance that /// represents an object on the host platform. Stored with [identifier]. /// /// Throws assertion error if the instance or its identifier has already been /// added. /// /// Returns unique identifier of the [instance] added. void addHostCreatedInstance(Copyable instance, int identifier) { assert(!containsIdentifier(identifier)); assert(getIdentifier(instance) == null); assert(identifier >= 0); _addInstanceWithIdentifier(instance, identifier); } void _addInstanceWithIdentifier(Copyable instance, int identifier) { _identifiers[instance] = identifier; _weakInstances[identifier] = WeakReference(instance); _finalizer.attach(instance, identifier, detach: instance); final Copyable copy = instance.copy(); _identifiers[copy] = identifier; _strongInstances[identifier] = copy; } /// Whether this manager contains the given [identifier]. bool containsIdentifier(int identifier) { return _weakInstances.containsKey(identifier) || _strongInstances.containsKey(identifier); } int _nextUniqueIdentifier() { late int identifier; do { identifier = _nextIdentifier; _nextIdentifier = (_nextIdentifier + 1) % _maxDartCreatedIdentifier; } while (containsIdentifier(identifier)); return identifier; } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/weak_reference_utils.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /// Helper method for creating callbacks methods with a weak reference. /// /// Example: /// ``` /// final JavascriptChannelRegistry javascriptChannelRegistry = ... /// /// final WKScriptMessageHandler handler = WKScriptMessageHandler( /// didReceiveScriptMessage: withWeakRefenceTo( /// javascriptChannelRegistry, /// (WeakReference weakReference) { /// return ( /// WKUserContentController userContentController, /// WKScriptMessage message, /// ) { /// weakReference.target?.onJavascriptChannelMessage( /// message.name, /// message.body!.toString(), /// ); /// }; /// }, /// ), /// ); /// ``` S withWeakRefenceTo( T reference, S Function(WeakReference weakReference) onCreate, ) { final WeakReference weakReference = WeakReference(reference); return onCreate(weakReference); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.g.dart ================================================ // Copyright 2013 The Flutter 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.13), 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'; /// Mirror of NSKeyValueObservingOptions. /// /// See https://developer.apple.com/documentation/foundation/nskeyvalueobservingoptions?language=objc. enum NSKeyValueObservingOptionsEnum { newValue, oldValue, initialValue, priorNotification, } /// Mirror of NSKeyValueChange. /// /// See https://developer.apple.com/documentation/foundation/nskeyvaluechange?language=objc. enum NSKeyValueChangeEnum { setting, insertion, removal, replacement, } /// Mirror of NSKeyValueChangeKey. /// /// See https://developer.apple.com/documentation/foundation/nskeyvaluechangekey?language=objc. enum NSKeyValueChangeKeyEnum { indexes, kind, newValue, notificationIsPrior, oldValue, } /// Mirror of WKUserScriptInjectionTime. /// /// See https://developer.apple.com/documentation/webkit/wkuserscriptinjectiontime?language=objc. enum WKUserScriptInjectionTimeEnum { atDocumentStart, atDocumentEnd, } /// Mirror of WKAudiovisualMediaTypes. /// /// See [WKAudiovisualMediaTypes](https://developer.apple.com/documentation/webkit/wkaudiovisualmediatypes?language=objc). enum WKAudiovisualMediaTypeEnum { none, audio, video, all, } /// Mirror of WKWebsiteDataTypes. /// /// See https://developer.apple.com/documentation/webkit/wkwebsitedatarecord/data_store_record_types?language=objc. enum WKWebsiteDataTypeEnum { cookies, memoryCache, diskCache, offlineWebApplicationCache, localStorage, sessionStorage, webSQLDatabases, indexedDBDatabases, } /// Mirror of WKNavigationActionPolicy. /// /// See https://developer.apple.com/documentation/webkit/wknavigationactionpolicy?language=objc. enum WKNavigationActionPolicyEnum { allow, cancel, } /// Mirror of NSHTTPCookiePropertyKey. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiepropertykey. enum NSHttpCookiePropertyKeyEnum { comment, commentUrl, discard, domain, expires, maximumAge, name, originUrl, path, port, sameSitePolicy, secure, value, version, } /// An object that contains information about an action that causes navigation /// to occur. /// /// Wraps [WKNavigationType](https://developer.apple.com/documentation/webkit/wknavigationaction?language=objc). enum WKNavigationType { /// A link activation. /// /// See https://developer.apple.com/documentation/webkit/wknavigationtype/wknavigationtypelinkactivated?language=objc. linkActivated, /// A request to submit a form. /// /// See https://developer.apple.com/documentation/webkit/wknavigationtype/wknavigationtypeformsubmitted?language=objc. submitted, /// A request for the frame’s next or previous item. /// /// See https://developer.apple.com/documentation/webkit/wknavigationtype/wknavigationtypebackforward?language=objc. backForward, /// A request to reload the webpage. /// /// See https://developer.apple.com/documentation/webkit/wknavigationtype/wknavigationtypereload?language=objc. reload, /// A request to resubmit a form. /// /// See https://developer.apple.com/documentation/webkit/wknavigationtype/wknavigationtypeformresubmitted?language=objc. formResubmitted, /// A navigation request that originates for some other reason. /// /// See https://developer.apple.com/documentation/webkit/wknavigationtype/wknavigationtypeother?language=objc. other, } class NSKeyValueObservingOptionsEnumData { NSKeyValueObservingOptionsEnumData({ required this.value, }); NSKeyValueObservingOptionsEnum value; Object encode() { return [ value.index, ]; } static NSKeyValueObservingOptionsEnumData decode(Object result) { result as List; return NSKeyValueObservingOptionsEnumData( value: NSKeyValueObservingOptionsEnum.values[result[0]! as int], ); } } class NSKeyValueChangeKeyEnumData { NSKeyValueChangeKeyEnumData({ required this.value, }); NSKeyValueChangeKeyEnum value; Object encode() { return [ value.index, ]; } static NSKeyValueChangeKeyEnumData decode(Object result) { result as List; return NSKeyValueChangeKeyEnumData( value: NSKeyValueChangeKeyEnum.values[result[0]! as int], ); } } class WKUserScriptInjectionTimeEnumData { WKUserScriptInjectionTimeEnumData({ required this.value, }); WKUserScriptInjectionTimeEnum value; Object encode() { return [ value.index, ]; } static WKUserScriptInjectionTimeEnumData decode(Object result) { result as List; return WKUserScriptInjectionTimeEnumData( value: WKUserScriptInjectionTimeEnum.values[result[0]! as int], ); } } class WKAudiovisualMediaTypeEnumData { WKAudiovisualMediaTypeEnumData({ required this.value, }); WKAudiovisualMediaTypeEnum value; Object encode() { return [ value.index, ]; } static WKAudiovisualMediaTypeEnumData decode(Object result) { result as List; return WKAudiovisualMediaTypeEnumData( value: WKAudiovisualMediaTypeEnum.values[result[0]! as int], ); } } class WKWebsiteDataTypeEnumData { WKWebsiteDataTypeEnumData({ required this.value, }); WKWebsiteDataTypeEnum value; Object encode() { return [ value.index, ]; } static WKWebsiteDataTypeEnumData decode(Object result) { result as List; return WKWebsiteDataTypeEnumData( value: WKWebsiteDataTypeEnum.values[result[0]! as int], ); } } class WKNavigationActionPolicyEnumData { WKNavigationActionPolicyEnumData({ required this.value, }); WKNavigationActionPolicyEnum value; Object encode() { return [ value.index, ]; } static WKNavigationActionPolicyEnumData decode(Object result) { result as List; return WKNavigationActionPolicyEnumData( value: WKNavigationActionPolicyEnum.values[result[0]! as int], ); } } class NSHttpCookiePropertyKeyEnumData { NSHttpCookiePropertyKeyEnumData({ required this.value, }); NSHttpCookiePropertyKeyEnum value; Object encode() { return [ value.index, ]; } static NSHttpCookiePropertyKeyEnumData decode(Object result) { result as List; return NSHttpCookiePropertyKeyEnumData( value: NSHttpCookiePropertyKeyEnum.values[result[0]! as int], ); } } /// Mirror of NSURLRequest. /// /// See https://developer.apple.com/documentation/foundation/nsurlrequest?language=objc. class NSUrlRequestData { NSUrlRequestData({ required this.url, this.httpMethod, this.httpBody, required this.allHttpHeaderFields, }); String url; String? httpMethod; Uint8List? httpBody; Map allHttpHeaderFields; Object encode() { return [ url, httpMethod, httpBody, allHttpHeaderFields, ]; } static NSUrlRequestData decode(Object result) { result as List; return NSUrlRequestData( url: result[0]! as String, httpMethod: result[1] as String?, httpBody: result[2] as Uint8List?, allHttpHeaderFields: (result[3] as Map?)!.cast(), ); } } /// Mirror of WKUserScript. /// /// See https://developer.apple.com/documentation/webkit/wkuserscript?language=objc. class WKUserScriptData { WKUserScriptData({ required this.source, this.injectionTime, required this.isMainFrameOnly, }); String source; WKUserScriptInjectionTimeEnumData? injectionTime; bool isMainFrameOnly; Object encode() { return [ source, injectionTime?.encode(), isMainFrameOnly, ]; } static WKUserScriptData decode(Object result) { result as List; return WKUserScriptData( source: result[0]! as String, injectionTime: result[1] != null ? WKUserScriptInjectionTimeEnumData.decode( result[1]! as List) : null, isMainFrameOnly: result[2]! as bool, ); } } /// Mirror of WKNavigationAction. /// /// See https://developer.apple.com/documentation/webkit/wknavigationaction. class WKNavigationActionData { WKNavigationActionData({ required this.request, required this.targetFrame, required this.navigationType, }); NSUrlRequestData request; WKFrameInfoData targetFrame; WKNavigationType navigationType; Object encode() { return [ request.encode(), targetFrame.encode(), navigationType.index, ]; } static WKNavigationActionData decode(Object result) { result as List; return WKNavigationActionData( request: NSUrlRequestData.decode(result[0]! as List), targetFrame: WKFrameInfoData.decode(result[1]! as List), navigationType: WKNavigationType.values[result[2]! as int], ); } } /// Mirror of WKFrameInfo. /// /// See https://developer.apple.com/documentation/webkit/wkframeinfo?language=objc. class WKFrameInfoData { WKFrameInfoData({ required this.isMainFrame, }); bool isMainFrame; Object encode() { return [ isMainFrame, ]; } static WKFrameInfoData decode(Object result) { result as List; return WKFrameInfoData( isMainFrame: result[0]! as bool, ); } } /// Mirror of NSError. /// /// See https://developer.apple.com/documentation/foundation/nserror?language=objc. class NSErrorData { NSErrorData({ required this.code, required this.domain, required this.localizedDescription, }); int code; String domain; String localizedDescription; Object encode() { return [ code, domain, localizedDescription, ]; } static NSErrorData decode(Object result) { result as List; return NSErrorData( code: result[0]! as int, domain: result[1]! as String, localizedDescription: result[2]! as String, ); } } /// Mirror of WKScriptMessage. /// /// See https://developer.apple.com/documentation/webkit/wkscriptmessage?language=objc. class WKScriptMessageData { WKScriptMessageData({ required this.name, this.body, }); String name; Object? body; Object encode() { return [ name, body, ]; } static WKScriptMessageData decode(Object result) { result as List; return WKScriptMessageData( name: result[0]! as String, body: result[1] as Object?, ); } } /// Mirror of NSHttpCookieData. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookie?language=objc. class NSHttpCookieData { NSHttpCookieData({ required this.propertyKeys, required this.propertyValues, }); List propertyKeys; List propertyValues; Object encode() { return [ propertyKeys, propertyValues, ]; } static NSHttpCookieData decode(Object result) { result as List; return NSHttpCookieData( propertyKeys: (result[0] as List?)! .cast(), propertyValues: (result[1] as List?)!.cast(), ); } } class _WKWebsiteDataStoreHostApiCodec extends StandardMessageCodec { const _WKWebsiteDataStoreHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is WKWebsiteDataTypeEnumData) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return WKWebsiteDataTypeEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } /// Mirror of WKWebsiteDataStore. /// /// See https://developer.apple.com/documentation/webkit/wkwebsitedatastore?language=objc. class WKWebsiteDataStoreHostApi { /// Constructor for [WKWebsiteDataStoreHostApi]. 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. WKWebsiteDataStoreHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = _WKWebsiteDataStoreHostApiCodec(); Future createFromWebViewConfiguration( int arg_identifier, int arg_configurationIdentifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebsiteDataStoreHostApi.createFromWebViewConfiguration', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_configurationIdentifier]) 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 createDefaultDataStore(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebsiteDataStoreHostApi.createDefaultDataStore', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future removeDataOfTypes( int arg_identifier, List arg_dataTypes, double arg_modificationTimeInSecondsSinceEpoch) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebsiteDataStoreHostApi.removeDataOfTypes', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([ arg_identifier, arg_dataTypes, arg_modificationTimeInSecondsSinceEpoch ]) 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?)!; } } } /// Mirror of UIView. /// /// See https://developer.apple.com/documentation/uikit/uiview?language=objc. class UIViewHostApi { /// Constructor for [UIViewHostApi]. 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. UIViewHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = StandardMessageCodec(); Future setBackgroundColor(int arg_identifier, int? arg_value) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UIViewHostApi.setBackgroundColor', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_value]) 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 setOpaque(int arg_identifier, bool arg_opaque) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UIViewHostApi.setOpaque', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_opaque]) 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; } } } /// Mirror of UIScrollView. /// /// See https://developer.apple.com/documentation/uikit/uiscrollview?language=objc. class UIScrollViewHostApi { /// Constructor for [UIScrollViewHostApi]. 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. UIScrollViewHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = StandardMessageCodec(); Future createFromWebView( int arg_identifier, int arg_webViewIdentifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UIScrollViewHostApi.createFromWebView', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier, arg_webViewIdentifier]) 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> getContentOffset(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UIScrollViewHostApi.getContentOffset', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { return (replyList[0] as List?)!.cast(); } } Future scrollBy(int arg_identifier, double arg_x, double arg_y) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UIScrollViewHostApi.scrollBy', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_x, arg_y]) 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 setContentOffset( int arg_identifier, double arg_x, double arg_y) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UIScrollViewHostApi.setContentOffset', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_x, arg_y]) 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; } } } class _WKWebViewConfigurationHostApiCodec extends StandardMessageCodec { const _WKWebViewConfigurationHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is WKAudiovisualMediaTypeEnumData) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } /// Mirror of WKWebViewConfiguration. /// /// See https://developer.apple.com/documentation/webkit/wkwebviewconfiguration?language=objc. class WKWebViewConfigurationHostApi { /// Constructor for [WKWebViewConfigurationHostApi]. 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. WKWebViewConfigurationHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = _WKWebViewConfigurationHostApiCodec(); Future create(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewConfigurationHostApi.create', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future createFromWebView( int arg_identifier, int arg_webViewIdentifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewConfigurationHostApi.createFromWebView', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier, arg_webViewIdentifier]) 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 setAllowsInlineMediaPlayback( int arg_identifier, bool arg_allow) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewConfigurationHostApi.setAllowsInlineMediaPlayback', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_allow]) 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 setMediaTypesRequiringUserActionForPlayback(int arg_identifier, List arg_types) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewConfigurationHostApi.setMediaTypesRequiringUserActionForPlayback', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_types]) 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; } } } /// Handles callbacks from an WKWebViewConfiguration instance. /// /// See https://developer.apple.com/documentation/webkit/wkwebviewconfiguration?language=objc. abstract class WKWebViewConfigurationFlutterApi { static const MessageCodec codec = StandardMessageCodec(); void create(int identifier); static void setup(WKWebViewConfigurationFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewConfigurationFlutterApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewConfigurationFlutterApi.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.WKWebViewConfigurationFlutterApi.create was null, expected non-null int.'); api.create(arg_identifier!); return; }); } } } } class _WKUserContentControllerHostApiCodec extends StandardMessageCodec { const _WKUserContentControllerHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is WKUserScriptData) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is WKUserScriptInjectionTimeEnumData) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return WKUserScriptData.decode(readValue(buffer)!); case 129: return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } /// Mirror of WKUserContentController. /// /// See https://developer.apple.com/documentation/webkit/wkusercontentcontroller?language=objc. class WKUserContentControllerHostApi { /// Constructor for [WKUserContentControllerHostApi]. 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. WKUserContentControllerHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = _WKUserContentControllerHostApiCodec(); Future createFromWebViewConfiguration( int arg_identifier, int arg_configurationIdentifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKUserContentControllerHostApi.createFromWebViewConfiguration', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_configurationIdentifier]) 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 addScriptMessageHandler( int arg_identifier, int arg_handlerIdentifier, String arg_name) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKUserContentControllerHostApi.addScriptMessageHandler', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_handlerIdentifier, arg_name]) 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 removeScriptMessageHandler( int arg_identifier, String arg_name) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKUserContentControllerHostApi.removeScriptMessageHandler', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_name]) 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 removeAllScriptMessageHandlers(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKUserContentControllerHostApi.removeAllScriptMessageHandlers', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future addUserScript( int arg_identifier, WKUserScriptData arg_userScript) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKUserContentControllerHostApi.addUserScript', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_userScript]) 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 removeAllUserScripts(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKUserContentControllerHostApi.removeAllUserScripts', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } } /// Mirror of WKUserPreferences. /// /// See https://developer.apple.com/documentation/webkit/wkpreferences?language=objc. class WKPreferencesHostApi { /// Constructor for [WKPreferencesHostApi]. 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. WKPreferencesHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = StandardMessageCodec(); Future createFromWebViewConfiguration( int arg_identifier, int arg_configurationIdentifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKPreferencesHostApi.createFromWebViewConfiguration', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_configurationIdentifier]) 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 setJavaScriptEnabled( int arg_identifier, bool arg_enabled) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKPreferencesHostApi.setJavaScriptEnabled', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_enabled]) 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; } } } /// Mirror of WKScriptMessageHandler. /// /// See https://developer.apple.com/documentation/webkit/wkscriptmessagehandler?language=objc. class WKScriptMessageHandlerHostApi { /// Constructor for [WKScriptMessageHandlerHostApi]. 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. WKScriptMessageHandlerHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = StandardMessageCodec(); Future create(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKScriptMessageHandlerHostApi.create', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } } class _WKScriptMessageHandlerFlutterApiCodec extends StandardMessageCodec { const _WKScriptMessageHandlerFlutterApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is WKScriptMessageData) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return WKScriptMessageData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } /// Handles callbacks from an WKScriptMessageHandler instance. /// /// See https://developer.apple.com/documentation/webkit/wkscriptmessagehandler?language=objc. abstract class WKScriptMessageHandlerFlutterApi { static const MessageCodec codec = _WKScriptMessageHandlerFlutterApiCodec(); void didReceiveScriptMessage(int identifier, int userContentControllerIdentifier, WKScriptMessageData message); static void setup(WKScriptMessageHandlerFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKScriptMessageHandlerFlutterApi.didReceiveScriptMessage', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKScriptMessageHandlerFlutterApi.didReceiveScriptMessage 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.WKScriptMessageHandlerFlutterApi.didReceiveScriptMessage was null, expected non-null int.'); final int? arg_userContentControllerIdentifier = (args[1] as int?); assert(arg_userContentControllerIdentifier != null, 'Argument for dev.flutter.pigeon.WKScriptMessageHandlerFlutterApi.didReceiveScriptMessage was null, expected non-null int.'); final WKScriptMessageData? arg_message = (args[2] as WKScriptMessageData?); assert(arg_message != null, 'Argument for dev.flutter.pigeon.WKScriptMessageHandlerFlutterApi.didReceiveScriptMessage was null, expected non-null WKScriptMessageData.'); api.didReceiveScriptMessage(arg_identifier!, arg_userContentControllerIdentifier!, arg_message!); return; }); } } } } /// Mirror of WKNavigationDelegate. /// /// See https://developer.apple.com/documentation/webkit/wknavigationdelegate?language=objc. class WKNavigationDelegateHostApi { /// Constructor for [WKNavigationDelegateHostApi]. 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. WKNavigationDelegateHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = StandardMessageCodec(); Future create(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKNavigationDelegateHostApi.create', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } } class _WKNavigationDelegateFlutterApiCodec extends StandardMessageCodec { const _WKNavigationDelegateFlutterApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is NSErrorData) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is NSUrlRequestData) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else if (value is WKFrameInfoData) { buffer.putUint8(130); writeValue(buffer, value.encode()); } else if (value is WKNavigationActionData) { buffer.putUint8(131); writeValue(buffer, value.encode()); } else if (value is WKNavigationActionPolicyEnumData) { buffer.putUint8(132); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return NSErrorData.decode(readValue(buffer)!); case 129: return NSUrlRequestData.decode(readValue(buffer)!); case 130: return WKFrameInfoData.decode(readValue(buffer)!); case 131: return WKNavigationActionData.decode(readValue(buffer)!); case 132: return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } /// Handles callbacks from an WKNavigationDelegate instance. /// /// See https://developer.apple.com/documentation/webkit/wknavigationdelegate?language=objc. abstract class WKNavigationDelegateFlutterApi { static const MessageCodec codec = _WKNavigationDelegateFlutterApiCodec(); void didFinishNavigation(int identifier, int webViewIdentifier, String? url); void didStartProvisionalNavigation( int identifier, int webViewIdentifier, String? url); Future decidePolicyForNavigationAction( int identifier, int webViewIdentifier, WKNavigationActionData navigationAction); void didFailNavigation( int identifier, int webViewIdentifier, NSErrorData error); void didFailProvisionalNavigation( int identifier, int webViewIdentifier, NSErrorData error); void webViewWebContentProcessDidTerminate( int identifier, int webViewIdentifier); static void setup(WKNavigationDelegateFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didFinishNavigation', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didFinishNavigation 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.WKNavigationDelegateFlutterApi.didFinishNavigation was null, expected non-null int.'); final int? arg_webViewIdentifier = (args[1] as int?); assert(arg_webViewIdentifier != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didFinishNavigation was null, expected non-null int.'); final String? arg_url = (args[2] as String?); api.didFinishNavigation( arg_identifier!, arg_webViewIdentifier!, arg_url); return; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didStartProvisionalNavigation', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didStartProvisionalNavigation 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.WKNavigationDelegateFlutterApi.didStartProvisionalNavigation was null, expected non-null int.'); final int? arg_webViewIdentifier = (args[1] as int?); assert(arg_webViewIdentifier != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didStartProvisionalNavigation was null, expected non-null int.'); final String? arg_url = (args[2] as String?); api.didStartProvisionalNavigation( arg_identifier!, arg_webViewIdentifier!, arg_url); return; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKNavigationDelegateFlutterApi.decidePolicyForNavigationAction', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.decidePolicyForNavigationAction 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.WKNavigationDelegateFlutterApi.decidePolicyForNavigationAction was null, expected non-null int.'); final int? arg_webViewIdentifier = (args[1] as int?); assert(arg_webViewIdentifier != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.decidePolicyForNavigationAction was null, expected non-null int.'); final WKNavigationActionData? arg_navigationAction = (args[2] as WKNavigationActionData?); assert(arg_navigationAction != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.decidePolicyForNavigationAction was null, expected non-null WKNavigationActionData.'); final WKNavigationActionPolicyEnumData output = await api.decidePolicyForNavigationAction(arg_identifier!, arg_webViewIdentifier!, arg_navigationAction!); return output; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didFailNavigation', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didFailNavigation 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.WKNavigationDelegateFlutterApi.didFailNavigation was null, expected non-null int.'); final int? arg_webViewIdentifier = (args[1] as int?); assert(arg_webViewIdentifier != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didFailNavigation was null, expected non-null int.'); final NSErrorData? arg_error = (args[2] as NSErrorData?); assert(arg_error != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didFailNavigation was null, expected non-null NSErrorData.'); api.didFailNavigation( arg_identifier!, arg_webViewIdentifier!, arg_error!); return; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didFailProvisionalNavigation', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didFailProvisionalNavigation 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.WKNavigationDelegateFlutterApi.didFailProvisionalNavigation was null, expected non-null int.'); final int? arg_webViewIdentifier = (args[1] as int?); assert(arg_webViewIdentifier != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didFailProvisionalNavigation was null, expected non-null int.'); final NSErrorData? arg_error = (args[2] as NSErrorData?); assert(arg_error != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didFailProvisionalNavigation was null, expected non-null NSErrorData.'); api.didFailProvisionalNavigation( arg_identifier!, arg_webViewIdentifier!, arg_error!); return; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKNavigationDelegateFlutterApi.webViewWebContentProcessDidTerminate', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.webViewWebContentProcessDidTerminate 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.WKNavigationDelegateFlutterApi.webViewWebContentProcessDidTerminate was null, expected non-null int.'); final int? arg_webViewIdentifier = (args[1] as int?); assert(arg_webViewIdentifier != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.webViewWebContentProcessDidTerminate was null, expected non-null int.'); api.webViewWebContentProcessDidTerminate( arg_identifier!, arg_webViewIdentifier!); return; }); } } } } class _NSObjectHostApiCodec extends StandardMessageCodec { const _NSObjectHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is NSKeyValueObservingOptionsEnumData) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } /// Mirror of NSObject. /// /// See https://developer.apple.com/documentation/objectivec/nsobject. class NSObjectHostApi { /// Constructor for [NSObjectHostApi]. 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. NSObjectHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = _NSObjectHostApiCodec(); Future dispose(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.NSObjectHostApi.dispose', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future addObserver( int arg_identifier, int arg_observerIdentifier, String arg_keyPath, List arg_options) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.NSObjectHostApi.addObserver', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([ arg_identifier, arg_observerIdentifier, arg_keyPath, 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; } } Future removeObserver(int arg_identifier, int arg_observerIdentifier, String arg_keyPath) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.NSObjectHostApi.removeObserver', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send( [arg_identifier, arg_observerIdentifier, arg_keyPath]) 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; } } } class _NSObjectFlutterApiCodec extends StandardMessageCodec { const _NSObjectFlutterApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is NSErrorData) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is NSHttpCookieData) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else if (value is NSHttpCookiePropertyKeyEnumData) { buffer.putUint8(130); writeValue(buffer, value.encode()); } else if (value is NSKeyValueChangeKeyEnumData) { buffer.putUint8(131); writeValue(buffer, value.encode()); } else if (value is NSKeyValueObservingOptionsEnumData) { buffer.putUint8(132); writeValue(buffer, value.encode()); } else if (value is NSUrlRequestData) { buffer.putUint8(133); writeValue(buffer, value.encode()); } else if (value is WKAudiovisualMediaTypeEnumData) { buffer.putUint8(134); writeValue(buffer, value.encode()); } else if (value is WKFrameInfoData) { buffer.putUint8(135); writeValue(buffer, value.encode()); } else if (value is WKNavigationActionData) { buffer.putUint8(136); writeValue(buffer, value.encode()); } else if (value is WKNavigationActionPolicyEnumData) { buffer.putUint8(137); writeValue(buffer, value.encode()); } else if (value is WKScriptMessageData) { buffer.putUint8(138); writeValue(buffer, value.encode()); } else if (value is WKUserScriptData) { buffer.putUint8(139); writeValue(buffer, value.encode()); } else if (value is WKUserScriptInjectionTimeEnumData) { buffer.putUint8(140); writeValue(buffer, value.encode()); } else if (value is WKWebsiteDataTypeEnumData) { buffer.putUint8(141); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return NSErrorData.decode(readValue(buffer)!); case 129: return NSHttpCookieData.decode(readValue(buffer)!); case 130: return NSHttpCookiePropertyKeyEnumData.decode(readValue(buffer)!); case 131: return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!); case 132: return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!); case 133: return NSUrlRequestData.decode(readValue(buffer)!); case 134: return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!); case 135: return WKFrameInfoData.decode(readValue(buffer)!); case 136: return WKNavigationActionData.decode(readValue(buffer)!); case 137: return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!); case 138: return WKScriptMessageData.decode(readValue(buffer)!); case 139: return WKUserScriptData.decode(readValue(buffer)!); case 140: return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!); case 141: return WKWebsiteDataTypeEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } /// Handles callbacks from an NSObject instance. /// /// See https://developer.apple.com/documentation/objectivec/nsobject. abstract class NSObjectFlutterApi { static const MessageCodec codec = _NSObjectFlutterApiCodec(); void observeValue( int identifier, String keyPath, int objectIdentifier, List changeKeys, List changeValues); void dispose(int identifier); static void setup(NSObjectFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.NSObjectFlutterApi.observeValue', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.NSObjectFlutterApi.observeValue 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.NSObjectFlutterApi.observeValue was null, expected non-null int.'); final String? arg_keyPath = (args[1] as String?); assert(arg_keyPath != null, 'Argument for dev.flutter.pigeon.NSObjectFlutterApi.observeValue was null, expected non-null String.'); final int? arg_objectIdentifier = (args[2] as int?); assert(arg_objectIdentifier != null, 'Argument for dev.flutter.pigeon.NSObjectFlutterApi.observeValue was null, expected non-null int.'); final List? arg_changeKeys = (args[3] as List?)?.cast(); assert(arg_changeKeys != null, 'Argument for dev.flutter.pigeon.NSObjectFlutterApi.observeValue was null, expected non-null List.'); final List? arg_changeValues = (args[4] as List?)?.cast(); assert(arg_changeValues != null, 'Argument for dev.flutter.pigeon.NSObjectFlutterApi.observeValue was null, expected non-null List.'); api.observeValue(arg_identifier!, arg_keyPath!, arg_objectIdentifier!, arg_changeKeys!, arg_changeValues!); return; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.NSObjectFlutterApi.dispose', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.NSObjectFlutterApi.dispose 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.NSObjectFlutterApi.dispose was null, expected non-null int.'); api.dispose(arg_identifier!); return; }); } } } } class _WKWebViewHostApiCodec extends StandardMessageCodec { const _WKWebViewHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is NSErrorData) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is NSHttpCookieData) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else if (value is NSHttpCookiePropertyKeyEnumData) { buffer.putUint8(130); writeValue(buffer, value.encode()); } else if (value is NSKeyValueChangeKeyEnumData) { buffer.putUint8(131); writeValue(buffer, value.encode()); } else if (value is NSKeyValueObservingOptionsEnumData) { buffer.putUint8(132); writeValue(buffer, value.encode()); } else if (value is NSUrlRequestData) { buffer.putUint8(133); writeValue(buffer, value.encode()); } else if (value is WKAudiovisualMediaTypeEnumData) { buffer.putUint8(134); writeValue(buffer, value.encode()); } else if (value is WKFrameInfoData) { buffer.putUint8(135); writeValue(buffer, value.encode()); } else if (value is WKNavigationActionData) { buffer.putUint8(136); writeValue(buffer, value.encode()); } else if (value is WKNavigationActionPolicyEnumData) { buffer.putUint8(137); writeValue(buffer, value.encode()); } else if (value is WKScriptMessageData) { buffer.putUint8(138); writeValue(buffer, value.encode()); } else if (value is WKUserScriptData) { buffer.putUint8(139); writeValue(buffer, value.encode()); } else if (value is WKUserScriptInjectionTimeEnumData) { buffer.putUint8(140); writeValue(buffer, value.encode()); } else if (value is WKWebsiteDataTypeEnumData) { buffer.putUint8(141); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return NSErrorData.decode(readValue(buffer)!); case 129: return NSHttpCookieData.decode(readValue(buffer)!); case 130: return NSHttpCookiePropertyKeyEnumData.decode(readValue(buffer)!); case 131: return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!); case 132: return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!); case 133: return NSUrlRequestData.decode(readValue(buffer)!); case 134: return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!); case 135: return WKFrameInfoData.decode(readValue(buffer)!); case 136: return WKNavigationActionData.decode(readValue(buffer)!); case 137: return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!); case 138: return WKScriptMessageData.decode(readValue(buffer)!); case 139: return WKUserScriptData.decode(readValue(buffer)!); case 140: return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!); case 141: return WKWebsiteDataTypeEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } /// Mirror of WKWebView. /// /// See https://developer.apple.com/documentation/webkit/wkwebview?language=objc. class WKWebViewHostApi { /// Constructor for [WKWebViewHostApi]. 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. WKWebViewHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = _WKWebViewHostApiCodec(); Future create( int arg_identifier, int arg_configurationIdentifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.create', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_configurationIdentifier]) 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 setUIDelegate( int arg_identifier, int? arg_uiDelegateIdentifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.setUIDelegate', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier, arg_uiDelegateIdentifier]) 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 setNavigationDelegate( int arg_identifier, int? arg_navigationDelegateIdentifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.setNavigationDelegate', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_navigationDelegateIdentifier]) 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 getUrl(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.getUrl', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return (replyList[0] as String?); } } Future getEstimatedProgress(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.getEstimatedProgress', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { return (replyList[0] as double?)!; } } Future loadRequest( int arg_identifier, NSUrlRequestData arg_request) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.loadRequest', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_request]) 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 loadHtmlString( int arg_identifier, String arg_string, String? arg_baseUrl) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.loadHtmlString', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier, arg_string, arg_baseUrl]) 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 loadFileUrl( int arg_identifier, String arg_url, String arg_readAccessUrl) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.loadFileUrl', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_url, arg_readAccessUrl]) 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 loadFlutterAsset(int arg_identifier, String arg_key) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.loadFlutterAsset', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_key]) 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 canGoBack(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.canGoBack', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { return (replyList[0] as bool?)!; } } Future canGoForward(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.canGoForward', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else if (replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { return (replyList[0] as bool?)!; } } Future goBack(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.goBack', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future goForward(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.goForward', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future reload(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.reload', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } Future getTitle(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.getTitle', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return (replyList[0] as String?); } } Future setAllowsBackForwardNavigationGestures( int arg_identifier, bool arg_allow) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.setAllowsBackForwardNavigationGestures', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_allow]) 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 setCustomUserAgent( int arg_identifier, String? arg_userAgent) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.setCustomUserAgent', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_userAgent]) 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 evaluateJavaScript( int arg_identifier, String arg_javaScriptString) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.evaluateJavaScript', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier, arg_javaScriptString]) 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 Object?); } } } /// Mirror of WKUIDelegate. /// /// See https://developer.apple.com/documentation/webkit/wkuidelegate?language=objc. class WKUIDelegateHostApi { /// Constructor for [WKUIDelegateHostApi]. 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. WKUIDelegateHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = StandardMessageCodec(); Future create(int arg_identifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKUIDelegateHostApi.create', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([arg_identifier]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', message: 'Unable to establish connection on channel.', ); } else if (replyList.length > 1) { throw PlatformException( code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2], ); } else { return; } } } class _WKUIDelegateFlutterApiCodec extends StandardMessageCodec { const _WKUIDelegateFlutterApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is NSUrlRequestData) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is WKFrameInfoData) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else if (value is WKNavigationActionData) { buffer.putUint8(130); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return NSUrlRequestData.decode(readValue(buffer)!); case 129: return WKFrameInfoData.decode(readValue(buffer)!); case 130: return WKNavigationActionData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } /// Handles callbacks from an WKUIDelegate instance. /// /// See https://developer.apple.com/documentation/webkit/wkuidelegate?language=objc. abstract class WKUIDelegateFlutterApi { static const MessageCodec codec = _WKUIDelegateFlutterApiCodec(); void onCreateWebView(int identifier, int webViewIdentifier, int configurationIdentifier, WKNavigationActionData navigationAction); static void setup(WKUIDelegateFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKUIDelegateFlutterApi.onCreateWebView', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKUIDelegateFlutterApi.onCreateWebView 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.WKUIDelegateFlutterApi.onCreateWebView was null, expected non-null int.'); final int? arg_webViewIdentifier = (args[1] as int?); assert(arg_webViewIdentifier != null, 'Argument for dev.flutter.pigeon.WKUIDelegateFlutterApi.onCreateWebView was null, expected non-null int.'); final int? arg_configurationIdentifier = (args[2] as int?); assert(arg_configurationIdentifier != null, 'Argument for dev.flutter.pigeon.WKUIDelegateFlutterApi.onCreateWebView was null, expected non-null int.'); final WKNavigationActionData? arg_navigationAction = (args[3] as WKNavigationActionData?); assert(arg_navigationAction != null, 'Argument for dev.flutter.pigeon.WKUIDelegateFlutterApi.onCreateWebView was null, expected non-null WKNavigationActionData.'); api.onCreateWebView(arg_identifier!, arg_webViewIdentifier!, arg_configurationIdentifier!, arg_navigationAction!); return; }); } } } } class _WKHttpCookieStoreHostApiCodec extends StandardMessageCodec { const _WKHttpCookieStoreHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is NSHttpCookieData) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is NSHttpCookiePropertyKeyEnumData) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return NSHttpCookieData.decode(readValue(buffer)!); case 129: return NSHttpCookiePropertyKeyEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } /// Mirror of WKHttpCookieStore. /// /// See https://developer.apple.com/documentation/webkit/wkhttpcookiestore?language=objc. class WKHttpCookieStoreHostApi { /// Constructor for [WKHttpCookieStoreHostApi]. 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. WKHttpCookieStoreHostApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = _WKHttpCookieStoreHostApiCodec(); Future createFromWebsiteDataStore( int arg_identifier, int arg_websiteDataStoreIdentifier) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKHttpCookieStoreHostApi.createFromWebsiteDataStore', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_websiteDataStoreIdentifier]) 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 setCookie( int arg_identifier, NSHttpCookieData arg_cookie) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKHttpCookieStoreHostApi.setCookie', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel .send([arg_identifier, arg_cookie]) 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; } } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import '../common/instance_manager.dart'; import '../common/weak_reference_utils.dart'; import 'foundation_api_impls.dart'; /// The values that can be returned in a change map. /// /// Wraps [NSKeyValueObservingOptions](https://developer.apple.com/documentation/foundation/nskeyvalueobservingoptions?language=objc). enum NSKeyValueObservingOptions { /// Indicates that the change map should provide the new attribute value, if applicable. /// /// See https://developer.apple.com/documentation/foundation/nskeyvalueobservingoptions/nskeyvalueobservingoptionnew?language=objc. newValue, /// Indicates that the change map should contain the old attribute value, if applicable. /// /// See https://developer.apple.com/documentation/foundation/nskeyvalueobservingoptions/nskeyvalueobservingoptionold?language=objc. oldValue, /// Indicates a notification should be sent to the observer immediately. /// /// See https://developer.apple.com/documentation/foundation/nskeyvalueobservingoptions/nskeyvalueobservingoptioninitial?language=objc. initialValue, /// Whether separate notifications should be sent to the observer before and after each change. /// /// See https://developer.apple.com/documentation/foundation/nskeyvalueobservingoptions/nskeyvalueobservingoptionprior?language=objc. priorNotification, } /// The kinds of changes that can be observed. /// /// Wraps [NSKeyValueChange](https://developer.apple.com/documentation/foundation/nskeyvaluechange?language=objc). enum NSKeyValueChange { /// Indicates that the value of the observed key path was set to a new value. /// /// See https://developer.apple.com/documentation/foundation/nskeyvaluechange/nskeyvaluechangesetting?language=objc. setting, /// Indicates that an object has been inserted into the to-many relationship that is being observed. /// /// See https://developer.apple.com/documentation/foundation/nskeyvaluechange/nskeyvaluechangeinsertion?language=objc. insertion, /// Indicates that an object has been removed from the to-many relationship that is being observed. /// /// See https://developer.apple.com/documentation/foundation/nskeyvaluechange/nskeyvaluechangeremoval?language=objc. removal, /// Indicates that an object has been replaced in the to-many relationship that is being observed. /// /// See https://developer.apple.com/documentation/foundation/nskeyvaluechange/nskeyvaluechangereplacement?language=objc. replacement, } /// The keys that can appear in the change map. /// /// Wraps [NSKeyValueChangeKey](https://developer.apple.com/documentation/foundation/nskeyvaluechangekey?language=objc). enum NSKeyValueChangeKey { /// Indicates changes made in a collection. /// /// See https://developer.apple.com/documentation/foundation/nskeyvaluechangeindexeskey?language=objc. indexes, /// Indicates what sort of change has occurred. /// /// See https://developer.apple.com/documentation/foundation/nskeyvaluechangekindkey?language=objc. kind, /// Indicates the new value for the attribute. /// /// See https://developer.apple.com/documentation/foundation/nskeyvaluechangenewkey?language=objc. newValue, /// Indicates a notification is sent prior to a change. /// /// See https://developer.apple.com/documentation/foundation/nskeyvaluechangenotificationispriorkey?language=objc. notificationIsPrior, /// Indicates the value of this key is the value before the attribute was changed. /// /// See https://developer.apple.com/documentation/foundation/nskeyvaluechangeoldkey?language=objc. oldValue, } /// The supported keys in a cookie attributes dictionary. /// /// Wraps [NSHTTPCookiePropertyKey](https://developer.apple.com/documentation/foundation/nshttpcookiepropertykey). enum NSHttpCookiePropertyKey { /// A String object containing the comment for the cookie. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiecomment. comment, /// A String object containing the comment URL for the cookie. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiecommenturl. commentUrl, /// A String object stating whether the cookie should be discarded at the end of the session. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiediscard. discard, /// A String object specifying the expiration date for the cookie. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiedomain. domain, /// A String object specifying the expiration date for the cookie. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookieexpires. expires, /// A String object containing an integer value stating how long in seconds the cookie should be kept, at most. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiemaximumage. maximumAge, /// A String object containing the name of the cookie (required). /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiename. name, /// A String object containing the URL that set this cookie. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookieoriginurl. originUrl, /// A String object containing the path for the cookie. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiepath. path, /// A String object containing comma-separated integer values specifying the ports for the cookie. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookieport. port, /// A String indicating the same-site policy for the cookie. /// /// This is only supported on iOS version 13+. This value will be ignored on /// versions < 13. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiesamesitepolicy. sameSitePolicy, /// A String object indicating that the cookie should be transmitted only over secure channels. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiesecure. secure, /// A String object containing the value of the cookie. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookievalue. value, /// A String object that specifies the version of the cookie. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookieversion. version, } /// A URL load request that is independent of protocol or URL scheme. /// /// Wraps [NSUrlRequest](https://developer.apple.com/documentation/foundation/nsurlrequest?language=objc). @immutable class NSUrlRequest { /// Constructs an [NSUrlRequest]. const NSUrlRequest({ required this.url, this.httpMethod, this.httpBody, this.allHttpHeaderFields = const {}, }); /// The URL being requested. final String url; /// The HTTP request method. /// /// The default HTTP method is “GET”. final String? httpMethod; /// Data sent as the message body of a request, as in an HTTP POST request. final Uint8List? httpBody; /// All of the HTTP header fields for a request. final Map allHttpHeaderFields; } /// Information about an error condition. /// /// Wraps [NSError](https://developer.apple.com/documentation/foundation/nserror?language=objc). @immutable class NSError { /// Constructs an [NSError]. const NSError({ required this.code, required this.domain, required this.localizedDescription, }); /// The error code. /// /// Note that errors are domain-specific. final int code; /// A string containing the error domain. final String domain; /// A string containing the localized description of the error. final String localizedDescription; } /// A representation of an HTTP cookie. /// /// Wraps [NSHTTPCookie](https://developer.apple.com/documentation/foundation/nshttpcookie). @immutable class NSHttpCookie { /// Initializes an HTTP cookie object using the provided properties. const NSHttpCookie.withProperties(this.properties); /// Properties of the new cookie object. final Map properties; } /// The root class of most Objective-C class hierarchies. @immutable class NSObject with Copyable { /// Constructs a [NSObject] without creating the associated /// Objective-C object. /// /// This should only be used by subclasses created by this library or to /// create copies. NSObject.detached({ this.observeValue, BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, }) : _api = NSObjectHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ) { // Ensures FlutterApis for the Foundation library are set up. FoundationFlutterApis.instance.ensureSetUp(); } /// Release the reference to the Objective-C object. static void dispose(NSObject instance) { instance._api.instanceManager.removeWeakReference(instance); } /// Global instance of [InstanceManager]. static final InstanceManager globalInstanceManager = InstanceManager(onWeakReferenceRemoved: (int instanceId) { NSObjectHostApiImpl().dispose(instanceId); }); final NSObjectHostApiImpl _api; /// Informs the observing object when the value at the specified key path has /// changed. /// /// {@template webview_flutter_wkwebview.foundation.callbacks} /// For the associated Objective-C object to be automatically garbage /// collected, it is required that this Function doesn't contain a strong /// reference to the encapsulating class instance. Consider using /// `WeakReference` when referencing an object not received as a parameter. /// Otherwise, use [NSObject.dispose] to release the associated Objective-C /// object manually. /// /// See [withWeakRefenceTo]. /// {@endtemplate} final void Function( String keyPath, NSObject object, Map change, )? observeValue; /// Registers the observer object to receive KVO notifications. Future addObserver( NSObject observer, { required String keyPath, required Set options, }) { assert(options.isNotEmpty); return _api.addObserverForInstances( this, observer, keyPath, options, ); } /// Stops the observer object from receiving change notifications for the property. Future removeObserver(NSObject observer, {required String keyPath}) { return _api.removeObserverForInstances(this, observer, keyPath); } @override NSObject copy() { return NSObject.detached( observeValue: observeValue, binaryMessenger: _api.binaryMessenger, instanceManager: _api.instanceManager, ); } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation_api_impls.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import '../common/instance_manager.dart'; import '../common/web_kit.g.dart'; import 'foundation.dart'; Iterable _toNSKeyValueObservingOptionsEnumData( Iterable options, ) { return options.map(( NSKeyValueObservingOptions option, ) { late final NSKeyValueObservingOptionsEnum? value; switch (option) { case NSKeyValueObservingOptions.newValue: value = NSKeyValueObservingOptionsEnum.newValue; break; case NSKeyValueObservingOptions.oldValue: value = NSKeyValueObservingOptionsEnum.oldValue; break; case NSKeyValueObservingOptions.initialValue: value = NSKeyValueObservingOptionsEnum.initialValue; break; case NSKeyValueObservingOptions.priorNotification: value = NSKeyValueObservingOptionsEnum.priorNotification; break; } return NSKeyValueObservingOptionsEnumData(value: value); }); } extension _NSKeyValueChangeKeyEnumDataConverter on NSKeyValueChangeKeyEnumData { NSKeyValueChangeKey toNSKeyValueChangeKey() { return NSKeyValueChangeKey.values.firstWhere( (NSKeyValueChangeKey element) => element.name == value.name, ); } } /// Handles initialization of Flutter APIs for the Foundation library. class FoundationFlutterApis { /// Constructs a [FoundationFlutterApis]. @visibleForTesting FoundationFlutterApis({ BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, }) : _binaryMessenger = binaryMessenger, object = NSObjectFlutterApiImpl( instanceManager: instanceManager, ); static FoundationFlutterApis _instance = FoundationFlutterApis(); /// Sets the global instance containing the Flutter Apis for the Foundation library. @visibleForTesting static set instance(FoundationFlutterApis instance) { _instance = instance; } /// Global instance containing the Flutter Apis for the Foundation library. static FoundationFlutterApis get instance { return _instance; } final BinaryMessenger? _binaryMessenger; bool _hasBeenSetUp = false; /// Flutter Api for [NSObject]. @visibleForTesting final NSObjectFlutterApiImpl object; /// Ensures all the Flutter APIs have been set up to receive calls from native code. void ensureSetUp() { if (!_hasBeenSetUp) { NSObjectFlutterApi.setup( object, binaryMessenger: _binaryMessenger, ); _hasBeenSetUp = true; } } } /// Host api implementation for [NSObject]. class NSObjectHostApiImpl extends NSObjectHostApi { /// Constructs an [NSObjectHostApiImpl]. NSObjectHostApiImpl({ this.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager, super(binaryMessenger: binaryMessenger); /// Sends 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 Objective-C objects. final InstanceManager instanceManager; /// Calls [addObserver] with the ids of the provided object instances. Future addObserverForInstances( NSObject instance, NSObject observer, String keyPath, Set options, ) { return addObserver( instanceManager.getIdentifier(instance)!, instanceManager.getIdentifier(observer)!, keyPath, _toNSKeyValueObservingOptionsEnumData(options).toList(), ); } /// Calls [removeObserver] with the ids of the provided object instances. Future removeObserverForInstances( NSObject instance, NSObject observer, String keyPath, ) { return removeObserver( instanceManager.getIdentifier(instance)!, instanceManager.getIdentifier(observer)!, keyPath, ); } } /// Flutter api implementation for [NSObject]. class NSObjectFlutterApiImpl extends NSObjectFlutterApi { /// Constructs a [NSObjectFlutterApiImpl]. NSObjectFlutterApiImpl({InstanceManager? instanceManager}) : instanceManager = instanceManager ?? NSObject.globalInstanceManager; /// Maintains instances stored to communicate with native language objects. final InstanceManager instanceManager; NSObject _getObject(int identifier) { return instanceManager.getInstanceWithWeakReference(identifier)!; } @override void observeValue( int identifier, String keyPath, int objectIdentifier, List changeKeys, List changeValues, ) { final void Function(String, NSObject, Map)? function = _getObject(identifier).observeValue; function?.call( keyPath, instanceManager.getInstanceWithWeakReference(objectIdentifier)! as NSObject, Map.fromIterables( changeKeys.map( (NSKeyValueChangeKeyEnumData? data) { return data!.toNSKeyValueChangeKey(); }, ), changeValues), ); } @override void dispose(int identifier) { instanceManager.remove(identifier); } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/lib/src/legacy/web_kit_webview_widget.dart ================================================ // Copyright 2013 The Flutter 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 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:path/path.dart' as path; // ignore: implementation_imports import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; import '../common/weak_reference_utils.dart'; import '../foundation/foundation.dart'; import '../web_kit/web_kit.dart'; /// A [Widget] that displays a [WKWebView]. class WebKitWebViewWidget extends StatefulWidget { /// Constructs a [WebKitWebViewWidget]. const WebKitWebViewWidget({ super.key, required this.creationParams, required this.callbacksHandler, required this.javascriptChannelRegistry, required this.onBuildWidget, this.configuration, @visibleForTesting this.webViewProxy = const WebViewWidgetProxy(), }); /// The initial parameters used to setup the WebView. final CreationParams creationParams; /// The handler of callbacks made made by [NavigationDelegate]. final WebViewPlatformCallbacksHandler callbacksHandler; /// Manager of named JavaScript channels and forwarding incoming messages on the correct channel. final JavascriptChannelRegistry javascriptChannelRegistry; /// A collection of properties used to initialize a web view. /// /// If null, a default configuration is used. final WKWebViewConfiguration? configuration; /// The handler for constructing [WKWebView]s and calling static methods. /// /// This should only be changed for testing purposes. final WebViewWidgetProxy webViewProxy; /// A callback to build a widget once [WKWebView] has been initialized. final Widget Function(WebKitWebViewPlatformController controller) onBuildWidget; @override State createState() => _WebKitWebViewWidgetState(); } class _WebKitWebViewWidgetState extends State { late final WebKitWebViewPlatformController controller; @override void initState() { super.initState(); controller = WebKitWebViewPlatformController( creationParams: widget.creationParams, callbacksHandler: widget.callbacksHandler, javascriptChannelRegistry: widget.javascriptChannelRegistry, configuration: widget.configuration, webViewProxy: widget.webViewProxy, ); } @override Widget build(BuildContext context) { return widget.onBuildWidget(controller); } } /// An implementation of [WebViewPlatformController] with the WebKit api. class WebKitWebViewPlatformController extends WebViewPlatformController { /// Construct a [WebKitWebViewPlatformController]. WebKitWebViewPlatformController({ required CreationParams creationParams, required this.callbacksHandler, required this.javascriptChannelRegistry, WKWebViewConfiguration? configuration, @visibleForTesting this.webViewProxy = const WebViewWidgetProxy(), }) : super(callbacksHandler) { _setCreationParams( creationParams, configuration: configuration ?? WKWebViewConfiguration(), ); } bool _zoomEnabled = true; bool _hasNavigationDelegate = false; bool _progressObserverSet = false; final Map _scriptMessageHandlers = {}; /// Handles callbacks that are made by navigation. final WebViewPlatformCallbacksHandler callbacksHandler; /// Manages named JavaScript channels and forwarding incoming messages on the correct channel. final JavascriptChannelRegistry javascriptChannelRegistry; /// Handles constructing a [WKWebView]. /// /// This should only be changed when used for testing. final WebViewWidgetProxy webViewProxy; /// Represents the WebView maintained by platform code. late final WKWebView webView; /// Used to integrate custom user interface elements into web view interactions. @visibleForTesting late final WKUIDelegate uiDelegate = webViewProxy.createUIDelgate( onCreateWebView: ( WKWebView webView, WKWebViewConfiguration configuration, WKNavigationAction navigationAction, ) { if (!navigationAction.targetFrame.isMainFrame) { webView.loadRequest(navigationAction.request); } }, ); /// Methods for handling navigation changes and tracking navigation requests. @visibleForTesting late final WKNavigationDelegate navigationDelegate = withWeakRefenceTo( this, (WeakReference weakReference) { return webViewProxy.createNavigationDelegate( didFinishNavigation: (WKWebView webView, String? url) { weakReference.target?.callbacksHandler.onPageFinished(url ?? ''); }, didStartProvisionalNavigation: (WKWebView webView, String? url) { weakReference.target?.callbacksHandler.onPageStarted(url ?? ''); }, decidePolicyForNavigationAction: ( WKWebView webView, WKNavigationAction action, ) async { if (weakReference.target == null) { return WKNavigationActionPolicy.allow; } if (!weakReference.target!._hasNavigationDelegate) { return WKNavigationActionPolicy.allow; } final bool allow = await weakReference.target!.callbacksHandler.onNavigationRequest( url: action.request.url, isForMainFrame: action.targetFrame.isMainFrame, ); return allow ? WKNavigationActionPolicy.allow : WKNavigationActionPolicy.cancel; }, didFailNavigation: (WKWebView webView, NSError error) { weakReference.target?.callbacksHandler.onWebResourceError( _toWebResourceError(error), ); }, didFailProvisionalNavigation: (WKWebView webView, NSError error) { weakReference.target?.callbacksHandler.onWebResourceError( _toWebResourceError(error), ); }, webViewWebContentProcessDidTerminate: (WKWebView webView) { weakReference.target?.callbacksHandler.onWebResourceError( WebResourceError( errorCode: WKErrorCode.webContentProcessTerminated, // Value from https://developer.apple.com/documentation/webkit/wkerrordomain?language=objc. domain: 'WKErrorDomain', description: '', errorType: WebResourceErrorType.webContentProcessTerminated, ), ); }, ); }, ); Future _setCreationParams( CreationParams params, { required WKWebViewConfiguration configuration, }) async { _setWebViewConfiguration( configuration, allowsInlineMediaPlayback: params.webSettings?.allowsInlineMediaPlayback, autoMediaPlaybackPolicy: params.autoMediaPlaybackPolicy, ); webView = webViewProxy.createWebView( configuration, observeValue: withWeakRefenceTo( callbacksHandler, (WeakReference weakReference) { return ( String keyPath, NSObject object, Map change, ) { final double progress = change[NSKeyValueChangeKey.newValue]! as double; weakReference.target?.onProgress((progress * 100).round()); }; }, ), ); webView.setUIDelegate(uiDelegate); await addJavascriptChannels(params.javascriptChannelNames); webView.setNavigationDelegate(navigationDelegate); if (params.userAgent != null) { webView.setCustomUserAgent(params.userAgent); } if (params.webSettings != null) { updateSettings(params.webSettings!); } if (params.backgroundColor != null) { webView.setOpaque(false); webView.setBackgroundColor(Colors.transparent); webView.scrollView.setBackgroundColor(params.backgroundColor); } if (params.initialUrl != null) { await loadUrl(params.initialUrl!, null); } } void _setWebViewConfiguration( WKWebViewConfiguration configuration, { required bool? allowsInlineMediaPlayback, required AutoMediaPlaybackPolicy autoMediaPlaybackPolicy, }) { if (allowsInlineMediaPlayback != null) { configuration.setAllowsInlineMediaPlayback(allowsInlineMediaPlayback); } late final bool requiresUserAction; switch (autoMediaPlaybackPolicy) { case AutoMediaPlaybackPolicy.require_user_action_for_all_media_types: requiresUserAction = true; break; case AutoMediaPlaybackPolicy.always_allow: requiresUserAction = false; break; } configuration .setMediaTypesRequiringUserActionForPlayback({ if (requiresUserAction) WKAudiovisualMediaType.all, if (!requiresUserAction) WKAudiovisualMediaType.none, }); } @override Future loadHtmlString(String html, {String? baseUrl}) { return webView.loadHtmlString(html, baseUrl: baseUrl); } @override Future loadFile(String absoluteFilePath) async { await webView.loadFileUrl( absoluteFilePath, readAccessUrl: path.dirname(absoluteFilePath), ); } @override Future clearCache() { return webView.configuration.websiteDataStore.removeDataOfTypes( { WKWebsiteDataType.memoryCache, WKWebsiteDataType.diskCache, WKWebsiteDataType.offlineWebApplicationCache, WKWebsiteDataType.localStorage, }, DateTime.fromMillisecondsSinceEpoch(0), ); } @override Future loadFlutterAsset(String key) async { assert(key.isNotEmpty); return webView.loadFlutterAsset(key); } @override Future loadUrl(String url, Map? headers) async { final NSUrlRequest request = NSUrlRequest( url: url, allHttpHeaderFields: headers ?? {}, ); return webView.loadRequest(request); } @override Future loadRequest(WebViewRequest request) async { if (!request.uri.hasScheme) { throw ArgumentError('WebViewRequest#uri is required to have a scheme.'); } final NSUrlRequest urlRequest = NSUrlRequest( url: request.uri.toString(), allHttpHeaderFields: request.headers, httpMethod: describeEnum(request.method), httpBody: request.body, ); return webView.loadRequest(urlRequest); } @override Future canGoBack() => webView.canGoBack(); @override Future canGoForward() => webView.canGoForward(); @override Future goBack() => webView.goBack(); @override Future goForward() => webView.goForward(); @override Future reload() => webView.reload(); @override Future evaluateJavascript(String javascript) async { final Object? result = await webView.evaluateJavaScript(javascript); return _asObjectiveCString(result); } @override Future runJavascript(String javascript) async { try { await webView.evaluateJavaScript(javascript); } on PlatformException catch (exception) { // WebKit will throw an error when the type of the evaluated value is // 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. final Object? details = exception.details; if (details is! NSError || details.code != WKErrorCode.javaScriptResultTypeIsUnsupported) { rethrow; } } } @override Future runJavascriptReturningResult(String javascript) async { final Object? result = await webView.evaluateJavaScript(javascript); if (result == null) { throw ArgumentError( 'Result of JavaScript execution returned a `null` value. ' 'Use `runJavascript` when expecting a null return value.', ); } return _asObjectiveCString(result); } @override Future getTitle() => webView.getTitle(); @override Future currentUrl() => webView.getUrl(); @override Future scrollTo(int x, int y) async { webView.scrollView.setContentOffset(Point( x.toDouble(), y.toDouble(), )); } @override Future scrollBy(int x, int y) async { await webView.scrollView.scrollBy(Point( x.toDouble(), y.toDouble(), )); } @override Future getScrollX() async { final Point offset = await webView.scrollView.getContentOffset(); return offset.x.toInt(); } @override Future getScrollY() async { final Point offset = await webView.scrollView.getContentOffset(); return offset.y.toInt(); } @override Future updateSettings(WebSettings setting) async { if (setting.hasNavigationDelegate != null) { _hasNavigationDelegate = setting.hasNavigationDelegate!; } await Future.wait(>[ _setUserAgent(setting.userAgent), if (setting.hasProgressTracking != null) _setHasProgressTracking(setting.hasProgressTracking!), if (setting.javascriptMode != null) _setJavaScriptMode(setting.javascriptMode!), if (setting.zoomEnabled != null) _setZoomEnabled(setting.zoomEnabled!), if (setting.gestureNavigationEnabled != null) webView.setAllowsBackForwardNavigationGestures( setting.gestureNavigationEnabled!, ), ]); } @override Future addJavascriptChannels(Set javascriptChannelNames) async { await Future.wait( javascriptChannelNames.where( (String channelName) { return !_scriptMessageHandlers.containsKey(channelName); }, ).map>( (String channelName) { final WKScriptMessageHandler handler = webViewProxy.createScriptMessageHandler( didReceiveScriptMessage: withWeakRefenceTo( javascriptChannelRegistry, (WeakReference weakReference) { return ( WKUserContentController userContentController, WKScriptMessage message, ) { weakReference.target?.onJavascriptChannelMessage( message.name, message.body!.toString(), ); }; }, ), ); _scriptMessageHandlers[channelName] = handler; final String wrapperSource = 'window.$channelName = webkit.messageHandlers.$channelName;'; final WKUserScript wrapperScript = WKUserScript( wrapperSource, WKUserScriptInjectionTime.atDocumentStart, isMainFrameOnly: false, ); webView.configuration.userContentController .addUserScript(wrapperScript); return webView.configuration.userContentController .addScriptMessageHandler( handler, channelName, ); }, ), ); } @override Future removeJavascriptChannels( Set javascriptChannelNames, ) async { if (javascriptChannelNames.isEmpty) { return; } await _resetUserScripts(removedJavaScriptChannels: javascriptChannelNames); } Future _setHasProgressTracking(bool hasProgressTracking) async { if (hasProgressTracking) { _progressObserverSet = true; await webView.addObserver( webView, keyPath: 'estimatedProgress', options: { NSKeyValueObservingOptions.newValue, }, ); } else if (_progressObserverSet) { // Calls to removeObserver before addObserver causes a crash. _progressObserverSet = false; await webView.removeObserver(webView, keyPath: 'estimatedProgress'); } } Future _setJavaScriptMode(JavascriptMode mode) { switch (mode) { case JavascriptMode.disabled: return webView.configuration.preferences.setJavaScriptEnabled(false); case JavascriptMode.unrestricted: return webView.configuration.preferences.setJavaScriptEnabled(true); } } Future _setUserAgent(WebSetting userAgent) async { if (userAgent.isPresent) { await webView.setCustomUserAgent(userAgent.value); } } Future _setZoomEnabled(bool zoomEnabled) async { if (_zoomEnabled == zoomEnabled) { return; } _zoomEnabled = zoomEnabled; if (!zoomEnabled) { return _disableZoom(); } return _resetUserScripts(); } Future _disableZoom() { const WKUserScript userScript = WKUserScript( "var meta = document.createElement('meta');\n" "meta.name = 'viewport';\n" "meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, " "user-scalable=no';\n" "var head = document.getElementsByTagName('head')[0];head.appendChild(meta);", WKUserScriptInjectionTime.atDocumentEnd, isMainFrameOnly: true, ); return webView.configuration.userContentController .addUserScript(userScript); } // WkWebView does not support removing a single user script, so all user // scripts and all message handlers are removed instead. And the JavaScript // channels that shouldn't be removed are re-registered. Note that this // workaround could interfere with exposing support for custom scripts from // applications. Future _resetUserScripts({ Set removedJavaScriptChannels = const {}, }) async { webView.configuration.userContentController.removeAllUserScripts(); // TODO(bparrishMines): This can be replaced with // `removeAllScriptMessageHandlers` once Dart supports runtime version // checking. (e.g. The equivalent to @availability in Objective-C.) _scriptMessageHandlers.keys.forEach( webView.configuration.userContentController.removeScriptMessageHandler, ); removedJavaScriptChannels.forEach(_scriptMessageHandlers.remove); final Set remainingNames = _scriptMessageHandlers.keys.toSet(); _scriptMessageHandlers.clear(); await Future.wait(>[ addJavascriptChannels(remainingNames), // Zoom is disabled with a WKUserScript, so this adds it back if it was // removed above. if (!_zoomEnabled) _disableZoom(), ]); } static WebResourceError _toWebResourceError(NSError error) { WebResourceErrorType? errorType; switch (error.code) { case WKErrorCode.unknown: errorType = WebResourceErrorType.unknown; break; case WKErrorCode.webContentProcessTerminated: errorType = WebResourceErrorType.webContentProcessTerminated; break; case WKErrorCode.webViewInvalidated: errorType = WebResourceErrorType.webViewInvalidated; break; case WKErrorCode.javaScriptExceptionOccurred: errorType = WebResourceErrorType.javaScriptExceptionOccurred; break; case WKErrorCode.javaScriptResultTypeIsUnsupported: errorType = WebResourceErrorType.javaScriptResultTypeIsUnsupported; break; } return WebResourceError( errorCode: error.code, domain: error.domain, description: error.localizedDescription, errorType: errorType, ); } // The legacy implementation of webview_flutter_wkwebview would convert // objects to strings before returning them to Dart. This method attempts // to converts Dart objects to Strings the way it is done in Objective-C // to avoid breaking users expecting the same String format. // TODO(bparrishMines): Remove this method with the next breaking change. // See https://github.com/flutter/flutter/issues/107491 String _asObjectiveCString(Object? value, {bool inContainer = false}) { if (value == null) { // An NSNull inside an NSArray or NSDictionary is represented as a String // differently than a nil. if (inContainer) { return '""'; } return '(null)'; } else if (value is bool) { return value ? '1' : '0'; } else if (value is double && value.truncate() == value) { return value.truncate().toString(); } else if (value is List) { final List stringValues = []; for (final Object? listValue in value) { stringValues.add(_asObjectiveCString(listValue, inContainer: true)); } return '(${stringValues.join(',')})'; } else if (value is Map) { final List stringValues = []; for (final MapEntry entry in value.entries) { stringValues.add( '${_asObjectiveCString(entry.key, inContainer: true)} ' '= ' '${_asObjectiveCString(entry.value, inContainer: true)}', ); } return '{${stringValues.join(';')}}'; } return value.toString(); } } /// Handles constructing objects and calling static methods. /// /// This should only be used for testing purposes. @visibleForTesting class WebViewWidgetProxy { /// Constructs a [WebViewWidgetProxy]. const WebViewWidgetProxy(); /// Constructs a [WKWebView]. WKWebView createWebView( WKWebViewConfiguration configuration, { void Function( String keyPath, NSObject object, Map change, )? observeValue, }) { return WKWebView(configuration, observeValue: observeValue); } /// Constructs a [WKScriptMessageHandler]. WKScriptMessageHandler createScriptMessageHandler({ required void Function( WKUserContentController userContentController, WKScriptMessage message, ) didReceiveScriptMessage, }) { return WKScriptMessageHandler( didReceiveScriptMessage: didReceiveScriptMessage, ); } /// Constructs a [WKUIDelegate]. WKUIDelegate createUIDelgate({ void Function( WKWebView webView, WKWebViewConfiguration configuration, WKNavigationAction navigationAction, )? onCreateWebView, }) { return WKUIDelegate(onCreateWebView: onCreateWebView); } /// Constructs a [WKNavigationDelegate]. WKNavigationDelegate createNavigationDelegate({ void Function(WKWebView webView, String? url)? didFinishNavigation, void Function(WKWebView webView, String? url)? didStartProvisionalNavigation, Future Function( WKWebView webView, WKNavigationAction navigationAction, )? decidePolicyForNavigationAction, void Function(WKWebView webView, NSError error)? didFailNavigation, void Function(WKWebView webView, NSError error)? didFailProvisionalNavigation, void Function(WKWebView webView)? webViewWebContentProcessDidTerminate, }) { return WKNavigationDelegate( didFinishNavigation: didFinishNavigation, didStartProvisionalNavigation: didStartProvisionalNavigation, decidePolicyForNavigationAction: decidePolicyForNavigationAction, didFailNavigation: didFailNavigation, didFailProvisionalNavigation: didFailProvisionalNavigation, webViewWebContentProcessDidTerminate: webViewWebContentProcessDidTerminate, ); } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/lib/src/legacy/webview_cupertino.dart ================================================ // Copyright 2013 The Flutter 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:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; // ignore: implementation_imports import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; import '../foundation/foundation.dart'; import 'web_kit_webview_widget.dart'; /// Builds an iOS webview. /// /// This is used as the default implementation for [WebView.platform] on iOS. It uses /// a [UiKitView] to embed the webview in the widget hierarchy, and uses a method channel to /// communicate with the platform code. class CupertinoWebView implements WebViewPlatform { @override Widget build({ required BuildContext context, required CreationParams creationParams, required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler, required JavascriptChannelRegistry javascriptChannelRegistry, WebViewPlatformCreatedCallback? onWebViewPlatformCreated, Set>? gestureRecognizers, }) { return WebKitWebViewWidget( creationParams: creationParams, callbacksHandler: webViewPlatformCallbacksHandler, javascriptChannelRegistry: javascriptChannelRegistry, onBuildWidget: (WebKitWebViewPlatformController controller) { return UiKitView( viewType: 'plugins.flutter.io/webview', onPlatformViewCreated: (int id) { if (onWebViewPlatformCreated != null) { onWebViewPlatformCreated(controller); } }, gestureRecognizers: gestureRecognizers, creationParams: NSObject.globalInstanceManager.getIdentifier(controller.webView), creationParamsCodec: const StandardMessageCodec(), ); }, ); } @override Future clearCookies() { if (WebViewCookieManagerPlatform.instance == null) { throw Exception( 'Could not clear cookies as no implementation for WebViewCookieManagerPlatform has been registered.'); } return WebViewCookieManagerPlatform.instance!.clearCookies(); } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/lib/src/legacy/wkwebview_cookie_manager.dart ================================================ // Copyright 2013 The Flutter 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: implementation_imports import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; import '../foundation/foundation.dart'; import '../web_kit/web_kit.dart'; /// Handles all cookie operations for the WebView platform. class WKWebViewCookieManager extends WebViewCookieManagerPlatform { /// Constructs a [WKWebViewCookieManager]. WKWebViewCookieManager({WKWebsiteDataStore? websiteDataStore}) : websiteDataStore = websiteDataStore ?? WKWebsiteDataStore.defaultDataStore; /// Manages stored data for [WKWebView]s. final WKWebsiteDataStore websiteDataStore; @override Future clearCookies() async { return websiteDataStore.removeDataOfTypes( {WKWebsiteDataType.cookies}, DateTime.fromMillisecondsSinceEpoch(0), ); } @override Future setCookie(WebViewCookie cookie) { if (!_isValidPath(cookie.path)) { throw ArgumentError( 'The path property for the provided cookie was not given a legal value.'); } return websiteDataStore.httpCookieStore.setCookie( NSHttpCookie.withProperties( { NSHttpCookiePropertyKey.name: cookie.name, NSHttpCookiePropertyKey.value: cookie.value, NSHttpCookiePropertyKey.domain: cookie.domain, NSHttpCookiePropertyKey.path: cookie.path, }, ), ); } bool _isValidPath(String path) { // Permitted ranges based on RFC6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 return !path.codeUnits.any( (int char) { return (char < 0x20 || char > 0x3A) && (char < 0x3C || char > 0x7E); }, ); } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/lib/src/ui_kit/ui_kit.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:math'; import 'package:flutter/foundation.dart'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#106316) // ignore: unnecessary_import import 'package:flutter/painting.dart' show Color; import 'package:flutter/services.dart'; import '../common/instance_manager.dart'; import '../foundation/foundation.dart'; import '../web_kit/web_kit.dart'; import 'ui_kit_api_impls.dart'; /// A view that allows the scrolling and zooming of its contained views. /// /// Wraps [UIScrollView](https://developer.apple.com/documentation/uikit/uiscrollview?language=objc). @immutable class UIScrollView extends UIView { /// Constructs a [UIScrollView] that is owned by [webView]. factory UIScrollView.fromWebView( WKWebView webView, { BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, }) { final UIScrollView scrollView = UIScrollView.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ); scrollView._scrollViewApi.createFromWebViewForInstances( scrollView, webView, ); return scrollView; } /// Constructs a [UIScrollView] without creating the associated /// Objective-C object. /// /// This should only be used by subclasses created by this library or to /// create copies. UIScrollView.detached({ super.observeValue, super.binaryMessenger, super.instanceManager, }) : _scrollViewApi = UIScrollViewHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ), super.detached(); final UIScrollViewHostApiImpl _scrollViewApi; /// Point at which the origin of the content view is offset from the origin of the scroll view. /// /// Represents [WKWebView.contentOffset](https://developer.apple.com/documentation/uikit/uiscrollview/1619404-contentoffset?language=objc). Future> getContentOffset() { return _scrollViewApi.getContentOffsetForInstances(this); } /// Move the scrolled position of this view. /// /// This method is not a part of UIKit and is only a helper method to make /// scrollBy atomic. Future scrollBy(Point offset) { return _scrollViewApi.scrollByForInstances(this, offset); } /// Set point at which the origin of the content view is offset from the origin of the scroll view. /// /// The default value is `Point(0.0, 0.0)`. /// /// Sets [WKWebView.contentOffset](https://developer.apple.com/documentation/uikit/uiscrollview/1619404-contentoffset?language=objc). Future setContentOffset(Point offset) { return _scrollViewApi.setContentOffsetForInstances(this, offset); } @override UIScrollView copy() { return UIScrollView.detached( observeValue: observeValue, binaryMessenger: _viewApi.binaryMessenger, instanceManager: _viewApi.instanceManager, ); } } /// Manages the content for a rectangular area on the screen. /// /// Wraps [UIView](https://developer.apple.com/documentation/uikit/uiview?language=objc). @immutable class UIView extends NSObject { /// Constructs a [UIView] without creating the associated /// Objective-C object. /// /// This should only be used by subclasses created by this library or to /// create copies. UIView.detached({ super.observeValue, super.binaryMessenger, super.instanceManager, }) : _viewApi = UIViewHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ), super.detached(); final UIViewHostApiImpl _viewApi; /// The view’s background color. /// /// The default value is null, which results in a transparent background color. /// /// Sets [UIView.backgroundColor](https://developer.apple.com/documentation/uikit/uiview/1622591-backgroundcolor?language=objc). Future setBackgroundColor(Color? color) { return _viewApi.setBackgroundColorForInstances(this, color); } /// Determines whether the view is opaque. /// /// Sets [UIView.opaque](https://developer.apple.com/documentation/uikit/uiview?language=objc). Future setOpaque(bool opaque) { return _viewApi.setOpaqueForInstances(this, opaque); } @override UIView copy() { return UIView.detached( observeValue: observeValue, binaryMessenger: _viewApi.binaryMessenger, instanceManager: _viewApi.instanceManager, ); } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/lib/src/ui_kit/ui_kit_api_impls.dart ================================================ // Copyright 2013 The Flutter 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 'dart:math'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#106316) // ignore: unnecessary_import import 'package:flutter/painting.dart' show Color; import 'package:flutter/services.dart'; import '../common/instance_manager.dart'; import '../common/web_kit.g.dart'; import '../foundation/foundation.dart'; import '../web_kit/web_kit.dart'; import 'ui_kit.dart'; /// Host api implementation for [UIScrollView]. class UIScrollViewHostApiImpl extends UIScrollViewHostApi { /// Constructs a [UIScrollViewHostApiImpl]. UIScrollViewHostApiImpl({ this.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager, super(binaryMessenger: binaryMessenger); /// Sends 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 Objective-C objects. final InstanceManager instanceManager; /// Calls [createFromWebView] with the ids of the provided object instances. Future createFromWebViewForInstances( UIScrollView instance, WKWebView webView, ) { return createFromWebView( instanceManager.addDartCreatedInstance(instance), instanceManager.getIdentifier(webView)!, ); } /// Calls [getContentOffset] with the ids of the provided object instances. Future> getContentOffsetForInstances( UIScrollView instance, ) async { final List point = await getContentOffset( instanceManager.getIdentifier(instance)!, ); return Point(point[0]!, point[1]!); } /// Calls [scrollBy] with the ids of the provided object instances. Future scrollByForInstances( UIScrollView instance, Point offset, ) { return scrollBy( instanceManager.getIdentifier(instance)!, offset.x, offset.y, ); } /// Calls [setContentOffset] with the ids of the provided object instances. Future setContentOffsetForInstances( UIScrollView instance, Point offset, ) async { return setContentOffset( instanceManager.getIdentifier(instance)!, offset.x, offset.y, ); } } /// Host api implementation for [UIView]. class UIViewHostApiImpl extends UIViewHostApi { /// Constructs a [UIViewHostApiImpl]. UIViewHostApiImpl({ this.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager, super(binaryMessenger: binaryMessenger); /// Sends 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 Objective-C objects. final InstanceManager instanceManager; /// Calls [setBackgroundColor] with the ids of the provided object instances. Future setBackgroundColorForInstances( UIView instance, Color? color, ) async { return setBackgroundColor( instanceManager.getIdentifier(instance)!, color?.value, ); } /// Calls [setOpaque] with the ids of the provided object instances. Future setOpaqueForInstances( UIView instance, bool opaque, ) async { return setOpaque(instanceManager.getIdentifier(instance)!, opaque); } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import '../common/instance_manager.dart'; import '../foundation/foundation.dart'; import '../ui_kit/ui_kit.dart'; import 'web_kit_api_impls.dart'; export 'web_kit_api_impls.dart' show WKNavigationType; /// Times at which to inject script content into a webpage. /// /// Wraps [WKUserScriptInjectionTime](https://developer.apple.com/documentation/webkit/wkuserscriptinjectiontime?language=objc). enum WKUserScriptInjectionTime { /// Inject the script after the creation of the webpage’s document element, but before loading any other content. /// /// See https://developer.apple.com/documentation/webkit/wkuserscriptinjectiontime/wkuserscriptinjectiontimeatdocumentstart?language=objc. atDocumentStart, /// Inject the script after the document finishes loading, but before loading any other subresources. /// /// See https://developer.apple.com/documentation/webkit/wkuserscriptinjectiontime/wkuserscriptinjectiontimeatdocumentend?language=objc. atDocumentEnd, } /// The media types that require a user gesture to begin playing. /// /// Wraps [WKAudiovisualMediaTypes](https://developer.apple.com/documentation/webkit/wkaudiovisualmediatypes?language=objc). enum WKAudiovisualMediaType { /// No media types require a user gesture to begin playing. /// /// See https://developer.apple.com/documentation/webkit/wkaudiovisualmediatypes/wkaudiovisualmediatypenone?language=objc. none, /// Media types that contain audio require a user gesture to begin playing. /// /// See https://developer.apple.com/documentation/webkit/wkaudiovisualmediatypes/wkaudiovisualmediatypeaudio?language=objc. audio, /// Media types that contain video require a user gesture to begin playing. /// /// See https://developer.apple.com/documentation/webkit/wkaudiovisualmediatypes/wkaudiovisualmediatypevideo?language=objc. video, /// All media types require a user gesture to begin playing. /// /// See https://developer.apple.com/documentation/webkit/wkaudiovisualmediatypes/wkaudiovisualmediatypeall?language=objc. all, } /// Types of data that websites store. /// /// See https://developer.apple.com/documentation/webkit/wkwebsitedatarecord/data_store_record_types?language=objc. enum WKWebsiteDataType { /// Cookies. cookies, /// In-memory caches. memoryCache, /// On-disk caches. diskCache, /// HTML offline web app caches. offlineWebApplicationCache, /// HTML local storage. localStorage, /// HTML session storage. sessionStorage, /// WebSQL databases. webSQLDatabases, /// IndexedDB databases. indexedDBDatabases, } /// Indicate whether to allow or cancel navigation to a webpage. /// /// Wraps [WKNavigationActionPolicy](https://developer.apple.com/documentation/webkit/wknavigationactionpolicy?language=objc). enum WKNavigationActionPolicy { /// Allow navigation to continue. /// /// See https://developer.apple.com/documentation/webkit/wknavigationactionpolicy/wknavigationactionpolicyallow?language=objc. allow, /// Cancel navigation. /// /// See https://developer.apple.com/documentation/webkit/wknavigationactionpolicy/wknavigationactionpolicycancel?language=objc. cancel, } /// Possible error values that WebKit APIs can return. /// /// See https://developer.apple.com/documentation/webkit/wkerrorcode. class WKErrorCode { WKErrorCode._(); /// Indicates an unknown issue occurred. /// /// See https://developer.apple.com/documentation/webkit/wkerrorcode/wkerrorunknown. static const int unknown = 1; /// Indicates the web process that contains the content is no longer running. /// /// See https://developer.apple.com/documentation/webkit/wkerrorcode/wkerrorwebcontentprocessterminated. static const int webContentProcessTerminated = 2; /// Indicates the web view was invalidated. /// /// See https://developer.apple.com/documentation/webkit/wkerrorcode/wkerrorwebviewinvalidated. static const int webViewInvalidated = 3; /// Indicates a JavaScript exception occurred. /// /// See https://developer.apple.com/documentation/webkit/wkerrorcode/wkerrorjavascriptexceptionoccurred. static const int javaScriptExceptionOccurred = 4; /// Indicates the result of JavaScript execution could not be returned. /// /// See https://developer.apple.com/documentation/webkit/wkerrorcode/wkerrorjavascriptresulttypeisunsupported. static const int javaScriptResultTypeIsUnsupported = 5; } /// A record of the data that a particular website stores persistently. /// /// Wraps [WKWebsiteDataRecord](https://developer.apple.com/documentation/webkit/wkwebsitedatarecord?language=objc). @immutable class WKWebsiteDataRecord { /// Constructs a [WKWebsiteDataRecord]. const WKWebsiteDataRecord({required this.displayName}); /// Identifying information that you display to users. final String displayName; } /// An object that contains information about an action that causes navigation to occur. /// /// Wraps [WKNavigationAction](https://developer.apple.com/documentation/webkit/wknavigationaction?language=objc). @immutable class WKNavigationAction { /// Constructs a [WKNavigationAction]. const WKNavigationAction({ required this.request, required this.targetFrame, required this.navigationType, }); /// The URL request object associated with the navigation action. final NSUrlRequest request; /// The frame in which to display the new content. final WKFrameInfo targetFrame; /// The type of action that triggered the navigation. final WKNavigationType navigationType; } /// An object that contains information about a frame on a webpage. /// /// An instance of this class is a transient, data-only object; it does not /// uniquely identify a frame across multiple delegate method calls. /// /// Wraps [WKFrameInfo](https://developer.apple.com/documentation/webkit/wkframeinfo?language=objc). @immutable class WKFrameInfo { /// Construct a [WKFrameInfo]. const WKFrameInfo({required this.isMainFrame}); /// Indicates whether the frame is the web site's main frame or a subframe. final bool isMainFrame; } /// A script that the web view injects into a webpage. /// /// Wraps [WKUserScript](https://developer.apple.com/documentation/webkit/wkuserscript?language=objc). @immutable class WKUserScript { /// Constructs a [UserScript]. const WKUserScript( this.source, this.injectionTime, { required this.isMainFrameOnly, }); /// The script’s source code. final String source; /// The time at which to inject the script into the webpage. final WKUserScriptInjectionTime injectionTime; /// Indicates whether to inject the script into the main frame or all frames. final bool isMainFrameOnly; } /// An object that encapsulates a message sent by JavaScript code from a webpage. /// /// Wraps [WKScriptMessage](https://developer.apple.com/documentation/webkit/wkscriptmessage?language=objc). @immutable class WKScriptMessage { /// Constructs a [WKScriptMessage]. const WKScriptMessage({required this.name, this.body}); /// The name of the message handler to which the message is sent. final String name; /// The body of the message. /// /// Allowed types are [num], [String], [List], [Map], and `null`. final Object? body; } /// Encapsulates the standard behaviors to apply to websites. /// /// Wraps [WKPreferences](https://developer.apple.com/documentation/webkit/wkpreferences?language=objc). @immutable class WKPreferences extends NSObject { /// Constructs a [WKPreferences] that is owned by [configuration]. factory WKPreferences.fromWebViewConfiguration( WKWebViewConfiguration configuration, { BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, }) { final WKPreferences preferences = WKPreferences.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ); preferences._preferencesApi.createFromWebViewConfigurationForInstances( preferences, configuration, ); return preferences; } /// Constructs a [WKPreferences] without creating the associated /// Objective-C object. /// /// This should only be used by subclasses created by this library or to /// create copies. WKPreferences.detached({ super.observeValue, super.binaryMessenger, super.instanceManager, }) : _preferencesApi = WKPreferencesHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ), super.detached(); final WKPreferencesHostApiImpl _preferencesApi; // TODO(bparrishMines): Deprecated for iOS 14.0+. Add support for alternative. /// Sets whether JavaScript is enabled. /// /// The default value is true. Future setJavaScriptEnabled(bool enabled) { return _preferencesApi.setJavaScriptEnabledForInstances(this, enabled); } @override WKPreferences copy() { return WKPreferences.detached( observeValue: observeValue, binaryMessenger: _preferencesApi.binaryMessenger, instanceManager: _preferencesApi.instanceManager, ); } } /// Manages cookies, disk and memory caches, and other types of data for a web view. /// /// Wraps [WKWebsiteDataStore](https://developer.apple.com/documentation/webkit/wkwebsitedatastore?language=objc). @immutable class WKWebsiteDataStore extends NSObject { /// Constructs a [WKWebsiteDataStore] without creating the associated /// Objective-C object. /// /// This should only be used by subclasses created by this library or to /// create copies. WKWebsiteDataStore.detached({ super.observeValue, super.binaryMessenger, super.instanceManager, }) : _websiteDataStoreApi = WKWebsiteDataStoreHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ), super.detached(); factory WKWebsiteDataStore._defaultDataStore() { final WKWebsiteDataStore websiteDataStore = WKWebsiteDataStore.detached(); websiteDataStore._websiteDataStoreApi.createDefaultDataStoreForInstances( websiteDataStore, ); return websiteDataStore; } /// Constructs a [WKWebsiteDataStore] that is owned by [configuration]. factory WKWebsiteDataStore.fromWebViewConfiguration( WKWebViewConfiguration configuration, { BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, }) { final WKWebsiteDataStore websiteDataStore = WKWebsiteDataStore.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ); websiteDataStore._websiteDataStoreApi .createFromWebViewConfigurationForInstances( websiteDataStore, configuration, ); return websiteDataStore; } /// Default data store that stores data persistently to disk. static final WKWebsiteDataStore defaultDataStore = WKWebsiteDataStore._defaultDataStore(); final WKWebsiteDataStoreHostApiImpl _websiteDataStoreApi; /// Manages the HTTP cookies associated with a particular web view. late final WKHttpCookieStore httpCookieStore = WKHttpCookieStore.fromWebsiteDataStore(this); /// Removes website data that changed after the specified date. /// /// Returns whether any data was removed. Future removeDataOfTypes( Set dataTypes, DateTime since, ) { return _websiteDataStoreApi.removeDataOfTypesForInstances( this, dataTypes, secondsModifiedSinceEpoch: since.millisecondsSinceEpoch / 1000, ); } @override WKWebsiteDataStore copy() { return WKWebsiteDataStore.detached( observeValue: observeValue, binaryMessenger: _websiteDataStoreApi.binaryMessenger, instanceManager: _websiteDataStoreApi.instanceManager, ); } } /// An object that manages the HTTP cookies associated with a particular web view. /// /// Wraps [WKHTTPCookieStore](https://developer.apple.com/documentation/webkit/wkhttpcookiestore?language=objc). @immutable class WKHttpCookieStore extends NSObject { /// Constructs a [WKHttpCookieStore] without creating the associated /// Objective-C object. /// /// This should only be used by subclasses created by this library or to /// create copies. WKHttpCookieStore.detached({ super.observeValue, super.binaryMessenger, super.instanceManager, }) : _httpCookieStoreApi = WKHttpCookieStoreHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ), super.detached(); /// Constructs a [WKHttpCookieStore] that is owned by [dataStore]. factory WKHttpCookieStore.fromWebsiteDataStore( WKWebsiteDataStore dataStore, { BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, }) { final WKHttpCookieStore cookieStore = WKHttpCookieStore.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ); cookieStore._httpCookieStoreApi.createFromWebsiteDataStoreForInstances( cookieStore, dataStore, ); return cookieStore; } final WKHttpCookieStoreHostApiImpl _httpCookieStoreApi; /// Adds a cookie to the cookie store. Future setCookie(NSHttpCookie cookie) { return _httpCookieStoreApi.setCookieForInstances(this, cookie); } @override WKHttpCookieStore copy() { return WKHttpCookieStore.detached( observeValue: observeValue, binaryMessenger: _httpCookieStoreApi.binaryMessenger, instanceManager: _httpCookieStoreApi.instanceManager, ); } } /// An interface for receiving messages from JavaScript code running in a webpage. /// /// Wraps [WKScriptMessageHandler](https://developer.apple.com/documentation/webkit/wkscriptmessagehandler?language=objc). @immutable class WKScriptMessageHandler extends NSObject { /// Constructs a [WKScriptMessageHandler]. WKScriptMessageHandler({ required this.didReceiveScriptMessage, super.observeValue, super.binaryMessenger, super.instanceManager, }) : _scriptMessageHandlerApi = WKScriptMessageHandlerHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ), super.detached() { // Ensures FlutterApis for the WebKit library are set up. WebKitFlutterApis.instance.ensureSetUp(); _scriptMessageHandlerApi.createForInstances(this); } /// Constructs a [WKScriptMessageHandler] without creating the associated /// Objective-C object. /// /// This should only be used by subclasses created by this library or to /// create copies. WKScriptMessageHandler.detached({ required this.didReceiveScriptMessage, super.observeValue, super.binaryMessenger, super.instanceManager, }) : _scriptMessageHandlerApi = WKScriptMessageHandlerHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ), super.detached(); final WKScriptMessageHandlerHostApiImpl _scriptMessageHandlerApi; /// Tells the handler that a webpage sent a script message. /// /// Use this method to respond to a message sent from the webpage’s /// JavaScript code. Use the [message] parameter to get the message contents and /// to determine the originating web view. /// /// {@macro webview_flutter_wkwebview.foundation.callbacks} final void Function( WKUserContentController userContentController, WKScriptMessage message, ) didReceiveScriptMessage; @override WKScriptMessageHandler copy() { return WKScriptMessageHandler.detached( didReceiveScriptMessage: didReceiveScriptMessage, observeValue: observeValue, binaryMessenger: _scriptMessageHandlerApi.binaryMessenger, instanceManager: _scriptMessageHandlerApi.instanceManager, ); } } /// Manages interactions between JavaScript code and your web view. /// /// Use this object to do the following: /// /// * Inject JavaScript code into webpages running in your web view. /// * Install custom JavaScript functions that call through to your app’s native /// code. /// /// Wraps [WKUserContentController](https://developer.apple.com/documentation/webkit/wkusercontentcontroller?language=objc). @immutable class WKUserContentController extends NSObject { /// Constructs a [WKUserContentController] that is owned by [configuration]. factory WKUserContentController.fromWebViewConfiguration( WKWebViewConfiguration configuration, { BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, }) { final WKUserContentController userContentController = WKUserContentController.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ); userContentController._userContentControllerApi .createFromWebViewConfigurationForInstances( userContentController, configuration, ); return userContentController; } /// Constructs a [WKUserContentController] without creating the associated /// Objective-C object. /// /// This should only be used outside of tests by subclasses created by this /// library or to create a copy for an InstanceManager. WKUserContentController.detached({ super.observeValue, super.binaryMessenger, super.instanceManager, }) : _userContentControllerApi = WKUserContentControllerHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ), super.detached(); final WKUserContentControllerHostApiImpl _userContentControllerApi; /// Installs a message handler that you can call from your JavaScript code. /// /// This name of the parameter must be unique within the user content /// controller and must not be an empty string. The user content controller /// uses this parameter to define a JavaScript function for your message /// handler in the page’s main content world. The name of this function is /// `window.webkit.messageHandlers..postMessage()`, where /// `` corresponds to the value of this parameter. For example, if you /// specify the string `MyFunction`, the user content controller defines the ` /// `window.webkit.messageHandlers.MyFunction.postMessage()` function in /// JavaScript. Future addScriptMessageHandler( WKScriptMessageHandler handler, String name, ) { assert(name.isNotEmpty); return _userContentControllerApi.addScriptMessageHandlerForInstances( this, handler, name, ); } /// Uninstalls the custom message handler with the specified name from your JavaScript code. /// /// If no message handler with this name exists in the user content /// controller, this method does nothing. /// /// Use this method to remove a message handler that you previously installed /// using the [addScriptMessageHandler] method. This method removes the /// message handler from the page content world. If you installed the message /// handler in a different content world, this method doesn’t remove it. Future removeScriptMessageHandler(String name) { return _userContentControllerApi.removeScriptMessageHandlerForInstances( this, name, ); } /// Uninstalls all custom message handlers associated with the user content /// controller. /// /// Only supported on iOS version 14+. Future removeAllScriptMessageHandlers() { return _userContentControllerApi.removeAllScriptMessageHandlersForInstances( this, ); } /// Injects the specified script into the webpage’s content. Future addUserScript(WKUserScript userScript) { return _userContentControllerApi.addUserScriptForInstances( this, userScript); } /// Removes all user scripts from the web view. Future removeAllUserScripts() { return _userContentControllerApi.removeAllUserScriptsForInstances(this); } @override WKUserContentController copy() { return WKUserContentController.detached( observeValue: observeValue, binaryMessenger: _userContentControllerApi.binaryMessenger, instanceManager: _userContentControllerApi.instanceManager, ); } } /// A collection of properties that you use to initialize a web view. /// /// Wraps [WKWebViewConfiguration](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration?language=objc). @immutable class WKWebViewConfiguration extends NSObject { /// Constructs a [WKWebViewConfiguration]. WKWebViewConfiguration({ super.observeValue, super.binaryMessenger, super.instanceManager, }) : _webViewConfigurationApi = WKWebViewConfigurationHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ), super.detached() { // Ensures FlutterApis for the WebKit library are set up. WebKitFlutterApis.instance.ensureSetUp(); _webViewConfigurationApi.createForInstances(this); } /// A WKWebViewConfiguration that is owned by webView. @visibleForTesting factory WKWebViewConfiguration.fromWebView( WKWebView webView, { BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, }) { final WKWebViewConfiguration configuration = WKWebViewConfiguration.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ); configuration._webViewConfigurationApi.createFromWebViewForInstances( configuration, webView, ); return configuration; } /// Constructs a [WKWebViewConfiguration] without creating the associated /// Objective-C object. /// /// This should only be used outside of tests by subclasses created by this /// library or to create a copy for an InstanceManager. WKWebViewConfiguration.detached({ super.observeValue, super.binaryMessenger, super.instanceManager, }) : _webViewConfigurationApi = WKWebViewConfigurationHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ), super.detached(); late final WKWebViewConfigurationHostApiImpl _webViewConfigurationApi; /// Coordinates interactions between your app’s code and the webpage’s scripts and other content. late final WKUserContentController userContentController = WKUserContentController.fromWebViewConfiguration( this, binaryMessenger: _webViewConfigurationApi.binaryMessenger, instanceManager: _webViewConfigurationApi.instanceManager, ); /// Manages the preference-related settings for the web view. late final WKPreferences preferences = WKPreferences.fromWebViewConfiguration( this, binaryMessenger: _webViewConfigurationApi.binaryMessenger, instanceManager: _webViewConfigurationApi.instanceManager, ); /// Used to get and set the site’s cookies and to track the cached data objects. /// /// Represents [WKWebViewConfiguration.webSiteDataStore](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/1395661-websitedatastore?language=objc). late final WKWebsiteDataStore websiteDataStore = WKWebsiteDataStore.fromWebViewConfiguration( this, binaryMessenger: _webViewConfigurationApi.binaryMessenger, instanceManager: _webViewConfigurationApi.instanceManager, ); /// Indicates whether HTML5 videos play inline or use the native full-screen controller. /// /// Sets [WKWebViewConfiguration.allowsInlineMediaPlayback](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/1614793-allowsinlinemediaplayback?language=objc). Future setAllowsInlineMediaPlayback(bool allow) { return _webViewConfigurationApi.setAllowsInlineMediaPlaybackForInstances( this, allow, ); } /// The media types that require a user gesture to begin playing. /// /// Use [WKAudiovisualMediaType.none] to indicate that no user gestures are /// required to begin playing media. /// /// Sets [WKWebViewConfiguration.mediaTypesRequiringUserActionForPlayback](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/1851524-mediatypesrequiringuseractionfor?language=objc). Future setMediaTypesRequiringUserActionForPlayback( Set types, ) { assert(types.isNotEmpty); return _webViewConfigurationApi .setMediaTypesRequiringUserActionForPlaybackForInstances( this, types, ); } @override WKWebViewConfiguration copy() { return WKWebViewConfiguration.detached( observeValue: observeValue, binaryMessenger: _webViewConfigurationApi.binaryMessenger, instanceManager: _webViewConfigurationApi.instanceManager, ); } } /// The methods for presenting native user interface elements on behalf of a webpage. /// /// Wraps [WKUIDelegate](https://developer.apple.com/documentation/webkit/wkuidelegate?language=objc). @immutable class WKUIDelegate extends NSObject { /// Constructs a [WKUIDelegate]. WKUIDelegate({ this.onCreateWebView, super.observeValue, super.binaryMessenger, super.instanceManager, }) : _uiDelegateApi = WKUIDelegateHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ), super.detached() { // Ensures FlutterApis for the WebKit library are set up. WebKitFlutterApis.instance.ensureSetUp(); _uiDelegateApi.createForInstances(this); } /// Constructs a [WKUIDelegate] without creating the associated Objective-C /// object. /// /// This should only be used by subclasses created by this library or to /// create copies. WKUIDelegate.detached({ this.onCreateWebView, super.observeValue, super.binaryMessenger, super.instanceManager, }) : _uiDelegateApi = WKUIDelegateHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ), super.detached(); final WKUIDelegateHostApiImpl _uiDelegateApi; /// Indicates a new [WKWebView] was requested to be created with [configuration]. /// /// {@macro webview_flutter_wkwebview.foundation.callbacks} final void Function( WKWebView webView, WKWebViewConfiguration configuration, WKNavigationAction navigationAction, )? onCreateWebView; @override WKUIDelegate copy() { return WKUIDelegate.detached( onCreateWebView: onCreateWebView, observeValue: observeValue, binaryMessenger: _uiDelegateApi.binaryMessenger, instanceManager: _uiDelegateApi.instanceManager, ); } } /// Methods for handling navigation changes and tracking navigation requests. /// /// Set the methods of the [WKNavigationDelegate] in the object you use to /// coordinate changes in your web view’s main frame. /// /// Wraps [WKNavigationDelegate](https://developer.apple.com/documentation/webkit/wknavigationdelegate?language=objc). @immutable class WKNavigationDelegate extends NSObject { /// Constructs a [WKNavigationDelegate]. WKNavigationDelegate({ this.didFinishNavigation, this.didStartProvisionalNavigation, this.decidePolicyForNavigationAction, this.didFailNavigation, this.didFailProvisionalNavigation, this.webViewWebContentProcessDidTerminate, super.observeValue, super.binaryMessenger, super.instanceManager, }) : _navigationDelegateApi = WKNavigationDelegateHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ), super.detached() { // Ensures FlutterApis for the WebKit library are set up. WebKitFlutterApis.instance.ensureSetUp(); _navigationDelegateApi.createForInstances(this); } /// Constructs a [WKNavigationDelegate] without creating the associated /// Objective-C object. /// /// This should only be used outside of tests by subclasses created by this /// library or to create a copy for an InstanceManager. WKNavigationDelegate.detached({ this.didFinishNavigation, this.didStartProvisionalNavigation, this.decidePolicyForNavigationAction, this.didFailNavigation, this.didFailProvisionalNavigation, this.webViewWebContentProcessDidTerminate, super.observeValue, super.binaryMessenger, super.instanceManager, }) : _navigationDelegateApi = WKNavigationDelegateHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ), super.detached(); final WKNavigationDelegateHostApiImpl _navigationDelegateApi; /// Called when navigation is complete. /// /// {@macro webview_flutter_wkwebview.foundation.callbacks} final void Function(WKWebView webView, String? url)? didFinishNavigation; /// Called when navigation from the main frame has started. /// /// {@macro webview_flutter_wkwebview.foundation.callbacks} final void Function(WKWebView webView, String? url)? didStartProvisionalNavigation; /// Called when permission is needed to navigate to new content. /// /// {@macro webview_flutter_wkwebview.foundation.callbacks} final Future Function( WKWebView webView, WKNavigationAction navigationAction, )? decidePolicyForNavigationAction; /// Called when an error occurred during navigation. /// /// {@macro webview_flutter_wkwebview.foundation.callbacks} final void Function(WKWebView webView, NSError error)? didFailNavigation; /// Called when an error occurred during the early navigation process. /// /// {@macro webview_flutter_wkwebview.foundation.callbacks} final void Function(WKWebView webView, NSError error)? didFailProvisionalNavigation; /// Called when the web view’s content process was terminated. /// /// {@macro webview_flutter_wkwebview.foundation.callbacks} final void Function(WKWebView webView)? webViewWebContentProcessDidTerminate; @override WKNavigationDelegate copy() { return WKNavigationDelegate.detached( didFinishNavigation: didFinishNavigation, didStartProvisionalNavigation: didStartProvisionalNavigation, decidePolicyForNavigationAction: decidePolicyForNavigationAction, didFailNavigation: didFailNavigation, didFailProvisionalNavigation: didFailProvisionalNavigation, webViewWebContentProcessDidTerminate: webViewWebContentProcessDidTerminate, observeValue: observeValue, binaryMessenger: _navigationDelegateApi.binaryMessenger, instanceManager: _navigationDelegateApi.instanceManager, ); } } /// Object that displays interactive web content, such as for an in-app browser. /// /// Wraps [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview?language=objc). @immutable class WKWebView extends UIView { /// Constructs a [WKWebView]. /// /// [configuration] contains the configuration details for the web view. This /// method saves a copy of your configuration object. Changes you make to your /// original object after calling this method have no effect on the web view’s /// configuration. For a list of configuration options and their default /// values, see [WKWebViewConfiguration]. If you didn’t create your web view /// using the `configuration` parameter, this value uses a default /// configuration object. WKWebView( WKWebViewConfiguration configuration, { super.observeValue, super.binaryMessenger, super.instanceManager, }) : _webViewApi = WKWebViewHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ), super.detached() { // Ensures FlutterApis for the WebKit library are set up. WebKitFlutterApis.instance.ensureSetUp(); _webViewApi.createForInstances(this, configuration); } /// Constructs a [WKWebView] without creating the associated Objective-C /// object. /// /// This should only be used outside of tests by subclasses created by this /// library or to create a copy for an InstanceManager. WKWebView.detached({ super.observeValue, super.binaryMessenger, super.instanceManager, }) : _webViewApi = WKWebViewHostApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ), super.detached(); final WKWebViewHostApiImpl _webViewApi; /// Contains the configuration details for the web view. /// /// Use the object in this property to obtain information about your web /// view’s configuration. Because this property returns a copy of the /// configuration object, changes you make to that object don’t affect the web /// view’s configuration. /// /// If you didn’t create your web view with a [WKWebViewConfiguration] this /// property contains a default configuration object. late final WKWebViewConfiguration configuration = WKWebViewConfiguration.fromWebView( this, binaryMessenger: _webViewApi.binaryMessenger, instanceManager: _webViewApi.instanceManager, ); /// The scrollable view associated with the web view. late final UIScrollView scrollView = UIScrollView.fromWebView( this, binaryMessenger: _webViewApi.binaryMessenger, instanceManager: _webViewApi.instanceManager, ); /// Used to integrate custom user interface elements into web view interactions. /// /// Sets [WKWebView.UIDelegate](https://developer.apple.com/documentation/webkit/wkwebview/1415009-uidelegate?language=objc). Future setUIDelegate(WKUIDelegate? delegate) { return _webViewApi.setUIDelegateForInstances(this, delegate); } /// The object you use to manage navigation behavior for the web view. /// /// Sets [WKWebView.navigationDelegate](https://developer.apple.com/documentation/webkit/wkwebview/1414971-navigationdelegate?language=objc). Future setNavigationDelegate(WKNavigationDelegate? delegate) { return _webViewApi.setNavigationDelegateForInstances(this, delegate); } /// The URL for the current webpage. /// /// Represents [WKWebView.URL](https://developer.apple.com/documentation/webkit/wkwebview/1415005-url?language=objc). Future getUrl() { return _webViewApi.getUrlForInstances(this); } /// An estimate of what fraction of the current navigation has been loaded. /// /// This value ranges from 0.0 to 1.0. /// /// Represents [WKWebView.estimatedProgress](https://developer.apple.com/documentation/webkit/wkwebview/1415007-estimatedprogress?language=objc). Future getEstimatedProgress() { return _webViewApi.getEstimatedProgressForInstances(this); } /// Loads the web content referenced by the specified URL request object and navigates to it. /// /// Use this method to load a page from a local or network-based URL. For /// example, you might use it to navigate to a network-based webpage. Future loadRequest(NSUrlRequest request) { return _webViewApi.loadRequestForInstances(this, request); } /// Loads the contents of the specified HTML string and navigates to it. Future loadHtmlString(String string, {String? baseUrl}) { return _webViewApi.loadHtmlStringForInstances(this, string, baseUrl); } /// Loads the web content from the specified file and navigates to it. Future loadFileUrl(String url, {required String readAccessUrl}) { return _webViewApi.loadFileUrlForInstances(this, url, readAccessUrl); } /// Loads the Flutter asset specified in the pubspec.yaml file. /// /// This method is not a part of WebKit and is only a Flutter specific helper /// method. Future loadFlutterAsset(String key) { return _webViewApi.loadFlutterAssetForInstances(this, key); } /// Indicates whether there is a valid back item in the back-forward list. Future canGoBack() { return _webViewApi.canGoBackForInstances(this); } /// Indicates whether there is a valid forward item in the back-forward list. Future canGoForward() { return _webViewApi.canGoForwardForInstances(this); } /// Navigates to the back item in the back-forward list. Future goBack() { return _webViewApi.goBackForInstances(this); } /// Navigates to the forward item in the back-forward list. Future goForward() { return _webViewApi.goForwardForInstances(this); } /// Reloads the current webpage. Future reload() { return _webViewApi.reloadForInstances(this); } /// The page title. /// /// Represents [WKWebView.title](https://developer.apple.com/documentation/webkit/wkwebview/1415015-title?language=objc). Future getTitle() { return _webViewApi.getTitleForInstances(this); } /// Indicates whether horizontal swipe gestures trigger page navigation. /// /// The default value is false. /// /// Sets [WKWebView.allowsBackForwardNavigationGestures](https://developer.apple.com/documentation/webkit/wkwebview/1414995-allowsbackforwardnavigationgestu?language=objc). Future setAllowsBackForwardNavigationGestures(bool allow) { return _webViewApi.setAllowsBackForwardNavigationGesturesForInstances( this, allow, ); } /// The custom user agent string. /// /// The default value of this property is null. /// /// Sets [WKWebView.customUserAgent](https://developer.apple.com/documentation/webkit/wkwebview/1414950-customuseragent?language=objc). Future setCustomUserAgent(String? userAgent) { return _webViewApi.setCustomUserAgentForInstances(this, userAgent); } /// Evaluates the specified JavaScript string. /// /// Throws a `PlatformException` if an error occurs or return value is not /// supported. Future evaluateJavaScript(String javaScriptString) { return _webViewApi.evaluateJavaScriptForInstances( this, javaScriptString, ); } @override WKWebView copy() { return WKWebView.detached( observeValue: observeValue, binaryMessenger: _webViewApi.binaryMessenger, instanceManager: _webViewApi.instanceManager, ); } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import '../common/instance_manager.dart'; import '../common/web_kit.g.dart'; import '../foundation/foundation.dart'; import 'web_kit.dart'; export '../common/web_kit.g.dart' show WKNavigationType; Iterable _toWKWebsiteDataTypeEnumData( Iterable types) { return types.map((WKWebsiteDataType type) { late final WKWebsiteDataTypeEnum value; switch (type) { case WKWebsiteDataType.cookies: value = WKWebsiteDataTypeEnum.cookies; break; case WKWebsiteDataType.memoryCache: value = WKWebsiteDataTypeEnum.memoryCache; break; case WKWebsiteDataType.diskCache: value = WKWebsiteDataTypeEnum.diskCache; break; case WKWebsiteDataType.offlineWebApplicationCache: value = WKWebsiteDataTypeEnum.offlineWebApplicationCache; break; case WKWebsiteDataType.localStorage: value = WKWebsiteDataTypeEnum.localStorage; break; case WKWebsiteDataType.sessionStorage: value = WKWebsiteDataTypeEnum.sessionStorage; break; case WKWebsiteDataType.webSQLDatabases: value = WKWebsiteDataTypeEnum.webSQLDatabases; break; case WKWebsiteDataType.indexedDBDatabases: value = WKWebsiteDataTypeEnum.indexedDBDatabases; break; } return WKWebsiteDataTypeEnumData(value: value); }); } extension _NSHttpCookieConverter on NSHttpCookie { NSHttpCookieData toNSHttpCookieData() { final Iterable keys = properties.keys; return NSHttpCookieData( propertyKeys: keys.map( (NSHttpCookiePropertyKey key) { return key.toNSHttpCookiePropertyKeyEnumData(); }, ).toList(), propertyValues: keys .map((NSHttpCookiePropertyKey key) => properties[key]!) .toList(), ); } } extension _WKNavigationActionPolicyConverter on WKNavigationActionPolicy { WKNavigationActionPolicyEnumData toWKNavigationActionPolicyEnumData() { return WKNavigationActionPolicyEnumData( value: WKNavigationActionPolicyEnum.values.firstWhere( (WKNavigationActionPolicyEnum element) => element.name == name, ), ); } } extension _NSHttpCookiePropertyKeyConverter on NSHttpCookiePropertyKey { NSHttpCookiePropertyKeyEnumData toNSHttpCookiePropertyKeyEnumData() { late final NSHttpCookiePropertyKeyEnum value; switch (this) { case NSHttpCookiePropertyKey.comment: value = NSHttpCookiePropertyKeyEnum.comment; break; case NSHttpCookiePropertyKey.commentUrl: value = NSHttpCookiePropertyKeyEnum.commentUrl; break; case NSHttpCookiePropertyKey.discard: value = NSHttpCookiePropertyKeyEnum.discard; break; case NSHttpCookiePropertyKey.domain: value = NSHttpCookiePropertyKeyEnum.domain; break; case NSHttpCookiePropertyKey.expires: value = NSHttpCookiePropertyKeyEnum.expires; break; case NSHttpCookiePropertyKey.maximumAge: value = NSHttpCookiePropertyKeyEnum.maximumAge; break; case NSHttpCookiePropertyKey.name: value = NSHttpCookiePropertyKeyEnum.name; break; case NSHttpCookiePropertyKey.originUrl: value = NSHttpCookiePropertyKeyEnum.originUrl; break; case NSHttpCookiePropertyKey.path: value = NSHttpCookiePropertyKeyEnum.path; break; case NSHttpCookiePropertyKey.port: value = NSHttpCookiePropertyKeyEnum.port; break; case NSHttpCookiePropertyKey.sameSitePolicy: value = NSHttpCookiePropertyKeyEnum.sameSitePolicy; break; case NSHttpCookiePropertyKey.secure: value = NSHttpCookiePropertyKeyEnum.secure; break; case NSHttpCookiePropertyKey.value: value = NSHttpCookiePropertyKeyEnum.value; break; case NSHttpCookiePropertyKey.version: value = NSHttpCookiePropertyKeyEnum.version; break; } return NSHttpCookiePropertyKeyEnumData(value: value); } } extension _WKUserScriptInjectionTimeConverter on WKUserScriptInjectionTime { WKUserScriptInjectionTimeEnumData toWKUserScriptInjectionTimeEnumData() { late final WKUserScriptInjectionTimeEnum value; switch (this) { case WKUserScriptInjectionTime.atDocumentStart: value = WKUserScriptInjectionTimeEnum.atDocumentStart; break; case WKUserScriptInjectionTime.atDocumentEnd: value = WKUserScriptInjectionTimeEnum.atDocumentEnd; break; } return WKUserScriptInjectionTimeEnumData(value: value); } } Iterable _toWKAudiovisualMediaTypeEnumData( Iterable types, ) { return types .map((WKAudiovisualMediaType type) { late final WKAudiovisualMediaTypeEnum value; switch (type) { case WKAudiovisualMediaType.none: value = WKAudiovisualMediaTypeEnum.none; break; case WKAudiovisualMediaType.audio: value = WKAudiovisualMediaTypeEnum.audio; break; case WKAudiovisualMediaType.video: value = WKAudiovisualMediaTypeEnum.video; break; case WKAudiovisualMediaType.all: value = WKAudiovisualMediaTypeEnum.all; break; } return WKAudiovisualMediaTypeEnumData(value: value); }); } extension _NavigationActionDataConverter on WKNavigationActionData { WKNavigationAction toNavigationAction() { return WKNavigationAction( request: request.toNSUrlRequest(), targetFrame: targetFrame.toWKFrameInfo(), navigationType: navigationType, ); } } extension _WKFrameInfoDataConverter on WKFrameInfoData { WKFrameInfo toWKFrameInfo() { return WKFrameInfo(isMainFrame: isMainFrame); } } extension _NSUrlRequestDataConverter on NSUrlRequestData { NSUrlRequest toNSUrlRequest() { return NSUrlRequest( url: url, httpBody: httpBody, httpMethod: httpMethod, allHttpHeaderFields: allHttpHeaderFields.cast(), ); } } extension _WKNSErrorDataConverter on NSErrorData { NSError toNSError() { return NSError( domain: domain, code: code, localizedDescription: localizedDescription, ); } } extension _WKScriptMessageDataConverter on WKScriptMessageData { WKScriptMessage toWKScriptMessage() { return WKScriptMessage(name: name, body: body); } } extension _WKUserScriptConverter on WKUserScript { WKUserScriptData toWKUserScriptData() { return WKUserScriptData( source: source, injectionTime: injectionTime.toWKUserScriptInjectionTimeEnumData(), isMainFrameOnly: isMainFrameOnly, ); } } extension _NSUrlRequestConverter on NSUrlRequest { NSUrlRequestData toNSUrlRequestData() { return NSUrlRequestData( url: url, httpMethod: httpMethod, httpBody: httpBody, allHttpHeaderFields: allHttpHeaderFields, ); } } /// Handles initialization of Flutter APIs for WebKit. class WebKitFlutterApis { /// Constructs a [WebKitFlutterApis]. @visibleForTesting WebKitFlutterApis({ BinaryMessenger? binaryMessenger, InstanceManager? instanceManager, }) : _binaryMessenger = binaryMessenger, navigationDelegate = WKNavigationDelegateFlutterApiImpl( instanceManager: instanceManager, ), scriptMessageHandler = WKScriptMessageHandlerFlutterApiImpl( instanceManager: instanceManager, ), uiDelegate = WKUIDelegateFlutterApiImpl( instanceManager: instanceManager, ), webViewConfiguration = WKWebViewConfigurationFlutterApiImpl( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ); static WebKitFlutterApis _instance = WebKitFlutterApis(); /// Sets the global instance containing the Flutter Apis for the WebKit library. @visibleForTesting static set instance(WebKitFlutterApis instance) { _instance = instance; } /// Global instance containing the Flutter Apis for the WebKit library. static WebKitFlutterApis get instance { return _instance; } final BinaryMessenger? _binaryMessenger; bool _hasBeenSetUp = false; /// Flutter Api for [WKNavigationDelegate]. @visibleForTesting final WKNavigationDelegateFlutterApiImpl navigationDelegate; /// Flutter Api for [WKScriptMessageHandler]. @visibleForTesting final WKScriptMessageHandlerFlutterApiImpl scriptMessageHandler; /// Flutter Api for [WKUIDelegate]. @visibleForTesting final WKUIDelegateFlutterApiImpl uiDelegate; /// Flutter Api for [WKWebViewConfiguration]. @visibleForTesting final WKWebViewConfigurationFlutterApiImpl webViewConfiguration; /// Ensures all the Flutter APIs have been set up to receive calls from native code. void ensureSetUp() { if (!_hasBeenSetUp) { WKNavigationDelegateFlutterApi.setup( navigationDelegate, binaryMessenger: _binaryMessenger, ); WKScriptMessageHandlerFlutterApi.setup( scriptMessageHandler, binaryMessenger: _binaryMessenger, ); WKUIDelegateFlutterApi.setup( uiDelegate, binaryMessenger: _binaryMessenger, ); WKWebViewConfigurationFlutterApi.setup( webViewConfiguration, binaryMessenger: _binaryMessenger, ); _hasBeenSetUp = true; } } } /// Host api implementation for [WKWebSiteDataStore]. class WKWebsiteDataStoreHostApiImpl extends WKWebsiteDataStoreHostApi { /// Constructs a [WebsiteDataStoreHostApiImpl]. WKWebsiteDataStoreHostApiImpl({ this.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager, super(binaryMessenger: binaryMessenger); /// Sends 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 Objective-C objects. final InstanceManager instanceManager; /// Calls [createFromWebViewConfiguration] with the ids of the provided object instances. Future createFromWebViewConfigurationForInstances( WKWebsiteDataStore instance, WKWebViewConfiguration configuration, ) { return createFromWebViewConfiguration( instanceManager.addDartCreatedInstance(instance), instanceManager.getIdentifier(configuration)!, ); } /// Calls [createDefaultDataStore] with the ids of the provided object instances. Future createDefaultDataStoreForInstances( WKWebsiteDataStore instance, ) { return createDefaultDataStore( instanceManager.addDartCreatedInstance(instance), ); } /// Calls [removeDataOfTypes] with the ids of the provided object instances. Future removeDataOfTypesForInstances( WKWebsiteDataStore instance, Set dataTypes, { required double secondsModifiedSinceEpoch, }) { return removeDataOfTypes( instanceManager.getIdentifier(instance)!, _toWKWebsiteDataTypeEnumData(dataTypes).toList(), secondsModifiedSinceEpoch, ); } } /// Host api implementation for [WKScriptMessageHandler]. class WKScriptMessageHandlerHostApiImpl extends WKScriptMessageHandlerHostApi { /// Constructs a [WKScriptMessageHandlerHostApiImpl]. WKScriptMessageHandlerHostApiImpl({ this.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager, super(binaryMessenger: binaryMessenger); /// Sends 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 Objective-C objects. final InstanceManager instanceManager; /// Calls [create] with the ids of the provided object instances. Future createForInstances(WKScriptMessageHandler instance) { return create(instanceManager.addDartCreatedInstance(instance)); } } /// Flutter api implementation for [WKScriptMessageHandler]. class WKScriptMessageHandlerFlutterApiImpl extends WKScriptMessageHandlerFlutterApi { /// Constructs a [WKScriptMessageHandlerFlutterApiImpl]. WKScriptMessageHandlerFlutterApiImpl({InstanceManager? instanceManager}) : instanceManager = instanceManager ?? NSObject.globalInstanceManager; /// Maintains instances stored to communicate with native language objects. final InstanceManager instanceManager; WKScriptMessageHandler _getHandler(int identifier) { return instanceManager.getInstanceWithWeakReference(identifier)!; } @override void didReceiveScriptMessage( int identifier, int userContentControllerIdentifier, WKScriptMessageData message, ) { _getHandler(identifier).didReceiveScriptMessage( instanceManager.getInstanceWithWeakReference( userContentControllerIdentifier, )! as WKUserContentController, message.toWKScriptMessage(), ); } } /// Host api implementation for [WKPreferences]. class WKPreferencesHostApiImpl extends WKPreferencesHostApi { /// Constructs a [WKPreferencesHostApiImpl]. WKPreferencesHostApiImpl({ this.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager, super(binaryMessenger: binaryMessenger); /// Sends 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 Objective-C objects. final InstanceManager instanceManager; /// Calls [createFromWebViewConfiguration] with the ids of the provided object instances. Future createFromWebViewConfigurationForInstances( WKPreferences instance, WKWebViewConfiguration configuration, ) { return createFromWebViewConfiguration( instanceManager.addDartCreatedInstance(instance), instanceManager.getIdentifier(configuration)!, ); } /// Calls [setJavaScriptEnabled] with the ids of the provided object instances. Future setJavaScriptEnabledForInstances( WKPreferences instance, bool enabled, ) { return setJavaScriptEnabled( instanceManager.getIdentifier(instance)!, enabled, ); } } /// Host api implementation for [WKHttpCookieStore]. class WKHttpCookieStoreHostApiImpl extends WKHttpCookieStoreHostApi { /// Constructs a [WKHttpCookieStoreHostApiImpl]. WKHttpCookieStoreHostApiImpl({ this.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager, super(binaryMessenger: binaryMessenger); /// Sends 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 Objective-C objects. final InstanceManager instanceManager; /// Calls [createFromWebsiteDataStore] with the ids of the provided object instances. Future createFromWebsiteDataStoreForInstances( WKHttpCookieStore instance, WKWebsiteDataStore dataStore, ) { return createFromWebsiteDataStore( instanceManager.addDartCreatedInstance(instance), instanceManager.getIdentifier(dataStore)!, ); } /// Calls [setCookie] with the ids of the provided object instances. Future setCookieForInstances( WKHttpCookieStore instance, NSHttpCookie cookie, ) { return setCookie( instanceManager.getIdentifier(instance)!, cookie.toNSHttpCookieData(), ); } } /// Host api implementation for [WKUserContentController]. class WKUserContentControllerHostApiImpl extends WKUserContentControllerHostApi { /// Constructs a [WKUserContentControllerHostApiImpl]. WKUserContentControllerHostApiImpl({ this.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager, super(binaryMessenger: binaryMessenger); /// Sends 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 Objective-C objects. final InstanceManager instanceManager; /// Calls [createFromWebViewConfiguration] with the ids of the provided object instances. Future createFromWebViewConfigurationForInstances( WKUserContentController instance, WKWebViewConfiguration configuration, ) { return createFromWebViewConfiguration( instanceManager.addDartCreatedInstance(instance), instanceManager.getIdentifier(configuration)!, ); } /// Calls [addScriptMessageHandler] with the ids of the provided object instances. Future addScriptMessageHandlerForInstances( WKUserContentController instance, WKScriptMessageHandler handler, String name, ) { return addScriptMessageHandler( instanceManager.getIdentifier(instance)!, instanceManager.getIdentifier(handler)!, name, ); } /// Calls [removeScriptMessageHandler] with the ids of the provided object instances. Future removeScriptMessageHandlerForInstances( WKUserContentController instance, String name, ) { return removeScriptMessageHandler( instanceManager.getIdentifier(instance)!, name, ); } /// Calls [removeAllScriptMessageHandlers] with the ids of the provided object instances. Future removeAllScriptMessageHandlersForInstances( WKUserContentController instance, ) { return removeAllScriptMessageHandlers( instanceManager.getIdentifier(instance)!, ); } /// Calls [addUserScript] with the ids of the provided object instances. Future addUserScriptForInstances( WKUserContentController instance, WKUserScript userScript, ) { return addUserScript( instanceManager.getIdentifier(instance)!, userScript.toWKUserScriptData(), ); } /// Calls [removeAllUserScripts] with the ids of the provided object instances. Future removeAllUserScriptsForInstances( WKUserContentController instance, ) { return removeAllUserScripts(instanceManager.getIdentifier(instance)!); } } /// Host api implementation for [WKWebViewConfiguration]. class WKWebViewConfigurationHostApiImpl extends WKWebViewConfigurationHostApi { /// Constructs a [WKWebViewConfigurationHostApiImpl]. WKWebViewConfigurationHostApiImpl({ this.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager, super(binaryMessenger: binaryMessenger); /// Sends 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 Objective-C objects. final InstanceManager instanceManager; /// Calls [create] with the ids of the provided object instances. Future createForInstances(WKWebViewConfiguration instance) { return create(instanceManager.addDartCreatedInstance(instance)); } /// Calls [createFromWebView] with the ids of the provided object instances. Future createFromWebViewForInstances( WKWebViewConfiguration instance, WKWebView webView, ) { return createFromWebView( instanceManager.addDartCreatedInstance(instance), instanceManager.getIdentifier(webView)!, ); } /// Calls [setAllowsInlineMediaPlayback] with the ids of the provided object instances. Future setAllowsInlineMediaPlaybackForInstances( WKWebViewConfiguration instance, bool allow, ) { return setAllowsInlineMediaPlayback( instanceManager.getIdentifier(instance)!, allow, ); } /// Calls [setMediaTypesRequiringUserActionForPlayback] with the ids of the provided object instances. Future setMediaTypesRequiringUserActionForPlaybackForInstances( WKWebViewConfiguration instance, Set types, ) { return setMediaTypesRequiringUserActionForPlayback( instanceManager.getIdentifier(instance)!, _toWKAudiovisualMediaTypeEnumData(types).toList(), ); } } /// Flutter api implementation for [WKWebViewConfiguration]. @immutable class WKWebViewConfigurationFlutterApiImpl extends WKWebViewConfigurationFlutterApi { /// Constructs a [WKWebViewConfigurationFlutterApiImpl]. WKWebViewConfigurationFlutterApiImpl({ this.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? NSObject.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( WKWebViewConfiguration.detached( binaryMessenger: binaryMessenger, instanceManager: instanceManager, ), identifier, ); } } /// Host api implementation for [WKUIDelegate]. class WKUIDelegateHostApiImpl extends WKUIDelegateHostApi { /// Constructs a [WKUIDelegateHostApiImpl]. WKUIDelegateHostApiImpl({ this.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager, super(binaryMessenger: binaryMessenger); /// Sends 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 Objective-C objects. final InstanceManager instanceManager; /// Calls [create] with the ids of the provided object instances. Future createForInstances(WKUIDelegate instance) async { return create(instanceManager.addDartCreatedInstance(instance)); } } /// Flutter api implementation for [WKUIDelegate]. class WKUIDelegateFlutterApiImpl extends WKUIDelegateFlutterApi { /// Constructs a [WKUIDelegateFlutterApiImpl]. WKUIDelegateFlutterApiImpl({InstanceManager? instanceManager}) : instanceManager = instanceManager ?? NSObject.globalInstanceManager; /// Maintains instances stored to communicate with native language objects. final InstanceManager instanceManager; WKUIDelegate _getDelegate(int identifier) { return instanceManager.getInstanceWithWeakReference(identifier)!; } @override void onCreateWebView( int identifier, int webViewIdentifier, int configurationIdentifier, WKNavigationActionData navigationAction, ) { final void Function(WKWebView, WKWebViewConfiguration, WKNavigationAction)? function = _getDelegate(identifier).onCreateWebView; function?.call( instanceManager.getInstanceWithWeakReference(webViewIdentifier)! as WKWebView, instanceManager.getInstanceWithWeakReference(configurationIdentifier)! as WKWebViewConfiguration, navigationAction.toNavigationAction(), ); } } /// Host api implementation for [WKNavigationDelegate]. class WKNavigationDelegateHostApiImpl extends WKNavigationDelegateHostApi { /// Constructs a [WKNavigationDelegateHostApiImpl]. WKNavigationDelegateHostApiImpl({ this.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager, super(binaryMessenger: binaryMessenger); /// Sends 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 Objective-C objects. final InstanceManager instanceManager; /// Calls [create] with the ids of the provided object instances. Future createForInstances(WKNavigationDelegate instance) async { return create(instanceManager.addDartCreatedInstance(instance)); } } /// Flutter api implementation for [WKNavigationDelegate]. class WKNavigationDelegateFlutterApiImpl extends WKNavigationDelegateFlutterApi { /// Constructs a [WKNavigationDelegateFlutterApiImpl]. WKNavigationDelegateFlutterApiImpl({InstanceManager? instanceManager}) : instanceManager = instanceManager ?? NSObject.globalInstanceManager; /// Maintains instances stored to communicate with native language objects. final InstanceManager instanceManager; WKNavigationDelegate _getDelegate(int identifier) { return instanceManager.getInstanceWithWeakReference(identifier)!; } @override void didFinishNavigation( int identifier, int webViewIdentifier, String? url, ) { final void Function(WKWebView, String?)? function = _getDelegate(identifier).didFinishNavigation; function?.call( instanceManager.getInstanceWithWeakReference(webViewIdentifier)! as WKWebView, url, ); } @override Future decidePolicyForNavigationAction( int identifier, int webViewIdentifier, WKNavigationActionData navigationAction, ) async { final Future Function( WKWebView, WKNavigationAction navigationAction, )? function = _getDelegate(identifier).decidePolicyForNavigationAction; if (function == null) { return WKNavigationActionPolicyEnumData( value: WKNavigationActionPolicyEnum.allow, ); } final WKNavigationActionPolicy policy = await function( instanceManager.getInstanceWithWeakReference(webViewIdentifier)! as WKWebView, navigationAction.toNavigationAction(), ); return policy.toWKNavigationActionPolicyEnumData(); } @override void didFailNavigation( int identifier, int webViewIdentifier, NSErrorData error, ) { final void Function(WKWebView, NSError)? function = _getDelegate(identifier).didFailNavigation; function?.call( instanceManager.getInstanceWithWeakReference(webViewIdentifier)! as WKWebView, error.toNSError(), ); } @override void didFailProvisionalNavigation( int identifier, int webViewIdentifier, NSErrorData error, ) { final void Function(WKWebView, NSError)? function = _getDelegate(identifier).didFailProvisionalNavigation; function?.call( instanceManager.getInstanceWithWeakReference(webViewIdentifier)! as WKWebView, error.toNSError(), ); } @override void didStartProvisionalNavigation( int identifier, int webViewIdentifier, String? url, ) { final void Function(WKWebView, String?)? function = _getDelegate(identifier).didStartProvisionalNavigation; function?.call( instanceManager.getInstanceWithWeakReference(webViewIdentifier)! as WKWebView, url, ); } @override void webViewWebContentProcessDidTerminate( int identifier, int webViewIdentifier, ) { final void Function(WKWebView)? function = _getDelegate(identifier).webViewWebContentProcessDidTerminate; function?.call( instanceManager.getInstanceWithWeakReference(webViewIdentifier)! as WKWebView, ); } } /// Host api implementation for [WKWebView]. class WKWebViewHostApiImpl extends WKWebViewHostApi { /// Constructs a [WKWebViewHostApiImpl]. WKWebViewHostApiImpl({ this.binaryMessenger, InstanceManager? instanceManager, }) : instanceManager = instanceManager ?? NSObject.globalInstanceManager, super(binaryMessenger: binaryMessenger); /// Sends 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 Objective-C objects. final InstanceManager instanceManager; /// Calls [create] with the ids of the provided object instances. Future createForInstances( WKWebView instance, WKWebViewConfiguration configuration, ) { return create( instanceManager.addDartCreatedInstance(instance), instanceManager.getIdentifier(configuration)!, ); } /// Calls [loadRequest] with the ids of the provided object instances. Future loadRequestForInstances( WKWebView webView, NSUrlRequest request, ) { return loadRequest( instanceManager.getIdentifier(webView)!, request.toNSUrlRequestData(), ); } /// Calls [loadHtmlString] with the ids of the provided object instances. Future loadHtmlStringForInstances( WKWebView instance, String string, String? baseUrl, ) { return loadHtmlString( instanceManager.getIdentifier(instance)!, string, baseUrl, ); } /// Calls [loadFileUrl] with the ids of the provided object instances. Future loadFileUrlForInstances( WKWebView instance, String url, String readAccessUrl, ) { return loadFileUrl( instanceManager.getIdentifier(instance)!, url, readAccessUrl, ); } /// Calls [loadFlutterAsset] with the ids of the provided object instances. Future loadFlutterAssetForInstances(WKWebView instance, String key) { return loadFlutterAsset( instanceManager.getIdentifier(instance)!, key, ); } /// Calls [canGoBack] with the ids of the provided object instances. Future canGoBackForInstances(WKWebView instance) { return canGoBack(instanceManager.getIdentifier(instance)!); } /// Calls [canGoForward] with the ids of the provided object instances. Future canGoForwardForInstances(WKWebView instance) { return canGoForward(instanceManager.getIdentifier(instance)!); } /// Calls [goBack] with the ids of the provided object instances. Future goBackForInstances(WKWebView instance) { return goBack(instanceManager.getIdentifier(instance)!); } /// Calls [goForward] with the ids of the provided object instances. Future goForwardForInstances(WKWebView instance) { return goForward(instanceManager.getIdentifier(instance)!); } /// Calls [reload] with the ids of the provided object instances. Future reloadForInstances(WKWebView instance) { return reload(instanceManager.getIdentifier(instance)!); } /// Calls [getUrl] with the ids of the provided object instances. Future getUrlForInstances(WKWebView instance) { return getUrl(instanceManager.getIdentifier(instance)!); } /// Calls [getTitle] with the ids of the provided object instances. Future getTitleForInstances(WKWebView instance) { return getTitle(instanceManager.getIdentifier(instance)!); } /// Calls [getEstimatedProgress] with the ids of the provided object instances. Future getEstimatedProgressForInstances(WKWebView instance) { return getEstimatedProgress(instanceManager.getIdentifier(instance)!); } /// Calls [setAllowsBackForwardNavigationGestures] with the ids of the provided object instances. Future setAllowsBackForwardNavigationGesturesForInstances( WKWebView instance, bool allow, ) { return setAllowsBackForwardNavigationGestures( instanceManager.getIdentifier(instance)!, allow, ); } /// Calls [setCustomUserAgent] with the ids of the provided object instances. Future setCustomUserAgentForInstances( WKWebView instance, String? userAgent, ) { return setCustomUserAgent( instanceManager.getIdentifier(instance)!, userAgent, ); } /// Calls [evaluateJavaScript] with the ids of the provided object instances. Future evaluateJavaScriptForInstances( WKWebView instance, String javaScriptString, ) async { try { final Object? result = await evaluateJavaScript( instanceManager.getIdentifier(instance)!, javaScriptString, ); return result; } on PlatformException catch (exception) { if (exception.details is! NSErrorData) { rethrow; } throw PlatformException( code: exception.code, message: exception.message, stacktrace: exception.stacktrace, details: (exception.details as NSErrorData).toNSError(), ); } } /// Calls [setNavigationDelegate] with the ids of the provided object instances. Future setNavigationDelegateForInstances( WKWebView instance, WKNavigationDelegate? delegate, ) { return setNavigationDelegate( instanceManager.getIdentifier(instance)!, delegate != null ? instanceManager.getIdentifier(delegate)! : null, ); } /// Calls [setUIDelegate] with the ids of the provided object instances. Future setUIDelegateForInstances( WKWebView instance, WKUIDelegate? delegate, ) { return setUIDelegate( instanceManager.getIdentifier(instance)!, delegate != null ? instanceManager.getIdentifier(delegate)! : null, ); } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart ================================================ // Copyright 2013 The Flutter 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 'common/instance_manager.dart'; import 'foundation/foundation.dart'; import 'web_kit/web_kit.dart'; // This convenience method was added because Dart doesn't support constant // function literals: https://github.com/dart-lang/language/issues/1048. WKWebsiteDataStore _defaultWebsiteDataStore() => WKWebsiteDataStore.defaultDataStore; /// Handles constructing objects and calling static methods for the WebKit /// native library. /// /// This class provides dependency injection for the implementations of the /// platform interface classes. Improving the ease of unit testing and/or /// overriding the underlying WebKit classes. /// /// By default each function calls the default constructor of the WebKit class /// it intends to return. class WebKitProxy { /// Constructs a [WebKitProxy]. const WebKitProxy({ this.createWebView = WKWebView.new, this.createWebViewConfiguration = WKWebViewConfiguration.new, this.createScriptMessageHandler = WKScriptMessageHandler.new, this.defaultWebsiteDataStore = _defaultWebsiteDataStore, this.createNavigationDelegate = WKNavigationDelegate.new, this.createUIDelegate = WKUIDelegate.new, }); /// Constructs a [WKWebView]. final WKWebView Function( WKWebViewConfiguration configuration, { void Function( String keyPath, NSObject object, Map change, )? observeValue, InstanceManager? instanceManager, }) createWebView; /// Constructs a [WKWebViewConfiguration]. final WKWebViewConfiguration Function({ InstanceManager? instanceManager, }) createWebViewConfiguration; /// Constructs a [WKScriptMessageHandler]. final WKScriptMessageHandler Function({ required void Function( WKUserContentController userContentController, WKScriptMessage message, ) didReceiveScriptMessage, }) createScriptMessageHandler; /// The default [WKWebsiteDataStore]. final WKWebsiteDataStore Function() defaultWebsiteDataStore; /// Constructs a [WKNavigationDelegate]. final WKNavigationDelegate Function({ void Function(WKWebView webView, String? url)? didFinishNavigation, void Function(WKWebView webView, String? url)? didStartProvisionalNavigation, Future Function( WKWebView webView, WKNavigationAction navigationAction, )? decidePolicyForNavigationAction, void Function(WKWebView webView, NSError error)? didFailNavigation, void Function(WKWebView webView, NSError error)? didFailProvisionalNavigation, void Function(WKWebView webView)? webViewWebContentProcessDidTerminate, }) createNavigationDelegate; /// Constructs a [WKUIDelegate]. final WKUIDelegate Function({ void Function( WKWebView webView, WKWebViewConfiguration configuration, WKNavigationAction navigationAction, )? onCreateWebView, }) createUIDelegate; } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart ================================================ // Copyright 2013 The Flutter 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 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:path/path.dart' as path; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'common/instance_manager.dart'; import 'common/weak_reference_utils.dart'; import 'foundation/foundation.dart'; import 'web_kit/web_kit.dart'; import 'webkit_proxy.dart'; /// Media types that can require a user gesture to begin playing. /// /// See [WebKitWebViewControllerCreationParams.mediaTypesRequiringUserAction]. enum PlaybackMediaTypes { /// A media type that contains audio. audio, /// A media type that contains video. video; WKAudiovisualMediaType _toWKAudiovisualMediaType() { switch (this) { case PlaybackMediaTypes.audio: return WKAudiovisualMediaType.audio; case PlaybackMediaTypes.video: return WKAudiovisualMediaType.video; } } } /// Object specifying creation parameters for a [WebKitWebViewController]. @immutable class WebKitWebViewControllerCreationParams extends PlatformWebViewControllerCreationParams { /// Constructs a [WebKitWebViewControllerCreationParams]. WebKitWebViewControllerCreationParams({ @visibleForTesting this.webKitProxy = const WebKitProxy(), this.mediaTypesRequiringUserAction = const { PlaybackMediaTypes.audio, PlaybackMediaTypes.video, }, this.allowsInlineMediaPlayback = false, @visibleForTesting InstanceManager? instanceManager, }) : _instanceManager = instanceManager ?? NSObject.globalInstanceManager { _configuration = webKitProxy.createWebViewConfiguration( instanceManager: _instanceManager, ); if (mediaTypesRequiringUserAction.isEmpty) { _configuration.setMediaTypesRequiringUserActionForPlayback( {WKAudiovisualMediaType.none}, ); } else { _configuration.setMediaTypesRequiringUserActionForPlayback( mediaTypesRequiringUserAction .map( (PlaybackMediaTypes type) => type._toWKAudiovisualMediaType(), ) .toSet(), ); } _configuration.setAllowsInlineMediaPlayback(allowsInlineMediaPlayback); } /// Constructs a [WebKitWebViewControllerCreationParams] using a /// [PlatformWebViewControllerCreationParams]. WebKitWebViewControllerCreationParams.fromPlatformWebViewControllerCreationParams( // Recommended placeholder to prevent being broken by platform interface. // ignore: avoid_unused_constructor_parameters PlatformWebViewControllerCreationParams params, { @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), Set mediaTypesRequiringUserAction = const { PlaybackMediaTypes.audio, PlaybackMediaTypes.video, }, bool allowsInlineMediaPlayback = false, @visibleForTesting InstanceManager? instanceManager, }) : this( webKitProxy: webKitProxy, mediaTypesRequiringUserAction: mediaTypesRequiringUserAction, allowsInlineMediaPlayback: allowsInlineMediaPlayback, instanceManager: instanceManager, ); late final WKWebViewConfiguration _configuration; /// Media types that require a user gesture to begin playing. /// /// Defaults to include [PlaybackMediaTypes.audio] and /// [PlaybackMediaTypes.video]. final Set mediaTypesRequiringUserAction; /// Whether inline playback of HTML5 videos is allowed. /// /// Defaults to false. final bool allowsInlineMediaPlayback; /// Handles constructing objects and calling static methods for the WebKit /// 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. class WebKitWebViewController extends PlatformWebViewController { /// Constructs a [WebKitWebViewController]. WebKitWebViewController(PlatformWebViewControllerCreationParams params) : super.implementation(params is WebKitWebViewControllerCreationParams ? params : WebKitWebViewControllerCreationParams .fromPlatformWebViewControllerCreationParams(params)) { _webView.addObserver( _webView, keyPath: 'estimatedProgress', options: { NSKeyValueObservingOptions.newValue, }, ); } /// The WebKit WebView being controlled. late final WKWebView _webView = _webKitParams.webKitProxy.createWebView( _webKitParams._configuration, observeValue: withWeakRefenceTo(this, ( WeakReference weakReference, ) { return ( String keyPath, NSObject object, Map change, ) { final ProgressCallback? progressCallback = weakReference.target?._currentNavigationDelegate?._onProgress; if (progressCallback != null) { final double progress = change[NSKeyValueChangeKey.newValue]! as double; progressCallback((progress * 100).round()); } }; }), instanceManager: _webKitParams._instanceManager, ); final Map _javaScriptChannelParams = {}; bool _zoomEnabled = true; WebKitNavigationDelegate? _currentNavigationDelegate; WebKitWebViewControllerCreationParams get _webKitParams => params as WebKitWebViewControllerCreationParams; /// Identifier used to retrieve the underlying native `WKWebView`. /// /// This is typically used by other plugins to retrieve the native `WKWebView` /// from an `FWFInstanceManager`. /// /// See Objective-C method /// `FLTWebViewFlutterPlugin:webViewForIdentifier:withPluginRegistry`. int get webViewIdentifier => _webKitParams._instanceManager.getIdentifier(_webView)!; @override Future loadFile(String absoluteFilePath) { return _webView.loadFileUrl( absoluteFilePath, readAccessUrl: path.dirname(absoluteFilePath), ); } @override Future loadFlutterAsset(String key) { assert(key.isNotEmpty); return _webView.loadFlutterAsset(key); } @override Future loadHtmlString(String html, {String? baseUrl}) { return _webView.loadHtmlString(html, baseUrl: baseUrl); } @override Future loadRequest(LoadRequestParams params) { if (!params.uri.hasScheme) { throw ArgumentError( 'LoadRequestParams#uri is required to have a scheme.', ); } return _webView.loadRequest(NSUrlRequest( url: params.uri.toString(), allHttpHeaderFields: params.headers, httpMethod: describeEnum(params.method), httpBody: params.body, )); } @override Future addJavaScriptChannel( JavaScriptChannelParams javaScriptChannelParams, ) { final WebKitJavaScriptChannelParams webKitParams = javaScriptChannelParams is WebKitJavaScriptChannelParams ? javaScriptChannelParams : WebKitJavaScriptChannelParams.fromJavaScriptChannelParams( javaScriptChannelParams); _javaScriptChannelParams[webKitParams.name] = webKitParams; final String wrapperSource = 'window.${webKitParams.name} = webkit.messageHandlers.${webKitParams.name};'; final WKUserScript wrapperScript = WKUserScript( wrapperSource, WKUserScriptInjectionTime.atDocumentStart, isMainFrameOnly: false, ); _webView.configuration.userContentController.addUserScript(wrapperScript); return _webView.configuration.userContentController.addScriptMessageHandler( webKitParams._messageHandler, webKitParams.name, ); } @override Future removeJavaScriptChannel(String javaScriptChannelName) async { assert(javaScriptChannelName.isNotEmpty); if (!_javaScriptChannelParams.containsKey(javaScriptChannelName)) { return; } await _resetUserScripts(removedJavaScriptChannel: javaScriptChannelName); } @override Future currentUrl() => _webView.getUrl(); @override Future canGoBack() => _webView.canGoBack(); @override Future canGoForward() => _webView.canGoForward(); @override Future goBack() => _webView.goBack(); @override Future goForward() => _webView.goForward(); @override Future reload() => _webView.reload(); @override Future clearCache() { return _webView.configuration.websiteDataStore.removeDataOfTypes( { WKWebsiteDataType.memoryCache, WKWebsiteDataType.diskCache, WKWebsiteDataType.offlineWebApplicationCache, }, DateTime.fromMillisecondsSinceEpoch(0), ); } @override Future clearLocalStorage() { return _webView.configuration.websiteDataStore.removeDataOfTypes( {WKWebsiteDataType.localStorage}, DateTime.fromMillisecondsSinceEpoch(0), ); } @override Future runJavaScript(String javaScript) async { try { await _webView.evaluateJavaScript(javaScript); } on PlatformException catch (exception) { // WebKit will throw an error when the type of the evaluated value is // 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. final Object? details = exception.details; if (details is! NSError || details.code != WKErrorCode.javaScriptResultTypeIsUnsupported) { rethrow; } } } @override Future runJavaScriptReturningResult(String javaScript) async { final Object? result = await _webView.evaluateJavaScript(javaScript); if (result == null) { throw ArgumentError( 'Result of JavaScript execution returned a `null` value. ' 'Use `runJavascript` when expecting a null return value.', ); } return result; } @override Future getTitle() => _webView.getTitle(); @override Future scrollTo(int x, int y) { return _webView.scrollView.setContentOffset(Point( x.toDouble(), y.toDouble(), )); } @override Future scrollBy(int x, int y) { return _webView.scrollView.scrollBy(Point( x.toDouble(), y.toDouble(), )); } @override Future getScrollPosition() async { final Point offset = await _webView.scrollView.getContentOffset(); return Offset(offset.x, offset.y); } /// Whether horizontal swipe gestures trigger page navigation. Future setAllowsBackForwardNavigationGestures(bool enabled) { return _webView.setAllowsBackForwardNavigationGestures(enabled); } @override Future setBackgroundColor(Color color) { return Future.wait(>[ _webView.setOpaque(false), _webView.setBackgroundColor(Colors.transparent), // This method must be called last. _webView.scrollView.setBackgroundColor(color), ]); } @override Future setJavaScriptMode(JavaScriptMode javaScriptMode) { switch (javaScriptMode) { case JavaScriptMode.disabled: return _webView.configuration.preferences.setJavaScriptEnabled(false); case JavaScriptMode.unrestricted: return _webView.configuration.preferences.setJavaScriptEnabled(true); } } @override Future setUserAgent(String? userAgent) { return _webView.setCustomUserAgent(userAgent); } @override Future enableZoom(bool enabled) async { if (_zoomEnabled == enabled) { return; } _zoomEnabled = enabled; if (enabled) { await _resetUserScripts(); } else { await _disableZoom(); } } @override Future setPlatformNavigationDelegate( covariant WebKitNavigationDelegate handler, ) { _currentNavigationDelegate = handler; return Future.wait(>[ _webView.setUIDelegate(handler._uiDelegate), _webView.setNavigationDelegate(handler._navigationDelegate) ]); } Future _disableZoom() { const WKUserScript userScript = WKUserScript( "var meta = document.createElement('meta');\n" "meta.name = 'viewport';\n" "meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, " "user-scalable=no';\n" "var head = document.getElementsByTagName('head')[0];head.appendChild(meta);", WKUserScriptInjectionTime.atDocumentEnd, isMainFrameOnly: true, ); return _webView.configuration.userContentController .addUserScript(userScript); } // WKWebView does not support removing a single user script, so all user // scripts and all message handlers are removed instead. And the JavaScript // channels that shouldn't be removed are re-registered. Note that this // workaround could interfere with exposing support for custom scripts from // applications. Future _resetUserScripts({String? removedJavaScriptChannel}) async { _webView.configuration.userContentController.removeAllUserScripts(); // TODO(bparrishMines): This can be replaced with // `removeAllScriptMessageHandlers` once Dart supports runtime version // checking. (e.g. The equivalent to @availability in Objective-C.) _javaScriptChannelParams.keys.forEach( _webView.configuration.userContentController.removeScriptMessageHandler, ); _javaScriptChannelParams.remove(removedJavaScriptChannel); await Future.wait(>[ for (JavaScriptChannelParams params in _javaScriptChannelParams.values) addJavaScriptChannel(params), // Zoom is disabled with a WKUserScript, so this adds it back if it was // removed above. if (!_zoomEnabled) _disableZoom(), ]); } } /// An implementation of [JavaScriptChannelParams] with the WebKit api. /// /// See [WebKitWebViewController.addJavaScriptChannel]. @immutable class WebKitJavaScriptChannelParams extends JavaScriptChannelParams { /// Constructs a [WebKitJavaScriptChannelParams]. WebKitJavaScriptChannelParams({ required super.name, required super.onMessageReceived, @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), }) : assert(name.isNotEmpty), _messageHandler = webKitProxy.createScriptMessageHandler( didReceiveScriptMessage: withWeakRefenceTo( onMessageReceived, (WeakReference weakReference) { return ( WKUserContentController controller, WKScriptMessage message, ) { if (weakReference.target != null) { weakReference.target!( JavaScriptMessage(message: message.body!.toString()), ); } }; }, ), ); /// Constructs a [WebKitJavaScriptChannelParams] using a /// [JavaScriptChannelParams]. WebKitJavaScriptChannelParams.fromJavaScriptChannelParams( JavaScriptChannelParams params, { @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), }) : this( name: params.name, onMessageReceived: params.onMessageReceived, webKitProxy: webKitProxy, ); final WKScriptMessageHandler _messageHandler; } /// Object specifying creation parameters for a [WebKitWebViewWidget]. @immutable class WebKitWebViewWidgetCreationParams extends PlatformWebViewWidgetCreationParams { /// Constructs a [WebKitWebViewWidgetCreationParams]. WebKitWebViewWidgetCreationParams({ super.key, required super.controller, super.layoutDirection, super.gestureRecognizers, @visibleForTesting InstanceManager? instanceManager, }) : _instanceManager = instanceManager ?? NSObject.globalInstanceManager; /// Constructs a [WebKitWebViewWidgetCreationParams] using a /// [PlatformWebViewWidgetCreationParams]. WebKitWebViewWidgetCreationParams.fromPlatformWebViewWidgetCreationParams( PlatformWebViewWidgetCreationParams params, { InstanceManager? instanceManager, }) : this( key: params.key, controller: params.controller, layoutDirection: params.layoutDirection, gestureRecognizers: params.gestureRecognizers, instanceManager: instanceManager, ); // Maintains instances used to communicate with the native objects they // represent. final InstanceManager _instanceManager; } /// An implementation of [PlatformWebViewWidget] with the WebKit api. class WebKitWebViewWidget extends PlatformWebViewWidget { /// Constructs a [WebKitWebViewWidget]. WebKitWebViewWidget(PlatformWebViewWidgetCreationParams params) : super.implementation( params is WebKitWebViewWidgetCreationParams ? params : WebKitWebViewWidgetCreationParams .fromPlatformWebViewWidgetCreationParams(params), ); WebKitWebViewWidgetCreationParams get _webKitParams => params as WebKitWebViewWidgetCreationParams; @override Widget build(BuildContext context) { return UiKitView( key: _webKitParams.key, viewType: 'plugins.flutter.io/webview', onPlatformViewCreated: (_) {}, layoutDirection: params.layoutDirection, gestureRecognizers: params.gestureRecognizers, creationParams: _webKitParams._instanceManager.getIdentifier( (params.controller as WebKitWebViewController)._webView), creationParamsCodec: const StandardMessageCodec(), ); } } /// An implementation of [WebResourceError] with the WebKit API. class WebKitWebResourceError extends WebResourceError { WebKitWebResourceError._(this._nsError, {required bool isForMainFrame}) : super( errorCode: _nsError.code, description: _nsError.localizedDescription, errorType: _toWebResourceErrorType(_nsError.code), isForMainFrame: isForMainFrame, ); static WebResourceErrorType? _toWebResourceErrorType(int code) { switch (code) { case WKErrorCode.unknown: return WebResourceErrorType.unknown; case WKErrorCode.webContentProcessTerminated: return WebResourceErrorType.webContentProcessTerminated; case WKErrorCode.webViewInvalidated: return WebResourceErrorType.webViewInvalidated; case WKErrorCode.javaScriptExceptionOccurred: return WebResourceErrorType.javaScriptExceptionOccurred; case WKErrorCode.javaScriptResultTypeIsUnsupported: return WebResourceErrorType.javaScriptResultTypeIsUnsupported; } return null; } /// A string representing the domain of the error. String? get domain => _nsError.domain; final NSError _nsError; } /// Object specifying creation parameters for a [WebKitNavigationDelegate]. @immutable class WebKitNavigationDelegateCreationParams extends PlatformNavigationDelegateCreationParams { /// Constructs a [WebKitNavigationDelegateCreationParams]. const WebKitNavigationDelegateCreationParams({ @visibleForTesting this.webKitProxy = const WebKitProxy(), }); /// Constructs a [WebKitNavigationDelegateCreationParams] using a /// [PlatformNavigationDelegateCreationParams]. const WebKitNavigationDelegateCreationParams.fromPlatformNavigationDelegateCreationParams( // Recommended placeholder to prevent being broken by platform interface. // ignore: avoid_unused_constructor_parameters PlatformNavigationDelegateCreationParams params, { @visibleForTesting WebKitProxy webKitProxy = const WebKitProxy(), }) : this(webKitProxy: webKitProxy); /// Handles constructing objects and calling static methods for the WebKit /// native library. @visibleForTesting final WebKitProxy webKitProxy; } /// An implementation of [PlatformNavigationDelegate] with the WebKit API. class WebKitNavigationDelegate extends PlatformNavigationDelegate { /// Constructs a [WebKitNavigationDelegate]. WebKitNavigationDelegate(PlatformNavigationDelegateCreationParams params) : super.implementation(params is WebKitNavigationDelegateCreationParams ? params : WebKitNavigationDelegateCreationParams .fromPlatformNavigationDelegateCreationParams(params)) { final WeakReference weakThis = WeakReference(this); _navigationDelegate = (this.params as WebKitNavigationDelegateCreationParams) .webKitProxy .createNavigationDelegate( didFinishNavigation: (WKWebView webView, String? url) { if (weakThis.target?._onPageFinished != null) { weakThis.target!._onPageFinished!(url ?? ''); } }, didStartProvisionalNavigation: (WKWebView webView, String? url) { if (weakThis.target?._onPageStarted != null) { weakThis.target!._onPageStarted!(url ?? ''); } }, decidePolicyForNavigationAction: ( WKWebView webView, WKNavigationAction action, ) async { if (weakThis.target?._onNavigationRequest != null) { final NavigationDecision decision = await weakThis.target!._onNavigationRequest!(NavigationRequest( url: action.request.url, isMainFrame: action.targetFrame.isMainFrame, )); switch (decision) { case NavigationDecision.prevent: return WKNavigationActionPolicy.cancel; case NavigationDecision.navigate: return WKNavigationActionPolicy.allow; } } return WKNavigationActionPolicy.allow; }, didFailNavigation: (WKWebView webView, NSError error) { if (weakThis.target?._onWebResourceError != null) { weakThis.target!._onWebResourceError!( WebKitWebResourceError._(error, isForMainFrame: true), ); } }, didFailProvisionalNavigation: (WKWebView webView, NSError error) { if (weakThis.target?._onWebResourceError != null) { weakThis.target!._onWebResourceError!( WebKitWebResourceError._(error, isForMainFrame: true), ); } }, webViewWebContentProcessDidTerminate: (WKWebView webView) { if (weakThis.target?._onWebResourceError != null) { weakThis.target!._onWebResourceError!( WebKitWebResourceError._( const NSError( code: WKErrorCode.webContentProcessTerminated, // Value from https://developer.apple.com/documentation/webkit/wkerrordomain?language=objc. domain: 'WKErrorDomain', localizedDescription: '', ), isForMainFrame: true, ), ); } }, ); _uiDelegate = (this.params as WebKitNavigationDelegateCreationParams) .webKitProxy .createUIDelegate( onCreateWebView: ( WKWebView webView, WKWebViewConfiguration configuration, WKNavigationAction navigationAction, ) { if (!navigationAction.targetFrame.isMainFrame) { webView.loadRequest(navigationAction.request); } }, ); } // Used to set `WKWebView.setNavigationDelegate` in `WebKitWebViewController`. late final WKNavigationDelegate _navigationDelegate; // Used to set `WKWebView.setUIDelegate` in `WebKitWebViewController`. late final WKUIDelegate _uiDelegate; PageEventCallback? _onPageFinished; PageEventCallback? _onPageStarted; ProgressCallback? _onProgress; WebResourceErrorCallback? _onWebResourceError; NavigationRequestCallback? _onNavigationRequest; @override Future setOnPageFinished(PageEventCallback onPageFinished) async { _onPageFinished = onPageFinished; } @override Future setOnPageStarted(PageEventCallback onPageStarted) async { _onPageStarted = onPageStarted; } @override Future setOnProgress(ProgressCallback onProgress) async { _onProgress = onProgress; } @override Future setOnWebResourceError( WebResourceErrorCallback onWebResourceError, ) async { _onWebResourceError = onWebResourceError; } @override Future setOnNavigationRequest( NavigationRequestCallback onNavigationRequest, ) async { _onNavigationRequest = onNavigationRequest; } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_cookie_manager.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'foundation/foundation.dart'; import 'web_kit/web_kit.dart'; import 'webkit_proxy.dart'; /// Object specifying creation parameters for a [WebKitWebViewCookieManager]. class WebKitWebViewCookieManagerCreationParams extends PlatformWebViewCookieManagerCreationParams { /// Constructs a [WebKitWebViewCookieManagerCreationParams]. WebKitWebViewCookieManagerCreationParams({ WebKitProxy? webKitProxy, }) : webKitProxy = webKitProxy ?? const WebKitProxy(); /// Constructs a [WebKitWebViewCookieManagerCreationParams] using a /// [PlatformWebViewCookieManagerCreationParams]. WebKitWebViewCookieManagerCreationParams.fromPlatformWebViewCookieManagerCreationParams( // Recommended placeholder to prevent being broken by platform interface. // ignore: avoid_unused_constructor_parameters PlatformWebViewCookieManagerCreationParams params, { @visibleForTesting WebKitProxy? webKitProxy, }) : this(webKitProxy: webKitProxy); /// Handles constructing objects and calling static methods for the WebKit /// native library. @visibleForTesting final WebKitProxy webKitProxy; /// Manages stored data for [WKWebView]s. late final WKWebsiteDataStore _websiteDataStore = webKitProxy.defaultWebsiteDataStore(); } /// An implementation of [PlatformWebViewCookieManager] with the WebKit api. class WebKitWebViewCookieManager extends PlatformWebViewCookieManager { /// Constructs a [WebKitWebViewCookieManager]. WebKitWebViewCookieManager(PlatformWebViewCookieManagerCreationParams params) : super.implementation( params is WebKitWebViewCookieManagerCreationParams ? params : WebKitWebViewCookieManagerCreationParams .fromPlatformWebViewCookieManagerCreationParams(params), ); WebKitWebViewCookieManagerCreationParams get _webkitParams => params as WebKitWebViewCookieManagerCreationParams; @override Future clearCookies() { return _webkitParams._websiteDataStore.removeDataOfTypes( {WKWebsiteDataType.cookies}, DateTime.fromMillisecondsSinceEpoch(0), ); } @override Future setCookie(WebViewCookie cookie) { if (!_isValidPath(cookie.path)) { throw ArgumentError( 'The path property for the provided cookie was not given a legal value.', ); } return _webkitParams._websiteDataStore.httpCookieStore.setCookie( NSHttpCookie.withProperties( { NSHttpCookiePropertyKey.name: cookie.name, NSHttpCookiePropertyKey.value: cookie.value, NSHttpCookiePropertyKey.domain: cookie.domain, NSHttpCookiePropertyKey.path: cookie.path, }, ), ); } bool _isValidPath(String path) { // Permitted ranges based on RFC6265bis: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-02#section-4.1.1 return !path.codeUnits.any( (int char) { return (char < 0x20 || char > 0x3A) && (char < 0x3C || char > 0x7E); }, ); } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_platform.dart ================================================ // Copyright 2013 The Flutter 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:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; import 'webkit_webview_controller.dart'; import 'webkit_webview_cookie_manager.dart'; /// Implementation of [WebViewPlatform] using the WebKit API. class WebKitWebViewPlatform extends WebViewPlatform { /// Registers this class as the default instance of [WebViewPlatform]. static void registerWith() { WebViewPlatform.instance = WebKitWebViewPlatform(); } @override WebKitWebViewController createPlatformWebViewController( PlatformWebViewControllerCreationParams params, ) { return WebKitWebViewController(params); } @override WebKitNavigationDelegate createPlatformNavigationDelegate( PlatformNavigationDelegateCreationParams params, ) { return WebKitNavigationDelegate(params); } @override WebKitWebViewWidget createPlatformWebViewWidget( PlatformWebViewWidgetCreationParams params, ) { return WebKitWebViewWidget(params); } @override WebKitWebViewCookieManager createPlatformCookieManager( PlatformWebViewCookieManagerCreationParams params, ) { return WebKitWebViewCookieManager(params); } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/lib/src/webview_flutter_wkwebview_legacy.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'legacy/webview_cupertino.dart'; export 'legacy/wkwebview_cookie_manager.dart'; ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/lib/webview_flutter_wkwebview.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. library webview_flutter_wkwebview; export 'src/webkit_webview_controller.dart'; export 'src/webkit_webview_cookie_manager.dart'; export 'src/webkit_webview_platform.dart'; ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart ================================================ // Copyright 2013 The Flutter 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/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', 'found in the LICENSE file.', ]), objcHeaderOut: 'ios/Classes/FWFGeneratedWebKitApis.h', objcSourceOut: 'ios/Classes/FWFGeneratedWebKitApis.m', objcOptions: ObjcOptions( header: 'ios/Classes/FWFGeneratedWebKitApis.h', prefix: 'FWF', copyrightHeader: [ 'Copyright 2013 The Flutter Authors. All rights reserved.', 'Use of this source code is governed by a BSD-style license that can be', 'found in the LICENSE file.', ], ), ), ) /// Mirror of NSKeyValueObservingOptions. /// /// See https://developer.apple.com/documentation/foundation/nskeyvalueobservingoptions?language=objc. enum NSKeyValueObservingOptionsEnum { newValue, oldValue, initialValue, priorNotification, } // 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 NSKeyValueObservingOptionsEnumData { late NSKeyValueObservingOptionsEnum value; } /// Mirror of NSKeyValueChange. /// /// See https://developer.apple.com/documentation/foundation/nskeyvaluechange?language=objc. enum NSKeyValueChangeEnum { setting, insertion, removal, replacement, } // 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 NSKeyValueChangeEnumData { late NSKeyValueChangeEnum value; } /// Mirror of NSKeyValueChangeKey. /// /// See https://developer.apple.com/documentation/foundation/nskeyvaluechangekey?language=objc. enum NSKeyValueChangeKeyEnum { indexes, kind, newValue, notificationIsPrior, oldValue, } // 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 NSKeyValueChangeKeyEnumData { late NSKeyValueChangeKeyEnum value; } /// Mirror of WKUserScriptInjectionTime. /// /// See https://developer.apple.com/documentation/webkit/wkuserscriptinjectiontime?language=objc. enum WKUserScriptInjectionTimeEnum { atDocumentStart, atDocumentEnd, } // 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 WKUserScriptInjectionTimeEnumData { late WKUserScriptInjectionTimeEnum value; } /// Mirror of WKAudiovisualMediaTypes. /// /// See [WKAudiovisualMediaTypes](https://developer.apple.com/documentation/webkit/wkaudiovisualmediatypes?language=objc). enum WKAudiovisualMediaTypeEnum { none, audio, video, all, } // 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 WKAudiovisualMediaTypeEnumData { late WKAudiovisualMediaTypeEnum value; } /// Mirror of WKWebsiteDataTypes. /// /// See https://developer.apple.com/documentation/webkit/wkwebsitedatarecord/data_store_record_types?language=objc. enum WKWebsiteDataTypeEnum { cookies, memoryCache, diskCache, offlineWebApplicationCache, localStorage, sessionStorage, webSQLDatabases, indexedDBDatabases, } // 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 WKWebsiteDataTypeEnumData { late WKWebsiteDataTypeEnum value; } /// Mirror of WKNavigationActionPolicy. /// /// See https://developer.apple.com/documentation/webkit/wknavigationactionpolicy?language=objc. enum WKNavigationActionPolicyEnum { allow, cancel, } // 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 WKNavigationActionPolicyEnumData { late WKNavigationActionPolicyEnum value; } /// Mirror of NSHTTPCookiePropertyKey. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiepropertykey. enum NSHttpCookiePropertyKeyEnum { comment, commentUrl, discard, domain, expires, maximumAge, name, originUrl, path, port, sameSitePolicy, secure, value, version, } // 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 NSHttpCookiePropertyKeyEnumData { late NSHttpCookiePropertyKeyEnum value; } /// An object that contains information about an action that causes navigation /// to occur. /// /// Wraps [WKNavigationType](https://developer.apple.com/documentation/webkit/wknavigationaction?language=objc). enum WKNavigationType { /// A link activation. /// /// See https://developer.apple.com/documentation/webkit/wknavigationtype/wknavigationtypelinkactivated?language=objc. linkActivated, /// A request to submit a form. /// /// See https://developer.apple.com/documentation/webkit/wknavigationtype/wknavigationtypeformsubmitted?language=objc. submitted, /// A request for the frame’s next or previous item. /// /// See https://developer.apple.com/documentation/webkit/wknavigationtype/wknavigationtypebackforward?language=objc. backForward, /// A request to reload the webpage. /// /// See https://developer.apple.com/documentation/webkit/wknavigationtype/wknavigationtypereload?language=objc. reload, /// A request to resubmit a form. /// /// See https://developer.apple.com/documentation/webkit/wknavigationtype/wknavigationtypeformresubmitted?language=objc. formResubmitted, /// A navigation request that originates for some other reason. /// /// See https://developer.apple.com/documentation/webkit/wknavigationtype/wknavigationtypeother?language=objc. other, } /// Mirror of NSURLRequest. /// /// See https://developer.apple.com/documentation/foundation/nsurlrequest?language=objc. class NSUrlRequestData { late String url; late String? httpMethod; late Uint8List? httpBody; late Map allHttpHeaderFields; } /// Mirror of WKUserScript. /// /// See https://developer.apple.com/documentation/webkit/wkuserscript?language=objc. class WKUserScriptData { late String source; late WKUserScriptInjectionTimeEnumData? injectionTime; late bool isMainFrameOnly; } /// Mirror of WKNavigationAction. /// /// See https://developer.apple.com/documentation/webkit/wknavigationaction. class WKNavigationActionData { late NSUrlRequestData request; late WKFrameInfoData targetFrame; late WKNavigationType navigationType; } /// Mirror of WKFrameInfo. /// /// See https://developer.apple.com/documentation/webkit/wkframeinfo?language=objc. class WKFrameInfoData { late bool isMainFrame; } /// Mirror of NSError. /// /// See https://developer.apple.com/documentation/foundation/nserror?language=objc. class NSErrorData { late int code; late String domain; late String localizedDescription; } /// Mirror of WKScriptMessage. /// /// See https://developer.apple.com/documentation/webkit/wkscriptmessage?language=objc. class WKScriptMessageData { late String name; late Object? body; } /// Mirror of NSHttpCookieData. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookie?language=objc. class NSHttpCookieData { // TODO(bparrishMines): Change to a map when Objective-C data classes conform // to `NSCopying`. See https://github.com/flutter/flutter/issues/103383. // `NSDictionary`s are unable to use data classes as keys because they don't // conform to `NSCopying`. This splits the map of properties into a list of // keys and values with the ordered maintained. late List propertyKeys; late List propertyValues; } /// Mirror of WKWebsiteDataStore. /// /// See https://developer.apple.com/documentation/webkit/wkwebsitedatastore?language=objc. @HostApi(dartHostTestHandler: 'TestWKWebsiteDataStoreHostApi') abstract class WKWebsiteDataStoreHostApi { @ObjCSelector( 'createFromWebViewConfigurationWithIdentifier:configurationIdentifier:', ) void createFromWebViewConfiguration( int identifier, int configurationIdentifier, ); @ObjCSelector('createDefaultDataStoreWithIdentifier:') void createDefaultDataStore(int identifier); @ObjCSelector( 'removeDataFromDataStoreWithIdentifier:ofTypes:modifiedSince:', ) @async bool removeDataOfTypes( int identifier, List dataTypes, double modificationTimeInSecondsSinceEpoch, ); } /// Mirror of UIView. /// /// See https://developer.apple.com/documentation/uikit/uiview?language=objc. @HostApi(dartHostTestHandler: 'TestUIViewHostApi') abstract class UIViewHostApi { @ObjCSelector('setBackgroundColorForViewWithIdentifier:toValue:') void setBackgroundColor(int identifier, int? value); @ObjCSelector('setOpaqueForViewWithIdentifier:isOpaque:') void setOpaque(int identifier, bool opaque); } /// Mirror of UIScrollView. /// /// See https://developer.apple.com/documentation/uikit/uiscrollview?language=objc. @HostApi(dartHostTestHandler: 'TestUIScrollViewHostApi') abstract class UIScrollViewHostApi { @ObjCSelector('createFromWebViewWithIdentifier:webViewIdentifier:') void createFromWebView(int identifier, int webViewIdentifier); @ObjCSelector('contentOffsetForScrollViewWithIdentifier:') List getContentOffset(int identifier); @ObjCSelector('scrollByForScrollViewWithIdentifier:x:y:') void scrollBy(int identifier, double x, double y); @ObjCSelector('setContentOffsetForScrollViewWithIdentifier:toX:y:') void setContentOffset(int identifier, double x, double y); } /// Mirror of WKWebViewConfiguration. /// /// See https://developer.apple.com/documentation/webkit/wkwebviewconfiguration?language=objc. @HostApi(dartHostTestHandler: 'TestWKWebViewConfigurationHostApi') abstract class WKWebViewConfigurationHostApi { @ObjCSelector('createWithIdentifier:') void create(int identifier); @ObjCSelector('createFromWebViewWithIdentifier:webViewIdentifier:') void createFromWebView(int identifier, int webViewIdentifier); @ObjCSelector( 'setAllowsInlineMediaPlaybackForConfigurationWithIdentifier:isAllowed:', ) void setAllowsInlineMediaPlayback(int identifier, bool allow); @ObjCSelector( 'setMediaTypesRequiresUserActionForConfigurationWithIdentifier:forTypes:', ) void setMediaTypesRequiringUserActionForPlayback( int identifier, List types, ); } /// Handles callbacks from an WKWebViewConfiguration instance. /// /// See https://developer.apple.com/documentation/webkit/wkwebviewconfiguration?language=objc. @FlutterApi() abstract class WKWebViewConfigurationFlutterApi { @ObjCSelector('createWithIdentifier:') void create(int identifier); } /// Mirror of WKUserContentController. /// /// See https://developer.apple.com/documentation/webkit/wkusercontentcontroller?language=objc. @HostApi(dartHostTestHandler: 'TestWKUserContentControllerHostApi') abstract class WKUserContentControllerHostApi { @ObjCSelector( 'createFromWebViewConfigurationWithIdentifier:configurationIdentifier:', ) void createFromWebViewConfiguration( int identifier, int configurationIdentifier, ); @ObjCSelector( 'addScriptMessageHandlerForControllerWithIdentifier:handlerIdentifier:ofName:', ) void addScriptMessageHandler( int identifier, int handlerIdentifier, String name, ); @ObjCSelector('removeScriptMessageHandlerForControllerWithIdentifier:name:') void removeScriptMessageHandler(int identifier, String name); @ObjCSelector('removeAllScriptMessageHandlersForControllerWithIdentifier:') void removeAllScriptMessageHandlers(int identifier); @ObjCSelector('addUserScriptForControllerWithIdentifier:userScript:') void addUserScript(int identifier, WKUserScriptData userScript); @ObjCSelector('removeAllUserScriptsForControllerWithIdentifier:') void removeAllUserScripts(int identifier); } /// Mirror of WKUserPreferences. /// /// See https://developer.apple.com/documentation/webkit/wkpreferences?language=objc. @HostApi(dartHostTestHandler: 'TestWKPreferencesHostApi') abstract class WKPreferencesHostApi { @ObjCSelector( 'createFromWebViewConfigurationWithIdentifier:configurationIdentifier:', ) void createFromWebViewConfiguration( int identifier, int configurationIdentifier, ); @ObjCSelector('setJavaScriptEnabledForPreferencesWithIdentifier:isEnabled:') void setJavaScriptEnabled(int identifier, bool enabled); } /// Mirror of WKScriptMessageHandler. /// /// See https://developer.apple.com/documentation/webkit/wkscriptmessagehandler?language=objc. @HostApi(dartHostTestHandler: 'TestWKScriptMessageHandlerHostApi') abstract class WKScriptMessageHandlerHostApi { @ObjCSelector('createWithIdentifier:') void create(int identifier); } /// Handles callbacks from an WKScriptMessageHandler instance. /// /// See https://developer.apple.com/documentation/webkit/wkscriptmessagehandler?language=objc. @FlutterApi() abstract class WKScriptMessageHandlerFlutterApi { @ObjCSelector( 'didReceiveScriptMessageForHandlerWithIdentifier:userContentControllerIdentifier:message:', ) void didReceiveScriptMessage( int identifier, int userContentControllerIdentifier, WKScriptMessageData message, ); } /// Mirror of WKNavigationDelegate. /// /// See https://developer.apple.com/documentation/webkit/wknavigationdelegate?language=objc. @HostApi(dartHostTestHandler: 'TestWKNavigationDelegateHostApi') abstract class WKNavigationDelegateHostApi { @ObjCSelector('createWithIdentifier:') void create(int identifier); } /// Handles callbacks from an WKNavigationDelegate instance. /// /// See https://developer.apple.com/documentation/webkit/wknavigationdelegate?language=objc. @FlutterApi() abstract class WKNavigationDelegateFlutterApi { @ObjCSelector( 'didFinishNavigationForDelegateWithIdentifier:webViewIdentifier:URL:', ) void didFinishNavigation( int identifier, int webViewIdentifier, String? url, ); @ObjCSelector( 'didStartProvisionalNavigationForDelegateWithIdentifier:webViewIdentifier:URL:', ) void didStartProvisionalNavigation( int identifier, int webViewIdentifier, String? url, ); @ObjCSelector( 'decidePolicyForNavigationActionForDelegateWithIdentifier:webViewIdentifier:navigationAction:', ) @async WKNavigationActionPolicyEnumData decidePolicyForNavigationAction( int identifier, int webViewIdentifier, WKNavigationActionData navigationAction, ); @ObjCSelector( 'didFailNavigationForDelegateWithIdentifier:webViewIdentifier:error:', ) void didFailNavigation( int identifier, int webViewIdentifier, NSErrorData error, ); @ObjCSelector( 'didFailProvisionalNavigationForDelegateWithIdentifier:webViewIdentifier:error:', ) void didFailProvisionalNavigation( int identifier, int webViewIdentifier, NSErrorData error, ); @ObjCSelector( 'webViewWebContentProcessDidTerminateForDelegateWithIdentifier:webViewIdentifier:', ) void webViewWebContentProcessDidTerminate( int identifier, int webViewIdentifier, ); } /// Mirror of NSObject. /// /// See https://developer.apple.com/documentation/objectivec/nsobject. @HostApi(dartHostTestHandler: 'TestNSObjectHostApi') abstract class NSObjectHostApi { @ObjCSelector('disposeObjectWithIdentifier:') void dispose(int identifier); @ObjCSelector( 'addObserverForObjectWithIdentifier:observerIdentifier:keyPath:options:', ) void addObserver( int identifier, int observerIdentifier, String keyPath, List options, ); @ObjCSelector( 'removeObserverForObjectWithIdentifier:observerIdentifier:keyPath:', ) void removeObserver(int identifier, int observerIdentifier, String keyPath); } /// Handles callbacks from an NSObject instance. /// /// See https://developer.apple.com/documentation/objectivec/nsobject. @FlutterApi() abstract class NSObjectFlutterApi { @ObjCSelector( 'observeValueForObjectWithIdentifier:keyPath:objectIdentifier:changeKeys:changeValues:', ) void observeValue( int identifier, String keyPath, int objectIdentifier, // TODO(bparrishMines): Change to a map when Objective-C data classes conform // to `NSCopying`. See https://github.com/flutter/flutter/issues/103383. // `NSDictionary`s are unable to use data classes as keys because they don't // conform to `NSCopying`. This splits the map of properties into a list of // keys and values with the ordered maintained. List changeKeys, List changeValues, ); @ObjCSelector('disposeObjectWithIdentifier:') void dispose(int identifier); } /// Mirror of WKWebView. /// /// See https://developer.apple.com/documentation/webkit/wkwebview?language=objc. @HostApi(dartHostTestHandler: 'TestWKWebViewHostApi') abstract class WKWebViewHostApi { @ObjCSelector('createWithIdentifier:configurationIdentifier:') void create(int identifier, int configurationIdentifier); @ObjCSelector('setUIDelegateForWebViewWithIdentifier:delegateIdentifier:') void setUIDelegate(int identifier, int? uiDelegateIdentifier); @ObjCSelector( 'setNavigationDelegateForWebViewWithIdentifier:delegateIdentifier:', ) void setNavigationDelegate(int identifier, int? navigationDelegateIdentifier); @ObjCSelector('URLForWebViewWithIdentifier:') String? getUrl(int identifier); @ObjCSelector('estimatedProgressForWebViewWithIdentifier:') double getEstimatedProgress(int identifier); @ObjCSelector('loadRequestForWebViewWithIdentifier:request:') void loadRequest(int identifier, NSUrlRequestData request); @ObjCSelector('loadHTMLForWebViewWithIdentifier:HTMLString:baseURL:') void loadHtmlString(int identifier, String string, String? baseUrl); @ObjCSelector('loadFileForWebViewWithIdentifier:fileURL:readAccessURL:') void loadFileUrl(int identifier, String url, String readAccessUrl); @ObjCSelector('loadAssetForWebViewWithIdentifier:assetKey:') void loadFlutterAsset(int identifier, String key); @ObjCSelector('canGoBackForWebViewWithIdentifier:') bool canGoBack(int identifier); @ObjCSelector('canGoForwardForWebViewWithIdentifier:') bool canGoForward(int identifier); @ObjCSelector('goBackForWebViewWithIdentifier:') void goBack(int identifier); @ObjCSelector('goForwardForWebViewWithIdentifier:') void goForward(int identifier); @ObjCSelector('reloadWebViewWithIdentifier:') void reload(int identifier); @ObjCSelector('titleForWebViewWithIdentifier:') String? getTitle(int identifier); @ObjCSelector('setAllowsBackForwardForWebViewWithIdentifier:isAllowed:') void setAllowsBackForwardNavigationGestures(int identifier, bool allow); @ObjCSelector('setUserAgentForWebViewWithIdentifier:userAgent:') void setCustomUserAgent(int identifier, String? userAgent); @ObjCSelector('evaluateJavaScriptForWebViewWithIdentifier:javaScriptString:') @async Object? evaluateJavaScript(int identifier, String javaScriptString); } /// Mirror of WKUIDelegate. /// /// See https://developer.apple.com/documentation/webkit/wkuidelegate?language=objc. @HostApi(dartHostTestHandler: 'TestWKUIDelegateHostApi') abstract class WKUIDelegateHostApi { @ObjCSelector('createWithIdentifier:') void create(int identifier); } /// Handles callbacks from an WKUIDelegate instance. /// /// See https://developer.apple.com/documentation/webkit/wkuidelegate?language=objc. @FlutterApi() abstract class WKUIDelegateFlutterApi { @ObjCSelector( 'onCreateWebViewForDelegateWithIdentifier:webViewIdentifier:configurationIdentifier:navigationAction:', ) void onCreateWebView( int identifier, int webViewIdentifier, int configurationIdentifier, WKNavigationActionData navigationAction, ); } /// Mirror of WKHttpCookieStore. /// /// See https://developer.apple.com/documentation/webkit/wkhttpcookiestore?language=objc. @HostApi(dartHostTestHandler: 'TestWKHttpCookieStoreHostApi') abstract class WKHttpCookieStoreHostApi { @ObjCSelector('createFromWebsiteDataStoreWithIdentifier:dataStoreIdentifier:') void createFromWebsiteDataStore( int identifier, int websiteDataStoreIdentifier, ); @ObjCSelector('setCookieForStoreWithIdentifier:cookie:') @async void setCookie(int identifier, NSHttpCookieData cookie); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml ================================================ 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.1.0 environment: sdk: ">=2.17.0 <3.0.0" flutter: ">=3.0.0" flutter: plugin: implements: webview_flutter platforms: ios: pluginClass: FLTWebViewFlutterPlugin dartPluginClass: WebKitWebViewPlatform dependencies: flutter: sdk: flutter path: ^1.8.0 webview_flutter_platform_interface: ^2.0.0 dev_dependencies: build_runner: ^2.1.5 flutter_driver: sdk: flutter flutter_test: sdk: flutter mockito: ^5.3.2 pigeon: ^4.2.13 ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_cookie_manager_test.dart ================================================ // Copyright 2013 The Flutter 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:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart'; import 'package:webview_flutter_wkwebview/src/legacy/wkwebview_cookie_manager.dart'; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; import 'web_kit_cookie_manager_test.mocks.dart'; @GenerateMocks([ WKHttpCookieStore, WKWebsiteDataStore, ]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('WebKitWebViewWidget', () { late MockWKWebsiteDataStore mockWebsiteDataStore; late MockWKHttpCookieStore mockWKHttpCookieStore; late WKWebViewCookieManager cookieManager; setUp(() { mockWebsiteDataStore = MockWKWebsiteDataStore(); mockWKHttpCookieStore = MockWKHttpCookieStore(); when(mockWebsiteDataStore.httpCookieStore) .thenReturn(mockWKHttpCookieStore); cookieManager = WKWebViewCookieManager(websiteDataStore: mockWebsiteDataStore); }); test('clearCookies', () async { when(mockWebsiteDataStore.removeDataOfTypes( {WKWebsiteDataType.cookies}, any)) .thenAnswer((_) => Future.value(true)); expect(cookieManager.clearCookies(), completion(true)); when(mockWebsiteDataStore.removeDataOfTypes( {WKWebsiteDataType.cookies}, any)) .thenAnswer((_) => Future.value(false)); expect(cookieManager.clearCookies(), completion(false)); }); test('setCookie', () async { await cookieManager.setCookie( const WebViewCookie(name: 'a', value: 'b', domain: 'c', path: 'd'), ); final NSHttpCookie cookie = verify(mockWKHttpCookieStore.setCookie(captureAny)).captured.single as NSHttpCookie; expect( cookie.properties, { NSHttpCookiePropertyKey.name: 'a', NSHttpCookiePropertyKey.value: 'b', NSHttpCookiePropertyKey.domain: 'c', NSHttpCookiePropertyKey.path: 'd', }, ); }); test('setCookie throws argument error with invalid path', () async { expect( () => cookieManager.setCookie( WebViewCookie( name: 'a', value: 'b', domain: 'c', path: String.fromCharCode(0x1F), ), ), throwsArgumentError, ); }); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_cookie_manager_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_wkwebview/test/legacy/web_kit_cookie_manager_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i3; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart' as _i4; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.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 class _FakeWKHttpCookieStore_0 extends _i1.SmartFake implements _i2.WKHttpCookieStore { _FakeWKHttpCookieStore_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKWebsiteDataStore_1 extends _i1.SmartFake implements _i2.WKWebsiteDataStore { _FakeWKWebsiteDataStore_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [WKHttpCookieStore]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKHttpCookieStore extends _i1.Mock implements _i2.WKHttpCookieStore { MockWKHttpCookieStore() { _i1.throwOnMissingStub(this); } @override _i3.Future setCookie(_i4.NSHttpCookie? cookie) => (super.noSuchMethod( Invocation.method( #setCookie, [cookie], ), returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); @override _i2.WKHttpCookieStore copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKHttpCookieStore_0( this, Invocation.method( #copy, [], ), ), ) as _i2.WKHttpCookieStore); @override _i3.Future addObserver( _i4.NSObject? observer, { required String? keyPath, required Set<_i4.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); @override _i3.Future removeObserver( _i4.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); } /// A class which mocks [WKWebsiteDataStore]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKWebsiteDataStore extends _i1.Mock implements _i2.WKWebsiteDataStore { MockWKWebsiteDataStore() { _i1.throwOnMissingStub(this); } @override _i2.WKHttpCookieStore get httpCookieStore => (super.noSuchMethod( Invocation.getter(#httpCookieStore), returnValue: _FakeWKHttpCookieStore_0( this, Invocation.getter(#httpCookieStore), ), ) as _i2.WKHttpCookieStore); @override _i3.Future removeDataOfTypes( Set<_i2.WKWebsiteDataType>? dataTypes, DateTime? since, ) => (super.noSuchMethod( Invocation.method( #removeDataOfTypes, [ dataTypes, since, ], ), returnValue: _i3.Future.value(false), ) as _i3.Future); @override _i2.WKWebsiteDataStore copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKWebsiteDataStore_1( this, Invocation.method( #copy, [], ), ), ) as _i2.WKWebsiteDataStore); @override _i3.Future addObserver( _i4.NSObject? observer, { required String? keyPath, required Set<_i4.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); @override _i3.Future removeObserver( _i4.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:math'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#106316) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart'; import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart'; import 'package:webview_flutter_wkwebview/src/legacy/web_kit_webview_widget.dart'; import 'package:webview_flutter_wkwebview/src/ui_kit/ui_kit.dart'; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; import 'web_kit_webview_widget_test.mocks.dart'; @GenerateMocks([ UIScrollView, WKNavigationDelegate, WKPreferences, WKScriptMessageHandler, WKWebView, WKWebViewConfiguration, WKWebsiteDataStore, WKUIDelegate, WKUserContentController, JavascriptChannelRegistry, WebViewPlatformCallbacksHandler, WebViewWidgetProxy, ]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('WebKitWebViewWidget', () { late MockWKWebView mockWebView; late MockWebViewWidgetProxy mockWebViewWidgetProxy; late MockWKUserContentController mockUserContentController; late MockWKPreferences mockPreferences; late MockWKWebViewConfiguration mockWebViewConfiguration; late MockWKUIDelegate mockUIDelegate; late MockUIScrollView mockScrollView; late MockWKWebsiteDataStore mockWebsiteDataStore; late MockWKNavigationDelegate mockNavigationDelegate; late MockWebViewPlatformCallbacksHandler mockCallbacksHandler; late MockJavascriptChannelRegistry mockJavascriptChannelRegistry; late WebKitWebViewPlatformController testController; setUp(() { mockWebView = MockWKWebView(); mockWebViewConfiguration = MockWKWebViewConfiguration(); mockUserContentController = MockWKUserContentController(); mockPreferences = MockWKPreferences(); mockUIDelegate = MockWKUIDelegate(); mockScrollView = MockUIScrollView(); mockWebsiteDataStore = MockWKWebsiteDataStore(); mockNavigationDelegate = MockWKNavigationDelegate(); mockWebViewWidgetProxy = MockWebViewWidgetProxy(); when( mockWebViewWidgetProxy.createWebView( any, observeValue: anyNamed('observeValue'), ), ).thenReturn(mockWebView); when( mockWebViewWidgetProxy.createUIDelgate( onCreateWebView: captureAnyNamed('onCreateWebView'), ), ).thenReturn(mockUIDelegate); when(mockWebViewWidgetProxy.createNavigationDelegate( didFinishNavigation: anyNamed('didFinishNavigation'), didStartProvisionalNavigation: anyNamed('didStartProvisionalNavigation'), decidePolicyForNavigationAction: anyNamed('decidePolicyForNavigationAction'), didFailNavigation: anyNamed('didFailNavigation'), didFailProvisionalNavigation: anyNamed('didFailProvisionalNavigation'), webViewWebContentProcessDidTerminate: anyNamed('webViewWebContentProcessDidTerminate'), )).thenReturn(mockNavigationDelegate); when(mockWebView.configuration).thenReturn(mockWebViewConfiguration); when(mockWebViewConfiguration.userContentController).thenReturn( mockUserContentController, ); when(mockWebViewConfiguration.preferences).thenReturn(mockPreferences); when(mockWebView.scrollView).thenReturn(mockScrollView); when(mockWebViewConfiguration.websiteDataStore).thenReturn( mockWebsiteDataStore, ); mockCallbacksHandler = MockWebViewPlatformCallbacksHandler(); mockJavascriptChannelRegistry = MockJavascriptChannelRegistry(); }); // Builds a WebViewCupertinoWidget with default parameters. Future buildWidget( WidgetTester tester, { CreationParams? creationParams, bool hasNavigationDelegate = false, bool hasProgressTracking = false, }) async { await tester.pumpWidget(WebKitWebViewWidget( creationParams: creationParams ?? CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), hasNavigationDelegate: hasNavigationDelegate, hasProgressTracking: hasProgressTracking, )), callbacksHandler: mockCallbacksHandler, javascriptChannelRegistry: mockJavascriptChannelRegistry, webViewProxy: mockWebViewWidgetProxy, configuration: mockWebViewConfiguration, onBuildWidget: (WebKitWebViewPlatformController controller) { testController = controller; return Container(); }, )); await tester.pumpAndSettle(); } testWidgets('build $WebKitWebViewWidget', (WidgetTester tester) async { await buildWidget(tester); }); testWidgets('Requests to open a new window loads request in same window', (WidgetTester tester) async { await buildWidget(tester); final void Function(WKWebView, WKWebViewConfiguration, WKNavigationAction) onCreateWebView = verify(mockWebViewWidgetProxy.createUIDelgate( onCreateWebView: captureAnyNamed('onCreateWebView'))) .captured .single as void Function( WKWebView, WKWebViewConfiguration, WKNavigationAction); const NSUrlRequest request = NSUrlRequest(url: 'https://google.com'); onCreateWebView( mockWebView, mockWebViewConfiguration, const WKNavigationAction( request: request, targetFrame: WKFrameInfo(isMainFrame: false), navigationType: WKNavigationType.linkActivated, ), ); verify(mockWebView.loadRequest(request)); }); group('CreationParams', () { testWidgets('initialUrl', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( initialUrl: 'https://www.google.com', webSettings: WebSettings( userAgent: const WebSetting.absent(), hasNavigationDelegate: false, ), ), ); final NSUrlRequest request = verify(mockWebView.loadRequest(captureAny)) .captured .single as NSUrlRequest; expect(request.url, 'https://www.google.com'); }); testWidgets('backgroundColor', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( backgroundColor: Colors.red, webSettings: WebSettings( userAgent: const WebSetting.absent(), hasNavigationDelegate: false, ), ), ); verify(mockWebView.setOpaque(false)); verify(mockWebView.setBackgroundColor(Colors.transparent)); verify(mockScrollView.setBackgroundColor(Colors.red)); }); testWidgets('userAgent', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( userAgent: 'MyUserAgent', webSettings: WebSettings( userAgent: const WebSetting.absent(), hasNavigationDelegate: false, ), ), ); verify(mockWebView.setCustomUserAgent('MyUserAgent')); }); testWidgets('autoMediaPlaybackPolicy true', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), hasNavigationDelegate: false, ), ), ); verify(mockWebViewConfiguration .setMediaTypesRequiringUserActionForPlayback(< WKAudiovisualMediaType>{ WKAudiovisualMediaType.all, })); }); testWidgets('autoMediaPlaybackPolicy false', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( autoMediaPlaybackPolicy: AutoMediaPlaybackPolicy.always_allow, webSettings: WebSettings( userAgent: const WebSetting.absent(), hasNavigationDelegate: false, ), ), ); verify(mockWebViewConfiguration .setMediaTypesRequiringUserActionForPlayback(< WKAudiovisualMediaType>{ WKAudiovisualMediaType.none, })); }); testWidgets('javascriptChannelNames', (WidgetTester tester) async { when( mockWebViewWidgetProxy.createScriptMessageHandler( didReceiveScriptMessage: anyNamed('didReceiveScriptMessage'), ), ).thenReturn( MockWKScriptMessageHandler(), ); await buildWidget( tester, creationParams: CreationParams( javascriptChannelNames: {'a', 'b'}, webSettings: WebSettings( userAgent: const WebSetting.absent(), hasNavigationDelegate: false, ), ), ); final List javaScriptChannels = verify( mockUserContentController.addScriptMessageHandler( captureAny, captureAny, ), ).captured; expect( javaScriptChannels[0], isA(), ); expect(javaScriptChannels[1], 'a'); expect( javaScriptChannels[2], isA(), ); expect(javaScriptChannels[3], 'b'); }); group('WebSettings', () { testWidgets('javascriptMode', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), javascriptMode: JavascriptMode.unrestricted, hasNavigationDelegate: false, ), ), ); verify(mockPreferences.setJavaScriptEnabled(true)); }); testWidgets('userAgent', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.of('myUserAgent'), hasNavigationDelegate: false, ), ), ); verify(mockWebView.setCustomUserAgent('myUserAgent')); }); testWidgets( 'enabling zoom re-adds JavaScript channels', (WidgetTester tester) async { when( mockWebViewWidgetProxy.createScriptMessageHandler( didReceiveScriptMessage: anyNamed('didReceiveScriptMessage'), ), ).thenReturn( MockWKScriptMessageHandler(), ); await buildWidget( tester, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), zoomEnabled: false, hasNavigationDelegate: false, ), javascriptChannelNames: {'myChannel'}, ), ); clearInteractions(mockUserContentController); await testController.updateSettings(WebSettings( userAgent: const WebSetting.absent(), zoomEnabled: true, )); final List javaScriptChannels = verifyInOrder([ mockUserContentController.removeAllUserScripts(), mockUserContentController.removeScriptMessageHandler('myChannel'), mockUserContentController.addScriptMessageHandler( captureAny, captureAny, ), ]).captured[2]; expect( javaScriptChannels[0], isA(), ); expect(javaScriptChannels[1], 'myChannel'); }, ); testWidgets( 'enabling zoom removes script', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), zoomEnabled: false, hasNavigationDelegate: false, ), ), ); clearInteractions(mockUserContentController); await testController.updateSettings(WebSettings( userAgent: const WebSetting.absent(), zoomEnabled: true, )); verify(mockUserContentController.removeAllUserScripts()); verifyNever(mockUserContentController.addScriptMessageHandler( any, any, )); }, ); testWidgets('zoomEnabled is false', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), zoomEnabled: false, hasNavigationDelegate: false, ), ), ); final WKUserScript zoomScript = verify(mockUserContentController.addUserScript(captureAny)) .captured .first as WKUserScript; expect(zoomScript.isMainFrameOnly, isTrue); expect(zoomScript.injectionTime, WKUserScriptInjectionTime.atDocumentEnd); expect( zoomScript.source, "var meta = document.createElement('meta');\n" "meta.name = 'viewport';\n" "meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, " "user-scalable=no';\n" "var head = document.getElementsByTagName('head')[0];head.appendChild(meta);", ); }); testWidgets('allowsInlineMediaPlayback', (WidgetTester tester) async { await buildWidget( tester, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), allowsInlineMediaPlayback: true, ), ), ); verify(mockWebViewConfiguration.setAllowsInlineMediaPlayback(true)); }); }); }); group('WebKitWebViewPlatformController', () { testWidgets('loadFile', (WidgetTester tester) async { await buildWidget(tester); await testController.loadFile('/path/to/file.html'); verify(mockWebView.loadFileUrl( '/path/to/file.html', readAccessUrl: '/path/to', )); }); testWidgets('loadFlutterAsset', (WidgetTester tester) async { await buildWidget(tester); await testController.loadFlutterAsset('test_assets/index.html'); verify(mockWebView.loadFlutterAsset('test_assets/index.html')); }); testWidgets('loadHtmlString', (WidgetTester tester) async { await buildWidget(tester); const String htmlString = 'Test data.'; await testController.loadHtmlString(htmlString, baseUrl: 'baseUrl'); verify(mockWebView.loadHtmlString( 'Test data.', baseUrl: 'baseUrl', )); }); testWidgets('loadUrl', (WidgetTester tester) async { await buildWidget(tester); await testController.loadUrl( 'https://www.google.com', {'a': 'header'}, ); final NSUrlRequest request = verify(mockWebView.loadRequest(captureAny)) .captured .single as NSUrlRequest; expect(request.url, 'https://www.google.com'); expect(request.allHttpHeaderFields, {'a': 'header'}); }); group('loadRequest', () { testWidgets('Throws ArgumentError for empty scheme', (WidgetTester tester) async { await buildWidget(tester); expect( () async => testController.loadRequest( WebViewRequest( uri: Uri.parse('www.google.com'), method: WebViewRequestMethod.get, ), ), throwsA(const TypeMatcher())); }); testWidgets('GET without headers', (WidgetTester tester) async { await buildWidget(tester); await testController.loadRequest(WebViewRequest( uri: Uri.parse('https://www.google.com'), method: WebViewRequestMethod.get, )); final NSUrlRequest request = verify(mockWebView.loadRequest(captureAny)).captured.single as NSUrlRequest; expect(request.url, 'https://www.google.com'); expect(request.allHttpHeaderFields, {}); expect(request.httpMethod, 'get'); }); testWidgets('GET with headers', (WidgetTester tester) async { await buildWidget(tester); await testController.loadRequest(WebViewRequest( uri: Uri.parse('https://www.google.com'), method: WebViewRequestMethod.get, headers: {'a': 'header'}, )); final NSUrlRequest request = verify(mockWebView.loadRequest(captureAny)).captured.single as NSUrlRequest; expect(request.url, 'https://www.google.com'); expect(request.allHttpHeaderFields, {'a': 'header'}); expect(request.httpMethod, 'get'); }); testWidgets('POST without body', (WidgetTester tester) async { await buildWidget(tester); await testController.loadRequest(WebViewRequest( uri: Uri.parse('https://www.google.com'), method: WebViewRequestMethod.post, )); final NSUrlRequest request = verify(mockWebView.loadRequest(captureAny)).captured.single as NSUrlRequest; expect(request.url, 'https://www.google.com'); expect(request.httpMethod, 'post'); }); testWidgets('POST with body', (WidgetTester tester) async { await buildWidget(tester); await testController.loadRequest(WebViewRequest( uri: Uri.parse('https://www.google.com'), method: WebViewRequestMethod.post, body: Uint8List.fromList('Test Body'.codeUnits))); final NSUrlRequest request = verify(mockWebView.loadRequest(captureAny)).captured.single as NSUrlRequest; expect(request.url, 'https://www.google.com'); expect(request.httpMethod, 'post'); expect( request.httpBody, Uint8List.fromList('Test Body'.codeUnits), ); }); }); testWidgets('canGoBack', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.canGoBack()).thenAnswer( (_) => Future.value(false), ); expect(testController.canGoBack(), completion(false)); }); testWidgets('canGoForward', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.canGoForward()).thenAnswer( (_) => Future.value(true), ); expect(testController.canGoForward(), completion(true)); }); testWidgets('goBack', (WidgetTester tester) async { await buildWidget(tester); await testController.goBack(); verify(mockWebView.goBack()); }); testWidgets('goForward', (WidgetTester tester) async { await buildWidget(tester); await testController.goForward(); verify(mockWebView.goForward()); }); testWidgets('reload', (WidgetTester tester) async { await buildWidget(tester); await testController.reload(); verify(mockWebView.reload()); }); testWidgets('evaluateJavascript', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value('returnString'), ); expect( testController.evaluateJavascript('runJavaScript'), completion('returnString'), ); }); testWidgets('evaluateJavascript with null return value', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value(), ); // The legacy implementation of webview_flutter_wkwebview would convert // objects to strings before returning them to Dart. This verifies null // is represented the way it is in Objective-C. expect( testController.evaluateJavascript('runJavaScript'), completion('(null)'), ); }); testWidgets('evaluateJavascript with bool return value', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value(true), ); // The legacy implementation of webview_flutter_wkwebview would convert // objects to strings before returning them to Dart. This verifies bool // is represented the way it is in Objective-C. // `NSNumber.description` converts bool values to a 1 or 0. expect( testController.evaluateJavascript('runJavaScript'), completion('1'), ); }); testWidgets('evaluateJavascript with double return value', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value(1.0), ); // The legacy implementation of webview_flutter_wkwebview would convert // objects to strings before returning them to Dart. This verifies // double is represented the way it is in Objective-C. If a double // doesn't contain any decimal values, it gets truncated to an int. // This should be happenning because NSNumber convertes float values // with no decimals to an int when using `NSNumber.description`. expect( testController.evaluateJavascript('runJavaScript'), completion('1'), ); }); testWidgets('evaluateJavascript with list return value', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value([1, 'string', null]), ); // The legacy implementation of webview_flutter_wkwebview would convert // objects to strings before returning them to Dart. This verifies list // is represented the way it is in Objective-C. expect( testController.evaluateJavascript('runJavaScript'), completion('(1,string,"")'), ); }); testWidgets('evaluateJavascript with map return value', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value({ 1: 'string', null: null, }), ); // The legacy implementation of webview_flutter_wkwebview would convert // objects to strings before returning them to Dart. This verifies map // is represented the way it is in Objective-C. expect( testController.evaluateJavascript('runJavaScript'), completion('{1 = string;"" = ""}'), ); }); testWidgets('evaluateJavascript throws exception', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.evaluateJavaScript('runJavaScript')) .thenThrow(Error()); expect( testController.evaluateJavascript('runJavaScript'), throwsA(isA()), ); }); testWidgets('runJavascriptReturningResult', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value('returnString'), ); expect( testController.runJavascriptReturningResult('runJavaScript'), completion('returnString'), ); }); testWidgets( 'runJavascriptReturningResult throws error on null return value', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value(), ); expect( () => testController.runJavascriptReturningResult('runJavaScript'), throwsArgumentError, ); }); testWidgets('runJavascriptReturningResult with bool return value', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value(false), ); // The legacy implementation of webview_flutter_wkwebview would convert // objects to strings before returning them to Dart. This verifies bool // is represented the way it is in Objective-C. // `NSNumber.description` converts bool values to a 1 or 0. expect( testController.runJavascriptReturningResult('runJavaScript'), completion('0'), ); }); testWidgets('runJavascript', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value('returnString'), ); expect( testController.runJavascript('runJavaScript'), completes, ); }); testWidgets( 'runJavascript ignores exception with unsupported javascript type', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.evaluateJavaScript('runJavaScript')) .thenThrow(PlatformException( code: '', details: const NSError( code: WKErrorCode.javaScriptResultTypeIsUnsupported, domain: '', localizedDescription: '', ), )); expect( testController.runJavascript('runJavaScript'), completes, ); }); testWidgets('getTitle', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.getTitle()) .thenAnswer((_) => Future.value('Web Title')); expect(testController.getTitle(), completion('Web Title')); }); testWidgets('currentUrl', (WidgetTester tester) async { await buildWidget(tester); when(mockWebView.getUrl()) .thenAnswer((_) => Future.value('myUrl.com')); expect(testController.currentUrl(), completion('myUrl.com')); }); testWidgets('scrollTo', (WidgetTester tester) async { await buildWidget(tester); await testController.scrollTo(2, 4); verify(mockScrollView.setContentOffset(const Point(2.0, 4.0))); }); testWidgets('scrollBy', (WidgetTester tester) async { await buildWidget(tester); await testController.scrollBy(2, 4); verify(mockScrollView.scrollBy(const Point(2.0, 4.0))); }); testWidgets('getScrollX', (WidgetTester tester) async { await buildWidget(tester); when(mockScrollView.getContentOffset()).thenAnswer( (_) => Future>.value(const Point(8.0, 16.0))); expect(testController.getScrollX(), completion(8.0)); }); testWidgets('getScrollY', (WidgetTester tester) async { await buildWidget(tester); await buildWidget(tester); when(mockScrollView.getContentOffset()).thenAnswer( (_) => Future>.value(const Point(8.0, 16.0))); expect(testController.getScrollY(), completion(16.0)); }); testWidgets('clearCache', (WidgetTester tester) async { await buildWidget(tester); when( mockWebsiteDataStore.removeDataOfTypes( { WKWebsiteDataType.memoryCache, WKWebsiteDataType.diskCache, WKWebsiteDataType.offlineWebApplicationCache, WKWebsiteDataType.localStorage, }, DateTime.fromMillisecondsSinceEpoch(0), ), ).thenAnswer((_) => Future.value(false)); expect(testController.clearCache(), completes); }); testWidgets('addJavascriptChannels', (WidgetTester tester) async { when( mockWebViewWidgetProxy.createScriptMessageHandler( didReceiveScriptMessage: anyNamed('didReceiveScriptMessage'), ), ).thenReturn( MockWKScriptMessageHandler(), ); await buildWidget(tester); await testController.addJavascriptChannels({'c', 'd'}); final List javaScriptChannels = verify( mockUserContentController.addScriptMessageHandler( captureAny, captureAny), ).captured; expect( javaScriptChannels[0], isA(), ); expect(javaScriptChannels[1], 'c'); expect( javaScriptChannels[2], isA(), ); expect(javaScriptChannels[3], 'd'); final List userScripts = verify(mockUserContentController.addUserScript(captureAny)) .captured .cast(); expect(userScripts[0].source, 'window.c = webkit.messageHandlers.c;'); expect( userScripts[0].injectionTime, WKUserScriptInjectionTime.atDocumentStart, ); expect(userScripts[0].isMainFrameOnly, false); expect(userScripts[1].source, 'window.d = webkit.messageHandlers.d;'); expect( userScripts[1].injectionTime, WKUserScriptInjectionTime.atDocumentStart, ); expect(userScripts[0].isMainFrameOnly, false); }); testWidgets('removeJavascriptChannels', (WidgetTester tester) async { when( mockWebViewWidgetProxy.createScriptMessageHandler( didReceiveScriptMessage: anyNamed('didReceiveScriptMessage'), ), ).thenReturn( MockWKScriptMessageHandler(), ); await buildWidget(tester); await testController.addJavascriptChannels({'c', 'd'}); reset(mockUserContentController); await testController.removeJavascriptChannels({'c'}); verify(mockUserContentController.removeAllUserScripts()); verify(mockUserContentController.removeScriptMessageHandler('c')); verify(mockUserContentController.removeScriptMessageHandler('d')); final List javaScriptChannels = verify( mockUserContentController.addScriptMessageHandler( captureAny, captureAny, ), ).captured; expect( javaScriptChannels[0], isA(), ); expect(javaScriptChannels[1], 'd'); final List userScripts = verify(mockUserContentController.addUserScript(captureAny)) .captured .cast(); expect(userScripts[0].source, 'window.d = webkit.messageHandlers.d;'); expect( userScripts[0].injectionTime, WKUserScriptInjectionTime.atDocumentStart, ); expect(userScripts[0].isMainFrameOnly, false); }); testWidgets('removeJavascriptChannels with zoom disabled', (WidgetTester tester) async { when( mockWebViewWidgetProxy.createScriptMessageHandler( didReceiveScriptMessage: anyNamed('didReceiveScriptMessage'), ), ).thenReturn( MockWKScriptMessageHandler(), ); await buildWidget( tester, creationParams: CreationParams( webSettings: WebSettings( userAgent: const WebSetting.absent(), zoomEnabled: false, hasNavigationDelegate: false, ), ), ); await testController.addJavascriptChannels({'c'}); clearInteractions(mockUserContentController); await testController.removeJavascriptChannels({'c'}); final WKUserScript zoomScript = verify(mockUserContentController.addUserScript(captureAny)) .captured .first as WKUserScript; expect(zoomScript.isMainFrameOnly, isTrue); expect( zoomScript.injectionTime, WKUserScriptInjectionTime.atDocumentEnd); expect( zoomScript.source, "var meta = document.createElement('meta');\n" "meta.name = 'viewport';\n" "meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, " "user-scalable=no';\n" "var head = document.getElementsByTagName('head')[0];head.appendChild(meta);", ); }); }); group('WebViewPlatformCallbacksHandler', () { testWidgets('onPageStarted', (WidgetTester tester) async { await buildWidget(tester); final void Function(WKWebView, String) didStartProvisionalNavigation = verify(mockWebViewWidgetProxy.createNavigationDelegate( didFinishNavigation: anyNamed('didFinishNavigation'), didStartProvisionalNavigation: captureAnyNamed('didStartProvisionalNavigation'), decidePolicyForNavigationAction: anyNamed('decidePolicyForNavigationAction'), didFailNavigation: anyNamed('didFailNavigation'), didFailProvisionalNavigation: anyNamed('didFailProvisionalNavigation'), webViewWebContentProcessDidTerminate: anyNamed('webViewWebContentProcessDidTerminate'), )).captured.single as void Function(WKWebView, String); didStartProvisionalNavigation(mockWebView, 'https://google.com'); verify(mockCallbacksHandler.onPageStarted('https://google.com')); }); testWidgets('onPageFinished', (WidgetTester tester) async { await buildWidget(tester); final void Function(WKWebView, String) didFinishNavigation = verify(mockWebViewWidgetProxy.createNavigationDelegate( didFinishNavigation: captureAnyNamed('didFinishNavigation'), didStartProvisionalNavigation: anyNamed('didStartProvisionalNavigation'), decidePolicyForNavigationAction: anyNamed('decidePolicyForNavigationAction'), didFailNavigation: anyNamed('didFailNavigation'), didFailProvisionalNavigation: anyNamed('didFailProvisionalNavigation'), webViewWebContentProcessDidTerminate: anyNamed('webViewWebContentProcessDidTerminate'), )).captured.single as void Function(WKWebView, String); didFinishNavigation(mockWebView, 'https://google.com'); verify(mockCallbacksHandler.onPageFinished('https://google.com')); }); testWidgets('onWebResourceError from didFailNavigation', (WidgetTester tester) async { await buildWidget(tester); final void Function(WKWebView, NSError) didFailNavigation = verify(mockWebViewWidgetProxy.createNavigationDelegate( didFinishNavigation: anyNamed('didFinishNavigation'), didStartProvisionalNavigation: anyNamed('didStartProvisionalNavigation'), decidePolicyForNavigationAction: anyNamed('decidePolicyForNavigationAction'), didFailNavigation: captureAnyNamed('didFailNavigation'), didFailProvisionalNavigation: anyNamed('didFailProvisionalNavigation'), webViewWebContentProcessDidTerminate: anyNamed('webViewWebContentProcessDidTerminate'), )).captured.single as void Function(WKWebView, NSError); didFailNavigation( mockWebView, const NSError( code: WKErrorCode.webViewInvalidated, domain: 'domain', localizedDescription: 'my desc', ), ); final WebResourceError error = verify(mockCallbacksHandler.onWebResourceError(captureAny)) .captured .single as WebResourceError; expect(error.description, 'my desc'); expect(error.errorCode, WKErrorCode.webViewInvalidated); expect(error.domain, 'domain'); expect(error.errorType, WebResourceErrorType.webViewInvalidated); }); testWidgets('onWebResourceError from didFailProvisionalNavigation', (WidgetTester tester) async { await buildWidget(tester); final void Function(WKWebView, NSError) didFailProvisionalNavigation = verify(mockWebViewWidgetProxy.createNavigationDelegate( didFinishNavigation: anyNamed('didFinishNavigation'), didStartProvisionalNavigation: anyNamed('didStartProvisionalNavigation'), decidePolicyForNavigationAction: anyNamed('decidePolicyForNavigationAction'), didFailNavigation: anyNamed('didFailNavigation'), didFailProvisionalNavigation: captureAnyNamed('didFailProvisionalNavigation'), webViewWebContentProcessDidTerminate: anyNamed('webViewWebContentProcessDidTerminate'), )).captured.single as void Function(WKWebView, NSError); didFailProvisionalNavigation( mockWebView, const NSError( code: WKErrorCode.webContentProcessTerminated, domain: 'domain', localizedDescription: 'my desc', ), ); final WebResourceError error = verify(mockCallbacksHandler.onWebResourceError(captureAny)) .captured .single as WebResourceError; expect(error.description, 'my desc'); expect(error.errorCode, WKErrorCode.webContentProcessTerminated); expect(error.domain, 'domain'); expect( error.errorType, WebResourceErrorType.webContentProcessTerminated, ); }); testWidgets( 'onWebResourceError from webViewWebContentProcessDidTerminate', (WidgetTester tester) async { await buildWidget(tester); final void Function(WKWebView) webViewWebContentProcessDidTerminate = verify(mockWebViewWidgetProxy.createNavigationDelegate( didFinishNavigation: anyNamed('didFinishNavigation'), didStartProvisionalNavigation: anyNamed('didStartProvisionalNavigation'), decidePolicyForNavigationAction: anyNamed('decidePolicyForNavigationAction'), didFailNavigation: anyNamed('didFailNavigation'), didFailProvisionalNavigation: anyNamed('didFailProvisionalNavigation'), webViewWebContentProcessDidTerminate: captureAnyNamed('webViewWebContentProcessDidTerminate'), )).captured.single as void Function(WKWebView); webViewWebContentProcessDidTerminate(mockWebView); final WebResourceError error = verify(mockCallbacksHandler.onWebResourceError(captureAny)) .captured .single as WebResourceError; expect(error.description, ''); expect(error.errorCode, WKErrorCode.webContentProcessTerminated); expect(error.domain, 'WKErrorDomain'); expect( error.errorType, WebResourceErrorType.webContentProcessTerminated, ); }); testWidgets('onNavigationRequest from decidePolicyForNavigationAction', (WidgetTester tester) async { await buildWidget(tester, hasNavigationDelegate: true); final Future Function( WKWebView, WKNavigationAction) decidePolicyForNavigationAction = verify(mockWebViewWidgetProxy.createNavigationDelegate( didFinishNavigation: anyNamed('didFinishNavigation'), didStartProvisionalNavigation: anyNamed('didStartProvisionalNavigation'), decidePolicyForNavigationAction: captureAnyNamed('decidePolicyForNavigationAction'), didFailNavigation: anyNamed('didFailNavigation'), didFailProvisionalNavigation: anyNamed('didFailProvisionalNavigation'), webViewWebContentProcessDidTerminate: anyNamed('webViewWebContentProcessDidTerminate'), )).captured.single as Future Function( WKWebView, WKNavigationAction); when(mockCallbacksHandler.onNavigationRequest( isForMainFrame: argThat(isFalse, named: 'isForMainFrame'), url: 'https://google.com', )).thenReturn(true); expect( decidePolicyForNavigationAction( mockWebView, const WKNavigationAction( request: NSUrlRequest(url: 'https://google.com'), targetFrame: WKFrameInfo(isMainFrame: false), navigationType: WKNavigationType.linkActivated, ), ), completion(WKNavigationActionPolicy.allow), ); verify(mockCallbacksHandler.onNavigationRequest( url: 'https://google.com', isForMainFrame: false, )); }); testWidgets('onProgress', (WidgetTester tester) async { await buildWidget(tester, hasProgressTracking: true); verify(mockWebView.addObserver( mockWebView, keyPath: 'estimatedProgress', options: { NSKeyValueObservingOptions.newValue, }, )); final void Function(String, NSObject, Map) observeValue = verify(mockWebViewWidgetProxy.createWebView(any, observeValue: captureAnyNamed('observeValue'))) .captured .single as void Function( String, NSObject, Map); observeValue( 'estimatedProgress', mockWebView, {NSKeyValueChangeKey.newValue: 0.32}, ); verify(mockCallbacksHandler.onProgress(32)); }); testWidgets('progress observer is not removed without being set first', (WidgetTester tester) async { await buildWidget(tester); verifyNever(mockWebView.removeObserver( mockWebView, keyPath: 'estimatedProgress', )); }); }); group('JavascriptChannelRegistry', () { testWidgets('onJavascriptChannelMessage', (WidgetTester tester) async { when( mockWebViewWidgetProxy.createScriptMessageHandler( didReceiveScriptMessage: anyNamed('didReceiveScriptMessage'), ), ).thenReturn( MockWKScriptMessageHandler(), ); await buildWidget(tester); await testController.addJavascriptChannels({'hello'}); final void Function(WKUserContentController, WKScriptMessage) didReceiveScriptMessage = verify( mockWebViewWidgetProxy.createScriptMessageHandler( didReceiveScriptMessage: captureAnyNamed('didReceiveScriptMessage'))) .captured .single as void Function(WKUserContentController, WKScriptMessage); didReceiveScriptMessage( mockUserContentController, const WKScriptMessage(name: 'hello', body: 'A message.'), ); verify(mockJavascriptChannelRegistry.onJavascriptChannelMessage( 'hello', 'A message.', )); }); }); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i5; import 'dart:math' as _i2; import 'dart:ui' as _i6; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_platform_interface/src/legacy/types/javascript_channel.dart' as _i9; import 'package:webview_flutter_platform_interface/src/legacy/types/types.dart' as _i10; import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart' as _i8; import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart' as _i7; import 'package:webview_flutter_wkwebview/src/legacy/web_kit_webview_widget.dart' as _i11; import 'package:webview_flutter_wkwebview/src/ui_kit/ui_kit.dart' as _i3; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart' as _i4; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class class _FakePoint_0 extends _i1.SmartFake implements _i2.Point { _FakePoint_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeUIScrollView_1 extends _i1.SmartFake implements _i3.UIScrollView { _FakeUIScrollView_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKNavigationDelegate_2 extends _i1.SmartFake implements _i4.WKNavigationDelegate { _FakeWKNavigationDelegate_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKPreferences_3 extends _i1.SmartFake implements _i4.WKPreferences { _FakeWKPreferences_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKScriptMessageHandler_4 extends _i1.SmartFake implements _i4.WKScriptMessageHandler { _FakeWKScriptMessageHandler_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKWebViewConfiguration_5 extends _i1.SmartFake implements _i4.WKWebViewConfiguration { _FakeWKWebViewConfiguration_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKWebView_6 extends _i1.SmartFake implements _i4.WKWebView { _FakeWKWebView_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKUserContentController_7 extends _i1.SmartFake implements _i4.WKUserContentController { _FakeWKUserContentController_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKWebsiteDataStore_8 extends _i1.SmartFake implements _i4.WKWebsiteDataStore { _FakeWKWebsiteDataStore_8( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKHttpCookieStore_9 extends _i1.SmartFake implements _i4.WKHttpCookieStore { _FakeWKHttpCookieStore_9( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKUIDelegate_10 extends _i1.SmartFake implements _i4.WKUIDelegate { _FakeWKUIDelegate_10( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [UIScrollView]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockUIScrollView extends _i1.Mock implements _i3.UIScrollView { MockUIScrollView() { _i1.throwOnMissingStub(this); } @override _i5.Future<_i2.Point> getContentOffset() => (super.noSuchMethod( Invocation.method( #getContentOffset, [], ), returnValue: _i5.Future<_i2.Point>.value(_FakePoint_0( this, Invocation.method( #getContentOffset, [], ), )), ) as _i5.Future<_i2.Point>); @override _i5.Future scrollBy(_i2.Point? offset) => (super.noSuchMethod( Invocation.method( #scrollBy, [offset], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setContentOffset(_i2.Point? offset) => (super.noSuchMethod( Invocation.method( #setContentOffset, [offset], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i3.UIScrollView copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeUIScrollView_1( this, Invocation.method( #copy, [], ), ), ) as _i3.UIScrollView); @override _i5.Future setBackgroundColor(_i6.Color? color) => (super.noSuchMethod( Invocation.method( #setBackgroundColor, [color], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setOpaque(bool? opaque) => (super.noSuchMethod( Invocation.method( #setOpaque, [opaque], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future addObserver( _i7.NSObject? observer, { required String? keyPath, required Set<_i7.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeObserver( _i7.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } /// A class which mocks [WKNavigationDelegate]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKNavigationDelegate extends _i1.Mock implements _i4.WKNavigationDelegate { MockWKNavigationDelegate() { _i1.throwOnMissingStub(this); } @override _i4.WKNavigationDelegate copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKNavigationDelegate_2( this, Invocation.method( #copy, [], ), ), ) as _i4.WKNavigationDelegate); @override _i5.Future addObserver( _i7.NSObject? observer, { required String? keyPath, required Set<_i7.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeObserver( _i7.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } /// A class which mocks [WKPreferences]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKPreferences extends _i1.Mock implements _i4.WKPreferences { MockWKPreferences() { _i1.throwOnMissingStub(this); } @override _i5.Future setJavaScriptEnabled(bool? enabled) => (super.noSuchMethod( Invocation.method( #setJavaScriptEnabled, [enabled], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i4.WKPreferences copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKPreferences_3( this, Invocation.method( #copy, [], ), ), ) as _i4.WKPreferences); @override _i5.Future addObserver( _i7.NSObject? observer, { required String? keyPath, required Set<_i7.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeObserver( _i7.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } /// A class which mocks [WKScriptMessageHandler]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKScriptMessageHandler extends _i1.Mock implements _i4.WKScriptMessageHandler { MockWKScriptMessageHandler() { _i1.throwOnMissingStub(this); } @override void Function( _i4.WKUserContentController, _i4.WKScriptMessage, ) get didReceiveScriptMessage => (super.noSuchMethod( Invocation.getter(#didReceiveScriptMessage), returnValue: ( _i4.WKUserContentController userContentController, _i4.WKScriptMessage message, ) {}, ) as void Function( _i4.WKUserContentController, _i4.WKScriptMessage, )); @override _i4.WKScriptMessageHandler copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKScriptMessageHandler_4( this, Invocation.method( #copy, [], ), ), ) as _i4.WKScriptMessageHandler); @override _i5.Future addObserver( _i7.NSObject? observer, { required String? keyPath, required Set<_i7.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeObserver( _i7.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } /// A class which mocks [WKWebView]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKWebView extends _i1.Mock implements _i4.WKWebView { MockWKWebView() { _i1.throwOnMissingStub(this); } @override _i4.WKWebViewConfiguration get configuration => (super.noSuchMethod( Invocation.getter(#configuration), returnValue: _FakeWKWebViewConfiguration_5( this, Invocation.getter(#configuration), ), ) as _i4.WKWebViewConfiguration); @override _i3.UIScrollView get scrollView => (super.noSuchMethod( Invocation.getter(#scrollView), returnValue: _FakeUIScrollView_1( this, Invocation.getter(#scrollView), ), ) as _i3.UIScrollView); @override _i5.Future setUIDelegate(_i4.WKUIDelegate? delegate) => (super.noSuchMethod( Invocation.method( #setUIDelegate, [delegate], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setNavigationDelegate(_i4.WKNavigationDelegate? delegate) => (super.noSuchMethod( Invocation.method( #setNavigationDelegate, [delegate], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future getUrl() => (super.noSuchMethod( Invocation.method( #getUrl, [], ), returnValue: _i5.Future.value(), ) as _i5.Future); @override _i5.Future getEstimatedProgress() => (super.noSuchMethod( Invocation.method( #getEstimatedProgress, [], ), returnValue: _i5.Future.value(0.0), ) as _i5.Future); @override _i5.Future loadRequest(_i7.NSUrlRequest? request) => (super.noSuchMethod( Invocation.method( #loadRequest, [request], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future loadHtmlString( String? string, { String? baseUrl, }) => (super.noSuchMethod( Invocation.method( #loadHtmlString, [string], {#baseUrl: baseUrl}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future loadFileUrl( String? url, { required String? readAccessUrl, }) => (super.noSuchMethod( Invocation.method( #loadFileUrl, [url], {#readAccessUrl: readAccessUrl}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future loadFlutterAsset(String? key) => (super.noSuchMethod( Invocation.method( #loadFlutterAsset, [key], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future canGoBack() => (super.noSuchMethod( Invocation.method( #canGoBack, [], ), returnValue: _i5.Future.value(false), ) as _i5.Future); @override _i5.Future canGoForward() => (super.noSuchMethod( Invocation.method( #canGoForward, [], ), returnValue: _i5.Future.value(false), ) as _i5.Future); @override _i5.Future goBack() => (super.noSuchMethod( Invocation.method( #goBack, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future goForward() => (super.noSuchMethod( Invocation.method( #goForward, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future reload() => (super.noSuchMethod( Invocation.method( #reload, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future getTitle() => (super.noSuchMethod( Invocation.method( #getTitle, [], ), returnValue: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setAllowsBackForwardNavigationGestures(bool? allow) => (super.noSuchMethod( Invocation.method( #setAllowsBackForwardNavigationGestures, [allow], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setCustomUserAgent(String? userAgent) => (super.noSuchMethod( Invocation.method( #setCustomUserAgent, [userAgent], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future evaluateJavaScript(String? javaScriptString) => (super.noSuchMethod( Invocation.method( #evaluateJavaScript, [javaScriptString], ), returnValue: _i5.Future.value(), ) as _i5.Future); @override _i4.WKWebView copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKWebView_6( this, Invocation.method( #copy, [], ), ), ) as _i4.WKWebView); @override _i5.Future setBackgroundColor(_i6.Color? color) => (super.noSuchMethod( Invocation.method( #setBackgroundColor, [color], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setOpaque(bool? opaque) => (super.noSuchMethod( Invocation.method( #setOpaque, [opaque], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future addObserver( _i7.NSObject? observer, { required String? keyPath, required Set<_i7.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeObserver( _i7.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } /// A class which mocks [WKWebViewConfiguration]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKWebViewConfiguration extends _i1.Mock implements _i4.WKWebViewConfiguration { MockWKWebViewConfiguration() { _i1.throwOnMissingStub(this); } @override _i4.WKUserContentController get userContentController => (super.noSuchMethod( Invocation.getter(#userContentController), returnValue: _FakeWKUserContentController_7( this, Invocation.getter(#userContentController), ), ) as _i4.WKUserContentController); @override _i4.WKPreferences get preferences => (super.noSuchMethod( Invocation.getter(#preferences), returnValue: _FakeWKPreferences_3( this, Invocation.getter(#preferences), ), ) as _i4.WKPreferences); @override _i4.WKWebsiteDataStore get websiteDataStore => (super.noSuchMethod( Invocation.getter(#websiteDataStore), returnValue: _FakeWKWebsiteDataStore_8( this, Invocation.getter(#websiteDataStore), ), ) as _i4.WKWebsiteDataStore); @override _i5.Future setAllowsInlineMediaPlayback(bool? allow) => (super.noSuchMethod( Invocation.method( #setAllowsInlineMediaPlayback, [allow], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setMediaTypesRequiringUserActionForPlayback( Set<_i4.WKAudiovisualMediaType>? types) => (super.noSuchMethod( Invocation.method( #setMediaTypesRequiringUserActionForPlayback, [types], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i4.WKWebViewConfiguration copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKWebViewConfiguration_5( this, Invocation.method( #copy, [], ), ), ) as _i4.WKWebViewConfiguration); @override _i5.Future addObserver( _i7.NSObject? observer, { required String? keyPath, required Set<_i7.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeObserver( _i7.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } /// A class which mocks [WKWebsiteDataStore]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKWebsiteDataStore extends _i1.Mock implements _i4.WKWebsiteDataStore { MockWKWebsiteDataStore() { _i1.throwOnMissingStub(this); } @override _i4.WKHttpCookieStore get httpCookieStore => (super.noSuchMethod( Invocation.getter(#httpCookieStore), returnValue: _FakeWKHttpCookieStore_9( this, Invocation.getter(#httpCookieStore), ), ) as _i4.WKHttpCookieStore); @override _i5.Future removeDataOfTypes( Set<_i4.WKWebsiteDataType>? dataTypes, DateTime? since, ) => (super.noSuchMethod( Invocation.method( #removeDataOfTypes, [ dataTypes, since, ], ), returnValue: _i5.Future.value(false), ) as _i5.Future); @override _i4.WKWebsiteDataStore copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKWebsiteDataStore_8( this, Invocation.method( #copy, [], ), ), ) as _i4.WKWebsiteDataStore); @override _i5.Future addObserver( _i7.NSObject? observer, { required String? keyPath, required Set<_i7.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeObserver( _i7.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } /// A class which mocks [WKUIDelegate]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKUIDelegate extends _i1.Mock implements _i4.WKUIDelegate { MockWKUIDelegate() { _i1.throwOnMissingStub(this); } @override _i4.WKUIDelegate copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKUIDelegate_10( this, Invocation.method( #copy, [], ), ), ) as _i4.WKUIDelegate); @override _i5.Future addObserver( _i7.NSObject? observer, { required String? keyPath, required Set<_i7.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeObserver( _i7.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } /// A class which mocks [WKUserContentController]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKUserContentController extends _i1.Mock implements _i4.WKUserContentController { MockWKUserContentController() { _i1.throwOnMissingStub(this); } @override _i5.Future addScriptMessageHandler( _i4.WKScriptMessageHandler? handler, String? name, ) => (super.noSuchMethod( Invocation.method( #addScriptMessageHandler, [ handler, name, ], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeScriptMessageHandler(String? name) => (super.noSuchMethod( Invocation.method( #removeScriptMessageHandler, [name], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeAllScriptMessageHandlers() => (super.noSuchMethod( Invocation.method( #removeAllScriptMessageHandlers, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future addUserScript(_i4.WKUserScript? userScript) => (super.noSuchMethod( Invocation.method( #addUserScript, [userScript], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeAllUserScripts() => (super.noSuchMethod( Invocation.method( #removeAllUserScripts, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i4.WKUserContentController copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKUserContentController_7( this, Invocation.method( #copy, [], ), ), ) as _i4.WKUserContentController); @override _i5.Future addObserver( _i7.NSObject? observer, { required String? keyPath, required Set<_i7.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeObserver( _i7.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } /// A class which mocks [JavascriptChannelRegistry]. /// /// See the documentation for Mockito's code generation for more information. class MockJavascriptChannelRegistry extends _i1.Mock implements _i8.JavascriptChannelRegistry { MockJavascriptChannelRegistry() { _i1.throwOnMissingStub(this); } @override Map get channels => (super.noSuchMethod( Invocation.getter(#channels), returnValue: {}, ) as Map); @override void onJavascriptChannelMessage( String? channel, String? message, ) => super.noSuchMethod( Invocation.method( #onJavascriptChannelMessage, [ channel, message, ], ), returnValueForMissingStub: null, ); @override void updateJavascriptChannelsFromSet(Set<_i9.JavascriptChannel>? channels) => super.noSuchMethod( Invocation.method( #updateJavascriptChannelsFromSet, [channels], ), returnValueForMissingStub: null, ); } /// A class which mocks [WebViewPlatformCallbacksHandler]. /// /// See the documentation for Mockito's code generation for more information. class MockWebViewPlatformCallbacksHandler extends _i1.Mock implements _i8.WebViewPlatformCallbacksHandler { MockWebViewPlatformCallbacksHandler() { _i1.throwOnMissingStub(this); } @override _i5.FutureOr onNavigationRequest({ required String? url, required bool? isForMainFrame, }) => (super.noSuchMethod( Invocation.method( #onNavigationRequest, [], { #url: url, #isForMainFrame: isForMainFrame, }, ), returnValue: _i5.Future.value(false), ) as _i5.FutureOr); @override void onPageStarted(String? url) => super.noSuchMethod( Invocation.method( #onPageStarted, [url], ), returnValueForMissingStub: null, ); @override void onPageFinished(String? url) => super.noSuchMethod( Invocation.method( #onPageFinished, [url], ), returnValueForMissingStub: null, ); @override void onProgress(int? progress) => super.noSuchMethod( Invocation.method( #onProgress, [progress], ), returnValueForMissingStub: null, ); @override void onWebResourceError(_i10.WebResourceError? error) => super.noSuchMethod( Invocation.method( #onWebResourceError, [error], ), returnValueForMissingStub: null, ); } /// A class which mocks [WebViewWidgetProxy]. /// /// See the documentation for Mockito's code generation for more information. class MockWebViewWidgetProxy extends _i1.Mock implements _i11.WebViewWidgetProxy { MockWebViewWidgetProxy() { _i1.throwOnMissingStub(this); } @override _i4.WKWebView createWebView( _i4.WKWebViewConfiguration? configuration, { void Function( String, _i7.NSObject, Map<_i7.NSKeyValueChangeKey, Object?>, )? observeValue, }) => (super.noSuchMethod( Invocation.method( #createWebView, [configuration], {#observeValue: observeValue}, ), returnValue: _FakeWKWebView_6( this, Invocation.method( #createWebView, [configuration], {#observeValue: observeValue}, ), ), ) as _i4.WKWebView); @override _i4.WKScriptMessageHandler createScriptMessageHandler( {required void Function( _i4.WKUserContentController, _i4.WKScriptMessage, )? didReceiveScriptMessage}) => (super.noSuchMethod( Invocation.method( #createScriptMessageHandler, [], {#didReceiveScriptMessage: didReceiveScriptMessage}, ), returnValue: _FakeWKScriptMessageHandler_4( this, Invocation.method( #createScriptMessageHandler, [], {#didReceiveScriptMessage: didReceiveScriptMessage}, ), ), ) as _i4.WKScriptMessageHandler); @override _i4.WKUIDelegate createUIDelgate( {void Function( _i4.WKWebView, _i4.WKWebViewConfiguration, _i4.WKNavigationAction, )? onCreateWebView}) => (super.noSuchMethod( Invocation.method( #createUIDelgate, [], {#onCreateWebView: onCreateWebView}, ), returnValue: _FakeWKUIDelegate_10( this, Invocation.method( #createUIDelgate, [], {#onCreateWebView: onCreateWebView}, ), ), ) as _i4.WKUIDelegate); @override _i4.WKNavigationDelegate createNavigationDelegate({ void Function( _i4.WKWebView, String?, )? didFinishNavigation, void Function( _i4.WKWebView, String?, )? didStartProvisionalNavigation, _i5.Future<_i4.WKNavigationActionPolicy> Function( _i4.WKWebView, _i4.WKNavigationAction, )? decidePolicyForNavigationAction, void Function( _i4.WKWebView, _i7.NSError, )? didFailNavigation, void Function( _i4.WKWebView, _i7.NSError, )? didFailProvisionalNavigation, void Function(_i4.WKWebView)? webViewWebContentProcessDidTerminate, }) => (super.noSuchMethod( Invocation.method( #createNavigationDelegate, [], { #didFinishNavigation: didFinishNavigation, #didStartProvisionalNavigation: didStartProvisionalNavigation, #decidePolicyForNavigationAction: decidePolicyForNavigationAction, #didFailNavigation: didFailNavigation, #didFailProvisionalNavigation: didFailProvisionalNavigation, #webViewWebContentProcessDidTerminate: webViewWebContentProcessDidTerminate, }, ), returnValue: _FakeWKNavigationDelegate_2( this, Invocation.method( #createNavigationDelegate, [], { #didFinishNavigation: didFinishNavigation, #didStartProvisionalNavigation: didStartProvisionalNavigation, #decidePolicyForNavigationAction: decidePolicyForNavigationAction, #didFailNavigation: didFailNavigation, #didFailProvisionalNavigation: didFailProvisionalNavigation, #webViewWebContentProcessDidTerminate: webViewWebContentProcessDidTerminate, }, ), ), ) as _i4.WKNavigationDelegate); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/src/common/instance_manager_test.dart ================================================ // Copyright 2013 The Flutter 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:webview_flutter_wkwebview/src/common/instance_manager.dart'; void main() { group('InstanceManager', () { test('addHostCreatedInstance', () { final CopyableObject object = CopyableObject(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addHostCreatedInstance(object, 0); expect(instanceManager.getIdentifier(object), 0); expect( instanceManager.getInstanceWithWeakReference(0), object, ); }); test('addHostCreatedInstance prevents already used objects and ids', () { final CopyableObject object = CopyableObject(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addHostCreatedInstance(object, 0); expect( () => instanceManager.addHostCreatedInstance(object, 0), throwsAssertionError, ); expect( () => instanceManager.addHostCreatedInstance(CopyableObject(), 0), throwsAssertionError, ); }); test('addFlutterCreatedInstance', () { final CopyableObject object = CopyableObject(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addDartCreatedInstance(object); final int? instanceId = instanceManager.getIdentifier(object); expect(instanceId, isNotNull); expect( instanceManager.getInstanceWithWeakReference(instanceId!), object, ); }); test('removeWeakReference', () { final CopyableObject object = CopyableObject(); int? weakInstanceId; final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (int instanceId) { weakInstanceId = instanceId; }); instanceManager.addHostCreatedInstance(object, 0); expect(instanceManager.removeWeakReference(object), 0); expect( instanceManager.getInstanceWithWeakReference(0), isA(), ); expect(weakInstanceId, 0); }); test('removeWeakReference removes only weak reference', () { final CopyableObject object = CopyableObject(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addHostCreatedInstance(object, 0); expect(instanceManager.removeWeakReference(object), 0); final CopyableObject copy = instanceManager.getInstanceWithWeakReference( 0, )!; expect(identical(object, copy), isFalse); }); test('removeStrongReference', () { final CopyableObject object = CopyableObject(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addHostCreatedInstance(object, 0); instanceManager.removeWeakReference(object); expect(instanceManager.remove(0), isA()); expect(instanceManager.containsIdentifier(0), isFalse); }); test('removeStrongReference removes only strong reference', () { final CopyableObject object = CopyableObject(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addHostCreatedInstance(object, 0); expect(instanceManager.remove(0), isA()); expect( instanceManager.getInstanceWithWeakReference(0), object, ); }); test('getInstance can add a new weak reference', () { final CopyableObject object = CopyableObject(); final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); instanceManager.addHostCreatedInstance(object, 0); instanceManager.removeWeakReference(object); final CopyableObject newWeakCopy = instanceManager.getInstanceWithWeakReference( 0, )!; expect(identical(object, newWeakCopy), isFalse); }); }); } class CopyableObject with Copyable { @override Copyable copy() { return CopyableObject(); } @override int get hashCode { return 0; } @override bool operator ==(Object other) { return other is CopyableObject; } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.g.dart ================================================ // Copyright 2013 The Flutter 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.13), 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:webview_flutter_wkwebview/src/common/web_kit.g.dart'; class _TestWKWebsiteDataStoreHostApiCodec extends StandardMessageCodec { const _TestWKWebsiteDataStoreHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is WKWebsiteDataTypeEnumData) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return WKWebsiteDataTypeEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } /// Mirror of WKWebsiteDataStore. /// /// See https://developer.apple.com/documentation/webkit/wkwebsitedatastore?language=objc. abstract class TestWKWebsiteDataStoreHostApi { static const MessageCodec codec = _TestWKWebsiteDataStoreHostApiCodec(); void createFromWebViewConfiguration( int identifier, int configurationIdentifier); void createDefaultDataStore(int identifier); Future removeDataOfTypes( int identifier, List dataTypes, double modificationTimeInSecondsSinceEpoch); static void setup(TestWKWebsiteDataStoreHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebsiteDataStoreHostApi.createFromWebViewConfiguration', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebsiteDataStoreHostApi.createFromWebViewConfiguration 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.WKWebsiteDataStoreHostApi.createFromWebViewConfiguration was null, expected non-null int.'); final int? arg_configurationIdentifier = (args[1] as int?); assert(arg_configurationIdentifier != null, 'Argument for dev.flutter.pigeon.WKWebsiteDataStoreHostApi.createFromWebViewConfiguration was null, expected non-null int.'); api.createFromWebViewConfiguration( arg_identifier!, arg_configurationIdentifier!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebsiteDataStoreHostApi.createDefaultDataStore', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebsiteDataStoreHostApi.createDefaultDataStore 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.WKWebsiteDataStoreHostApi.createDefaultDataStore was null, expected non-null int.'); api.createDefaultDataStore(arg_identifier!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebsiteDataStoreHostApi.removeDataOfTypes', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebsiteDataStoreHostApi.removeDataOfTypes 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.WKWebsiteDataStoreHostApi.removeDataOfTypes was null, expected non-null int.'); final List? arg_dataTypes = (args[1] as List?)?.cast(); assert(arg_dataTypes != null, 'Argument for dev.flutter.pigeon.WKWebsiteDataStoreHostApi.removeDataOfTypes was null, expected non-null List.'); final double? arg_modificationTimeInSecondsSinceEpoch = (args[2] as double?); assert(arg_modificationTimeInSecondsSinceEpoch != null, 'Argument for dev.flutter.pigeon.WKWebsiteDataStoreHostApi.removeDataOfTypes was null, expected non-null double.'); final bool output = await api.removeDataOfTypes(arg_identifier!, arg_dataTypes!, arg_modificationTimeInSecondsSinceEpoch!); return [output]; }); } } } } /// Mirror of UIView. /// /// See https://developer.apple.com/documentation/uikit/uiview?language=objc. abstract class TestUIViewHostApi { static const MessageCodec codec = StandardMessageCodec(); void setBackgroundColor(int identifier, int? value); void setOpaque(int identifier, bool opaque); static void setup(TestUIViewHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UIViewHostApi.setBackgroundColor', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.UIViewHostApi.setBackgroundColor 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.UIViewHostApi.setBackgroundColor was null, expected non-null int.'); final int? arg_value = (args[1] as int?); api.setBackgroundColor(arg_identifier!, arg_value); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UIViewHostApi.setOpaque', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.UIViewHostApi.setOpaque 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.UIViewHostApi.setOpaque was null, expected non-null int.'); final bool? arg_opaque = (args[1] as bool?); assert(arg_opaque != null, 'Argument for dev.flutter.pigeon.UIViewHostApi.setOpaque was null, expected non-null bool.'); api.setOpaque(arg_identifier!, arg_opaque!); return []; }); } } } } /// Mirror of UIScrollView. /// /// See https://developer.apple.com/documentation/uikit/uiscrollview?language=objc. abstract class TestUIScrollViewHostApi { static const MessageCodec codec = StandardMessageCodec(); void createFromWebView(int identifier, int webViewIdentifier); List getContentOffset(int identifier); void scrollBy(int identifier, double x, double y); void setContentOffset(int identifier, double x, double y); static void setup(TestUIScrollViewHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UIScrollViewHostApi.createFromWebView', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.UIScrollViewHostApi.createFromWebView 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.UIScrollViewHostApi.createFromWebView was null, expected non-null int.'); final int? arg_webViewIdentifier = (args[1] as int?); assert(arg_webViewIdentifier != null, 'Argument for dev.flutter.pigeon.UIScrollViewHostApi.createFromWebView was null, expected non-null int.'); api.createFromWebView(arg_identifier!, arg_webViewIdentifier!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UIScrollViewHostApi.getContentOffset', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.UIScrollViewHostApi.getContentOffset 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.UIScrollViewHostApi.getContentOffset was null, expected non-null int.'); final List output = api.getContentOffset(arg_identifier!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UIScrollViewHostApi.scrollBy', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.UIScrollViewHostApi.scrollBy 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.UIScrollViewHostApi.scrollBy was null, expected non-null int.'); final double? arg_x = (args[1] as double?); assert(arg_x != null, 'Argument for dev.flutter.pigeon.UIScrollViewHostApi.scrollBy was null, expected non-null double.'); final double? arg_y = (args[2] as double?); assert(arg_y != null, 'Argument for dev.flutter.pigeon.UIScrollViewHostApi.scrollBy was null, expected non-null double.'); api.scrollBy(arg_identifier!, arg_x!, arg_y!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.UIScrollViewHostApi.setContentOffset', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.UIScrollViewHostApi.setContentOffset 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.UIScrollViewHostApi.setContentOffset was null, expected non-null int.'); final double? arg_x = (args[1] as double?); assert(arg_x != null, 'Argument for dev.flutter.pigeon.UIScrollViewHostApi.setContentOffset was null, expected non-null double.'); final double? arg_y = (args[2] as double?); assert(arg_y != null, 'Argument for dev.flutter.pigeon.UIScrollViewHostApi.setContentOffset was null, expected non-null double.'); api.setContentOffset(arg_identifier!, arg_x!, arg_y!); return []; }); } } } } class _TestWKWebViewConfigurationHostApiCodec extends StandardMessageCodec { const _TestWKWebViewConfigurationHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is WKAudiovisualMediaTypeEnumData) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } /// Mirror of WKWebViewConfiguration. /// /// See https://developer.apple.com/documentation/webkit/wkwebviewconfiguration?language=objc. abstract class TestWKWebViewConfigurationHostApi { static const MessageCodec codec = _TestWKWebViewConfigurationHostApiCodec(); void create(int identifier); void createFromWebView(int identifier, int webViewIdentifier); void setAllowsInlineMediaPlayback(int identifier, bool allow); void setMediaTypesRequiringUserActionForPlayback( int identifier, List types); static void setup(TestWKWebViewConfigurationHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewConfigurationHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewConfigurationHostApi.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.WKWebViewConfigurationHostApi.create was null, expected non-null int.'); api.create(arg_identifier!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewConfigurationHostApi.createFromWebView', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewConfigurationHostApi.createFromWebView 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.WKWebViewConfigurationHostApi.createFromWebView was null, expected non-null int.'); final int? arg_webViewIdentifier = (args[1] as int?); assert(arg_webViewIdentifier != null, 'Argument for dev.flutter.pigeon.WKWebViewConfigurationHostApi.createFromWebView was null, expected non-null int.'); api.createFromWebView(arg_identifier!, arg_webViewIdentifier!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewConfigurationHostApi.setAllowsInlineMediaPlayback', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewConfigurationHostApi.setAllowsInlineMediaPlayback 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.WKWebViewConfigurationHostApi.setAllowsInlineMediaPlayback was null, expected non-null int.'); final bool? arg_allow = (args[1] as bool?); assert(arg_allow != null, 'Argument for dev.flutter.pigeon.WKWebViewConfigurationHostApi.setAllowsInlineMediaPlayback was null, expected non-null bool.'); api.setAllowsInlineMediaPlayback(arg_identifier!, arg_allow!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewConfigurationHostApi.setMediaTypesRequiringUserActionForPlayback', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewConfigurationHostApi.setMediaTypesRequiringUserActionForPlayback 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.WKWebViewConfigurationHostApi.setMediaTypesRequiringUserActionForPlayback was null, expected non-null int.'); final List? arg_types = (args[1] as List?) ?.cast(); assert(arg_types != null, 'Argument for dev.flutter.pigeon.WKWebViewConfigurationHostApi.setMediaTypesRequiringUserActionForPlayback was null, expected non-null List.'); api.setMediaTypesRequiringUserActionForPlayback( arg_identifier!, arg_types!); return []; }); } } } } class _TestWKUserContentControllerHostApiCodec extends StandardMessageCodec { const _TestWKUserContentControllerHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is WKUserScriptData) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is WKUserScriptInjectionTimeEnumData) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return WKUserScriptData.decode(readValue(buffer)!); case 129: return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } /// Mirror of WKUserContentController. /// /// See https://developer.apple.com/documentation/webkit/wkusercontentcontroller?language=objc. abstract class TestWKUserContentControllerHostApi { static const MessageCodec codec = _TestWKUserContentControllerHostApiCodec(); void createFromWebViewConfiguration( int identifier, int configurationIdentifier); void addScriptMessageHandler( int identifier, int handlerIdentifier, String name); void removeScriptMessageHandler(int identifier, String name); void removeAllScriptMessageHandlers(int identifier); void addUserScript(int identifier, WKUserScriptData userScript); void removeAllUserScripts(int identifier); static void setup(TestWKUserContentControllerHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKUserContentControllerHostApi.createFromWebViewConfiguration', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKUserContentControllerHostApi.createFromWebViewConfiguration 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.WKUserContentControllerHostApi.createFromWebViewConfiguration was null, expected non-null int.'); final int? arg_configurationIdentifier = (args[1] as int?); assert(arg_configurationIdentifier != null, 'Argument for dev.flutter.pigeon.WKUserContentControllerHostApi.createFromWebViewConfiguration was null, expected non-null int.'); api.createFromWebViewConfiguration( arg_identifier!, arg_configurationIdentifier!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKUserContentControllerHostApi.addScriptMessageHandler', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKUserContentControllerHostApi.addScriptMessageHandler 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.WKUserContentControllerHostApi.addScriptMessageHandler was null, expected non-null int.'); final int? arg_handlerIdentifier = (args[1] as int?); assert(arg_handlerIdentifier != null, 'Argument for dev.flutter.pigeon.WKUserContentControllerHostApi.addScriptMessageHandler was null, expected non-null int.'); final String? arg_name = (args[2] as String?); assert(arg_name != null, 'Argument for dev.flutter.pigeon.WKUserContentControllerHostApi.addScriptMessageHandler was null, expected non-null String.'); api.addScriptMessageHandler( arg_identifier!, arg_handlerIdentifier!, arg_name!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKUserContentControllerHostApi.removeScriptMessageHandler', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKUserContentControllerHostApi.removeScriptMessageHandler 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.WKUserContentControllerHostApi.removeScriptMessageHandler was null, expected non-null int.'); final String? arg_name = (args[1] as String?); assert(arg_name != null, 'Argument for dev.flutter.pigeon.WKUserContentControllerHostApi.removeScriptMessageHandler was null, expected non-null String.'); api.removeScriptMessageHandler(arg_identifier!, arg_name!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKUserContentControllerHostApi.removeAllScriptMessageHandlers', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKUserContentControllerHostApi.removeAllScriptMessageHandlers 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.WKUserContentControllerHostApi.removeAllScriptMessageHandlers was null, expected non-null int.'); api.removeAllScriptMessageHandlers(arg_identifier!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKUserContentControllerHostApi.addUserScript', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKUserContentControllerHostApi.addUserScript 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.WKUserContentControllerHostApi.addUserScript was null, expected non-null int.'); final WKUserScriptData? arg_userScript = (args[1] as WKUserScriptData?); assert(arg_userScript != null, 'Argument for dev.flutter.pigeon.WKUserContentControllerHostApi.addUserScript was null, expected non-null WKUserScriptData.'); api.addUserScript(arg_identifier!, arg_userScript!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKUserContentControllerHostApi.removeAllUserScripts', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKUserContentControllerHostApi.removeAllUserScripts 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.WKUserContentControllerHostApi.removeAllUserScripts was null, expected non-null int.'); api.removeAllUserScripts(arg_identifier!); return []; }); } } } } /// Mirror of WKUserPreferences. /// /// See https://developer.apple.com/documentation/webkit/wkpreferences?language=objc. abstract class TestWKPreferencesHostApi { static const MessageCodec codec = StandardMessageCodec(); void createFromWebViewConfiguration( int identifier, int configurationIdentifier); void setJavaScriptEnabled(int identifier, bool enabled); static void setup(TestWKPreferencesHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKPreferencesHostApi.createFromWebViewConfiguration', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKPreferencesHostApi.createFromWebViewConfiguration 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.WKPreferencesHostApi.createFromWebViewConfiguration was null, expected non-null int.'); final int? arg_configurationIdentifier = (args[1] as int?); assert(arg_configurationIdentifier != null, 'Argument for dev.flutter.pigeon.WKPreferencesHostApi.createFromWebViewConfiguration was null, expected non-null int.'); api.createFromWebViewConfiguration( arg_identifier!, arg_configurationIdentifier!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKPreferencesHostApi.setJavaScriptEnabled', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKPreferencesHostApi.setJavaScriptEnabled 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.WKPreferencesHostApi.setJavaScriptEnabled was null, expected non-null int.'); final bool? arg_enabled = (args[1] as bool?); assert(arg_enabled != null, 'Argument for dev.flutter.pigeon.WKPreferencesHostApi.setJavaScriptEnabled was null, expected non-null bool.'); api.setJavaScriptEnabled(arg_identifier!, arg_enabled!); return []; }); } } } } /// Mirror of WKScriptMessageHandler. /// /// See https://developer.apple.com/documentation/webkit/wkscriptmessagehandler?language=objc. abstract class TestWKScriptMessageHandlerHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int identifier); static void setup(TestWKScriptMessageHandlerHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKScriptMessageHandlerHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKScriptMessageHandlerHostApi.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.WKScriptMessageHandlerHostApi.create was null, expected non-null int.'); api.create(arg_identifier!); return []; }); } } } } /// Mirror of WKNavigationDelegate. /// /// See https://developer.apple.com/documentation/webkit/wknavigationdelegate?language=objc. abstract class TestWKNavigationDelegateHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int identifier); static void setup(TestWKNavigationDelegateHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKNavigationDelegateHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKNavigationDelegateHostApi.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.WKNavigationDelegateHostApi.create was null, expected non-null int.'); api.create(arg_identifier!); return []; }); } } } } class _TestNSObjectHostApiCodec extends StandardMessageCodec { const _TestNSObjectHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is NSKeyValueObservingOptionsEnumData) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } /// Mirror of NSObject. /// /// See https://developer.apple.com/documentation/objectivec/nsobject. abstract class TestNSObjectHostApi { static const MessageCodec codec = _TestNSObjectHostApiCodec(); void dispose(int identifier); void addObserver(int identifier, int observerIdentifier, String keyPath, List options); void removeObserver(int identifier, int observerIdentifier, String keyPath); static void setup(TestNSObjectHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.NSObjectHostApi.dispose', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.NSObjectHostApi.dispose 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.NSObjectHostApi.dispose was null, expected non-null int.'); api.dispose(arg_identifier!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.NSObjectHostApi.addObserver', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.NSObjectHostApi.addObserver 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.NSObjectHostApi.addObserver was null, expected non-null int.'); final int? arg_observerIdentifier = (args[1] as int?); assert(arg_observerIdentifier != null, 'Argument for dev.flutter.pigeon.NSObjectHostApi.addObserver was null, expected non-null int.'); final String? arg_keyPath = (args[2] as String?); assert(arg_keyPath != null, 'Argument for dev.flutter.pigeon.NSObjectHostApi.addObserver was null, expected non-null String.'); final List? arg_options = (args[3] as List?) ?.cast(); assert(arg_options != null, 'Argument for dev.flutter.pigeon.NSObjectHostApi.addObserver was null, expected non-null List.'); api.addObserver(arg_identifier!, arg_observerIdentifier!, arg_keyPath!, arg_options!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.NSObjectHostApi.removeObserver', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.NSObjectHostApi.removeObserver 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.NSObjectHostApi.removeObserver was null, expected non-null int.'); final int? arg_observerIdentifier = (args[1] as int?); assert(arg_observerIdentifier != null, 'Argument for dev.flutter.pigeon.NSObjectHostApi.removeObserver was null, expected non-null int.'); final String? arg_keyPath = (args[2] as String?); assert(arg_keyPath != null, 'Argument for dev.flutter.pigeon.NSObjectHostApi.removeObserver was null, expected non-null String.'); api.removeObserver( arg_identifier!, arg_observerIdentifier!, arg_keyPath!); return []; }); } } } } class _TestWKWebViewHostApiCodec extends StandardMessageCodec { const _TestWKWebViewHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is NSErrorData) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is NSHttpCookieData) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else if (value is NSHttpCookiePropertyKeyEnumData) { buffer.putUint8(130); writeValue(buffer, value.encode()); } else if (value is NSKeyValueChangeKeyEnumData) { buffer.putUint8(131); writeValue(buffer, value.encode()); } else if (value is NSKeyValueObservingOptionsEnumData) { buffer.putUint8(132); writeValue(buffer, value.encode()); } else if (value is NSUrlRequestData) { buffer.putUint8(133); writeValue(buffer, value.encode()); } else if (value is WKAudiovisualMediaTypeEnumData) { buffer.putUint8(134); writeValue(buffer, value.encode()); } else if (value is WKFrameInfoData) { buffer.putUint8(135); writeValue(buffer, value.encode()); } else if (value is WKNavigationActionData) { buffer.putUint8(136); writeValue(buffer, value.encode()); } else if (value is WKNavigationActionPolicyEnumData) { buffer.putUint8(137); writeValue(buffer, value.encode()); } else if (value is WKScriptMessageData) { buffer.putUint8(138); writeValue(buffer, value.encode()); } else if (value is WKUserScriptData) { buffer.putUint8(139); writeValue(buffer, value.encode()); } else if (value is WKUserScriptInjectionTimeEnumData) { buffer.putUint8(140); writeValue(buffer, value.encode()); } else if (value is WKWebsiteDataTypeEnumData) { buffer.putUint8(141); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return NSErrorData.decode(readValue(buffer)!); case 129: return NSHttpCookieData.decode(readValue(buffer)!); case 130: return NSHttpCookiePropertyKeyEnumData.decode(readValue(buffer)!); case 131: return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!); case 132: return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!); case 133: return NSUrlRequestData.decode(readValue(buffer)!); case 134: return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!); case 135: return WKFrameInfoData.decode(readValue(buffer)!); case 136: return WKNavigationActionData.decode(readValue(buffer)!); case 137: return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!); case 138: return WKScriptMessageData.decode(readValue(buffer)!); case 139: return WKUserScriptData.decode(readValue(buffer)!); case 140: return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!); case 141: return WKWebsiteDataTypeEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } /// Mirror of WKWebView. /// /// See https://developer.apple.com/documentation/webkit/wkwebview?language=objc. abstract class TestWKWebViewHostApi { static const MessageCodec codec = _TestWKWebViewHostApiCodec(); void create(int identifier, int configurationIdentifier); void setUIDelegate(int identifier, int? uiDelegateIdentifier); void setNavigationDelegate(int identifier, int? navigationDelegateIdentifier); String? getUrl(int identifier); double getEstimatedProgress(int identifier); void loadRequest(int identifier, NSUrlRequestData request); void loadHtmlString(int identifier, String string, String? baseUrl); void loadFileUrl(int identifier, String url, String readAccessUrl); void loadFlutterAsset(int identifier, String key); bool canGoBack(int identifier); bool canGoForward(int identifier); void goBack(int identifier); void goForward(int identifier); void reload(int identifier); String? getTitle(int identifier); void setAllowsBackForwardNavigationGestures(int identifier, bool allow); void setCustomUserAgent(int identifier, String? userAgent); Future evaluateJavaScript(int identifier, String javaScriptString); static void setup(TestWKWebViewHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.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.WKWebViewHostApi.create was null, expected non-null int.'); final int? arg_configurationIdentifier = (args[1] as int?); assert(arg_configurationIdentifier != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.create was null, expected non-null int.'); api.create(arg_identifier!, arg_configurationIdentifier!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.setUIDelegate', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.setUIDelegate 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.WKWebViewHostApi.setUIDelegate was null, expected non-null int.'); final int? arg_uiDelegateIdentifier = (args[1] as int?); api.setUIDelegate(arg_identifier!, arg_uiDelegateIdentifier); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.setNavigationDelegate', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.setNavigationDelegate 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.WKWebViewHostApi.setNavigationDelegate was null, expected non-null int.'); final int? arg_navigationDelegateIdentifier = (args[1] as int?); api.setNavigationDelegate( arg_identifier!, arg_navigationDelegateIdentifier); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.getUrl', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.getUrl 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.WKWebViewHostApi.getUrl was null, expected non-null int.'); final String? output = api.getUrl(arg_identifier!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.getEstimatedProgress', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.getEstimatedProgress 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.WKWebViewHostApi.getEstimatedProgress was null, expected non-null int.'); final double output = api.getEstimatedProgress(arg_identifier!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.loadRequest', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.loadRequest 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.WKWebViewHostApi.loadRequest was null, expected non-null int.'); final NSUrlRequestData? arg_request = (args[1] as NSUrlRequestData?); assert(arg_request != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.loadRequest was null, expected non-null NSUrlRequestData.'); api.loadRequest(arg_identifier!, arg_request!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.loadHtmlString', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.loadHtmlString 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.WKWebViewHostApi.loadHtmlString was null, expected non-null int.'); final String? arg_string = (args[1] as String?); assert(arg_string != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.loadHtmlString was null, expected non-null String.'); final String? arg_baseUrl = (args[2] as String?); api.loadHtmlString(arg_identifier!, arg_string!, arg_baseUrl); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.loadFileUrl', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.loadFileUrl 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.WKWebViewHostApi.loadFileUrl was null, expected non-null int.'); final String? arg_url = (args[1] as String?); assert(arg_url != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.loadFileUrl was null, expected non-null String.'); final String? arg_readAccessUrl = (args[2] as String?); assert(arg_readAccessUrl != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.loadFileUrl was null, expected non-null String.'); api.loadFileUrl(arg_identifier!, arg_url!, arg_readAccessUrl!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.loadFlutterAsset', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.loadFlutterAsset 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.WKWebViewHostApi.loadFlutterAsset was null, expected non-null int.'); final String? arg_key = (args[1] as String?); assert(arg_key != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.loadFlutterAsset was null, expected non-null String.'); api.loadFlutterAsset(arg_identifier!, arg_key!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.canGoBack', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.canGoBack 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.WKWebViewHostApi.canGoBack was null, expected non-null int.'); final bool output = api.canGoBack(arg_identifier!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.canGoForward', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.canGoForward 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.WKWebViewHostApi.canGoForward was null, expected non-null int.'); final bool output = api.canGoForward(arg_identifier!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.goBack', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.goBack 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.WKWebViewHostApi.goBack was null, expected non-null int.'); api.goBack(arg_identifier!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.goForward', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.goForward 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.WKWebViewHostApi.goForward was null, expected non-null int.'); api.goForward(arg_identifier!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.reload', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.reload 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.WKWebViewHostApi.reload was null, expected non-null int.'); api.reload(arg_identifier!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.getTitle', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.getTitle 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.WKWebViewHostApi.getTitle was null, expected non-null int.'); final String? output = api.getTitle(arg_identifier!); return [output]; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.setAllowsBackForwardNavigationGestures', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.setAllowsBackForwardNavigationGestures 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.WKWebViewHostApi.setAllowsBackForwardNavigationGestures was null, expected non-null int.'); final bool? arg_allow = (args[1] as bool?); assert(arg_allow != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.setAllowsBackForwardNavigationGestures was null, expected non-null bool.'); api.setAllowsBackForwardNavigationGestures( arg_identifier!, arg_allow!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.setCustomUserAgent', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.setCustomUserAgent 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.WKWebViewHostApi.setCustomUserAgent was null, expected non-null int.'); final String? arg_userAgent = (args[1] as String?); api.setCustomUserAgent(arg_identifier!, arg_userAgent); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKWebViewHostApi.evaluateJavaScript', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.evaluateJavaScript 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.WKWebViewHostApi.evaluateJavaScript was null, expected non-null int.'); final String? arg_javaScriptString = (args[1] as String?); assert(arg_javaScriptString != null, 'Argument for dev.flutter.pigeon.WKWebViewHostApi.evaluateJavaScript was null, expected non-null String.'); final Object? output = await api.evaluateJavaScript( arg_identifier!, arg_javaScriptString!); return [output]; }); } } } } /// Mirror of WKUIDelegate. /// /// See https://developer.apple.com/documentation/webkit/wkuidelegate?language=objc. abstract class TestWKUIDelegateHostApi { static const MessageCodec codec = StandardMessageCodec(); void create(int identifier); static void setup(TestWKUIDelegateHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKUIDelegateHostApi.create', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKUIDelegateHostApi.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.WKUIDelegateHostApi.create was null, expected non-null int.'); api.create(arg_identifier!); return []; }); } } } } class _TestWKHttpCookieStoreHostApiCodec extends StandardMessageCodec { const _TestWKHttpCookieStoreHostApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { if (value is NSHttpCookieData) { buffer.putUint8(128); writeValue(buffer, value.encode()); } else if (value is NSHttpCookiePropertyKeyEnumData) { buffer.putUint8(129); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } } @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: return NSHttpCookieData.decode(readValue(buffer)!); case 129: return NSHttpCookiePropertyKeyEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } } } /// Mirror of WKHttpCookieStore. /// /// See https://developer.apple.com/documentation/webkit/wkhttpcookiestore?language=objc. abstract class TestWKHttpCookieStoreHostApi { static const MessageCodec codec = _TestWKHttpCookieStoreHostApiCodec(); void createFromWebsiteDataStore( int identifier, int websiteDataStoreIdentifier); Future setCookie(int identifier, NSHttpCookieData cookie); static void setup(TestWKHttpCookieStoreHostApi? api, {BinaryMessenger? binaryMessenger}) { { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKHttpCookieStoreHostApi.createFromWebsiteDataStore', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKHttpCookieStoreHostApi.createFromWebsiteDataStore 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.WKHttpCookieStoreHostApi.createFromWebsiteDataStore was null, expected non-null int.'); final int? arg_websiteDataStoreIdentifier = (args[1] as int?); assert(arg_websiteDataStoreIdentifier != null, 'Argument for dev.flutter.pigeon.WKHttpCookieStoreHostApi.createFromWebsiteDataStore was null, expected non-null int.'); api.createFromWebsiteDataStore( arg_identifier!, arg_websiteDataStoreIdentifier!); return []; }); } } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKHttpCookieStoreHostApi.setCookie', codec, binaryMessenger: binaryMessenger); if (api == null) { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.WKHttpCookieStoreHostApi.setCookie 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.WKHttpCookieStoreHostApi.setCookie was null, expected non-null int.'); final NSHttpCookieData? arg_cookie = (args[1] as NSHttpCookieData?); assert(arg_cookie != null, 'Argument for dev.flutter.pigeon.WKHttpCookieStoreHostApi.setCookie was null, expected non-null NSHttpCookieData.'); await api.setCookie(arg_identifier!, arg_cookie!); return []; }); } } } } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.dart ================================================ // Copyright 2013 The Flutter 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_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.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.g.dart'; import 'foundation_test.mocks.dart'; @GenerateMocks([ TestNSObjectHostApi, ]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('Foundation', () { late InstanceManager instanceManager; setUp(() { instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); }); group('NSObject', () { late MockTestNSObjectHostApi mockPlatformHostApi; late NSObject object; setUp(() { mockPlatformHostApi = MockTestNSObjectHostApi(); TestNSObjectHostApi.setup(mockPlatformHostApi); object = NSObject.detached(instanceManager: instanceManager); instanceManager.addDartCreatedInstance(object); }); tearDown(() { TestNSObjectHostApi.setup(null); }); test('addObserver', () async { final NSObject observer = NSObject.detached( instanceManager: instanceManager, ); instanceManager.addDartCreatedInstance(observer); await object.addObserver( observer, keyPath: 'aKeyPath', options: { NSKeyValueObservingOptions.initialValue, NSKeyValueObservingOptions.priorNotification, }, ); final List optionsData = verify(mockPlatformHostApi.addObserver( instanceManager.getIdentifier(object), instanceManager.getIdentifier(observer), 'aKeyPath', captureAny, )).captured.single as List; expect(optionsData, hasLength(2)); expect( optionsData[0]!.value, NSKeyValueObservingOptionsEnum.initialValue, ); expect( optionsData[1]!.value, NSKeyValueObservingOptionsEnum.priorNotification, ); }); test('removeObserver', () async { final NSObject observer = NSObject.detached( instanceManager: instanceManager, ); instanceManager.addDartCreatedInstance(observer); await object.removeObserver(observer, keyPath: 'aKeyPath'); verify(mockPlatformHostApi.removeObserver( instanceManager.getIdentifier(object), instanceManager.getIdentifier(observer), 'aKeyPath', )); }); test('NSObjectHostApi.dispose', () async { int? callbackIdentifier; final InstanceManager instanceManager = InstanceManager(onWeakReferenceRemoved: (int identifier) { callbackIdentifier = identifier; }); final NSObject object = NSObject.detached( instanceManager: instanceManager, ); final int identifier = instanceManager.addDartCreatedInstance(object); NSObject.dispose(object); expect(callbackIdentifier, identifier); }); test('observeValue', () async { final Completer> argsCompleter = Completer>(); FoundationFlutterApis.instance = FoundationFlutterApis( instanceManager: instanceManager, ); object = NSObject.detached( instanceManager: instanceManager, observeValue: ( String keyPath, NSObject object, Map change, ) { argsCompleter.complete([keyPath, object, change]); }, ); instanceManager.addHostCreatedInstance(object, 1); FoundationFlutterApis.instance.object.observeValue( 1, 'keyPath', 1, [ NSKeyValueChangeKeyEnumData(value: NSKeyValueChangeKeyEnum.oldValue) ], ['value'], ); expect( argsCompleter.future, completion([ 'keyPath', object, { NSKeyValueChangeKey.oldValue: 'value', }, ]), ); }); test('NSObjectFlutterApi.dispose', () { FoundationFlutterApis.instance = FoundationFlutterApis( instanceManager: instanceManager, ); object = NSObject.detached(instanceManager: instanceManager); instanceManager.addHostCreatedInstance(object, 1); instanceManager.removeWeakReference(object); FoundationFlutterApis.instance.object.dispose(1); expect(instanceManager.containsIdentifier(1), isFalse); }); }); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/src/foundation/foundation_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_wkwebview/test/src/foundation/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:webview_flutter_wkwebview/src/common/web_kit.g.dart' as _i3; import '../common/test_web_kit.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 [TestNSObjectHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestNSObjectHostApi extends _i1.Mock implements _i2.TestNSObjectHostApi { MockTestNSObjectHostApi() { _i1.throwOnMissingStub(this); } @override void dispose(int? identifier) => super.noSuchMethod( Invocation.method( #dispose, [identifier], ), returnValueForMissingStub: null, ); @override void addObserver( int? identifier, int? observerIdentifier, String? keyPath, List<_i3.NSKeyValueObservingOptionsEnumData?>? options, ) => super.noSuchMethod( Invocation.method( #addObserver, [ identifier, observerIdentifier, keyPath, options, ], ), returnValueForMissingStub: null, ); @override void removeObserver( int? identifier, int? observerIdentifier, String? keyPath, ) => super.noSuchMethod( Invocation.method( #removeObserver, [ identifier, observerIdentifier, keyPath, ], ), returnValueForMissingStub: null, ); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:math'; import 'package:flutter/material.dart'; import 'package: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/ui_kit/ui_kit.dart'; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; import '../common/test_web_kit.g.dart'; import 'ui_kit_test.mocks.dart'; @GenerateMocks([ TestWKWebViewConfigurationHostApi, TestWKWebViewHostApi, TestUIScrollViewHostApi, TestUIViewHostApi, ]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('UIKit', () { late InstanceManager instanceManager; setUp(() { instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); }); group('UIScrollView', () { late MockTestUIScrollViewHostApi mockPlatformHostApi; late UIScrollView scrollView; late int scrollViewInstanceId; setUp(() { mockPlatformHostApi = MockTestUIScrollViewHostApi(); TestUIScrollViewHostApi.setup(mockPlatformHostApi); TestWKWebViewConfigurationHostApi.setup( MockTestWKWebViewConfigurationHostApi(), ); TestWKWebViewHostApi.setup(MockTestWKWebViewHostApi()); final WKWebView webView = WKWebView( WKWebViewConfiguration(instanceManager: instanceManager), instanceManager: instanceManager, ); scrollView = UIScrollView.fromWebView( webView, instanceManager: instanceManager, ); scrollViewInstanceId = instanceManager.getIdentifier(scrollView)!; }); tearDown(() { TestUIScrollViewHostApi.setup(null); TestWKWebViewConfigurationHostApi.setup(null); TestWKWebViewHostApi.setup(null); }); test('getContentOffset', () async { when(mockPlatformHostApi.getContentOffset(scrollViewInstanceId)) .thenReturn([4.0, 10.0]); expect( scrollView.getContentOffset(), completion(const Point(4.0, 10.0)), ); }); test('scrollBy', () async { await scrollView.scrollBy(const Point(4.0, 10.0)); verify(mockPlatformHostApi.scrollBy(scrollViewInstanceId, 4.0, 10.0)); }); test('setContentOffset', () async { await scrollView.setContentOffset(const Point(4.0, 10.0)); verify(mockPlatformHostApi.setContentOffset( scrollViewInstanceId, 4.0, 10.0, )); }); }); group('UIView', () { late MockTestUIViewHostApi mockPlatformHostApi; late UIView view; late int viewInstanceId; setUp(() { mockPlatformHostApi = MockTestUIViewHostApi(); TestUIViewHostApi.setup(mockPlatformHostApi); view = UIView.detached(instanceManager: instanceManager); viewInstanceId = instanceManager.addDartCreatedInstance(view); }); tearDown(() { TestUIViewHostApi.setup(null); }); test('setBackgroundColor', () async { await view.setBackgroundColor(Colors.red); verify(mockPlatformHostApi.setBackgroundColor( viewInstanceId, Colors.red.value, )); }); test('setOpaque', () async { await view.setOpaque(false); verify(mockPlatformHostApi.setOpaque(viewInstanceId, false)); }); }); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_wkwebview/test/src/ui_kit/ui_kit_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_wkwebview/src/common/web_kit.g.dart' as _i3; import '../common/test_web_kit.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 [TestWKWebViewConfigurationHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestWKWebViewConfigurationHostApi extends _i1.Mock implements _i2.TestWKWebViewConfigurationHostApi { MockTestWKWebViewConfigurationHostApi() { _i1.throwOnMissingStub(this); } @override void create(int? identifier) => super.noSuchMethod( Invocation.method( #create, [identifier], ), returnValueForMissingStub: null, ); @override void createFromWebView( int? identifier, int? webViewIdentifier, ) => super.noSuchMethod( Invocation.method( #createFromWebView, [ identifier, webViewIdentifier, ], ), returnValueForMissingStub: null, ); @override void setAllowsInlineMediaPlayback( int? identifier, bool? allow, ) => super.noSuchMethod( Invocation.method( #setAllowsInlineMediaPlayback, [ identifier, allow, ], ), returnValueForMissingStub: null, ); @override void setMediaTypesRequiringUserActionForPlayback( int? identifier, List<_i3.WKAudiovisualMediaTypeEnumData?>? types, ) => super.noSuchMethod( Invocation.method( #setMediaTypesRequiringUserActionForPlayback, [ identifier, types, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [TestWKWebViewHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestWKWebViewHostApi extends _i1.Mock implements _i2.TestWKWebViewHostApi { MockTestWKWebViewHostApi() { _i1.throwOnMissingStub(this); } @override void create( int? identifier, int? configurationIdentifier, ) => super.noSuchMethod( Invocation.method( #create, [ identifier, configurationIdentifier, ], ), returnValueForMissingStub: null, ); @override void setUIDelegate( int? identifier, int? uiDelegateIdentifier, ) => super.noSuchMethod( Invocation.method( #setUIDelegate, [ identifier, uiDelegateIdentifier, ], ), returnValueForMissingStub: null, ); @override void setNavigationDelegate( int? identifier, int? navigationDelegateIdentifier, ) => super.noSuchMethod( Invocation.method( #setNavigationDelegate, [ identifier, navigationDelegateIdentifier, ], ), returnValueForMissingStub: null, ); @override String? getUrl(int? identifier) => (super.noSuchMethod(Invocation.method( #getUrl, [identifier], )) as String?); @override double getEstimatedProgress(int? identifier) => (super.noSuchMethod( Invocation.method( #getEstimatedProgress, [identifier], ), returnValue: 0.0, ) as double); @override void loadRequest( int? identifier, _i3.NSUrlRequestData? request, ) => super.noSuchMethod( Invocation.method( #loadRequest, [ identifier, request, ], ), returnValueForMissingStub: null, ); @override void loadHtmlString( int? identifier, String? string, String? baseUrl, ) => super.noSuchMethod( Invocation.method( #loadHtmlString, [ identifier, string, baseUrl, ], ), returnValueForMissingStub: null, ); @override void loadFileUrl( int? identifier, String? url, String? readAccessUrl, ) => super.noSuchMethod( Invocation.method( #loadFileUrl, [ identifier, url, readAccessUrl, ], ), returnValueForMissingStub: null, ); @override void loadFlutterAsset( int? identifier, String? key, ) => super.noSuchMethod( Invocation.method( #loadFlutterAsset, [ identifier, key, ], ), returnValueForMissingStub: null, ); @override bool canGoBack(int? identifier) => (super.noSuchMethod( Invocation.method( #canGoBack, [identifier], ), returnValue: false, ) as bool); @override bool canGoForward(int? identifier) => (super.noSuchMethod( Invocation.method( #canGoForward, [identifier], ), returnValue: false, ) as bool); @override void goBack(int? identifier) => super.noSuchMethod( Invocation.method( #goBack, [identifier], ), returnValueForMissingStub: null, ); @override void goForward(int? identifier) => super.noSuchMethod( Invocation.method( #goForward, [identifier], ), returnValueForMissingStub: null, ); @override void reload(int? identifier) => super.noSuchMethod( Invocation.method( #reload, [identifier], ), returnValueForMissingStub: null, ); @override String? getTitle(int? identifier) => (super.noSuchMethod(Invocation.method( #getTitle, [identifier], )) as String?); @override void setAllowsBackForwardNavigationGestures( int? identifier, bool? allow, ) => super.noSuchMethod( Invocation.method( #setAllowsBackForwardNavigationGestures, [ identifier, allow, ], ), returnValueForMissingStub: null, ); @override void setCustomUserAgent( int? identifier, String? userAgent, ) => super.noSuchMethod( Invocation.method( #setCustomUserAgent, [ identifier, userAgent, ], ), returnValueForMissingStub: null, ); @override _i4.Future evaluateJavaScript( int? identifier, String? javaScriptString, ) => (super.noSuchMethod( Invocation.method( #evaluateJavaScript, [ identifier, javaScriptString, ], ), returnValue: _i4.Future.value(), ) as _i4.Future); } /// A class which mocks [TestUIScrollViewHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestUIScrollViewHostApi extends _i1.Mock implements _i2.TestUIScrollViewHostApi { MockTestUIScrollViewHostApi() { _i1.throwOnMissingStub(this); } @override void createFromWebView( int? identifier, int? webViewIdentifier, ) => super.noSuchMethod( Invocation.method( #createFromWebView, [ identifier, webViewIdentifier, ], ), returnValueForMissingStub: null, ); @override List getContentOffset(int? identifier) => (super.noSuchMethod( Invocation.method( #getContentOffset, [identifier], ), returnValue: [], ) as List); @override void scrollBy( int? identifier, double? x, double? y, ) => super.noSuchMethod( Invocation.method( #scrollBy, [ identifier, x, y, ], ), returnValueForMissingStub: null, ); @override void setContentOffset( int? identifier, double? x, double? y, ) => super.noSuchMethod( Invocation.method( #setContentOffset, [ identifier, x, y, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [TestUIViewHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestUIViewHostApi extends _i1.Mock implements _i2.TestUIViewHostApi { MockTestUIViewHostApi() { _i1.throwOnMissingStub(this); } @override void setBackgroundColor( int? identifier, int? value, ) => super.noSuchMethod( Invocation.method( #setBackgroundColor, [ identifier, value, ], ), returnValueForMissingStub: null, ); @override void setOpaque( int? identifier, bool? opaque, ) => super.noSuchMethod( Invocation.method( #setOpaque, [ identifier, opaque, ], ), returnValueForMissingStub: null, ); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter_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.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.g.dart'; import 'web_kit_test.mocks.dart'; @GenerateMocks([ TestWKHttpCookieStoreHostApi, TestWKNavigationDelegateHostApi, TestWKPreferencesHostApi, TestWKScriptMessageHandlerHostApi, TestWKUIDelegateHostApi, TestWKUserContentControllerHostApi, TestWKWebViewConfigurationHostApi, TestWKWebViewHostApi, TestWKWebsiteDataStoreHostApi, ]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('WebKit', () { late InstanceManager instanceManager; late WebKitFlutterApis flutterApis; setUp(() { instanceManager = InstanceManager(onWeakReferenceRemoved: (_) {}); flutterApis = WebKitFlutterApis(instanceManager: instanceManager); WebKitFlutterApis.instance = flutterApis; }); group('WKWebsiteDataStore', () { late MockTestWKWebsiteDataStoreHostApi mockPlatformHostApi; late WKWebsiteDataStore websiteDataStore; late WKWebViewConfiguration webViewConfiguration; setUp(() { mockPlatformHostApi = MockTestWKWebsiteDataStoreHostApi(); TestWKWebsiteDataStoreHostApi.setup(mockPlatformHostApi); TestWKWebViewConfigurationHostApi.setup( MockTestWKWebViewConfigurationHostApi(), ); webViewConfiguration = WKWebViewConfiguration( instanceManager: instanceManager, ); websiteDataStore = WKWebsiteDataStore.fromWebViewConfiguration( webViewConfiguration, instanceManager: instanceManager, ); }); tearDown(() { TestWKWebsiteDataStoreHostApi.setup(null); TestWKWebViewConfigurationHostApi.setup(null); }); test('WKWebViewConfigurationFlutterApi.create', () { final WebKitFlutterApis flutterApis = WebKitFlutterApis( instanceManager: instanceManager, ); flutterApis.webViewConfiguration.create(2); expect(instanceManager.containsIdentifier(2), isTrue); expect( instanceManager.getInstanceWithWeakReference(2), isA(), ); }); test('createFromWebViewConfiguration', () { verify(mockPlatformHostApi.createFromWebViewConfiguration( instanceManager.getIdentifier(websiteDataStore), instanceManager.getIdentifier(webViewConfiguration), )); }); test('createDefaultDataStore', () { final WKWebsiteDataStore defaultDataStore = WKWebsiteDataStore.defaultDataStore; verify( mockPlatformHostApi.createDefaultDataStore( NSObject.globalInstanceManager.getIdentifier(defaultDataStore), ), ); }); test('removeDataOfTypes', () { when(mockPlatformHostApi.removeDataOfTypes( any, any, any, )).thenAnswer((_) => Future.value(true)); expect( websiteDataStore.removeDataOfTypes( {WKWebsiteDataType.cookies}, DateTime.fromMillisecondsSinceEpoch(5000), ), completion(true), ); final List capturedArgs = verify(mockPlatformHostApi.removeDataOfTypes( instanceManager.getIdentifier(websiteDataStore), captureAny, 5.0, )).captured; final List typeData = (capturedArgs.single as List) .cast(); expect(typeData.single.value, WKWebsiteDataTypeEnum.cookies); }); }); group('WKHttpCookieStore', () { late MockTestWKHttpCookieStoreHostApi mockPlatformHostApi; late WKHttpCookieStore httpCookieStore; late WKWebsiteDataStore websiteDataStore; setUp(() { mockPlatformHostApi = MockTestWKHttpCookieStoreHostApi(); TestWKHttpCookieStoreHostApi.setup(mockPlatformHostApi); TestWKWebViewConfigurationHostApi.setup( MockTestWKWebViewConfigurationHostApi(), ); TestWKWebsiteDataStoreHostApi.setup( MockTestWKWebsiteDataStoreHostApi(), ); websiteDataStore = WKWebsiteDataStore.fromWebViewConfiguration( WKWebViewConfiguration(instanceManager: instanceManager), instanceManager: instanceManager, ); httpCookieStore = WKHttpCookieStore.fromWebsiteDataStore( websiteDataStore, instanceManager: instanceManager, ); }); tearDown(() { TestWKHttpCookieStoreHostApi.setup(null); TestWKWebsiteDataStoreHostApi.setup(null); TestWKWebViewConfigurationHostApi.setup(null); }); test('createFromWebsiteDataStore', () { verify(mockPlatformHostApi.createFromWebsiteDataStore( instanceManager.getIdentifier(httpCookieStore), instanceManager.getIdentifier(websiteDataStore), )); }); test('setCookie', () async { await httpCookieStore.setCookie( const NSHttpCookie.withProperties({ NSHttpCookiePropertyKey.comment: 'aComment', })); final NSHttpCookieData cookie = verify( mockPlatformHostApi.setCookie( instanceManager.getIdentifier(httpCookieStore), captureAny, ), ).captured.single as NSHttpCookieData; expect( cookie.propertyKeys.single!.value, NSHttpCookiePropertyKeyEnum.comment, ); expect(cookie.propertyValues.single, 'aComment'); }); }); group('WKScriptMessageHandler', () { late MockTestWKScriptMessageHandlerHostApi mockPlatformHostApi; late WKScriptMessageHandler scriptMessageHandler; setUp(() async { mockPlatformHostApi = MockTestWKScriptMessageHandlerHostApi(); TestWKScriptMessageHandlerHostApi.setup(mockPlatformHostApi); scriptMessageHandler = WKScriptMessageHandler( didReceiveScriptMessage: (_, __) {}, instanceManager: instanceManager, ); }); tearDown(() { TestWKScriptMessageHandlerHostApi.setup(null); }); test('create', () async { verify(mockPlatformHostApi.create( instanceManager.getIdentifier(scriptMessageHandler), )); }); test('didReceiveScriptMessage', () async { final Completer> argsCompleter = Completer>(); WebKitFlutterApis.instance = WebKitFlutterApis( instanceManager: instanceManager, ); scriptMessageHandler = WKScriptMessageHandler( instanceManager: instanceManager, didReceiveScriptMessage: ( WKUserContentController userContentController, WKScriptMessage message, ) { argsCompleter.complete([userContentController, message]); }, ); final WKUserContentController userContentController = WKUserContentController.detached( instanceManager: instanceManager, ); instanceManager.addHostCreatedInstance(userContentController, 2); WebKitFlutterApis.instance.scriptMessageHandler.didReceiveScriptMessage( instanceManager.getIdentifier(scriptMessageHandler)!, 2, WKScriptMessageData(name: 'name'), ); expect( argsCompleter.future, completion([userContentController, isA()]), ); }); }); group('WKPreferences', () { late MockTestWKPreferencesHostApi mockPlatformHostApi; late WKPreferences preferences; late WKWebViewConfiguration webViewConfiguration; setUp(() { mockPlatformHostApi = MockTestWKPreferencesHostApi(); TestWKPreferencesHostApi.setup(mockPlatformHostApi); TestWKWebViewConfigurationHostApi.setup( MockTestWKWebViewConfigurationHostApi(), ); webViewConfiguration = WKWebViewConfiguration( instanceManager: instanceManager, ); preferences = WKPreferences.fromWebViewConfiguration( webViewConfiguration, instanceManager: instanceManager, ); }); tearDown(() { TestWKPreferencesHostApi.setup(null); TestWKWebViewConfigurationHostApi.setup(null); }); test('createFromWebViewConfiguration', () async { verify(mockPlatformHostApi.createFromWebViewConfiguration( instanceManager.getIdentifier(preferences), instanceManager.getIdentifier(webViewConfiguration), )); }); test('setJavaScriptEnabled', () async { await preferences.setJavaScriptEnabled(true); verify(mockPlatformHostApi.setJavaScriptEnabled( instanceManager.getIdentifier(preferences), true, )); }); }); group('WKUserContentController', () { late MockTestWKUserContentControllerHostApi mockPlatformHostApi; late WKUserContentController userContentController; late WKWebViewConfiguration webViewConfiguration; setUp(() { mockPlatformHostApi = MockTestWKUserContentControllerHostApi(); TestWKUserContentControllerHostApi.setup(mockPlatformHostApi); TestWKWebViewConfigurationHostApi.setup( MockTestWKWebViewConfigurationHostApi(), ); webViewConfiguration = WKWebViewConfiguration( instanceManager: instanceManager, ); userContentController = WKUserContentController.fromWebViewConfiguration( webViewConfiguration, instanceManager: instanceManager, ); }); tearDown(() { TestWKUserContentControllerHostApi.setup(null); TestWKWebViewConfigurationHostApi.setup(null); }); test('createFromWebViewConfiguration', () async { verify(mockPlatformHostApi.createFromWebViewConfiguration( instanceManager.getIdentifier(userContentController), instanceManager.getIdentifier(webViewConfiguration), )); }); test('addScriptMessageHandler', () async { TestWKScriptMessageHandlerHostApi.setup( MockTestWKScriptMessageHandlerHostApi(), ); final WKScriptMessageHandler handler = WKScriptMessageHandler( didReceiveScriptMessage: (_, __) {}, instanceManager: instanceManager, ); userContentController.addScriptMessageHandler(handler, 'handlerName'); verify(mockPlatformHostApi.addScriptMessageHandler( instanceManager.getIdentifier(userContentController), instanceManager.getIdentifier(handler), 'handlerName', )); }); test('removeScriptMessageHandler', () async { userContentController.removeScriptMessageHandler('handlerName'); verify(mockPlatformHostApi.removeScriptMessageHandler( instanceManager.getIdentifier(userContentController), 'handlerName', )); }); test('removeAllScriptMessageHandlers', () async { userContentController.removeAllScriptMessageHandlers(); verify(mockPlatformHostApi.removeAllScriptMessageHandlers( instanceManager.getIdentifier(userContentController), )); }); test('addUserScript', () { userContentController.addUserScript(const WKUserScript( 'aScript', WKUserScriptInjectionTime.atDocumentEnd, isMainFrameOnly: false, )); verify(mockPlatformHostApi.addUserScript( instanceManager.getIdentifier(userContentController), argThat(isA()), )); }); test('removeAllUserScripts', () { userContentController.removeAllUserScripts(); verify(mockPlatformHostApi.removeAllUserScripts( instanceManager.getIdentifier(userContentController), )); }); }); group('WKWebViewConfiguration', () { late MockTestWKWebViewConfigurationHostApi mockPlatformHostApi; late WKWebViewConfiguration webViewConfiguration; setUp(() async { mockPlatformHostApi = MockTestWKWebViewConfigurationHostApi(); TestWKWebViewConfigurationHostApi.setup(mockPlatformHostApi); webViewConfiguration = WKWebViewConfiguration( instanceManager: instanceManager, ); }); tearDown(() { TestWKWebViewConfigurationHostApi.setup(null); }); test('create', () async { verify( mockPlatformHostApi.create(instanceManager.getIdentifier( webViewConfiguration, )), ); }); test('createFromWebView', () async { TestWKWebViewHostApi.setup(MockTestWKWebViewHostApi()); final WKWebView webView = WKWebView( webViewConfiguration, instanceManager: instanceManager, ); final WKWebViewConfiguration configurationFromWebView = WKWebViewConfiguration.fromWebView( webView, instanceManager: instanceManager, ); verify(mockPlatformHostApi.createFromWebView( instanceManager.getIdentifier(configurationFromWebView), instanceManager.getIdentifier(webView), )); }); test('allowsInlineMediaPlayback', () { webViewConfiguration.setAllowsInlineMediaPlayback(true); verify(mockPlatformHostApi.setAllowsInlineMediaPlayback( instanceManager.getIdentifier(webViewConfiguration), true, )); }); test('mediaTypesRequiringUserActionForPlayback', () { webViewConfiguration.setMediaTypesRequiringUserActionForPlayback( { WKAudiovisualMediaType.audio, WKAudiovisualMediaType.video, }, ); final List typeData = verify( mockPlatformHostApi.setMediaTypesRequiringUserActionForPlayback( instanceManager.getIdentifier(webViewConfiguration), captureAny, )).captured.single as List; expect(typeData, hasLength(2)); expect(typeData[0]!.value, WKAudiovisualMediaTypeEnum.audio); expect(typeData[1]!.value, WKAudiovisualMediaTypeEnum.video); }); }); group('WKNavigationDelegate', () { late MockTestWKNavigationDelegateHostApi mockPlatformHostApi; late WKWebView webView; late WKNavigationDelegate navigationDelegate; setUp(() async { mockPlatformHostApi = MockTestWKNavigationDelegateHostApi(); TestWKNavigationDelegateHostApi.setup(mockPlatformHostApi); TestWKWebViewConfigurationHostApi.setup( MockTestWKWebViewConfigurationHostApi(), ); TestWKWebViewHostApi.setup(MockTestWKWebViewHostApi()); webView = WKWebView( WKWebViewConfiguration(instanceManager: instanceManager), instanceManager: instanceManager, ); navigationDelegate = WKNavigationDelegate( instanceManager: instanceManager, ); }); tearDown(() { TestWKNavigationDelegateHostApi.setup(null); TestWKWebViewConfigurationHostApi.setup(null); TestWKWebViewHostApi.setup(null); }); test('create', () async { navigationDelegate = WKNavigationDelegate( instanceManager: instanceManager, ); verify(mockPlatformHostApi.create( instanceManager.getIdentifier(navigationDelegate), )); }); test('didFinishNavigation', () async { final Completer> argsCompleter = Completer>(); WebKitFlutterApis.instance = WebKitFlutterApis( instanceManager: instanceManager, ); navigationDelegate = WKNavigationDelegate( instanceManager: instanceManager, didFinishNavigation: (WKWebView webView, String? url) { argsCompleter.complete([webView, url]); }, ); WebKitFlutterApis.instance.navigationDelegate.didFinishNavigation( instanceManager.getIdentifier(navigationDelegate)!, instanceManager.getIdentifier(webView)!, 'url', ); expect(argsCompleter.future, completion([webView, 'url'])); }); test('didStartProvisionalNavigation', () async { final Completer> argsCompleter = Completer>(); WebKitFlutterApis.instance = WebKitFlutterApis( instanceManager: instanceManager, ); navigationDelegate = WKNavigationDelegate( instanceManager: instanceManager, didStartProvisionalNavigation: (WKWebView webView, String? url) { argsCompleter.complete([webView, url]); }, ); WebKitFlutterApis.instance.navigationDelegate .didStartProvisionalNavigation( instanceManager.getIdentifier(navigationDelegate)!, instanceManager.getIdentifier(webView)!, 'url', ); expect(argsCompleter.future, completion([webView, 'url'])); }); test('decidePolicyForNavigationAction', () async { WebKitFlutterApis.instance = WebKitFlutterApis( instanceManager: instanceManager, ); navigationDelegate = WKNavigationDelegate( instanceManager: instanceManager, decidePolicyForNavigationAction: ( WKWebView webView, WKNavigationAction navigationAction, ) async { return WKNavigationActionPolicy.cancel; }, ); final WKNavigationActionPolicyEnumData policyData = await WebKitFlutterApis.instance.navigationDelegate .decidePolicyForNavigationAction( instanceManager.getIdentifier(navigationDelegate)!, instanceManager.getIdentifier(webView)!, WKNavigationActionData( request: NSUrlRequestData( url: 'url', allHttpHeaderFields: {}, ), targetFrame: WKFrameInfoData(isMainFrame: false), navigationType: WKNavigationType.linkActivated, ), ); expect(policyData.value, WKNavigationActionPolicyEnum.cancel); }); test('didFailNavigation', () async { final Completer> argsCompleter = Completer>(); WebKitFlutterApis.instance = WebKitFlutterApis( instanceManager: instanceManager, ); navigationDelegate = WKNavigationDelegate( instanceManager: instanceManager, didFailNavigation: (WKWebView webView, NSError error) { argsCompleter.complete([webView, error]); }, ); WebKitFlutterApis.instance.navigationDelegate.didFailNavigation( instanceManager.getIdentifier(navigationDelegate)!, instanceManager.getIdentifier(webView)!, NSErrorData( code: 23, domain: 'Hello', localizedDescription: 'localiziedDescription', ), ); expect( argsCompleter.future, completion([webView, isA()]), ); }); test('didFailProvisionalNavigation', () async { final Completer> argsCompleter = Completer>(); WebKitFlutterApis.instance = WebKitFlutterApis( instanceManager: instanceManager, ); navigationDelegate = WKNavigationDelegate( instanceManager: instanceManager, didFailProvisionalNavigation: (WKWebView webView, NSError error) { argsCompleter.complete([webView, error]); }, ); WebKitFlutterApis.instance.navigationDelegate .didFailProvisionalNavigation( instanceManager.getIdentifier(navigationDelegate)!, instanceManager.getIdentifier(webView)!, NSErrorData( code: 23, domain: 'Hello', localizedDescription: 'localiziedDescription', ), ); expect( argsCompleter.future, completion([webView, isA()]), ); }); test('webViewWebContentProcessDidTerminate', () async { final Completer> argsCompleter = Completer>(); WebKitFlutterApis.instance = WebKitFlutterApis( instanceManager: instanceManager, ); navigationDelegate = WKNavigationDelegate( instanceManager: instanceManager, webViewWebContentProcessDidTerminate: (WKWebView webView) { argsCompleter.complete([webView]); }, ); WebKitFlutterApis.instance.navigationDelegate .webViewWebContentProcessDidTerminate( instanceManager.getIdentifier(navigationDelegate)!, instanceManager.getIdentifier(webView)!, ); expect(argsCompleter.future, completion([webView])); }); }); group('WKWebView', () { late MockTestWKWebViewHostApi mockPlatformHostApi; late WKWebViewConfiguration webViewConfiguration; late WKWebView webView; late int webViewInstanceId; setUp(() { mockPlatformHostApi = MockTestWKWebViewHostApi(); TestWKWebViewHostApi.setup(mockPlatformHostApi); TestWKWebViewConfigurationHostApi.setup( MockTestWKWebViewConfigurationHostApi()); webViewConfiguration = WKWebViewConfiguration( instanceManager: instanceManager, ); webView = WKWebView( webViewConfiguration, instanceManager: instanceManager, ); webViewInstanceId = instanceManager.getIdentifier(webView)!; }); tearDown(() { TestWKWebViewHostApi.setup(null); TestWKWebViewConfigurationHostApi.setup(null); }); test('create', () async { verify(mockPlatformHostApi.create( instanceManager.getIdentifier(webView), instanceManager.getIdentifier( webViewConfiguration, ), )); }); test('setUIDelegate', () async { TestWKUIDelegateHostApi.setup(MockTestWKUIDelegateHostApi()); final WKUIDelegate uiDelegate = WKUIDelegate( instanceManager: instanceManager, ); await webView.setUIDelegate(uiDelegate); verify(mockPlatformHostApi.setUIDelegate( webViewInstanceId, instanceManager.getIdentifier(uiDelegate), )); TestWKUIDelegateHostApi.setup(null); }); test('setNavigationDelegate', () async { TestWKNavigationDelegateHostApi.setup( MockTestWKNavigationDelegateHostApi(), ); final WKNavigationDelegate navigationDelegate = WKNavigationDelegate( instanceManager: instanceManager, ); await webView.setNavigationDelegate(navigationDelegate); verify(mockPlatformHostApi.setNavigationDelegate( webViewInstanceId, instanceManager.getIdentifier(navigationDelegate), )); TestWKNavigationDelegateHostApi.setup(null); }); test('getUrl', () { when( mockPlatformHostApi.getUrl(webViewInstanceId), ).thenReturn('www.flutter.dev'); expect(webView.getUrl(), completion('www.flutter.dev')); }); test('getEstimatedProgress', () { when( mockPlatformHostApi.getEstimatedProgress(webViewInstanceId), ).thenReturn(54.5); expect(webView.getEstimatedProgress(), completion(54.5)); }); test('loadRequest', () { webView.loadRequest(const NSUrlRequest(url: 'www.flutter.dev')); verify(mockPlatformHostApi.loadRequest( webViewInstanceId, argThat(isA()), )); }); test('loadHtmlString', () { webView.loadHtmlString('a', baseUrl: 'b'); verify(mockPlatformHostApi.loadHtmlString(webViewInstanceId, 'a', 'b')); }); test('loadFileUrl', () { webView.loadFileUrl('a', readAccessUrl: 'b'); verify(mockPlatformHostApi.loadFileUrl(webViewInstanceId, 'a', 'b')); }); test('loadFlutterAsset', () { webView.loadFlutterAsset('a'); verify(mockPlatformHostApi.loadFlutterAsset(webViewInstanceId, 'a')); }); test('canGoBack', () { when(mockPlatformHostApi.canGoBack(webViewInstanceId)).thenReturn(true); expect(webView.canGoBack(), completion(isTrue)); }); test('canGoForward', () { when(mockPlatformHostApi.canGoForward(webViewInstanceId)) .thenReturn(false); expect(webView.canGoForward(), completion(isFalse)); }); test('goBack', () { webView.goBack(); verify(mockPlatformHostApi.goBack(webViewInstanceId)); }); test('goForward', () { webView.goForward(); verify(mockPlatformHostApi.goForward(webViewInstanceId)); }); test('reload', () { webView.reload(); verify(mockPlatformHostApi.reload(webViewInstanceId)); }); test('getTitle', () { when(mockPlatformHostApi.getTitle(webViewInstanceId)) .thenReturn('MyTitle'); expect(webView.getTitle(), completion('MyTitle')); }); test('setAllowsBackForwardNavigationGestures', () { webView.setAllowsBackForwardNavigationGestures(false); verify(mockPlatformHostApi.setAllowsBackForwardNavigationGestures( webViewInstanceId, false, )); }); test('customUserAgent', () { webView.setCustomUserAgent('hello'); verify(mockPlatformHostApi.setCustomUserAgent( webViewInstanceId, 'hello', )); }); test('evaluateJavaScript', () { when(mockPlatformHostApi.evaluateJavaScript(webViewInstanceId, 'gogo')) .thenAnswer((_) => Future.value('stopstop')); expect(webView.evaluateJavaScript('gogo'), completion('stopstop')); }); test('evaluateJavaScript returns NSError', () { when(mockPlatformHostApi.evaluateJavaScript(webViewInstanceId, 'gogo')) .thenThrow( PlatformException( code: '', details: NSErrorData( code: 0, domain: 'domain', localizedDescription: 'desc', ), ), ); expect( webView.evaluateJavaScript('gogo'), throwsA( isA().having( (PlatformException exception) => exception.details, 'details', isA(), ), ), ); }); }); group('WKUIDelegate', () { late MockTestWKUIDelegateHostApi mockPlatformHostApi; late WKUIDelegate uiDelegate; setUp(() async { mockPlatformHostApi = MockTestWKUIDelegateHostApi(); TestWKUIDelegateHostApi.setup(mockPlatformHostApi); uiDelegate = WKUIDelegate(instanceManager: instanceManager); }); tearDown(() { TestWKUIDelegateHostApi.setup(null); }); test('create', () async { verify(mockPlatformHostApi.create( instanceManager.getIdentifier(uiDelegate), )); }); test('onCreateWebView', () async { final Completer> argsCompleter = Completer>(); WebKitFlutterApis.instance = WebKitFlutterApis( instanceManager: instanceManager, ); uiDelegate = WKUIDelegate( instanceManager: instanceManager, onCreateWebView: ( WKWebView webView, WKWebViewConfiguration configuration, WKNavigationAction navigationAction, ) { argsCompleter.complete([ webView, configuration, navigationAction, ]); }, ); final WKWebView webView = WKWebView.detached( instanceManager: instanceManager, ); instanceManager.addHostCreatedInstance(webView, 2); final WKWebViewConfiguration configuration = WKWebViewConfiguration.detached( instanceManager: instanceManager, ); instanceManager.addHostCreatedInstance(configuration, 3); WebKitFlutterApis.instance.uiDelegate.onCreateWebView( instanceManager.getIdentifier(uiDelegate)!, 2, 3, WKNavigationActionData( request: NSUrlRequestData( url: 'url', allHttpHeaderFields: {}, ), targetFrame: WKFrameInfoData(isMainFrame: false), navigationType: WKNavigationType.linkActivated, ), ); expect( argsCompleter.future, completion([ webView, configuration, isA(), ]), ); }); }); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i3; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_wkwebview/src/common/web_kit.g.dart' as _i4; import '../common/test_web_kit.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 [TestWKHttpCookieStoreHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestWKHttpCookieStoreHostApi extends _i1.Mock implements _i2.TestWKHttpCookieStoreHostApi { MockTestWKHttpCookieStoreHostApi() { _i1.throwOnMissingStub(this); } @override void createFromWebsiteDataStore( int? identifier, int? websiteDataStoreIdentifier, ) => super.noSuchMethod( Invocation.method( #createFromWebsiteDataStore, [ identifier, websiteDataStoreIdentifier, ], ), returnValueForMissingStub: null, ); @override _i3.Future setCookie( int? identifier, _i4.NSHttpCookieData? cookie, ) => (super.noSuchMethod( Invocation.method( #setCookie, [ identifier, cookie, ], ), returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); } /// A class which mocks [TestWKNavigationDelegateHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestWKNavigationDelegateHostApi extends _i1.Mock implements _i2.TestWKNavigationDelegateHostApi { MockTestWKNavigationDelegateHostApi() { _i1.throwOnMissingStub(this); } @override void create(int? identifier) => super.noSuchMethod( Invocation.method( #create, [identifier], ), returnValueForMissingStub: null, ); } /// A class which mocks [TestWKPreferencesHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestWKPreferencesHostApi extends _i1.Mock implements _i2.TestWKPreferencesHostApi { MockTestWKPreferencesHostApi() { _i1.throwOnMissingStub(this); } @override void createFromWebViewConfiguration( int? identifier, int? configurationIdentifier, ) => super.noSuchMethod( Invocation.method( #createFromWebViewConfiguration, [ identifier, configurationIdentifier, ], ), returnValueForMissingStub: null, ); @override void setJavaScriptEnabled( int? identifier, bool? enabled, ) => super.noSuchMethod( Invocation.method( #setJavaScriptEnabled, [ identifier, enabled, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [TestWKScriptMessageHandlerHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestWKScriptMessageHandlerHostApi extends _i1.Mock implements _i2.TestWKScriptMessageHandlerHostApi { MockTestWKScriptMessageHandlerHostApi() { _i1.throwOnMissingStub(this); } @override void create(int? identifier) => super.noSuchMethod( Invocation.method( #create, [identifier], ), returnValueForMissingStub: null, ); } /// A class which mocks [TestWKUIDelegateHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestWKUIDelegateHostApi extends _i1.Mock implements _i2.TestWKUIDelegateHostApi { MockTestWKUIDelegateHostApi() { _i1.throwOnMissingStub(this); } @override void create(int? identifier) => super.noSuchMethod( Invocation.method( #create, [identifier], ), returnValueForMissingStub: null, ); } /// A class which mocks [TestWKUserContentControllerHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestWKUserContentControllerHostApi extends _i1.Mock implements _i2.TestWKUserContentControllerHostApi { MockTestWKUserContentControllerHostApi() { _i1.throwOnMissingStub(this); } @override void createFromWebViewConfiguration( int? identifier, int? configurationIdentifier, ) => super.noSuchMethod( Invocation.method( #createFromWebViewConfiguration, [ identifier, configurationIdentifier, ], ), returnValueForMissingStub: null, ); @override void addScriptMessageHandler( int? identifier, int? handlerIdentifier, String? name, ) => super.noSuchMethod( Invocation.method( #addScriptMessageHandler, [ identifier, handlerIdentifier, name, ], ), returnValueForMissingStub: null, ); @override void removeScriptMessageHandler( int? identifier, String? name, ) => super.noSuchMethod( Invocation.method( #removeScriptMessageHandler, [ identifier, name, ], ), returnValueForMissingStub: null, ); @override void removeAllScriptMessageHandlers(int? identifier) => super.noSuchMethod( Invocation.method( #removeAllScriptMessageHandlers, [identifier], ), returnValueForMissingStub: null, ); @override void addUserScript( int? identifier, _i4.WKUserScriptData? userScript, ) => super.noSuchMethod( Invocation.method( #addUserScript, [ identifier, userScript, ], ), returnValueForMissingStub: null, ); @override void removeAllUserScripts(int? identifier) => super.noSuchMethod( Invocation.method( #removeAllUserScripts, [identifier], ), returnValueForMissingStub: null, ); } /// A class which mocks [TestWKWebViewConfigurationHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestWKWebViewConfigurationHostApi extends _i1.Mock implements _i2.TestWKWebViewConfigurationHostApi { MockTestWKWebViewConfigurationHostApi() { _i1.throwOnMissingStub(this); } @override void create(int? identifier) => super.noSuchMethod( Invocation.method( #create, [identifier], ), returnValueForMissingStub: null, ); @override void createFromWebView( int? identifier, int? webViewIdentifier, ) => super.noSuchMethod( Invocation.method( #createFromWebView, [ identifier, webViewIdentifier, ], ), returnValueForMissingStub: null, ); @override void setAllowsInlineMediaPlayback( int? identifier, bool? allow, ) => super.noSuchMethod( Invocation.method( #setAllowsInlineMediaPlayback, [ identifier, allow, ], ), returnValueForMissingStub: null, ); @override void setMediaTypesRequiringUserActionForPlayback( int? identifier, List<_i4.WKAudiovisualMediaTypeEnumData?>? types, ) => super.noSuchMethod( Invocation.method( #setMediaTypesRequiringUserActionForPlayback, [ identifier, types, ], ), returnValueForMissingStub: null, ); } /// A class which mocks [TestWKWebViewHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestWKWebViewHostApi extends _i1.Mock implements _i2.TestWKWebViewHostApi { MockTestWKWebViewHostApi() { _i1.throwOnMissingStub(this); } @override void create( int? identifier, int? configurationIdentifier, ) => super.noSuchMethod( Invocation.method( #create, [ identifier, configurationIdentifier, ], ), returnValueForMissingStub: null, ); @override void setUIDelegate( int? identifier, int? uiDelegateIdentifier, ) => super.noSuchMethod( Invocation.method( #setUIDelegate, [ identifier, uiDelegateIdentifier, ], ), returnValueForMissingStub: null, ); @override void setNavigationDelegate( int? identifier, int? navigationDelegateIdentifier, ) => super.noSuchMethod( Invocation.method( #setNavigationDelegate, [ identifier, navigationDelegateIdentifier, ], ), returnValueForMissingStub: null, ); @override String? getUrl(int? identifier) => (super.noSuchMethod(Invocation.method( #getUrl, [identifier], )) as String?); @override double getEstimatedProgress(int? identifier) => (super.noSuchMethod( Invocation.method( #getEstimatedProgress, [identifier], ), returnValue: 0.0, ) as double); @override void loadRequest( int? identifier, _i4.NSUrlRequestData? request, ) => super.noSuchMethod( Invocation.method( #loadRequest, [ identifier, request, ], ), returnValueForMissingStub: null, ); @override void loadHtmlString( int? identifier, String? string, String? baseUrl, ) => super.noSuchMethod( Invocation.method( #loadHtmlString, [ identifier, string, baseUrl, ], ), returnValueForMissingStub: null, ); @override void loadFileUrl( int? identifier, String? url, String? readAccessUrl, ) => super.noSuchMethod( Invocation.method( #loadFileUrl, [ identifier, url, readAccessUrl, ], ), returnValueForMissingStub: null, ); @override void loadFlutterAsset( int? identifier, String? key, ) => super.noSuchMethod( Invocation.method( #loadFlutterAsset, [ identifier, key, ], ), returnValueForMissingStub: null, ); @override bool canGoBack(int? identifier) => (super.noSuchMethod( Invocation.method( #canGoBack, [identifier], ), returnValue: false, ) as bool); @override bool canGoForward(int? identifier) => (super.noSuchMethod( Invocation.method( #canGoForward, [identifier], ), returnValue: false, ) as bool); @override void goBack(int? identifier) => super.noSuchMethod( Invocation.method( #goBack, [identifier], ), returnValueForMissingStub: null, ); @override void goForward(int? identifier) => super.noSuchMethod( Invocation.method( #goForward, [identifier], ), returnValueForMissingStub: null, ); @override void reload(int? identifier) => super.noSuchMethod( Invocation.method( #reload, [identifier], ), returnValueForMissingStub: null, ); @override String? getTitle(int? identifier) => (super.noSuchMethod(Invocation.method( #getTitle, [identifier], )) as String?); @override void setAllowsBackForwardNavigationGestures( int? identifier, bool? allow, ) => super.noSuchMethod( Invocation.method( #setAllowsBackForwardNavigationGestures, [ identifier, allow, ], ), returnValueForMissingStub: null, ); @override void setCustomUserAgent( int? identifier, String? userAgent, ) => super.noSuchMethod( Invocation.method( #setCustomUserAgent, [ identifier, userAgent, ], ), returnValueForMissingStub: null, ); @override _i3.Future evaluateJavaScript( int? identifier, String? javaScriptString, ) => (super.noSuchMethod( Invocation.method( #evaluateJavaScript, [ identifier, javaScriptString, ], ), returnValue: _i3.Future.value(), ) as _i3.Future); } /// A class which mocks [TestWKWebsiteDataStoreHostApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestWKWebsiteDataStoreHostApi extends _i1.Mock implements _i2.TestWKWebsiteDataStoreHostApi { MockTestWKWebsiteDataStoreHostApi() { _i1.throwOnMissingStub(this); } @override void createFromWebViewConfiguration( int? identifier, int? configurationIdentifier, ) => super.noSuchMethod( Invocation.method( #createFromWebViewConfiguration, [ identifier, configurationIdentifier, ], ), returnValueForMissingStub: null, ); @override void createDefaultDataStore(int? identifier) => super.noSuchMethod( Invocation.method( #createDefaultDataStore, [identifier], ), returnValueForMissingStub: null, ); @override _i3.Future removeDataOfTypes( int? identifier, List<_i4.WKWebsiteDataTypeEnumData?>? dataTypes, double? modificationTimeInSecondsSinceEpoch, ) => (super.noSuchMethod( Invocation.method( #removeDataOfTypes, [ identifier, dataTypes, modificationTimeInSecondsSinceEpoch, ], ), returnValue: _i3.Future.value(false), ) as _i3.Future); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/webkit_navigation_delegate_test.dart ================================================ // Copyright 2013 The Flutter 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/material.dart'; 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/foundation/foundation.dart'; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; import 'package:webview_flutter_wkwebview/src/webkit_proxy.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; import 'webkit_navigation_delegate_test.mocks.dart'; @GenerateMocks([WKWebView]) void main() { WidgetsFlutterBinding.ensureInitialized(); group('WebKitNavigationDelegate', () { test('WebKitNavigationDelegate uses params field in constructor', () async { await runZonedGuarded( () async => WebKitNavigationDelegate( const PlatformNavigationDelegateCreationParams(), ), (Object error, __) { expect(error, isNot(isA())); }, ); }); test('setOnPageFinished', () { final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams( webKitProxy: WebKitProxy( createNavigationDelegate: CapturingNavigationDelegate.new, createUIDelegate: CapturingUIDelegate.new, ), ), ); late final String callbackUrl; webKitDelgate.setOnPageFinished((String url) => callbackUrl = url); CapturingNavigationDelegate.lastCreatedDelegate.didFinishNavigation!( WKWebView.detached(), 'https://www.google.com', ); expect(callbackUrl, 'https://www.google.com'); }); test('setOnPageStarted', () { final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams( webKitProxy: WebKitProxy( createNavigationDelegate: CapturingNavigationDelegate.new, createUIDelegate: CapturingUIDelegate.new, ), ), ); late final String callbackUrl; webKitDelgate.setOnPageStarted((String url) => callbackUrl = url); CapturingNavigationDelegate .lastCreatedDelegate.didStartProvisionalNavigation!( WKWebView.detached(), 'https://www.google.com', ); expect(callbackUrl, 'https://www.google.com'); }); test('onWebResourceError from didFailNavigation', () { final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams( webKitProxy: WebKitProxy( createNavigationDelegate: CapturingNavigationDelegate.new, createUIDelegate: CapturingUIDelegate.new, ), ), ); late final WebKitWebResourceError callbackError; void onWebResourceError(WebResourceError error) { callbackError = error as WebKitWebResourceError; } webKitDelgate.setOnWebResourceError(onWebResourceError); CapturingNavigationDelegate.lastCreatedDelegate.didFailNavigation!( WKWebView.detached(), const NSError( code: WKErrorCode.webViewInvalidated, domain: 'domain', localizedDescription: 'my desc', ), ); expect(callbackError.description, 'my desc'); expect(callbackError.errorCode, WKErrorCode.webViewInvalidated); expect(callbackError.domain, 'domain'); expect(callbackError.errorType, WebResourceErrorType.webViewInvalidated); expect(callbackError.isForMainFrame, true); }); test('onWebResourceError from didFailProvisionalNavigation', () { final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams( webKitProxy: WebKitProxy( createNavigationDelegate: CapturingNavigationDelegate.new, createUIDelegate: CapturingUIDelegate.new, ), ), ); late final WebKitWebResourceError callbackError; void onWebResourceError(WebResourceError error) { callbackError = error as WebKitWebResourceError; } webKitDelgate.setOnWebResourceError(onWebResourceError); CapturingNavigationDelegate .lastCreatedDelegate.didFailProvisionalNavigation!( WKWebView.detached(), const NSError( code: WKErrorCode.webViewInvalidated, domain: 'domain', localizedDescription: 'my desc', ), ); expect(callbackError.description, 'my desc'); expect(callbackError.errorCode, WKErrorCode.webViewInvalidated); expect(callbackError.domain, 'domain'); expect(callbackError.errorType, WebResourceErrorType.webViewInvalidated); expect(callbackError.isForMainFrame, true); }); test('onWebResourceError from webViewWebContentProcessDidTerminate', () { final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams( webKitProxy: WebKitProxy( createNavigationDelegate: CapturingNavigationDelegate.new, createUIDelegate: CapturingUIDelegate.new, ), ), ); late final WebKitWebResourceError callbackError; void onWebResourceError(WebResourceError error) { callbackError = error as WebKitWebResourceError; } webKitDelgate.setOnWebResourceError(onWebResourceError); CapturingNavigationDelegate .lastCreatedDelegate.webViewWebContentProcessDidTerminate!( WKWebView.detached(), ); expect(callbackError.description, ''); expect(callbackError.errorCode, WKErrorCode.webContentProcessTerminated); expect(callbackError.domain, 'WKErrorDomain'); expect( callbackError.errorType, WebResourceErrorType.webContentProcessTerminated, ); expect(callbackError.isForMainFrame, true); }); test('onNavigationRequest from decidePolicyForNavigationAction', () { final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams( webKitProxy: WebKitProxy( createNavigationDelegate: CapturingNavigationDelegate.new, createUIDelegate: CapturingUIDelegate.new, ), ), ); late final NavigationRequest callbackRequest; FutureOr onNavigationRequest( NavigationRequest request) { callbackRequest = request; return NavigationDecision.navigate; } webKitDelgate.setOnNavigationRequest(onNavigationRequest); expect( CapturingNavigationDelegate .lastCreatedDelegate.decidePolicyForNavigationAction!( WKWebView.detached(), const WKNavigationAction( request: NSUrlRequest(url: 'https://www.google.com'), targetFrame: WKFrameInfo(isMainFrame: false), navigationType: WKNavigationType.linkActivated, ), ), completion(WKNavigationActionPolicy.allow), ); expect(callbackRequest.url, 'https://www.google.com'); expect(callbackRequest.isMainFrame, isFalse); }); test('Requests to open a new window loads request in same window', () { WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams( webKitProxy: WebKitProxy( createNavigationDelegate: CapturingNavigationDelegate.new, createUIDelegate: CapturingUIDelegate.new, ), ), ); final MockWKWebView mockWebView = MockWKWebView(); const NSUrlRequest request = NSUrlRequest(url: 'https://www.google.com'); CapturingUIDelegate.lastCreatedDelegate.onCreateWebView!( mockWebView, WKWebViewConfiguration.detached(), const WKNavigationAction( request: request, targetFrame: WKFrameInfo(isMainFrame: false), navigationType: WKNavigationType.linkActivated, ), ); verify(mockWebView.loadRequest(request)); }); }); } // Records the last created instance of itself. class CapturingNavigationDelegate extends WKNavigationDelegate { CapturingNavigationDelegate({ super.didFinishNavigation, super.didStartProvisionalNavigation, super.didFailNavigation, super.didFailProvisionalNavigation, super.decidePolicyForNavigationAction, super.webViewWebContentProcessDidTerminate, }) : super.detached() { lastCreatedDelegate = this; } static CapturingNavigationDelegate lastCreatedDelegate = CapturingNavigationDelegate(); } // Records the last created instance of itself. class CapturingUIDelegate extends WKUIDelegate { CapturingUIDelegate({super.onCreateWebView}) : super.detached() { lastCreatedDelegate = this; } static CapturingUIDelegate lastCreatedDelegate = CapturingUIDelegate(); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/webkit_navigation_delegate_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_wkwebview/test/webkit_navigation_delegate_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i4; import 'dart:ui' as _i6; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart' as _i5; import 'package:webview_flutter_wkwebview/src/ui_kit/ui_kit.dart' as _i3; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.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 class _FakeWKWebViewConfiguration_0 extends _i1.SmartFake implements _i2.WKWebViewConfiguration { _FakeWKWebViewConfiguration_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeUIScrollView_1 extends _i1.SmartFake implements _i3.UIScrollView { _FakeUIScrollView_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKWebView_2 extends _i1.SmartFake implements _i2.WKWebView { _FakeWKWebView_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [WKWebView]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKWebView extends _i1.Mock implements _i2.WKWebView { MockWKWebView() { _i1.throwOnMissingStub(this); } @override _i2.WKWebViewConfiguration get configuration => (super.noSuchMethod( Invocation.getter(#configuration), returnValue: _FakeWKWebViewConfiguration_0( this, Invocation.getter(#configuration), ), ) as _i2.WKWebViewConfiguration); @override _i3.UIScrollView get scrollView => (super.noSuchMethod( Invocation.getter(#scrollView), returnValue: _FakeUIScrollView_1( this, Invocation.getter(#scrollView), ), ) as _i3.UIScrollView); @override _i4.Future setUIDelegate(_i2.WKUIDelegate? delegate) => (super.noSuchMethod( Invocation.method( #setUIDelegate, [delegate], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future setNavigationDelegate(_i2.WKNavigationDelegate? delegate) => (super.noSuchMethod( Invocation.method( #setNavigationDelegate, [delegate], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future getUrl() => (super.noSuchMethod( Invocation.method( #getUrl, [], ), returnValue: _i4.Future.value(), ) as _i4.Future); @override _i4.Future getEstimatedProgress() => (super.noSuchMethod( Invocation.method( #getEstimatedProgress, [], ), returnValue: _i4.Future.value(0.0), ) as _i4.Future); @override _i4.Future loadRequest(_i5.NSUrlRequest? request) => (super.noSuchMethod( Invocation.method( #loadRequest, [request], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future loadHtmlString( String? string, { String? baseUrl, }) => (super.noSuchMethod( Invocation.method( #loadHtmlString, [string], {#baseUrl: baseUrl}, ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future loadFileUrl( String? url, { required String? readAccessUrl, }) => (super.noSuchMethod( Invocation.method( #loadFileUrl, [url], {#readAccessUrl: readAccessUrl}, ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future loadFlutterAsset(String? key) => (super.noSuchMethod( Invocation.method( #loadFlutterAsset, [key], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future canGoBack() => (super.noSuchMethod( Invocation.method( #canGoBack, [], ), returnValue: _i4.Future.value(false), ) as _i4.Future); @override _i4.Future canGoForward() => (super.noSuchMethod( Invocation.method( #canGoForward, [], ), returnValue: _i4.Future.value(false), ) as _i4.Future); @override _i4.Future goBack() => (super.noSuchMethod( Invocation.method( #goBack, [], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future goForward() => (super.noSuchMethod( Invocation.method( #goForward, [], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future reload() => (super.noSuchMethod( Invocation.method( #reload, [], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future getTitle() => (super.noSuchMethod( Invocation.method( #getTitle, [], ), returnValue: _i4.Future.value(), ) as _i4.Future); @override _i4.Future setAllowsBackForwardNavigationGestures(bool? allow) => (super.noSuchMethod( Invocation.method( #setAllowsBackForwardNavigationGestures, [allow], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future setCustomUserAgent(String? userAgent) => (super.noSuchMethod( Invocation.method( #setCustomUserAgent, [userAgent], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future evaluateJavaScript(String? javaScriptString) => (super.noSuchMethod( Invocation.method( #evaluateJavaScript, [javaScriptString], ), returnValue: _i4.Future.value(), ) as _i4.Future); @override _i2.WKWebView copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKWebView_2( this, Invocation.method( #copy, [], ), ), ) as _i2.WKWebView); @override _i4.Future setBackgroundColor(_i6.Color? color) => (super.noSuchMethod( Invocation.method( #setBackgroundColor, [color], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future setOpaque(bool? opaque) => (super.noSuchMethod( Invocation.method( #setOpaque, [opaque], ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future addObserver( _i5.NSObject? observer, { required String? keyPath, required Set<_i5.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); @override _i4.Future removeObserver( _i5.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:math'; // TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) // ignore: unnecessary_import import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; 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'; import 'package:webview_flutter_wkwebview/src/webkit_proxy.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; import 'webkit_webview_controller_test.mocks.dart'; @GenerateMocks([ UIScrollView, WKPreferences, WKUserContentController, WKWebsiteDataStore, WKWebView, WKWebViewConfiguration, ]) void main() { WidgetsFlutterBinding.ensureInitialized(); group('WebKitWebViewController', () { WebKitWebViewController createControllerWithMocks({ MockUIScrollView? mockScrollView, MockWKPreferences? mockPreferences, MockWKUserContentController? mockUserContentController, MockWKWebsiteDataStore? mockWebsiteDataStore, MockWKWebView Function( WKWebViewConfiguration configuration, { void Function( String keyPath, NSObject object, Map change, )? observeValue, })? createMockWebView, MockWKWebViewConfiguration? mockWebViewConfiguration, InstanceManager? instanceManager, }) { final MockWKWebViewConfiguration nonNullMockWebViewConfiguration = mockWebViewConfiguration ?? MockWKWebViewConfiguration(); late final MockWKWebView nonNullMockWebView; final PlatformWebViewControllerCreationParams controllerCreationParams = WebKitWebViewControllerCreationParams( webKitProxy: WebKitProxy( createWebViewConfiguration: ({InstanceManager? instanceManager}) { return nonNullMockWebViewConfiguration; }, createWebView: ( _, { void Function( String keyPath, NSObject object, Map change, )? observeValue, InstanceManager? instanceManager, }) { nonNullMockWebView = createMockWebView == null ? MockWKWebView() : createMockWebView( nonNullMockWebViewConfiguration, observeValue: observeValue, ); return nonNullMockWebView; }, ), instanceManager: instanceManager, ); final WebKitWebViewController controller = WebKitWebViewController( controllerCreationParams, ); when(nonNullMockWebView.scrollView) .thenReturn(mockScrollView ?? MockUIScrollView()); when(nonNullMockWebView.configuration) .thenReturn(nonNullMockWebViewConfiguration); when(nonNullMockWebViewConfiguration.preferences) .thenReturn(mockPreferences ?? MockWKPreferences()); when(nonNullMockWebViewConfiguration.userContentController).thenReturn( mockUserContentController ?? MockWKUserContentController()); when(nonNullMockWebViewConfiguration.websiteDataStore) .thenReturn(mockWebsiteDataStore ?? MockWKWebsiteDataStore()); return controller; } group('WebKitWebViewControllerCreationParams', () { test('allowsInlineMediaPlayback', () { final MockWKWebViewConfiguration mockConfiguration = MockWKWebViewConfiguration(); WebKitWebViewControllerCreationParams( webKitProxy: WebKitProxy( createWebViewConfiguration: ({InstanceManager? instanceManager}) { return mockConfiguration; }, ), allowsInlineMediaPlayback: true, ); verify( mockConfiguration.setAllowsInlineMediaPlayback(true), ); }); test('mediaTypesRequiringUserAction', () { final MockWKWebViewConfiguration mockConfiguration = MockWKWebViewConfiguration(); WebKitWebViewControllerCreationParams( webKitProxy: WebKitProxy( createWebViewConfiguration: ({InstanceManager? instanceManager}) { return mockConfiguration; }, ), mediaTypesRequiringUserAction: const { PlaybackMediaTypes.video, }, ); verify( mockConfiguration.setMediaTypesRequiringUserActionForPlayback( { WKAudiovisualMediaType.video, }, ), ); }); test('mediaTypesRequiringUserAction defaults to include audio and video', () { final MockWKWebViewConfiguration mockConfiguration = MockWKWebViewConfiguration(); WebKitWebViewControllerCreationParams( webKitProxy: WebKitProxy( createWebViewConfiguration: ({InstanceManager? instanceManager}) { return mockConfiguration; }, ), ); verify( mockConfiguration.setMediaTypesRequiringUserActionForPlayback( { WKAudiovisualMediaType.audio, WKAudiovisualMediaType.video, }, ), ); }); test('mediaTypesRequiringUserAction sets value to none if set is empty', () { final MockWKWebViewConfiguration mockConfiguration = MockWKWebViewConfiguration(); WebKitWebViewControllerCreationParams( webKitProxy: WebKitProxy( createWebViewConfiguration: ({InstanceManager? instanceManager}) { return mockConfiguration; }, ), mediaTypesRequiringUserAction: const {}, ); verify( mockConfiguration.setMediaTypesRequiringUserActionForPlayback( {WKAudiovisualMediaType.none}, ), ); }); }); test('loadFile', () async { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.loadFile('/path/to/file.html'); verify(mockWebView.loadFileUrl( '/path/to/file.html', readAccessUrl: '/path/to', )); }); test('loadFlutterAsset', () async { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.loadFlutterAsset('test_assets/index.html'); verify(mockWebView.loadFlutterAsset('test_assets/index.html')); }); test('loadHtmlString', () async { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); const String htmlString = 'Test data.'; await controller.loadHtmlString(htmlString, baseUrl: 'baseUrl'); verify(mockWebView.loadHtmlString( 'Test data.', baseUrl: 'baseUrl', )); }); group('loadRequest', () { test('Throws ArgumentError for empty scheme', () async { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); expect( () async => controller.loadRequest( LoadRequestParams(uri: Uri.parse('www.google.com')), ), throwsA(isA()), ); }); test('GET without headers', () async { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.loadRequest( LoadRequestParams(uri: Uri.parse('https://www.google.com')), ); final NSUrlRequest request = verify(mockWebView.loadRequest(captureAny)) .captured .single as NSUrlRequest; expect(request.url, 'https://www.google.com'); expect(request.allHttpHeaderFields, {}); expect(request.httpMethod, 'get'); }); test('GET with headers', () async { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.loadRequest( LoadRequestParams( uri: Uri.parse('https://www.google.com'), headers: const {'a': 'header'}, ), ); final NSUrlRequest request = verify(mockWebView.loadRequest(captureAny)) .captured .single as NSUrlRequest; expect(request.url, 'https://www.google.com'); expect(request.allHttpHeaderFields, {'a': 'header'}); expect(request.httpMethod, 'get'); }); test('POST without body', () async { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.loadRequest(LoadRequestParams( uri: Uri.parse('https://www.google.com'), method: LoadRequestMethod.post, )); final NSUrlRequest request = verify(mockWebView.loadRequest(captureAny)) .captured .single as NSUrlRequest; expect(request.url, 'https://www.google.com'); expect(request.httpMethod, 'post'); }); test('POST with body', () async { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.loadRequest(LoadRequestParams( uri: Uri.parse('https://www.google.com'), method: LoadRequestMethod.post, body: Uint8List.fromList('Test Body'.codeUnits), )); final NSUrlRequest request = verify(mockWebView.loadRequest(captureAny)) .captured .single as NSUrlRequest; expect(request.url, 'https://www.google.com'); expect(request.httpMethod, 'post'); expect( request.httpBody, Uint8List.fromList('Test Body'.codeUnits), ); }); }); test('canGoBack', () { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); when(mockWebView.canGoBack()).thenAnswer( (_) => Future.value(false), ); expect(controller.canGoBack(), completion(false)); }); test('canGoForward', () { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); when(mockWebView.canGoForward()).thenAnswer( (_) => Future.value(true), ); expect(controller.canGoForward(), completion(true)); }); test('goBack', () async { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.goBack(); verify(mockWebView.goBack()); }); test('goForward', () async { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.goForward(); verify(mockWebView.goForward()); }); test('reload', () async { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.reload(); verify(mockWebView.reload()); }); test('setAllowsBackForwardNavigationGestures', () async { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.setAllowsBackForwardNavigationGestures(true); verify(mockWebView.setAllowsBackForwardNavigationGestures(true)); }); test('runJavaScriptReturningResult', () { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); final Object result = Object(); when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value(result), ); expect( controller.runJavaScriptReturningResult('runJavaScript'), completion(result), ); }); test('runJavaScriptReturningResult throws error on null return value', () { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value(), ); expect( () => controller.runJavaScriptReturningResult('runJavaScript'), throwsArgumentError, ); }); test('runJavaScript', () { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); when(mockWebView.evaluateJavaScript('runJavaScript')).thenAnswer( (_) => Future.value('returnString'), ); expect( controller.runJavaScript('runJavaScript'), completes, ); }); test('runJavaScript ignores exception with unsupported javaScript type', () { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); when(mockWebView.evaluateJavaScript('runJavaScript')) .thenThrow(PlatformException( code: '', details: const NSError( code: WKErrorCode.javaScriptResultTypeIsUnsupported, domain: '', localizedDescription: '', ), )); expect( controller.runJavaScript('runJavaScript'), completes, ); }); test('getTitle', () async { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); when(mockWebView.getTitle()) .thenAnswer((_) => Future.value('Web Title')); expect(controller.getTitle(), completion('Web Title')); }); test('currentUrl', () { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); when(mockWebView.getUrl()) .thenAnswer((_) => Future.value('myUrl.com')); expect(controller.currentUrl(), completion('myUrl.com')); }); test('scrollTo', () async { final MockUIScrollView mockScrollView = MockUIScrollView(); final WebKitWebViewController controller = createControllerWithMocks( mockScrollView: mockScrollView, ); await controller.scrollTo(2, 4); verify(mockScrollView.setContentOffset(const Point(2.0, 4.0))); }); test('scrollBy', () async { final MockUIScrollView mockScrollView = MockUIScrollView(); final WebKitWebViewController controller = createControllerWithMocks( mockScrollView: mockScrollView, ); await controller.scrollBy(2, 4); verify(mockScrollView.scrollBy(const Point(2.0, 4.0))); }); test('getScrollPosition', () { final MockUIScrollView mockScrollView = MockUIScrollView(); final WebKitWebViewController controller = createControllerWithMocks( mockScrollView: mockScrollView, ); when(mockScrollView.getContentOffset()).thenAnswer( (_) => Future>.value(const Point(8.0, 16.0)), ); expect( controller.getScrollPosition(), completion(const Offset(8.0, 16.0)), ); }); test('disable zoom', () async { final MockWKUserContentController mockUserContentController = MockWKUserContentController(); final WebKitWebViewController controller = createControllerWithMocks( mockUserContentController: mockUserContentController, ); await controller.enableZoom(false); final WKUserScript zoomScript = verify(mockUserContentController.addUserScript(captureAny)) .captured .first as WKUserScript; expect(zoomScript.isMainFrameOnly, isTrue); expect(zoomScript.injectionTime, WKUserScriptInjectionTime.atDocumentEnd); expect( zoomScript.source, "var meta = document.createElement('meta');\n" "meta.name = 'viewport';\n" "meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, " "user-scalable=no';\n" "var head = document.getElementsByTagName('head')[0];head.appendChild(meta);", ); }); test('setBackgroundColor', () async { final MockWKWebView mockWebView = MockWKWebView(); final MockUIScrollView mockScrollView = MockUIScrollView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, mockScrollView: mockScrollView, ); controller.setBackgroundColor(Colors.red); // UIScrollView.setBackgroundColor must be called last. verifyInOrder([ mockWebView.setOpaque(false), mockWebView.setBackgroundColor(Colors.transparent), mockScrollView.setBackgroundColor(Colors.red), ]); }); test('userAgent', () async { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); await controller.setUserAgent('MyUserAgent'); verify(mockWebView.setCustomUserAgent('MyUserAgent')); }); test('enable JavaScript', () async { final MockWKPreferences mockPreferences = MockWKPreferences(); final WebKitWebViewController controller = createControllerWithMocks( mockPreferences: mockPreferences, ); await controller.setJavaScriptMode(JavaScriptMode.unrestricted); verify(mockPreferences.setJavaScriptEnabled(true)); }); test('disable JavaScript', () async { final MockWKPreferences mockPreferences = MockWKPreferences(); final WebKitWebViewController controller = createControllerWithMocks( mockPreferences: mockPreferences, ); await controller.setJavaScriptMode(JavaScriptMode.disabled); verify(mockPreferences.setJavaScriptEnabled(false)); }); test('clearCache', () { final MockWKWebsiteDataStore mockWebsiteDataStore = MockWKWebsiteDataStore(); final WebKitWebViewController controller = createControllerWithMocks( mockWebsiteDataStore: mockWebsiteDataStore, ); when( mockWebsiteDataStore.removeDataOfTypes( { WKWebsiteDataType.memoryCache, WKWebsiteDataType.diskCache, WKWebsiteDataType.offlineWebApplicationCache, }, DateTime.fromMillisecondsSinceEpoch(0), ), ).thenAnswer((_) => Future.value(false)); expect(controller.clearCache(), completes); }); test('clearLocalStorage', () { final MockWKWebsiteDataStore mockWebsiteDataStore = MockWKWebsiteDataStore(); final WebKitWebViewController controller = createControllerWithMocks( mockWebsiteDataStore: mockWebsiteDataStore, ); when( mockWebsiteDataStore.removeDataOfTypes( {WKWebsiteDataType.localStorage}, DateTime.fromMillisecondsSinceEpoch(0), ), ).thenAnswer((_) => Future.value(false)); expect(controller.clearLocalStorage(), completes); }); test('addJavaScriptChannel', () async { final WebKitProxy webKitProxy = WebKitProxy( createScriptMessageHandler: ({ required void Function( WKUserContentController userContentController, WKScriptMessage message, ) didReceiveScriptMessage, }) { return WKScriptMessageHandler.detached( didReceiveScriptMessage: didReceiveScriptMessage, ); }, ); final WebKitJavaScriptChannelParams javaScriptChannelParams = WebKitJavaScriptChannelParams( name: 'name', onMessageReceived: (JavaScriptMessage message) {}, webKitProxy: webKitProxy, ); final MockWKUserContentController mockUserContentController = MockWKUserContentController(); final WebKitWebViewController controller = createControllerWithMocks( mockUserContentController: mockUserContentController, ); await controller.addJavaScriptChannel(javaScriptChannelParams); verify(mockUserContentController.addScriptMessageHandler( argThat(isA()), 'name', )); final WKUserScript userScript = verify(mockUserContentController.addUserScript(captureAny)) .captured .single as WKUserScript; expect(userScript.source, 'window.name = webkit.messageHandlers.name;'); expect( userScript.injectionTime, WKUserScriptInjectionTime.atDocumentStart, ); }); test('removeJavaScriptChannel', () async { final WebKitProxy webKitProxy = WebKitProxy( createScriptMessageHandler: ({ required void Function( WKUserContentController userContentController, WKScriptMessage message, ) didReceiveScriptMessage, }) { return WKScriptMessageHandler.detached( didReceiveScriptMessage: didReceiveScriptMessage, ); }, ); final WebKitJavaScriptChannelParams javaScriptChannelParams = WebKitJavaScriptChannelParams( name: 'name', onMessageReceived: (JavaScriptMessage message) {}, webKitProxy: webKitProxy, ); final MockWKUserContentController mockUserContentController = MockWKUserContentController(); final WebKitWebViewController controller = createControllerWithMocks( mockUserContentController: mockUserContentController, ); await controller.addJavaScriptChannel(javaScriptChannelParams); reset(mockUserContentController); await controller.removeJavaScriptChannel('name'); verify(mockUserContentController.removeAllUserScripts()); verify(mockUserContentController.removeScriptMessageHandler('name')); verifyNoMoreInteractions(mockUserContentController); }); test('removeJavaScriptChannel with zoom disabled', () async { final WebKitProxy webKitProxy = WebKitProxy( createScriptMessageHandler: ({ required void Function( WKUserContentController userContentController, WKScriptMessage message, ) didReceiveScriptMessage, }) { return WKScriptMessageHandler.detached( didReceiveScriptMessage: didReceiveScriptMessage, ); }, ); final WebKitJavaScriptChannelParams javaScriptChannelParams = WebKitJavaScriptChannelParams( name: 'name', onMessageReceived: (JavaScriptMessage message) {}, webKitProxy: webKitProxy, ); final MockWKUserContentController mockUserContentController = MockWKUserContentController(); final WebKitWebViewController controller = createControllerWithMocks( mockUserContentController: mockUserContentController, ); await controller.enableZoom(false); await controller.addJavaScriptChannel(javaScriptChannelParams); clearInteractions(mockUserContentController); await controller.removeJavaScriptChannel('name'); final WKUserScript zoomScript = verify(mockUserContentController.addUserScript(captureAny)) .captured .first as WKUserScript; expect(zoomScript.isMainFrameOnly, isTrue); expect(zoomScript.injectionTime, WKUserScriptInjectionTime.atDocumentEnd); expect( zoomScript.source, "var meta = document.createElement('meta');\n" "meta.name = 'viewport';\n" "meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, " "user-scalable=no';\n" "var head = document.getElementsByTagName('head')[0];head.appendChild(meta);", ); }); test('setPlatformNavigationDelegate', () { final MockWKWebView mockWebView = MockWKWebView(); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, ); final WebKitNavigationDelegate navigationDelegate = WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams( webKitProxy: WebKitProxy( createNavigationDelegate: CapturingNavigationDelegate.new, createUIDelegate: CapturingUIDelegate.new, ), ), ); controller.setPlatformNavigationDelegate(navigationDelegate); verify( mockWebView.setNavigationDelegate( CapturingNavigationDelegate.lastCreatedDelegate, ), ); verify( mockWebView.setUIDelegate( CapturingUIDelegate.lastCreatedDelegate, ), ); }); test('setPlatformNavigationDelegate onProgress', () async { final MockWKWebView mockWebView = MockWKWebView(); late final void Function( String keyPath, NSObject object, Map change, ) webViewObserveValue; final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: ( _, { void Function( String keyPath, NSObject object, Map change, )? observeValue, }) { webViewObserveValue = observeValue!; return mockWebView; }, ); verify( mockWebView.addObserver( mockWebView, keyPath: 'estimatedProgress', options: { NSKeyValueObservingOptions.newValue, }, ), ); final WebKitNavigationDelegate navigationDelegate = WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams( webKitProxy: WebKitProxy( createNavigationDelegate: CapturingNavigationDelegate.new, createUIDelegate: WKUIDelegate.detached, ), ), ); late final int callbackProgress; navigationDelegate.setOnProgress( (int progress) => callbackProgress = progress, ); await controller.setPlatformNavigationDelegate(navigationDelegate); webViewObserveValue( 'estimatedProgress', mockWebView, {NSKeyValueChangeKey.newValue: 0.0}, ); expect(callbackProgress, 0); }); test( 'setPlatformNavigationDelegate onProgress can be changed by the WebKitNavigationDelegage', () async { final MockWKWebView mockWebView = MockWKWebView(); late final void Function( String keyPath, NSObject object, Map change, ) webViewObserveValue; final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: ( _, { void Function( String keyPath, NSObject object, Map change, )? observeValue, }) { webViewObserveValue = observeValue!; return mockWebView; }, ); final WebKitNavigationDelegate navigationDelegate = WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams( webKitProxy: WebKitProxy( createNavigationDelegate: CapturingNavigationDelegate.new, createUIDelegate: WKUIDelegate.detached, ), ), ); // First value of onProgress does nothing. await navigationDelegate.setOnProgress((_) {}); await controller.setPlatformNavigationDelegate(navigationDelegate); // Second value of onProgress sets `callbackProgress`. late final int callbackProgress; await navigationDelegate.setOnProgress( (int progress) => callbackProgress = progress, ); webViewObserveValue( 'estimatedProgress', mockWebView, {NSKeyValueChangeKey.newValue: 0.0}, ); expect(callbackProgress, 0); }); test('webViewIdentifier', () { final InstanceManager instanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); final MockWKWebView mockWebView = MockWKWebView(); when(mockWebView.copy()).thenReturn(MockWKWebView()); instanceManager.addHostCreatedInstance(mockWebView, 0); final WebKitWebViewController controller = createControllerWithMocks( createMockWebView: (_, {dynamic observeValue}) => mockWebView, instanceManager: instanceManager, ); expect( controller.webViewIdentifier, instanceManager.getIdentifier(mockWebView), ); }); }); group('WebKitJavaScriptChannelParams', () { test('onMessageReceived', () async { late final WKScriptMessageHandler messageHandler; final WebKitProxy webKitProxy = WebKitProxy( createScriptMessageHandler: ({ required void Function( WKUserContentController userContentController, WKScriptMessage message, ) didReceiveScriptMessage, }) { messageHandler = WKScriptMessageHandler.detached( didReceiveScriptMessage: didReceiveScriptMessage, ); return messageHandler; }, ); late final String callbackMessage; WebKitJavaScriptChannelParams( name: 'name', onMessageReceived: (JavaScriptMessage message) { callbackMessage = message.message; }, webKitProxy: webKitProxy, ); messageHandler.didReceiveScriptMessage( MockWKUserContentController(), const WKScriptMessage(name: 'name', body: 'myMessage'), ); expect(callbackMessage, 'myMessage'); }); }); } // Records the last created instance of itself. class CapturingNavigationDelegate extends WKNavigationDelegate { CapturingNavigationDelegate({ super.didFinishNavigation, super.didStartProvisionalNavigation, super.didFailNavigation, super.didFailProvisionalNavigation, super.decidePolicyForNavigationAction, super.webViewWebContentProcessDidTerminate, }) : super.detached() { lastCreatedDelegate = this; } static CapturingNavigationDelegate lastCreatedDelegate = CapturingNavigationDelegate(); } // Records the last created instance of itself. class CapturingUIDelegate extends WKUIDelegate { CapturingUIDelegate({super.onCreateWebView}) : super.detached() { lastCreatedDelegate = this; } static CapturingUIDelegate lastCreatedDelegate = CapturingUIDelegate(); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_wkwebview/test/webkit_webview_controller_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i5; import 'dart:math' as _i2; import 'dart:ui' as _i6; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart' as _i7; import 'package:webview_flutter_wkwebview/src/ui_kit/ui_kit.dart' as _i3; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart' as _i4; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class class _FakePoint_0 extends _i1.SmartFake implements _i2.Point { _FakePoint_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeUIScrollView_1 extends _i1.SmartFake implements _i3.UIScrollView { _FakeUIScrollView_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKPreferences_2 extends _i1.SmartFake implements _i4.WKPreferences { _FakeWKPreferences_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKUserContentController_3 extends _i1.SmartFake implements _i4.WKUserContentController { _FakeWKUserContentController_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKHttpCookieStore_4 extends _i1.SmartFake implements _i4.WKHttpCookieStore { _FakeWKHttpCookieStore_4( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKWebsiteDataStore_5 extends _i1.SmartFake implements _i4.WKWebsiteDataStore { _FakeWKWebsiteDataStore_5( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKWebViewConfiguration_6 extends _i1.SmartFake implements _i4.WKWebViewConfiguration { _FakeWKWebViewConfiguration_6( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKWebView_7 extends _i1.SmartFake implements _i4.WKWebView { _FakeWKWebView_7( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [UIScrollView]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockUIScrollView extends _i1.Mock implements _i3.UIScrollView { MockUIScrollView() { _i1.throwOnMissingStub(this); } @override _i5.Future<_i2.Point> getContentOffset() => (super.noSuchMethod( Invocation.method( #getContentOffset, [], ), returnValue: _i5.Future<_i2.Point>.value(_FakePoint_0( this, Invocation.method( #getContentOffset, [], ), )), ) as _i5.Future<_i2.Point>); @override _i5.Future scrollBy(_i2.Point? offset) => (super.noSuchMethod( Invocation.method( #scrollBy, [offset], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setContentOffset(_i2.Point? offset) => (super.noSuchMethod( Invocation.method( #setContentOffset, [offset], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i3.UIScrollView copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeUIScrollView_1( this, Invocation.method( #copy, [], ), ), ) as _i3.UIScrollView); @override _i5.Future setBackgroundColor(_i6.Color? color) => (super.noSuchMethod( Invocation.method( #setBackgroundColor, [color], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setOpaque(bool? opaque) => (super.noSuchMethod( Invocation.method( #setOpaque, [opaque], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future addObserver( _i7.NSObject? observer, { required String? keyPath, required Set<_i7.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeObserver( _i7.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } /// A class which mocks [WKPreferences]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKPreferences extends _i1.Mock implements _i4.WKPreferences { MockWKPreferences() { _i1.throwOnMissingStub(this); } @override _i5.Future setJavaScriptEnabled(bool? enabled) => (super.noSuchMethod( Invocation.method( #setJavaScriptEnabled, [enabled], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i4.WKPreferences copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKPreferences_2( this, Invocation.method( #copy, [], ), ), ) as _i4.WKPreferences); @override _i5.Future addObserver( _i7.NSObject? observer, { required String? keyPath, required Set<_i7.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeObserver( _i7.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } /// A class which mocks [WKUserContentController]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKUserContentController extends _i1.Mock implements _i4.WKUserContentController { MockWKUserContentController() { _i1.throwOnMissingStub(this); } @override _i5.Future addScriptMessageHandler( _i4.WKScriptMessageHandler? handler, String? name, ) => (super.noSuchMethod( Invocation.method( #addScriptMessageHandler, [ handler, name, ], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeScriptMessageHandler(String? name) => (super.noSuchMethod( Invocation.method( #removeScriptMessageHandler, [name], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeAllScriptMessageHandlers() => (super.noSuchMethod( Invocation.method( #removeAllScriptMessageHandlers, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future addUserScript(_i4.WKUserScript? userScript) => (super.noSuchMethod( Invocation.method( #addUserScript, [userScript], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeAllUserScripts() => (super.noSuchMethod( Invocation.method( #removeAllUserScripts, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i4.WKUserContentController copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKUserContentController_3( this, Invocation.method( #copy, [], ), ), ) as _i4.WKUserContentController); @override _i5.Future addObserver( _i7.NSObject? observer, { required String? keyPath, required Set<_i7.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeObserver( _i7.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } /// A class which mocks [WKWebsiteDataStore]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKWebsiteDataStore extends _i1.Mock implements _i4.WKWebsiteDataStore { MockWKWebsiteDataStore() { _i1.throwOnMissingStub(this); } @override _i4.WKHttpCookieStore get httpCookieStore => (super.noSuchMethod( Invocation.getter(#httpCookieStore), returnValue: _FakeWKHttpCookieStore_4( this, Invocation.getter(#httpCookieStore), ), ) as _i4.WKHttpCookieStore); @override _i5.Future removeDataOfTypes( Set<_i4.WKWebsiteDataType>? dataTypes, DateTime? since, ) => (super.noSuchMethod( Invocation.method( #removeDataOfTypes, [ dataTypes, since, ], ), returnValue: _i5.Future.value(false), ) as _i5.Future); @override _i4.WKWebsiteDataStore copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKWebsiteDataStore_5( this, Invocation.method( #copy, [], ), ), ) as _i4.WKWebsiteDataStore); @override _i5.Future addObserver( _i7.NSObject? observer, { required String? keyPath, required Set<_i7.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeObserver( _i7.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } /// A class which mocks [WKWebView]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKWebView extends _i1.Mock implements _i4.WKWebView { MockWKWebView() { _i1.throwOnMissingStub(this); } @override _i4.WKWebViewConfiguration get configuration => (super.noSuchMethod( Invocation.getter(#configuration), returnValue: _FakeWKWebViewConfiguration_6( this, Invocation.getter(#configuration), ), ) as _i4.WKWebViewConfiguration); @override _i3.UIScrollView get scrollView => (super.noSuchMethod( Invocation.getter(#scrollView), returnValue: _FakeUIScrollView_1( this, Invocation.getter(#scrollView), ), ) as _i3.UIScrollView); @override _i5.Future setUIDelegate(_i4.WKUIDelegate? delegate) => (super.noSuchMethod( Invocation.method( #setUIDelegate, [delegate], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setNavigationDelegate(_i4.WKNavigationDelegate? delegate) => (super.noSuchMethod( Invocation.method( #setNavigationDelegate, [delegate], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future getUrl() => (super.noSuchMethod( Invocation.method( #getUrl, [], ), returnValue: _i5.Future.value(), ) as _i5.Future); @override _i5.Future getEstimatedProgress() => (super.noSuchMethod( Invocation.method( #getEstimatedProgress, [], ), returnValue: _i5.Future.value(0.0), ) as _i5.Future); @override _i5.Future loadRequest(_i7.NSUrlRequest? request) => (super.noSuchMethod( Invocation.method( #loadRequest, [request], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future loadHtmlString( String? string, { String? baseUrl, }) => (super.noSuchMethod( Invocation.method( #loadHtmlString, [string], {#baseUrl: baseUrl}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future loadFileUrl( String? url, { required String? readAccessUrl, }) => (super.noSuchMethod( Invocation.method( #loadFileUrl, [url], {#readAccessUrl: readAccessUrl}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future loadFlutterAsset(String? key) => (super.noSuchMethod( Invocation.method( #loadFlutterAsset, [key], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future canGoBack() => (super.noSuchMethod( Invocation.method( #canGoBack, [], ), returnValue: _i5.Future.value(false), ) as _i5.Future); @override _i5.Future canGoForward() => (super.noSuchMethod( Invocation.method( #canGoForward, [], ), returnValue: _i5.Future.value(false), ) as _i5.Future); @override _i5.Future goBack() => (super.noSuchMethod( Invocation.method( #goBack, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future goForward() => (super.noSuchMethod( Invocation.method( #goForward, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future reload() => (super.noSuchMethod( Invocation.method( #reload, [], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future getTitle() => (super.noSuchMethod( Invocation.method( #getTitle, [], ), returnValue: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setAllowsBackForwardNavigationGestures(bool? allow) => (super.noSuchMethod( Invocation.method( #setAllowsBackForwardNavigationGestures, [allow], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setCustomUserAgent(String? userAgent) => (super.noSuchMethod( Invocation.method( #setCustomUserAgent, [userAgent], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future evaluateJavaScript(String? javaScriptString) => (super.noSuchMethod( Invocation.method( #evaluateJavaScript, [javaScriptString], ), returnValue: _i5.Future.value(), ) as _i5.Future); @override _i4.WKWebView copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKWebView_7( this, Invocation.method( #copy, [], ), ), ) as _i4.WKWebView); @override _i5.Future setBackgroundColor(_i6.Color? color) => (super.noSuchMethod( Invocation.method( #setBackgroundColor, [color], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setOpaque(bool? opaque) => (super.noSuchMethod( Invocation.method( #setOpaque, [opaque], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future addObserver( _i7.NSObject? observer, { required String? keyPath, required Set<_i7.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeObserver( _i7.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } /// A class which mocks [WKWebViewConfiguration]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKWebViewConfiguration extends _i1.Mock implements _i4.WKWebViewConfiguration { MockWKWebViewConfiguration() { _i1.throwOnMissingStub(this); } @override _i4.WKUserContentController get userContentController => (super.noSuchMethod( Invocation.getter(#userContentController), returnValue: _FakeWKUserContentController_3( this, Invocation.getter(#userContentController), ), ) as _i4.WKUserContentController); @override _i4.WKPreferences get preferences => (super.noSuchMethod( Invocation.getter(#preferences), returnValue: _FakeWKPreferences_2( this, Invocation.getter(#preferences), ), ) as _i4.WKPreferences); @override _i4.WKWebsiteDataStore get websiteDataStore => (super.noSuchMethod( Invocation.getter(#websiteDataStore), returnValue: _FakeWKWebsiteDataStore_5( this, Invocation.getter(#websiteDataStore), ), ) as _i4.WKWebsiteDataStore); @override _i5.Future setAllowsInlineMediaPlayback(bool? allow) => (super.noSuchMethod( Invocation.method( #setAllowsInlineMediaPlayback, [allow], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future setMediaTypesRequiringUserActionForPlayback( Set<_i4.WKAudiovisualMediaType>? types) => (super.noSuchMethod( Invocation.method( #setMediaTypesRequiringUserActionForPlayback, [types], ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i4.WKWebViewConfiguration copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKWebViewConfiguration_6( this, Invocation.method( #copy, [], ), ), ) as _i4.WKWebViewConfiguration); @override _i5.Future addObserver( _i7.NSObject? observer, { required String? keyPath, required Set<_i7.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); @override _i5.Future removeObserver( _i7.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i5.Future.value(), returnValueForMissingStub: _i5.Future.value(), ) as _i5.Future); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_cookie_manager_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package: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/foundation/foundation.dart'; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.dart'; import 'package:webview_flutter_wkwebview/src/webkit_proxy.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; import 'webkit_webview_cookie_manager_test.mocks.dart'; @GenerateMocks([WKWebsiteDataStore, WKHttpCookieStore]) void main() { WidgetsFlutterBinding.ensureInitialized(); group('WebKitWebViewCookieManager', () { test('clearCookies', () { final MockWKWebsiteDataStore mockWKWebsiteDataStore = MockWKWebsiteDataStore(); final WebKitWebViewCookieManager manager = WebKitWebViewCookieManager( WebKitWebViewCookieManagerCreationParams( webKitProxy: WebKitProxy( defaultWebsiteDataStore: () => mockWKWebsiteDataStore, ), ), ); when( mockWKWebsiteDataStore.removeDataOfTypes( {WKWebsiteDataType.cookies}, any, ), ).thenAnswer((_) => Future.value(true)); expect(manager.clearCookies(), completion(true)); when( mockWKWebsiteDataStore.removeDataOfTypes( {WKWebsiteDataType.cookies}, any, ), ).thenAnswer((_) => Future.value(false)); expect(manager.clearCookies(), completion(false)); }); test('setCookie', () async { final MockWKWebsiteDataStore mockWKWebsiteDataStore = MockWKWebsiteDataStore(); final MockWKHttpCookieStore mockCookieStore = MockWKHttpCookieStore(); when(mockWKWebsiteDataStore.httpCookieStore).thenReturn(mockCookieStore); final WebKitWebViewCookieManager manager = WebKitWebViewCookieManager( WebKitWebViewCookieManagerCreationParams( webKitProxy: WebKitProxy( defaultWebsiteDataStore: () => mockWKWebsiteDataStore, ), ), ); await manager.setCookie( const WebViewCookie(name: 'a', value: 'b', domain: 'c', path: 'd'), ); final NSHttpCookie cookie = verify(mockCookieStore.setCookie(captureAny)) .captured .single as NSHttpCookie; expect( cookie.properties, { NSHttpCookiePropertyKey.name: 'a', NSHttpCookiePropertyKey.value: 'b', NSHttpCookiePropertyKey.domain: 'c', NSHttpCookiePropertyKey.path: 'd', }, ); }); test('setCookie throws argument error with invalid path', () async { final MockWKWebsiteDataStore mockWKWebsiteDataStore = MockWKWebsiteDataStore(); final MockWKHttpCookieStore mockCookieStore = MockWKHttpCookieStore(); when(mockWKWebsiteDataStore.httpCookieStore).thenReturn(mockCookieStore); final WebKitWebViewCookieManager manager = WebKitWebViewCookieManager( WebKitWebViewCookieManagerCreationParams( webKitProxy: WebKitProxy( defaultWebsiteDataStore: () => mockWKWebsiteDataStore, ), ), ); expect( () => manager.setCookie( WebViewCookie( name: 'a', value: 'b', domain: 'c', path: String.fromCharCode(0x1F), ), ), throwsArgumentError, ); }); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_cookie_manager_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_wkwebview/test/webkit_webview_cookie_manager_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i3; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart' as _i4; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.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 class _FakeWKHttpCookieStore_0 extends _i1.SmartFake implements _i2.WKHttpCookieStore { _FakeWKHttpCookieStore_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKWebsiteDataStore_1 extends _i1.SmartFake implements _i2.WKWebsiteDataStore { _FakeWKWebsiteDataStore_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [WKWebsiteDataStore]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKWebsiteDataStore extends _i1.Mock implements _i2.WKWebsiteDataStore { MockWKWebsiteDataStore() { _i1.throwOnMissingStub(this); } @override _i2.WKHttpCookieStore get httpCookieStore => (super.noSuchMethod( Invocation.getter(#httpCookieStore), returnValue: _FakeWKHttpCookieStore_0( this, Invocation.getter(#httpCookieStore), ), ) as _i2.WKHttpCookieStore); @override _i3.Future removeDataOfTypes( Set<_i2.WKWebsiteDataType>? dataTypes, DateTime? since, ) => (super.noSuchMethod( Invocation.method( #removeDataOfTypes, [ dataTypes, since, ], ), returnValue: _i3.Future.value(false), ) as _i3.Future); @override _i2.WKWebsiteDataStore copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKWebsiteDataStore_1( this, Invocation.method( #copy, [], ), ), ) as _i2.WKWebsiteDataStore); @override _i3.Future addObserver( _i4.NSObject? observer, { required String? keyPath, required Set<_i4.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); @override _i3.Future removeObserver( _i4.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); } /// A class which mocks [WKHttpCookieStore]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKHttpCookieStore extends _i1.Mock implements _i2.WKHttpCookieStore { MockWKHttpCookieStore() { _i1.throwOnMissingStub(this); } @override _i3.Future setCookie(_i4.NSHttpCookie? cookie) => (super.noSuchMethod( Invocation.method( #setCookie, [cookie], ), returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); @override _i2.WKHttpCookieStore copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKHttpCookieStore_0( this, Invocation.method( #copy, [], ), ), ) as _i2.WKHttpCookieStore); @override _i3.Future addObserver( _i4.NSObject? observer, { required String? keyPath, required Set<_i4.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); @override _i3.Future removeObserver( _i4.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.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/web_kit/web_kit.dart'; import 'package:webview_flutter_wkwebview/src/webkit_proxy.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; import 'webkit_webview_widget_test.mocks.dart'; @GenerateMocks([WKWebViewConfiguration]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('WebKitWebViewWidget', () { testWidgets('build', (WidgetTester tester) async { final InstanceManager testInstanceManager = InstanceManager( onWeakReferenceRemoved: (_) {}, ); final WebKitWebViewController controller = WebKitWebViewController( WebKitWebViewControllerCreationParams( webKitProxy: WebKitProxy( createWebView: ( WKWebViewConfiguration configuration, { void Function( String keyPath, NSObject object, Map change, )? observeValue, InstanceManager? instanceManager, }) { final WKWebView webView = WKWebView.detached( instanceManager: testInstanceManager, ); testInstanceManager.addDartCreatedInstance(webView); return webView; }, createWebViewConfiguration: ({InstanceManager? instanceManager}) { return MockWKWebViewConfiguration(); }, ), ), ); final WebKitWebViewWidget widget = WebKitWebViewWidget( WebKitWebViewWidgetCreationParams( key: const Key('keyValue'), controller: controller, instanceManager: testInstanceManager, ), ); await tester.pumpWidget( Builder(builder: (BuildContext context) => widget.build(context)), ); expect(find.byType(UiKitView), findsOneWidget); expect(find.byKey(const Key('keyValue')), findsOneWidget); }); }); } ================================================ FILE: packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.mocks.dart ================================================ // Mocks generated by Mockito 5.3.2 from annotations // in webview_flutter_wkwebview/test/webkit_webview_widget_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i3; import 'package:mockito/mockito.dart' as _i1; import 'package:webview_flutter_wkwebview/src/foundation/foundation.dart' as _i4; import 'package:webview_flutter_wkwebview/src/web_kit/web_kit.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 class _FakeWKUserContentController_0 extends _i1.SmartFake implements _i2.WKUserContentController { _FakeWKUserContentController_0( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKPreferences_1 extends _i1.SmartFake implements _i2.WKPreferences { _FakeWKPreferences_1( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKWebsiteDataStore_2 extends _i1.SmartFake implements _i2.WKWebsiteDataStore { _FakeWKWebsiteDataStore_2( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } class _FakeWKWebViewConfiguration_3 extends _i1.SmartFake implements _i2.WKWebViewConfiguration { _FakeWKWebViewConfiguration_3( Object parent, Invocation parentInvocation, ) : super( parent, parentInvocation, ); } /// A class which mocks [WKWebViewConfiguration]. /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable class MockWKWebViewConfiguration extends _i1.Mock implements _i2.WKWebViewConfiguration { MockWKWebViewConfiguration() { _i1.throwOnMissingStub(this); } @override _i2.WKUserContentController get userContentController => (super.noSuchMethod( Invocation.getter(#userContentController), returnValue: _FakeWKUserContentController_0( this, Invocation.getter(#userContentController), ), ) as _i2.WKUserContentController); @override _i2.WKPreferences get preferences => (super.noSuchMethod( Invocation.getter(#preferences), returnValue: _FakeWKPreferences_1( this, Invocation.getter(#preferences), ), ) as _i2.WKPreferences); @override _i2.WKWebsiteDataStore get websiteDataStore => (super.noSuchMethod( Invocation.getter(#websiteDataStore), returnValue: _FakeWKWebsiteDataStore_2( this, Invocation.getter(#websiteDataStore), ), ) as _i2.WKWebsiteDataStore); @override _i3.Future setAllowsInlineMediaPlayback(bool? allow) => (super.noSuchMethod( Invocation.method( #setAllowsInlineMediaPlayback, [allow], ), returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); @override _i3.Future setMediaTypesRequiringUserActionForPlayback( Set<_i2.WKAudiovisualMediaType>? types) => (super.noSuchMethod( Invocation.method( #setMediaTypesRequiringUserActionForPlayback, [types], ), returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); @override _i2.WKWebViewConfiguration copy() => (super.noSuchMethod( Invocation.method( #copy, [], ), returnValue: _FakeWKWebViewConfiguration_3( this, Invocation.method( #copy, [], ), ), ) as _i2.WKWebViewConfiguration); @override _i3.Future addObserver( _i4.NSObject? observer, { required String? keyPath, required Set<_i4.NSKeyValueObservingOptions>? options, }) => (super.noSuchMethod( Invocation.method( #addObserver, [observer], { #keyPath: keyPath, #options: options, }, ), returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); @override _i3.Future removeObserver( _i4.NSObject? observer, { required String? keyPath, }) => (super.noSuchMethod( Invocation.method( #removeObserver, [observer], {#keyPath: keyPath}, ), returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); } ================================================ FILE: script/configs/README.md ================================================ This folder contains configuration files that are passed to commands in place of plugin lists. They are primarily used by CI to opt specific packages out of tests, but can also useful when running multi-plugin tests locally. **Any entry added to a file in this directory should include a comment**. Skipping tests or checks for plugins is usually not something we want to do, so should the comment should either include an issue link to the issue tracking removing it or—much more rarely—explaining why it is a permanent exclusion. ================================================ FILE: script/configs/custom_analysis.yaml ================================================ # Plugins that deliberately use their own analysis_options.yaml. # # This only exists to allow incrementally adopting new analysis options in # cases where a new option can't be applied to the entire repository at # once. Do not add anything to this file without an issue reference and # a concrete plan for removing it relatively quickly. # # DO NOT move or delete this file without updating # https://github.com/dart-lang/sdk/blob/master/tools/bots/flutter/analyze_flutter_plugins.sh # which references this file from source, but out-of-repo. # Contact stuartmorgan or devoncarew for assistance if necessary. ================================================ FILE: script/configs/exclude_all_packages_app.yaml ================================================ # This list should be kept as short as possible, and things should remain here # only as long as necessary, since in general the goal is for all of the latest # versions of plugins to be mutually compatible. # # An example use case for this list would be to temporarily add plugins while # updating multiple plugins for a breaking change in a common dependency in # cases where using a relaxed version constraint isn't possible. # This is a permament entry, as it should never be a direct app dependency. - plugin_platform_interface ================================================ FILE: script/configs/exclude_integration_android.yaml ================================================ # No integration tests to run: - espresso ================================================ FILE: script/configs/exclude_integration_ios.yaml ================================================ # Currently missing: https://github.com/flutter/flutter/issues/82208 - ios_platform_images # Can't use Flutter integration tests due to native modal UI. - file_selector_ios - file_selector ================================================ FILE: script/configs/exclude_integration_linux.yaml ================================================ # Can't use Flutter integration tests due to native modal UI. - file_selector - file_selector_linux ================================================ FILE: script/configs/exclude_integration_macos.yaml ================================================ # Can't use Flutter integration tests due to native modal UI. - file_selector - file_selector_macos ================================================ FILE: script/configs/exclude_integration_web.yaml ================================================ # Currently missing: https://github.com/flutter/flutter/issues/82211 - file_selector ================================================ FILE: script/configs/exclude_integration_win32.yaml ================================================ # Can't use Flutter integration tests due to native modal UI. - file_selector - file_selector_windows - image_picker_windows ================================================ FILE: script/configs/exclude_native_unit_android.yaml ================================================ # No need for unit tests: - espresso ================================================ FILE: script/configs/temp_exclude_excerpt.yaml ================================================ # Packages that have not yet adopted code-excerpt. # # This only exists to allow incrementally adopting the new requirement. # Packages shoud never be added to this list. # TODO(ecosystem): Remove everything from this list. See # https://github.com/flutter/flutter/issues/102679 - camera_web - espresso - google_maps_flutter/google_maps_flutter - google_sign_in/google_sign_in - google_sign_in_web - image_picker/image_picker - image_picker_for_web - in_app_purchase/in_app_purchase - ios_platform_images - path_provider/path_provider - plugin_platform_interface - quick_actions/quick_actions - shared_preferences/shared_preferences - webview_flutter_android - webview_flutter_web ================================================ FILE: script/install_chromium.sh ================================================ #!/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # This script may be run as: # $ CHROME_DOWNLOAD_DIR=./whatever script/install_chromium.sh set -e set -x # The target directory where chromium is going to be downloaded : "${CHROME_DOWNLOAD_DIR:=/tmp/chromium}" # Default value for the CHROME_DOWNLOAD_DIR env. # The build of Chromium used to test web functionality. # # Chromium builds can be located here: https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Linux_x64/ # # Check: https://github.com/flutter/engine/blob/master/lib/web_ui/dev/browser_lock.yaml : "${CHROMIUM_BUILD:=950363}" # Default value for the CHROMIUM_BUILD env. # Convenience defaults for CHROME_EXECUTABLE and CHROMEDRIVER_EXECUTABLE. These # two values should be set in the environment from CI, so this script can validate # that it has completed downloading chrome and driver successfully (and the expected # files are executable) : "${CHROME_EXECUTABLE:=$CHROME_DOWNLOAD_DIR/chrome-linux/chrome}" : "${CHROMEDRIVER_EXECUTABLE:=$CHROME_DOWNLOAD_DIR/chromedriver/chromedriver}" # The correct ChromeDriver is distributed alongside the chromium build above, as # `chromedriver_linux64.zip`, so no need to hardcode any extra info about it. readonly DOWNLOAD_ROOT="https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F${CHROMIUM_BUILD}%2F" # Install Chromium. mkdir "$CHROME_DOWNLOAD_DIR" readonly CHROMIUM_ZIP_FILE="$CHROME_DOWNLOAD_DIR/chromium.zip" wget --no-verbose "${DOWNLOAD_ROOT}chrome-linux.zip?alt=media" -O "$CHROMIUM_ZIP_FILE" unzip -q "$CHROMIUM_ZIP_FILE" -d "$CHROME_DOWNLOAD_DIR/" # Install ChromeDriver. readonly DRIVER_ZIP_FILE="$CHROME_DOWNLOAD_DIR/chromedriver.zip" wget --no-verbose "${DOWNLOAD_ROOT}chromedriver_linux64.zip?alt=media" -O "$DRIVER_ZIP_FILE" unzip -q "$DRIVER_ZIP_FILE" -d "$CHROME_DOWNLOAD_DIR/" # Rename CHROME_DOWNLOAD_DIR/chromedriver_linux64 to the expected CHROME_DOWNLOAD_DIR/chromedriver mv -T "$CHROME_DOWNLOAD_DIR/chromedriver_linux64" "$CHROME_DOWNLOAD_DIR/chromedriver" # Echo info at the end for ease of debugging. # # exports from this script cannot be used elsewhere in the .cirrus.yml file. set +x echo echo "$CHROME_EXECUTABLE" "$CHROME_EXECUTABLE" --version echo "$CHROMEDRIVER_EXECUTABLE" "$CHROMEDRIVER_EXECUTABLE" --version echo ================================================ FILE: script/tool/CHANGELOG.md ================================================ ## 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. ## 0.13.1 * Updates `version-check` to recognize Pigeon's platform test structure. * Pins `package:git` dependency to `2.0.x` until `dart >=2.18.0` becomes our oldest legacy. * Updates test mocks. ## 0.13.0 * Renames `all-plugins-app` to `create-all-packages-app` to clarify what it actually does. Also renames the project directory it creates from `all_plugins` to `all_packages`. ## 0.12.1 * Modifies `publish_check_command.dart` to do a `dart pub get` in all examples of the package being checked. Workaround for [dart-lang/pub#3618](https://github.com/dart-lang/pub/issues/3618). ## 0.12.0 * Changes the behavior of `--packages-for-branch` on main/master to run for packages changed in the last commit, rather than running for all packages. This allows CI to test the same filtered set of packages in post-submit as are tested in presubmit. * Adds a `fix` command to run `dart fix --apply` in target packages. ## 0.11.0 * Renames `publish-plugin` to `publish`. * Renames arguments to `list`: * `--package` now lists top-level packages (previously `--plugin`). * `--package-or-subpackage` now lists top-level packages (previously `--package`). ## 0.10.0+1 * Recognizes `run_test.sh` as a developer-only file in `version-check`. * Adds `readme-check` validation that the example/README.md for a federated plugin's implementation packages has a warning about the intended use of the example instead of the template boilerplate. ## 0.10.0 * Improves the logic in `version-check` to determine what changes don't require version changes, as well as making any dev-only changes also not require changelog changes since in practice we almost always override the check in that case. * Removes special-case handling of Dependabot PRs, and the (fragile) `--change-description-file` flag was only still used for that case, as the improved diff analysis now handles that case more robustly. ## 0.9.3 * Raises minimum `compileSdkVersion` to 32 for the `all-plugins-app` command. ## 0.9.2 * Adds checking of `code-excerpt` configuration to `readme-check`, to validate that if the excerpting tags are added to a README they are actually being used. ## 0.9.1 * Adds a `--downgrade` flag to `analyze` for analyzing with the oldest possible versions of packages. ## 0.9.0 * Replaces PR-description-based version/changelog/breaking change check overrides in `version-check` with label-based overrides using a new `pr-labels` flag, since we don't actually have reliable access to the PR description in checks. ## 0.8.10 - Adds a new `remove-dev-dependencies` command to remove `dev_dependencies` entries to make legacy version analysis possible in more cases. - Adds a `--lib-only` option to `analyze` to allow only analyzing the client parts of a library for legacy verison compatibility. ## 0.8.9 - Includes `dev_dependencies` when overridding dependencies using `make-deps-path-based`. - Bypasses version and CHANGELOG checks for Dependabot PRs for packages that are known not to be client-affecting. ## 0.8.8 - Allows pre-release versions in `version-check`. ## 0.8.7 - Supports empty custom analysis allow list files. - `drive-examples` now validates files to ensure that they don't accidentally use `test(...)`. - Adds a new `dependabot-check` command to ensure complete Dependabot coverage. - Adds `skip-if-not-supporting-dart-version` to allow for the same use cases as `skip-if-not-supporting-flutter-version` but for packages without Flutter constraints. ## 0.8.6 - Adds `update-release-info` to apply changelog and optional version changes across multiple packages. - Fixes changelog validation when reverting to a `NEXT` state. - Fixes multiplication of `--force` flag when publishing multiple packages. - Adds minimum deployment target flags to `xcode-analyze` to allow enforcing deprecation warning handling in advance of actually dropping support for an OS version. - Checks for template boilerplate in `readme-check`. - `readme-check` now validates example READMEs when present. ## 0.8.5 - Updates `test` to inculde the Dart unit tests of examples, if any. - `drive-examples` now supports non-plugin packages. - Commands that iterate over examples now include non-Flutter example packages. ## 0.8.4 - `readme-check` now validates that there's a info tag on code blocks to identify (and for supported languages, syntax highlight) the language. - `readme-check` now has a `--require-excerpts` flag to require that any Dart code blocks be managed by `code_excerpter`. ## 0.8.3 - Adds a new `update-excerpts` command to maintain README files using the `code-excerpter` package from flutter/site-shared. - `license-check` now ignores submodules. - Allows `make-deps-path-based` to skip packages it has alredy rewritten, so that running multiple times won't fail after the first time. - Removes UWP support, since Flutter has dropped support for UWP. ## 0.8.2+1 - Adds a new `readme-check` command. - Updates `publish-plugin` command documentation. - Fixes `all-plugins-app` to preserve the original application's Dart SDK version to avoid changing language feature opt-ins that the template may rely on. - Fixes `custom-test` to run `pub get` before running Dart test scripts. ## 0.8.2 - Adds a new `custom-test` command. - Switches from deprecated `flutter packages` alias to `flutter pub`. ## 0.8.1 - Fixes an `analyze` regression in 0.8.0 with packages that have non-`example` sub-packages. ## 0.8.0 - Ensures that `firebase-test-lab` runs include an `integration_test` runner. - Adds a `make-deps-path-based` command to convert inter-repo package dependencies to path-based dependencies. - Adds a (hidden) `--run-on-dirty-packages` flag for use with `make-deps-path-based` in CI. - `--packages` now allows using a federated plugin's package as a target without fully specifying it (if it is not the same as the plugin's name). E.g., `--packages=path_provide_ios` now works. - `--run-on-changed-packages` now includes only the changed packages in a federated plugin, not all packages in that plugin. - Fixes `federation-safety-check` handling of plugin deletion, and of top-level files in unfederated plugins whose names match federated plugin heuristics (e.g., `packages/foo/foo_android.iml`). - Adds an auto-retry for failed Firebase Test Lab tests as a short-term patch for flake issues. - Adds support for `CHROME_EXECUTABLE` in `drive-examples` to match similar `flutter` behavior. - Validates `default_package` entries in plugins. - Removes `allow-warnings` from the `podspecs` command. - Adds `skip-if-not-supporting-flutter-version` to allow running tests using a version of Flutter that not all packages support. (E.g., to allow for running some tests against old versions of Flutter to help avoid accidental breakage.) ## 0.7.3 - `native-test` now builds unit tests before running them on Windows and Linux, matching the behavior of other platforms. - Adds `--log-timing` to add timing information to package headers in looping commands. - Adds a `--check-for-missing-changes` flag to `version-check` that requires version updates (except for recognized exemptions) and CHANGELOG changes when modifying packages, unless the PR description explains why it's not needed. ## 0.7.2 - Update Firebase Testlab deprecated test device. (Pixel 4 API 29 -> Pixel 5 API 30). - `native-test --android`, `--ios`, and `--macos` now fail plugins that don't have unit tests, rather than skipping them. - Added a new `federation-safety-check` command to help catch changes to federated packages that have been done in such a way that they will pass in CI, but fail once the change is landed and published. - `publish-check` now validates that there is an `AUTHORS` file. - Added flags to `version-check` to allow overriding the platform interface major version change restriction. - Improved error handling and error messages in CHANGELOG version checks. - `license-check` now validates Kotlin files. - `pubspec-check` now checks that the description is of the pub-recommended length. - Fix `license-check` when run on Windows with line ending conversion enabled. - Fixed `pubspec-check` on Windows. - Add support for `main` as a primary branch. `master` continues to work for compatibility. ## 0.7.1 - Add support for `.pluginToolsConfig.yaml` in the `build-examples` command. ## 0.7.0 - `native-test` now supports `--linux` for unit tests. - Formatting now skips Dart files that contain a line that exactly matches the string `// This file is hand-formatted.`. ## 0.6.0+1 - Fixed `build-examples` to work for non-plugin packages. ## 0.6.0 - Added Android native integration test support to `native-test`. - Added a new `android-lint` command to lint Android plugin native code. - Pubspec validation now checks for `implements` in implementation packages. - Pubspec valitation now checks the full relative path of `repository` entries. - `build-examples` now supports UWP plugins via a `--winuwp` flag. - `native-test` now supports `--windows` for unit tests. - **Breaking change**: `publish` no longer accepts `--no-tag-release` or `--no-push-flags`. Releases now always tag and push. - **Breaking change**: `publish`'s `--package` flag has been replaced with the `--packages` flag used by most other packages. - **Breaking change** Passing both `--run-on-changed-packages` and `--packages` is now an error; previously it the former would be ignored. ## 0.5.0 - `--exclude` and `--custom-analysis` now accept paths to YAML files that contain lists of packages to exclude, in addition to just package names, so that exclude lists can be maintained separately from scripts and CI configuration. - Added an `xctest` flag to select specific test targets, to allow running only unit tests or integration tests. - **Breaking change**: Split Xcode analysis out of `xctest` and into a new `xcode-analyze` command. - Fixed a bug that caused `firebase-test-lab` to hang if it tried to run more than one plugin's tests in a single run. - **Breaking change**: If `firebase-test-lab` is run on a package that supports Android, but for which no tests are run, it now fails instead of skipping. This matches `drive-examples`, as this command is what is used for driving Android Flutter integration tests on CI. - **Breaking change**: Replaced `xctest` with a new `native-test` command that will eventually be able to run native unit and integration tests for all platforms. - Adds the ability to disable test types via `--no-unit` or `--no-integration`. - **Breaking change**: Replaced `java-test` with Android unit test support for the new `native-test` command. - Commands that print a run summary at the end now track and log exclusions similarly to skips for easier auditing. - `version-check` now validates that `NEXT` is not present when changing the version. ## 0.4.1 - Improved `license-check` output. - Use `java -version` rather than `java --version`, for compatibility with more versions of Java. ## 0.4.0 - Modified the output format of many commands - **Breaking change**: `firebase-test-lab` no longer supports `*_e2e.dart` files, only `integration_test/*_test.dart`. - Add a summary to the end of successful command runs for commands using the new output format. - Fixed some cases where a failure in a command for a single package would immediately abort the test. - Deprecated `--plugins` in favor of new `--packages`. `--plugins` continues to work for now, but will be removed in the future. - Make `drive-examples` device detection robust against Flutter tool banners. - `format` is now supported on Windows. ## 0.3.0 - Add a --build-id flag to `firebase-test-lab` instead of hard-coding the use of `CIRRUS_BUILD_ID`. `CIRRUS_BUILD_ID` is the default value for that flag, for backward compatibility. - `xctest` now supports running macOS tests in addition to iOS - **Breaking change**: it now requires an `--ios` and/or `--macos` flag. - **Breaking change**: `build-examples` for iOS now uses `--ios` rather than `--ipa`. - The tooling now runs in strong null-safe mode. - `publish plugins` check against pub.dev to determine if a release should happen. - Modified the output format of many commands - Removed `podspec`'s `--skip` in favor of `--ignore` using the new structure. ## 0.2.0 - Remove `xctest`'s `--skip`, which is redundant with `--ignore`. ## 0.1.4 - Add a `pubspec-check` command ## 0.1.3 - Cosmetic fix to `publish-check` output - Add a --dart-sdk option to `analyze` - Allow reverts in `version-check` ## 0.1.2 - Add `against-pub` flag for version-check, which allows the command to check version with pub. - Add `machine` flag for publish-check, which replaces outputs to something parsable by machines. - Add `skip-conformation` flag to publish-plugin to allow auto publishing. - Change `run-on-changed-packages` to consider all packages as changed if any files have been changed that could affect the entire repository. ## 0.1.1 - Update the allowed third-party licenses for flutter/packages. ## 0.1.0+1 - Re-add the bin/ directory. ## 0.1.0 - **NOTE**: This is no longer intended as a general-purpose package, and is now supported only for flutter/plugins and flutter/tools. - Fix version checks - Remove handling of pre-release null-safe versions - Fix build all for null-safe template apps - Improve handling of web integration tests - Supports enforcing standardized copyright files - Improve handling of iOS tests ## v.0.0.45+3 - Pin `collection` to `1.14.13` to be able to target Flutter stable (v1.22.6). ## v.0.0.45+2 - Make `publish-plugin` to work on non-flutter packages. ## v.0.0.45+1 - Don't call `flutter format` if there are no Dart files to format. ## v.0.0.45 - Add exclude flag to exclude any plugin from further processing. ## v.0.0.44+7 - `all-plugins-app` doesn't override the AGP version. ## v.0.0.44+6 - Fix code formatting. ## v.0.0.44+5 - Remove `-v` flag on drive-examples. ## v.0.0.44+4 - Fix bug where directory isn't passed ## v.0.0.44+3 - More verbose logging ## v.0.0.44+2 - Remove pre-alpha Windows workaround to create examples on the fly. ## v.0.0.44+1 - Print packages that passed tests in `xctest` command. - Remove printing the whole list of simulators. ## v.0.0.44 - Add 'xctest' command to run xctests. ## v.0.0.43 - Allow minor `*-nullsafety` pre release packages. ## v.0.0.42+1 - Fix test command when `--enable-experiment` is called. ## v.0.0.42 - Allow `*-nullsafety` pre release packages. ## v.0.0.41 - Support `--enable-experiment` flag in subcommands `test`, `build-examples`, `drive-examples`, and `firebase-test-lab`. ## v.0.0.40 - Support `integration_test/` directory for `drive-examples` command ## v.0.0.39 - Support `integration_test/` directory for `package:integration_test` ## v.0.0.38 - Add C++ and ObjC++ to clang-format. ## v.0.0.37+2 - Make `http` and `http_multi_server` dependency version constraint more flexible. ## v.0.0.37+1 - All_plugin test puts the plugin dependencies into dependency_overrides. ## v.0.0.37 - Only builds mobile example apps when necessary. ## v.0.0.36+3 - Add support for Linux plugins. ## v.0.0.36+2 - Default to showing podspec lint warnings ## v.0.0.36+1 - Serialize linting podspecs. ## v.0.0.36 - Remove retry on Firebase Test Lab's call to gcloud set. - Remove quiet flag from Firebase Test Lab's gcloud set command. - Allow Firebase Test Lab command to continue past gcloud set network failures. This is a mitigation for the network service sometimes not responding, but it isn't actually necessary to have a network connection for this command. ## v.0.0.35+1 - Minor cleanup to the analyze test. ## v.0.0.35 - Firebase Test Lab command generates a configurable unique path suffix for results. ## v.0.0.34 - Firebase Test Lab command now only tries to configure the project once - Firebase Test Lab command now retries project configuration up to five times. ## v.0.0.33+1 - Fixes formatting issues that got past our CI due to https://github.com/flutter/flutter/issues/51585. - Changes the default package name for testing method `createFakePubspec` back its previous behavior. ## v.0.0.33 - Version check command now fails on breaking changes to platform interfaces. - Updated version check test to be more flexible. ## v.0.0.32+7 - Ensure that Firebase Test Lab tests have a unique storage bucket for each test run. ## v.0.0.32+6 - Ensure that Firebase Test Lab tests have a unique storage bucket for each package. ## v.0.0.32+5 - Remove --fail-fast and --silent from lint podspec command. ## v.0.0.32+4 - Update `publish-plugin` to use `flutter pub publish` instead of just `pub publish`. Enforces a `pub publish` command that matches the Dart SDK in the user's Flutter install. ## v.0.0.32+3 - Update Firebase Testlab deprecated test device. (Pixel 3 API 28 -> Pixel 4 API 29). ## v.0.0.32+2 - Runs pub get before building macos to avoid failures. ## v.0.0.32+1 - Default macOS example builds to false. Previously they were running whenever CI was itself running on macOS. ## v.0.0.32 - `analyze` now asserts that the global `analysis_options.yaml` is the only one by default. Individual directories can be excluded from this check with the new `--custom-analysis` flag. ## v.0.0.31+1 - Add --skip and --no-analyze flags to podspec command. ## v.0.0.31 - Add support for macos on `DriveExamplesCommand` and `BuildExamplesCommand`. ## v.0.0.30 - Adopt pedantic analysis options, fix firebase_test_lab_test. ## v.0.0.29 - Add a command to run pod lib lint on podspec files. ## v.0.0.28 - Increase Firebase test lab timeouts to 5 minutes. ## v.0.0.27 - Run tests with `--platform=chrome` for web plugins. ## v.0.0.26 - Add a command for publishing plugins to pub. ## v.0.0.25 - Update `DriveExamplesCommand` to use `ProcessRunner`. - Make `DriveExamplesCommand` rely on `ProcessRunner` to determine if the test fails or not. - Add simple tests for `DriveExamplesCommand`. ## v.0.0.24 - Gracefully handle pubspec.yaml files for new plugins. - Additional unit testing. ## v.0.0.23 - Add a test case for transitive dependency solving in the `create_all_plugins_app` command. ## v.0.0.22 - Updated firebase-test-lab command with updated conventions for test locations. - Updated firebase-test-lab to add an optional "device" argument. - Updated version-check command to always compare refs instead of using the working copy. - Added unit tests for the firebase-test-lab and version-check commands. - Add ProcessRunner to mock running processes for testing. ## v.0.0.21 - Support the `--plugins` argument for federated plugins. ## v.0.0.20 - Support for finding federated plugins, where one directory contains multiple packages for different platform implementations. ## v.0.0.19+3 - Use `package:file` for file I/O. ## v.0.0.19+2 - Use java as language when calling `flutter create`. ## v.0.0.19+1 - Rename command for `CreateAllPluginsAppCommand`. ## v.0.0.19 - Use flutter create to build app testing plugin compilation. ## v.0.0.18+2 - Fix `.travis.yml` file name in `README.md`. ## v0.0.18+1 - Skip version check if it contains `publish_to: none`. ## v0.0.18 - Add option to exclude packages from generated pubspec command. ## v0.0.17+4 - Avoid trying to version-check pubspecs that are missing a version. ## v0.0.17+3 - version-check accounts for [pre-1.0 patch versions](https://github.com/flutter/flutter/issues/35412). ## v0.0.17+2 - Fix exception handling for version checker ## v0.0.17+1 - Fix bug where we used a flag instead of an option ## v0.0.17 - Add a command for checking the version number ## v0.0.16 - Add a command for generating `pubspec.yaml` for All Plugins app. ## v0.0.15 - Add a command for running driver tests of plugin examples. ## v0.0.14 - Check for dependencies->flutter instead of top level flutter node. ## v0.0.13 - Differentiate between Flutter and non-Flutter (but potentially Flutter consumed) Dart packages. ================================================ FILE: script/tool/LICENSE ================================================ 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. ================================================ FILE: script/tool/README.md ================================================ # Removed See https://github.com/flutter/packages/blob/main/script/tool/README.md for the current location of this tooling. ## Temporary shim This is a temporary, minimal version of the tools sufficient to keep the following scripts running until the repository merge is complete and they are updated to use flutter/packages instead: - [dart-lang analysis](https://github.com/dart-lang/sdk/blob/main/tools/bots/flutter/analyze_flutter_plugins.sh) - [flutter/flutter analysis](https://github.com/flutter/flutter/blob/master/dev/bots/test.dart) ================================================ FILE: script/tool/analysis_options.yaml ================================================ include: ../../analysis_options.yaml linter: rules: avoid_print: false # The tool is a CLI, so printing is normal ================================================ FILE: script/tool/bin/flutter_plugin_tools.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. export 'package:flutter_plugin_tools/src/main.dart'; ================================================ FILE: script/tool/lib/src/analyze_command.dart ================================================ // Copyright 2013 The Flutter 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:file/file.dart'; import 'package:platform/platform.dart'; import 'package:yaml/yaml.dart'; import 'common/core.dart'; import 'common/package_looping_command.dart'; import 'common/process_runner.dart'; import 'common/repository_package.dart'; /// A command to run Dart analysis on packages. class AnalyzeCommand extends PackageLoopingCommand { /// Creates a analysis command instance. AnalyzeCommand( Directory packagesDir, { ProcessRunner processRunner = const ProcessRunner(), Platform platform = const LocalPlatform(), }) : super(packagesDir, processRunner: processRunner, platform: platform) { argParser.addMultiOption(_customAnalysisFlag, help: 'Directories (comma separated) that are allowed to have their own ' 'analysis options.\n\n' 'Alternately, a list of one or more YAML files that contain a list ' 'of allowed directories.', defaultsTo: []); argParser.addOption(_analysisSdk, valueHelp: 'dart-sdk', help: 'An optional path to a Dart SDK; this is used to override the ' 'SDK used to provide analysis.'); } static const String _customAnalysisFlag = 'custom-analysis'; static const String _analysisSdk = 'analysis-sdk'; late String _dartBinaryPath; Set _allowedCustomAnalysisDirectories = const {}; @override final String name = 'analyze'; @override final String description = 'Analyzes all packages using dart analyze.\n\n' 'This command requires "dart" and "flutter" to be in your path.'; @override final bool hasLongOutput = false; /// Checks that there are no unexpected analysis_options.yaml files. bool _hasUnexpecetdAnalysisOptions(RepositoryPackage package) { final List files = package.directory.listSync(recursive: true); for (final FileSystemEntity file in files) { if (file.basename != 'analysis_options.yaml' && file.basename != '.analysis_options') { continue; } final bool allowed = _allowedCustomAnalysisDirectories.any( (String directory) => directory.isNotEmpty && path.isWithin( packagesDir.childDirectory(directory).path, file.path)); if (allowed) { continue; } printError( 'Found an extra analysis_options.yaml at ${file.absolute.path}.'); printError( 'If this was deliberate, pass the package to the analyze command ' 'with the --$_customAnalysisFlag flag and try again.'); return true; } return false; } @override Future initializeRun() async { _allowedCustomAnalysisDirectories = getStringListArg(_customAnalysisFlag).expand((String item) { if (item.endsWith('.yaml')) { final File file = packagesDir.fileSystem.file(item); final Object? yaml = loadYaml(file.readAsStringSync()); if (yaml == null) { return []; } return (yaml as YamlList).toList().cast(); } return [item]; }).toSet(); // Use the Dart SDK override if one was passed in. final String? dartSdk = argResults![_analysisSdk] as String?; _dartBinaryPath = dartSdk == null ? 'dart' : path.join(dartSdk, 'bin', 'dart'); } @override Future runForPackage(RepositoryPackage package) async { // Analysis runs over the package and all subpackages (unless only lib/ is // being analyzed), so all of them need `flutter pub get` run before // analyzing. `example` packages can be skipped since 'flutter packages get' // automatically runs `pub get` in examples as part of handling the parent // directory. final List packagesToGet = [ package, ...await getSubpackages(package).toList(), ]; for (final RepositoryPackage packageToGet in packagesToGet) { if (packageToGet.directory.basename != 'example' || !RepositoryPackage(packageToGet.directory.parent) .pubspecFile .existsSync()) { if (!await _runPubCommand(packageToGet, 'get')) { return PackageResult.fail(['Unable to get dependencies']); } } } if (_hasUnexpecetdAnalysisOptions(package)) { return PackageResult.fail(['Unexpected local analysis options']); } final int exitCode = await processRunner.runAndStream( _dartBinaryPath, ['analyze', '--fatal-infos'], workingDir: package.directory); if (exitCode != 0) { return PackageResult.fail(); } return PackageResult.success(); } Future _runPubCommand(RepositoryPackage package, String command) async { final int exitCode = await processRunner.runAndStream( flutterCommand, ['pub', command], workingDir: package.directory); return exitCode == 0; } } ================================================ FILE: script/tool/lib/src/common/core.dart ================================================ // Copyright 2013 The Flutter 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:colorize/colorize.dart'; import 'package:file/file.dart'; /// The signature for a print handler for commands that allow overriding the /// print destination. typedef Print = void Function(Object? object); /// Key for APK (Android) platform. const String platformAndroid = 'android'; /// Key for IPA (iOS) platform. const String platformIOS = 'ios'; /// Key for linux platform. const String platformLinux = 'linux'; /// Key for macos platform. const String platformMacOS = 'macos'; /// Key for Web platform. const String platformWeb = 'web'; /// Key for windows platform. const String platformWindows = 'windows'; /// Key for enable experiment. const String kEnableExperiment = 'enable-experiment'; /// Target platforms supported by Flutter. // ignore: public_member_api_docs enum FlutterPlatform { android, ios, linux, macos, web, windows } /// Returns whether the given directory is a Dart package. bool isPackage(FileSystemEntity entity) { if (entity is! Directory) { return false; } // According to // https://dart.dev/guides/libraries/create-library-packages#what-makes-a-library-package // a package must also have a `lib/` directory, but in practice that's not // always true. flutter/plugins has some special cases (espresso, some // federated implementation packages) that don't have any source, so this // deliberately doesn't check that there's a lib directory. return entity.childFile('pubspec.yaml').existsSync(); } /// Prints `successMessage` in green. void printSuccess(String successMessage) { print(Colorize(successMessage)..green()); } /// Prints `errorMessage` in red. void printError(String errorMessage) { print(Colorize(errorMessage)..red()); } /// Error thrown when a command needs to exit with a non-zero exit code. /// /// While there is no specific definition of the meaning of different non-zero /// exit codes for this tool, commands should follow the general convention: /// 1: The command ran correctly, but found errors. /// 2: The command failed to run because the arguments were invalid. /// >2: The command failed to run correctly for some other reason. Ideally, /// each such failure should have a unique exit code within the context of /// that command. class ToolExit extends Error { /// Creates a tool exit with the given [exitCode]. ToolExit(this.exitCode); /// The code that the process should exit with. final int exitCode; } /// A exit code for [ToolExit] for a successful run that found errors. const int exitCommandFoundErrors = 1; /// A exit code for [ToolExit] for a failure to run due to invalid arguments. const int exitInvalidArguments = 2; ================================================ FILE: script/tool/lib/src/common/git_version_finder.dart ================================================ // Copyright 2013 The Flutter 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:git/git.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:yaml/yaml.dart'; /// Finding diffs based on `baseGitDir` and `baseSha`. class GitVersionFinder { /// Constructor GitVersionFinder(this.baseGitDir, String? baseSha) : _baseSha = baseSha; /// The top level directory of the git repo. /// /// That is where the .git/ folder exists. final GitDir baseGitDir; /// The base sha used to get diff. String? _baseSha; static bool _isPubspec(String file) { return file.trim().endsWith('pubspec.yaml'); } /// Get a list of all the pubspec.yaml file that is changed. Future> getChangedPubSpecs() async { return (await getChangedFiles()).where(_isPubspec).toList(); } /// Get a list of all the changed files. Future> getChangedFiles( {bool includeUncommitted = false}) async { final String baseSha = await getBaseSha(); final io.ProcessResult changedFilesCommand = await baseGitDir .runCommand([ 'diff', '--name-only', baseSha, if (!includeUncommitted) 'HEAD' ]); final String changedFilesStdout = changedFilesCommand.stdout.toString(); if (changedFilesStdout.isEmpty) { return []; } final List changedFiles = changedFilesStdout.split('\n') ..removeWhere((String element) => element.isEmpty); return changedFiles.toList(); } /// Get a list of all the changed files. Future> getDiffContents({ String? targetPath, bool includeUncommitted = false, }) async { final String baseSha = await getBaseSha(); final io.ProcessResult diffCommand = await baseGitDir.runCommand([ 'diff', baseSha, if (!includeUncommitted) 'HEAD', if (targetPath != null) ...['--', targetPath], ]); final String diffStdout = diffCommand.stdout.toString(); if (diffStdout.isEmpty) { return []; } final List changedFiles = diffStdout.split('\n') ..removeWhere((String element) => element.isEmpty); return changedFiles.toList(); } /// Get the package version specified in the pubspec file in `pubspecPath` and /// at the revision of `gitRef` (defaulting to the base if not provided). Future getPackageVersion(String pubspecPath, {String? gitRef}) async { final String ref = gitRef ?? (await getBaseSha()); io.ProcessResult gitShow; try { gitShow = await baseGitDir.runCommand(['show', '$ref:$pubspecPath']); } on io.ProcessException { return null; } final String fileContent = gitShow.stdout as String; if (fileContent.trim().isEmpty) { return null; } final YamlMap fileYaml = loadYaml(fileContent) as YamlMap; final String? versionString = fileYaml['version'] as String?; return versionString == null ? null : Version.parse(versionString); } /// Returns the base used to diff against. Future getBaseSha() async { String? baseSha = _baseSha; if (baseSha != null && baseSha.isNotEmpty) { return baseSha; } io.ProcessResult baseShaFromMergeBase = await baseGitDir.runCommand( ['merge-base', '--fork-point', 'FETCH_HEAD', 'HEAD'], throwOnError: false); final String stdout = (baseShaFromMergeBase.stdout as String? ?? '').trim(); final String stderr = (baseShaFromMergeBase.stderr as String? ?? '').trim(); if (stderr.isNotEmpty || stdout.isEmpty) { baseShaFromMergeBase = await baseGitDir .runCommand(['merge-base', 'FETCH_HEAD', 'HEAD']); } baseSha = (baseShaFromMergeBase.stdout as String).trim(); _baseSha = baseSha; return baseSha; } } ================================================ FILE: script/tool/lib/src/common/package_command.dart ================================================ // Copyright 2013 The Flutter 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 'dart:math'; import 'package:args/command_runner.dart'; import 'package:file/file.dart'; import 'package:git/git.dart'; import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'package:yaml/yaml.dart'; import 'core.dart'; import 'git_version_finder.dart'; import 'process_runner.dart'; import 'repository_package.dart'; /// An entry in package enumeration for APIs that need to include extra /// data about the entry. class PackageEnumerationEntry { /// Creates a new entry for the given package. PackageEnumerationEntry(this.package, {required this.excluded}); /// The package this entry corresponds to. Be sure to check `excluded` before /// using this, as having an entry does not necessarily mean that the package /// should be included in the processing of the enumeration. final RepositoryPackage package; /// Whether or not this package was excluded by the command invocation. final bool excluded; } /// Interface definition for all commands in this tool. // TODO(stuartmorgan): Move most of this logic to PackageLoopingCommand. abstract class PackageCommand extends Command { /// Creates a command to operate on [packagesDir] with the given environment. PackageCommand( this.packagesDir, { this.processRunner = const ProcessRunner(), this.platform = const LocalPlatform(), GitDir? gitDir, }) : _gitDir = gitDir { argParser.addMultiOption( _packagesArg, help: 'Specifies which packages the command should run on (before sharding).\n', valueHelp: 'package1,package2,...', aliases: [_pluginsLegacyAliasArg], ); argParser.addOption( _shardIndexArg, help: 'Specifies the zero-based index of the shard to ' 'which the command applies.', valueHelp: 'i', defaultsTo: '0', ); argParser.addOption( _shardCountArg, help: 'Specifies the number of shards into which packages are divided.', valueHelp: 'n', defaultsTo: '1', ); argParser.addMultiOption( _excludeArg, abbr: 'e', help: 'A list of packages to exclude from from this command.\n\n' 'Alternately, a list of one or more YAML files that contain a list ' 'of packages to exclude.', defaultsTo: [], ); argParser.addFlag(_runOnChangedPackagesArg, help: 'Run the command on changed packages.\n' 'If no packages have changed, or if there have been changes that may\n' 'affect all packages, the command runs on all packages.\n' 'Packages excluded with $_excludeArg are excluded even if changed.\n' 'See $_baseShaArg if a custom base is needed to determine the diff.\n\n' 'Cannot be combined with $_packagesArg.\n'); argParser.addFlag(_runOnDirtyPackagesArg, help: 'Run the command on packages with changes that have not been committed.\n' 'Packages excluded with $_excludeArg are excluded even if changed.\n' 'Cannot be combined with $_packagesArg.\n', hide: true); argParser.addFlag(_packagesForBranchArg, help: 'This runs on all packages changed in the last commit on main ' '(or master), and behaves like --run-on-changed-packages on ' 'any other branch.\n\n' 'Cannot be combined with $_packagesArg.\n\n' 'This is intended for use in CI.\n', hide: true); argParser.addOption(_baseShaArg, help: 'The base sha used to determine git diff. \n' 'This is useful when $_runOnChangedPackagesArg is specified.\n' 'If not specified, merge-base is used as base sha.'); argParser.addFlag(_logTimingArg, help: 'Logs timing information.\n\n' 'Currently only logs per-package timing for multi-package commands, ' 'but more information may be added in the future.'); } static const String _baseShaArg = 'base-sha'; static const String _excludeArg = 'exclude'; static const String _logTimingArg = 'log-timing'; static const String _packagesArg = 'packages'; static const String _packagesForBranchArg = 'packages-for-branch'; static const String _pluginsLegacyAliasArg = 'plugins'; static const String _runOnChangedPackagesArg = 'run-on-changed-packages'; static const String _runOnDirtyPackagesArg = 'run-on-dirty-packages'; static const String _shardCountArg = 'shardCount'; static const String _shardIndexArg = 'shardIndex'; /// The directory containing the packages. final Directory packagesDir; /// The process runner. /// /// This can be overridden for testing. final ProcessRunner processRunner; /// The current platform. /// /// This can be overridden for testing. final Platform platform; /// The git directory to use. If unset, [gitDir] populates it from the /// packages directory's enclosing repository. /// /// This can be mocked for testing. GitDir? _gitDir; int? _shardIndex; int? _shardCount; // Cached set of explicitly excluded packages. Set? _excludedPackages; /// A context that matches the default for [platform]. p.Context get path => platform.isWindows ? p.windows : p.posix; /// The command to use when running `flutter`. String get flutterCommand => platform.isWindows ? 'flutter.bat' : 'flutter'; /// The shard of the overall command execution that this instance should run. int get shardIndex { if (_shardIndex == null) { _checkSharding(); } return _shardIndex!; } /// The number of shards this command is divided into. int get shardCount { if (_shardCount == null) { _checkSharding(); } return _shardCount!; } /// Returns the [GitDir] containing [packagesDir]. Future get gitDir async { GitDir? gitDir = _gitDir; if (gitDir != null) { return gitDir; } // Ensure there are no symlinks in the path, as it can break // GitDir's allowSubdirectory:true. final String packagesPath = packagesDir.resolveSymbolicLinksSync(); if (!await GitDir.isGitDir(packagesPath)) { printError('$packagesPath is not a valid Git repository.'); throw ToolExit(2); } gitDir = await GitDir.fromExisting(packagesDir.path, allowSubdirectory: true); _gitDir = gitDir; return gitDir; } /// Convenience accessor for boolean arguments. bool getBoolArg(String key) { return (argResults![key] as bool?) ?? false; } /// Convenience accessor for String arguments. String getStringArg(String key) { return (argResults![key] as String?) ?? ''; } /// Convenience accessor for List arguments. List getStringListArg(String key) { // Clone the list so that if a caller modifies the result it won't change // the actual arguments list for future queries. return List.from(argResults![key] as List? ?? []); } /// If true, commands should log timing information that might be useful in /// analyzing their runtime (e.g., the per-package time for multi-package /// commands). bool get shouldLogTiming => getBoolArg(_logTimingArg); void _checkSharding() { final int? shardIndex = int.tryParse(getStringArg(_shardIndexArg)); final int? shardCount = int.tryParse(getStringArg(_shardCountArg)); if (shardIndex == null) { usageException('$_shardIndexArg must be an integer'); } if (shardCount == null) { usageException('$_shardCountArg must be an integer'); } if (shardCount < 1) { usageException('$_shardCountArg must be positive'); } if (shardIndex < 0 || shardCount <= shardIndex) { usageException( '$_shardIndexArg must be in the half-open range [0..$shardCount['); } _shardIndex = shardIndex; _shardCount = shardCount; } /// Returns the set of packages to exclude based on the `--exclude` argument. Set getExcludedPackageNames() { final Set excludedPackages = _excludedPackages ?? getStringListArg(_excludeArg).expand((String item) { if (item.endsWith('.yaml')) { final File file = packagesDir.fileSystem.file(item); return (loadYaml(file.readAsStringSync()) as YamlList) .toList() .cast(); } return [item]; }).toSet(); // Cache for future calls. _excludedPackages = excludedPackages; return excludedPackages; } /// Returns the root diretories of the packages involved in this command /// execution. /// /// Depending on the command arguments, this may be a user-specified set of /// packages, the set of packages that should be run for a given diff, or all /// packages. /// /// By default, packages excluded via --exclude will not be in the stream, but /// they can be included by passing false for [filterExcluded]. Stream getTargetPackages( {bool filterExcluded = true}) async* { // To avoid assuming consistency of `Directory.list` across command // invocations, we collect and sort the package folders before sharding. // This is considered an implementation detail which is why the API still // uses streams. final List allPackages = await _getAllPackages().toList(); allPackages.sort((PackageEnumerationEntry p1, PackageEnumerationEntry p2) => p1.package.path.compareTo(p2.package.path)); final int shardSize = allPackages.length ~/ shardCount + (allPackages.length % shardCount == 0 ? 0 : 1); final int start = min(shardIndex * shardSize, allPackages.length); final int end = min(start + shardSize, allPackages.length); for (final PackageEnumerationEntry package in allPackages.sublist(start, end)) { if (!(filterExcluded && package.excluded)) { yield package; } } } /// Returns the root Dart package folders of the packages involved in this /// command execution, assuming there is only one shard. Depending on the /// command arguments, this may be a user-specified set of packages, the /// set of packages that should be run for a given diff, or all packages. /// /// This will return packages that have been excluded by the --exclude /// parameter, annotated in the entry as excluded. /// /// Packages can exist in the following places relative to the packages /// directory: /// /// 1. As a Dart package in a directory which is a direct child of the /// packages directory. This is a non-plugin package, or a non-federated /// plugin. /// 2. Several plugin packages may live in a directory which is a direct /// child of the packages directory. This directory groups several Dart /// packages which implement a single plugin. This directory contains an /// "app-facing" package which declares the API for the plugin, a /// platform interface package which declares the API for implementations, /// and one or more platform-specific implementation packages. /// 3./4. Either of the above, but in a third_party/packages/ directory that /// is a sibling of the packages directory. This is used for a small number /// of packages in the flutter/packages repository. Stream _getAllPackages() async* { final Set packageSelectionFlags = { _packagesArg, _runOnChangedPackagesArg, _runOnDirtyPackagesArg, _packagesForBranchArg, }; if (packageSelectionFlags .where((String flag) => argResults!.wasParsed(flag)) .length > 1) { printError('Only one of --$_packagesArg, --$_runOnChangedPackagesArg, or ' '--$_packagesForBranchArg can be provided.'); throw ToolExit(exitInvalidArguments); } Set packages = Set.from(getStringListArg(_packagesArg)); final GitVersionFinder? changedFileFinder; if (getBoolArg(_runOnChangedPackagesArg)) { changedFileFinder = await retrieveVersionFinder(); } else if (getBoolArg(_packagesForBranchArg)) { final String? branch = await _getBranch(); if (branch == null) { 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. // 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) { print( '--$_packagesForBranchArg: using parent commit as the diff base.'); changedFileFinder = GitVersionFinder(await gitDir, 'HEAD~'); } else { changedFileFinder = await retrieveVersionFinder(); } } } else { changedFileFinder = null; } if (changedFileFinder != null) { final String baseSha = await changedFileFinder.getBaseSha(); final List changedFiles = await changedFileFinder.getChangedFiles(); if (_changesRequireFullTest(changedFiles)) { print('Running for all packages, since a file has changed that could ' 'affect the entire repository.'); } else { print( 'Running for all packages that have diffs relative to "$baseSha"\n'); packages = _getChangedPackageNames(changedFiles); } } else if (getBoolArg(_runOnDirtyPackagesArg)) { final GitVersionFinder gitVersionFinder = GitVersionFinder(await gitDir, 'HEAD'); print('Running for all packages that have uncommitted changes\n'); // _changesRequireFullTest is deliberately not used here, as this flag is // intended for use in CI to re-test packages changed by // 'make-deps-path-based'. packages = _getChangedPackageNames( await gitVersionFinder.getChangedFiles(includeUncommitted: true)); // For the same reason, empty is not treated as "all packages" as it is // for other flags. if (packages.isEmpty) { return; } } final Directory thirdPartyPackagesDirectory = packagesDir.parent .childDirectory('third_party') .childDirectory('packages'); final Set excludedPackageNames = getExcludedPackageNames(); for (final Directory dir in [ packagesDir, if (thirdPartyPackagesDirectory.existsSync()) thirdPartyPackagesDirectory, ]) { await for (final FileSystemEntity entity in dir.list(followLinks: false)) { // A top-level Dart package is a standard package. if (isPackage(entity)) { if (packages.isEmpty || packages.contains(p.basename(entity.path))) { yield PackageEnumerationEntry( RepositoryPackage(entity as Directory), excluded: excludedPackageNames.contains(entity.basename)); } } else if (entity is Directory) { // Look for Dart packages under this top-level directory; this is the // standard structure for federated plugins. await for (final FileSystemEntity subdir in entity.list(followLinks: false)) { if (isPackage(subdir)) { // There are three ways for a federated plugin to match: // - package name (path_provider_android) // - fully specified name (path_provider/path_provider_android) // - group name (path_provider), which matches all packages in // the group final Set possibleMatches = { path.basename(subdir.path), // package name path.basename(entity.path), // group name path.relative(subdir.path, from: dir.path), // fully specified }; if (packages.isEmpty || packages.intersection(possibleMatches).isNotEmpty) { yield PackageEnumerationEntry( RepositoryPackage(subdir as Directory), excluded: excludedPackageNames .intersection(possibleMatches) .isNotEmpty); } } } } } } } /// Returns all Dart package folders (typically, base package + example) of /// the packages involved in this command execution. /// /// By default, packages excluded via --exclude will not be in the stream, but /// they can be included by passing false for [filterExcluded]. /// /// Subpackages are guaranteed to be after the containing package in the /// stream. Stream getTargetPackagesAndSubpackages( {bool filterExcluded = true}) async* { await for (final PackageEnumerationEntry package in getTargetPackages(filterExcluded: filterExcluded)) { yield package; yield* getSubpackages(package.package).map( (RepositoryPackage subPackage) => PackageEnumerationEntry(subPackage, excluded: package.excluded)); } } /// Returns all Dart package folders (e.g., examples) under the given package. Stream getSubpackages(RepositoryPackage package, {bool filterExcluded = true}) async* { yield* package.directory .list(recursive: true, followLinks: false) .where(isPackage) .map((FileSystemEntity directory) => // isPackage guarantees that this cast is valid. RepositoryPackage(directory as Directory)); } /// Returns the files contained, recursively, within the packages /// involved in this command execution. Stream getFiles() { return getTargetPackages().asyncExpand( (PackageEnumerationEntry entry) => getFilesForPackage(entry.package)); } /// Returns the files contained, recursively, within [package]. Stream getFilesForPackage(RepositoryPackage package) { return package.directory .list(recursive: true, followLinks: false) .where((FileSystemEntity entity) => entity is File) .cast(); } /// Retrieve an instance of [GitVersionFinder] based on `_baseShaArg` and [gitDir]. /// /// Throws tool exit if [gitDir] nor root directory is a git directory. Future retrieveVersionFinder() async { final String baseSha = getStringArg(_baseShaArg); final GitVersionFinder gitVersionFinder = GitVersionFinder(await gitDir, baseSha); return gitVersionFinder; } // Returns the names of packages that have been changed given a list of // changed files. // // The names will either be the actual package names, or potentially // group/name specifiers (for example, path_provider/path_provider) for // packages in federated plugins. // // The paths must use POSIX separators (e.g., as provided by git output). Set _getChangedPackageNames(List changedFiles) { final Set packages = {}; // A helper function that returns true if candidatePackageName looks like an // implementation package of a plugin called pluginName. Used to determine // if .../packages/parentName/candidatePackageName/... // looks like a path in a federated plugin package (candidatePackageName) // rather than a top-level package (parentName). bool isFederatedPackage(String candidatePackageName, String parentName) { return candidatePackageName == parentName || candidatePackageName.startsWith('${parentName}_'); } for (final String path in changedFiles) { final List pathComponents = p.posix.split(path); final int packagesIndex = pathComponents.indexWhere((String element) => element == 'packages'); if (packagesIndex != -1) { // Find the name of the directory directly under packages. This is // either the name of the package, or a plugin group directory for // a federated plugin. final String topLevelName = pathComponents[packagesIndex + 1]; String packageName = topLevelName; if (packagesIndex + 2 < pathComponents.length && isFederatedPackage( pathComponents[packagesIndex + 2], topLevelName)) { // This looks like a federated package; use the full specifier if // the name would be ambiguous (i.e., for the app-facing package). packageName = pathComponents[packagesIndex + 2]; if (packageName == topLevelName) { packageName = '$topLevelName/$packageName'; } } packages.add(packageName); } } if (packages.isEmpty) { print('No changed packages.'); } else { final String changedPackages = packages.join(','); print('Changed packages: $changedPackages'); } 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'], throwOnError: false); if (branchResult.exitCode != 0) { return null; } return (branchResult.stdout as String).trim(); } // Returns true if one or more files changed that have the potential to affect // any packages (e.g., CI script changes). bool _changesRequireFullTest(List changedFiles) { const List specialFiles = [ '.ci.yaml', // LUCI config. '.cirrus.yml', // Cirrus config. '.clang-format', // ObjC and C/C++ formatting options. 'analysis_options.yaml', // Dart analysis settings. ]; const List specialDirectories = [ '.ci/', // Support files for CI. 'script/', // This tool, and its wrapper scripts. ]; // Directory entries must end with / to avoid over-matching, since the // check below is done via string prefixing. assert(specialDirectories.every((String dir) => dir.endsWith('/'))); return changedFiles.any((String path) => specialFiles.contains(path) || specialDirectories.any((String dir) => path.startsWith(dir))); } } ================================================ FILE: script/tool/lib/src/common/package_looping_command.dart ================================================ // Copyright 2013 The Flutter 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:colorize/colorize.dart'; import 'package:file/file.dart'; import 'package:git/git.dart'; import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'package:pub_semver/pub_semver.dart'; import 'core.dart'; import 'package_command.dart'; import 'process_runner.dart'; import 'repository_package.dart'; /// Enumeration options for package looping commands. enum PackageLoopingType { /// Only enumerates the top level packages, without including any of their /// subpackages. topLevelOnly, /// Enumerates the top level packages and any example packages they contain. includeExamples, /// Enumerates all packages recursively, including both example and /// non-example subpackages. includeAllSubpackages, } /// Possible outcomes of a command run for a package. enum RunState { /// The command succeeded for the package. succeeded, /// The command was skipped for the package. skipped, /// The command was skipped for the package because it was explicitly excluded /// in the command arguments. excluded, /// The command failed for the package. failed, } /// The result of a [runForPackage] call. class PackageResult { /// A successful result. PackageResult.success() : this._(RunState.succeeded); /// A run that was skipped as explained in [reason]. PackageResult.skip(String reason) : this._(RunState.skipped, [reason]); /// A run that was excluded by the command invocation. PackageResult.exclude() : this._(RunState.excluded); /// A run that failed. /// /// If [errors] are provided, they will be listed in the summary, otherwise /// the summary will simply show that the package failed. PackageResult.fail([List errors = const []]) : this._(RunState.failed, errors); const PackageResult._(this.state, [this.details = const []]); /// The state the package run completed with. final RunState state; /// Information about the result: /// - For `succeeded`, this is empty. /// - For `skipped`, it contains a single entry describing why the run was /// skipped. /// - For `failed`, it contains zero or more specific error details to be /// shown in the summary. final List details; } /// An abstract base class for a command that iterates over a set of packages /// controlled by a standard set of flags, running some actions on each package, /// and collecting and reporting the success/failure of those actions. abstract class PackageLoopingCommand extends PackageCommand { /// Creates a command to operate on [packagesDir] with the given environment. PackageLoopingCommand( Directory packagesDir, { ProcessRunner processRunner = const ProcessRunner(), Platform platform = const LocalPlatform(), GitDir? gitDir, }) : super(packagesDir, processRunner: processRunner, platform: platform, gitDir: gitDir) { argParser.addOption( _skipByFlutterVersionArg, help: 'Skip any packages that require a Flutter version newer than ' 'the provided version.', ); argParser.addOption( _skipByDartVersionArg, help: 'Skip any packages that require a Dart version newer than ' 'the provided version.', ); } static const String _skipByFlutterVersionArg = 'skip-if-not-supporting-flutter-version'; static const String _skipByDartVersionArg = 'skip-if-not-supporting-dart-version'; /// Packages that had at least one [logWarning] call. final Set _packagesWithWarnings = {}; /// Number of warnings that happened outside of a [runForPackage] call. int _otherWarningCount = 0; /// The package currently being run by [runForPackage]. PackageEnumerationEntry? _currentPackageEntry; /// Called during [run] before any calls to [runForPackage]. This provides an /// opportunity to fail early if the command can't be run (e.g., because the /// arguments are invalid), and to set up any run-level state. Future initializeRun() async {} /// Returns the packages to process. By default, this returns the packages /// defined by the standard tooling flags and the [inculdeSubpackages] option, /// but can be overridden for custom package enumeration. /// /// Note: Consistent behavior across commands whenever possibel is a goal for /// this tool, so this should be overridden only in rare cases. Stream getPackagesToProcess() async* { switch (packageLoopingType) { case PackageLoopingType.topLevelOnly: yield* getTargetPackages(filterExcluded: false); break; case PackageLoopingType.includeExamples: await for (final PackageEnumerationEntry packageEntry in getTargetPackages(filterExcluded: false)) { yield packageEntry; yield* Stream.fromIterable(packageEntry .package .getExamples() .map((RepositoryPackage package) => PackageEnumerationEntry( package, excluded: packageEntry.excluded))); } break; case PackageLoopingType.includeAllSubpackages: yield* getTargetPackagesAndSubpackages(filterExcluded: false); break; } } /// Runs the command for [package], returning a list of errors. /// /// Errors may either be an empty string if there is no context that should /// be included in the final error summary (e.g., a command that only has a /// single failure mode), or strings that should be listed for that package /// in the final summary. An empty list indicates success. Future runForPackage(RepositoryPackage package); /// Called during [run] after all calls to [runForPackage]. This provides an /// opportunity to do any cleanup of run-level state. Future completeRun() async {} /// If [captureOutput], this is called just before exiting with all captured /// [output]. Future handleCapturedOutput(List output) async {} /// Whether or not the output (if any) of [runForPackage] is long, or short. /// /// This changes the logging that happens at the start of each package's /// run; long output gets a banner-style message to make it easier to find, /// while short output gets a single-line entry. /// /// When this is false, runForPackage output should be indented if possible, /// to make the output structure easier to follow. bool get hasLongOutput => true; /// Whether to loop over top-level packages only, or some or all of their /// sub-packages as well. PackageLoopingType get packageLoopingType => PackageLoopingType.topLevelOnly; /// The text to output at the start when reporting one or more failures. /// This will be followed by a list of packages that reported errors, with /// the per-package details if any. /// /// This only needs to be overridden if the summary should provide extra /// context. String get failureListHeader => 'The following packages had errors:'; /// The text to output at the end when reporting one or more failures. This /// will be printed immediately after the a list of packages that reported /// errors. /// /// This only needs to be overridden if the summary should provide extra /// context. String get failureListFooter => 'See above for full details.'; /// The summary string used for a successful run in the final overview output. String get successSummaryMessage => 'ran'; /// If true, all printing (including the summary) will be redirected to a /// buffer, and provided in a call to [handleCapturedOutput] at the end of /// the run. /// /// Capturing output will disable any colorizing of output from this base /// class. bool get captureOutput => false; // ---------------------------------------- /// Logs that a warning occurred, and prints `warningMessage` in yellow. /// /// Warnings are not surfaced in CI summaries, so this is only useful for /// highlighting something when someone is already looking though the log /// messages. DO NOT RELY on someone noticing a warning; instead, use it for /// things that might be useful to someone debugging an unexpected result. void logWarning(String warningMessage) { _printColorized(warningMessage, Styles.YELLOW); if (_currentPackageEntry != null) { _packagesWithWarnings.add(_currentPackageEntry!); } else { ++_otherWarningCount; } } /// Returns the relative path from [from] to [entity] in Posix style. /// /// This should be used when, for example, printing package-relative paths in /// status or error messages. String getRelativePosixPath( FileSystemEntity entity, { required Directory from, }) => p.posix.joinAll(path.split(path.relative(entity.path, from: from.path))); /// The suggested indentation for printed output. String get indentation => hasLongOutput ? '' : ' '; // ---------------------------------------- @override Future run() async { bool succeeded; if (captureOutput) { final List output = []; final ZoneSpecification logSwitchSpecification = ZoneSpecification( print: (Zone self, ZoneDelegate parent, Zone zone, String message) { output.add(message); }); succeeded = await runZoned>(_runInternal, zoneSpecification: logSwitchSpecification); await handleCapturedOutput(output); } else { succeeded = await _runInternal(); } if (!succeeded) { throw ToolExit(exitCommandFoundErrors); } } Future _runInternal() async { _packagesWithWarnings.clear(); _otherWarningCount = 0; _currentPackageEntry = null; final String minFlutterVersionArg = getStringArg(_skipByFlutterVersionArg); final Version? minFlutterVersion = minFlutterVersionArg.isEmpty ? null : Version.parse(minFlutterVersionArg); final String minDartVersionArg = getStringArg(_skipByDartVersionArg); final Version? minDartVersion = minDartVersionArg.isEmpty ? null : Version.parse(minDartVersionArg); final DateTime runStart = DateTime.now(); await initializeRun(); final List targetPackages = await getPackagesToProcess().toList(); final Map results = {}; for (final PackageEnumerationEntry entry in targetPackages) { final DateTime packageStart = DateTime.now(); _currentPackageEntry = entry; _printPackageHeading(entry, startTime: runStart); // Command implementations should never see excluded packages; they are // included at this level only for logging. if (entry.excluded) { results[entry] = PackageResult.exclude(); continue; } PackageResult result; try { result = await _runForPackageIfSupported(entry.package, minFlutterVersion: minFlutterVersion, minDartVersion: minDartVersion); } catch (e, stack) { printError(e.toString()); printError(stack.toString()); result = PackageResult.fail(['Unhandled exception']); } if (result.state == RunState.skipped) { _printColorized('${indentation}SKIPPING: ${result.details.first}', Styles.DARK_GRAY); } results[entry] = result; // Only log an elapsed time for long output; for short output, comparing // the relative timestamps of successive entries should be trivial. if (shouldLogTiming && hasLongOutput) { final Duration elapsedTime = DateTime.now().difference(packageStart); _printColorized( '\n[${entry.package.displayName} completed in ' '${elapsedTime.inMinutes}m ${elapsedTime.inSeconds % 60}s]', Styles.DARK_GRAY); } } _currentPackageEntry = null; completeRun(); print('\n'); // If there were any errors reported, summarize them and exit. if (results.values .any((PackageResult result) => result.state == RunState.failed)) { _printFailureSummary(targetPackages, results); return false; } // Otherwise, print a summary of what ran for ease of auditing that all the // expected tests ran. _printRunSummary(targetPackages, results); print('\n'); _printSuccess('No issues found!'); return true; } /// Returns the result of running [runForPackage] if the package is supported /// by any run constraints, or a skip result if it is not. Future _runForPackageIfSupported( RepositoryPackage package, { Version? minFlutterVersion, Version? minDartVersion, }) async { if (minFlutterVersion != null) { final Pubspec pubspec = package.parsePubspec(); final VersionConstraint? flutterConstraint = pubspec.environment?['flutter']; if (flutterConstraint != null && !flutterConstraint.allows(minFlutterVersion)) { return PackageResult.skip( 'Does not support Flutter $minFlutterVersion'); } } if (minDartVersion != null) { final Pubspec pubspec = package.parsePubspec(); final VersionConstraint? dartConstraint = pubspec.environment?['sdk']; if (dartConstraint != null && !dartConstraint.allows(minDartVersion)) { return PackageResult.skip('Does not support Dart $minDartVersion'); } } return runForPackage(package); } void _printSuccess(String message) { captureOutput ? print(message) : printSuccess(message); } void _printError(String message) { captureOutput ? print(message) : printError(message); } /// Prints the status message indicating that the command is being run for /// [package]. /// /// Something is always printed to make it easier to distinguish between /// a command running for a package and producing no output, and a command /// not having been run for a package. void _printPackageHeading(PackageEnumerationEntry entry, {required DateTime startTime}) { final String packageDisplayName = entry.package.displayName; String heading = entry.excluded ? 'Not running for $packageDisplayName; excluded' : 'Running for $packageDisplayName'; if (shouldLogTiming) { final Duration relativeTime = DateTime.now().difference(startTime); final String timeString = _formatDurationAsRelativeTime(relativeTime); heading = hasLongOutput ? '$heading [@$timeString]' : '[$timeString] $heading'; } if (hasLongOutput) { heading = ''' ============================================================ || $heading ============================================================ '''; } else if (!entry.excluded) { heading = '$heading...'; } _printColorized(heading, entry.excluded ? Styles.DARK_GRAY : Styles.CYAN); } /// Prints a summary of packges run, packages skipped, and warnings. void _printRunSummary(List packages, Map results) { final Set skippedPackages = results.entries .where((MapEntry entry) => entry.value.state == RunState.skipped) .map((MapEntry entry) => entry.key) .toSet(); final int skipCount = skippedPackages.length + packages .where((PackageEnumerationEntry package) => package.excluded) .length; // Split the warnings into those from packages that ran, and those that // were skipped. final Set skippedPackagesWithWarnings = _packagesWithWarnings.intersection(skippedPackages); final int skippedWarningCount = skippedPackagesWithWarnings.length; final int runWarningCount = _packagesWithWarnings.length - skippedWarningCount; final String runWarningSummary = runWarningCount > 0 ? ' ($runWarningCount with warnings)' : ''; final String skippedWarningSummary = runWarningCount > 0 ? ' ($skippedWarningCount with warnings)' : ''; print('------------------------------------------------------------'); if (hasLongOutput) { _printPerPackageRunOverview(packages, skipped: skippedPackages); } print( 'Ran for ${packages.length - skipCount} package(s)$runWarningSummary'); if (skipCount > 0) { print('Skipped $skipCount package(s)$skippedWarningSummary'); } if (_otherWarningCount > 0) { print('$_otherWarningCount warnings not associated with a package'); } } /// Prints a one-line-per-package overview of the run results for each /// package. void _printPerPackageRunOverview( List packageEnumeration, {required Set skipped}) { print('Run overview:'); for (final PackageEnumerationEntry entry in packageEnumeration) { final bool hadWarning = _packagesWithWarnings.contains(entry); Styles style; String summary; if (entry.excluded) { summary = 'excluded'; style = Styles.DARK_GRAY; } else if (skipped.contains(entry)) { summary = 'skipped'; style = hadWarning ? Styles.LIGHT_YELLOW : Styles.DARK_GRAY; } else { summary = successSummaryMessage; style = hadWarning ? Styles.YELLOW : Styles.GREEN; } if (hadWarning) { summary += ' (with warning)'; } if (!captureOutput) { summary = (Colorize(summary)..apply(style)).toString(); } print(' ${entry.package.displayName} - $summary'); } print(''); } /// Prints a summary of all of the failures from [results]. void _printFailureSummary(List packageEnumeration, Map results) { const String indentation = ' '; _printError(failureListHeader); for (final PackageEnumerationEntry entry in packageEnumeration) { final PackageResult result = results[entry]!; if (result.state == RunState.failed) { final String errorIndentation = indentation * 2; String errorDetails = ''; if (result.details.isNotEmpty) { errorDetails = ':\n$errorIndentation${result.details.join('\n$errorIndentation')}'; } _printError('$indentation${entry.package.displayName}$errorDetails'); } } _printError(failureListFooter); } /// Prints [message] in [color] unless [captureOutput] is set, in which case /// it is printed without color. void _printColorized(String message, Styles color) { if (captureOutput) { print(message); } else { print(Colorize(message)..apply(color)); } } /// Returns a duration [d] formatted as minutes:seconds. Does not use hours, /// since time logging is primarily intended for CI, where durations should /// always be less than an hour. String _formatDurationAsRelativeTime(Duration d) { return '${d.inMinutes}:${(d.inSeconds % 60).toString().padLeft(2, '0')}'; } } ================================================ FILE: script/tool/lib/src/common/process_runner.dart ================================================ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:convert'; import 'dart:io' as io; import 'package:file/file.dart'; import 'core.dart'; /// A class used to run processes. /// /// We use this instead of directly running the process so it can be overridden /// in tests. class ProcessRunner { /// Creates a new process runner. const ProcessRunner(); /// Run the [executable] with [args] and stream output to stderr and stdout. /// /// The current working directory of [executable] can be overridden by /// passing [workingDir]. /// /// If [exitOnError] is set to `true`, then this will throw an error if /// the [executable] terminates with a non-zero exit code. /// /// Returns the exit code of the [executable]. Future runAndStream( String executable, List args, { Directory? workingDir, bool exitOnError = false, }) async { print( 'Running command: "$executable ${args.join(' ')}" in ${workingDir?.path ?? io.Directory.current.path}'); final io.Process process = await io.Process.start(executable, args, workingDirectory: workingDir?.path); await io.stdout.addStream(process.stdout); await io.stderr.addStream(process.stderr); if (exitOnError && await process.exitCode != 0) { final String error = _getErrorString(executable, args, workingDir: workingDir); print('$error See above for details.'); throw ToolExit(await process.exitCode); } return process.exitCode; } /// Run the [executable] with [args]. /// /// The current working directory of [executable] can be overridden by /// passing [workingDir]. /// /// If [exitOnError] is set to `true`, then this will throw an error if /// the [executable] terminates with a non-zero exit code. /// Defaults to `false`. /// /// If [logOnError] is set to `true`, it will print a formatted message about the error. /// Defaults to `false` /// /// Returns the [io.ProcessResult] of the [executable]. Future run(String executable, List args, {Directory? workingDir, bool exitOnError = false, bool logOnError = false, Encoding stdoutEncoding = io.systemEncoding, Encoding stderrEncoding = io.systemEncoding}) async { final io.ProcessResult result = await io.Process.run(executable, args, workingDirectory: workingDir?.path, stdoutEncoding: stdoutEncoding, stderrEncoding: stderrEncoding); if (result.exitCode != 0) { if (logOnError) { final String error = _getErrorString(executable, args, workingDir: workingDir); print('$error Stderr:\n${result.stdout}'); } if (exitOnError) { throw ToolExit(result.exitCode); } } return result; } /// Starts the [executable] with [args]. /// /// The current working directory of [executable] can be overridden by /// passing [workingDir]. /// /// Returns the started [io.Process]. Future start(String executable, List args, {Directory? workingDirectory}) async { final io.Process process = await io.Process.start(executable, args, workingDirectory: workingDirectory?.path); return process; } String _getErrorString(String executable, List args, {Directory? workingDir}) { final String workdir = workingDir == null ? '' : ' in ${workingDir.path}'; return 'ERROR: Unable to execute "$executable ${args.join(' ')}"$workdir.'; } } ================================================ FILE: script/tool/lib/src/common/repository_package.dart ================================================ // Copyright 2013 The Flutter 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/file.dart'; import 'package:path/path.dart' as p; import 'package:pubspec_parse/pubspec_parse.dart'; import 'core.dart'; export 'package:pubspec_parse/pubspec_parse.dart' show Pubspec; export 'core.dart' show FlutterPlatform; /// A package in the repository. // // TODO(stuartmorgan): Add more package-related info here, such as an on-demand // cache of the parsed pubspec. class RepositoryPackage { /// Creates a representation of the package at [directory]. RepositoryPackage(this.directory); /// The location of the package. final Directory directory; /// The path to the package. String get path => directory.path; /// Returns the string to use when referring to the package in user-targeted /// messages. /// /// Callers should not expect a specific format for this string, since /// it uses heuristics to try to be precise without being overly verbose. If /// an exact format (e.g., published name, or basename) is required, that /// should be used instead. String get displayName { List components = directory.fileSystem.path.split(directory.path); // Remove everything up to the packages directory. final int packagesIndex = components.indexOf('packages'); if (packagesIndex != -1) { components = components.sublist(packagesIndex + 1); } // For the common federated plugin pattern of `foo/foo_subpackage`, drop // the first part since it's not useful. if (components.length >= 2 && components[1].startsWith('${components[0]}_')) { components = components.sublist(1); } return p.posix.joinAll(components); } /// The package's top-level pubspec.yaml. File get pubspecFile => directory.childFile('pubspec.yaml'); /// The package's top-level README. File get readmeFile => directory.childFile('README.md'); /// The package's top-level README. File get changelogFile => directory.childFile('CHANGELOG.md'); /// The package's top-level README. File get authorsFile => directory.childFile('AUTHORS'); /// The lib directory containing the package's code. Directory get libDirectory => directory.childDirectory('lib'); /// The test directory containing the package's Dart tests. Directory get testDirectory => directory.childDirectory('test'); /// Returns the directory containing support for [platform]. Directory platformDirectory(FlutterPlatform platform) { late final String directoryName; switch (platform) { case FlutterPlatform.android: directoryName = 'android'; break; case FlutterPlatform.ios: directoryName = 'ios'; break; case FlutterPlatform.linux: directoryName = 'linux'; break; case FlutterPlatform.macos: directoryName = 'macos'; break; case FlutterPlatform.web: directoryName = 'web'; break; case FlutterPlatform.windows: directoryName = 'windows'; break; } return directory.childDirectory(directoryName); } late final Pubspec _parsedPubspec = Pubspec.parse(pubspecFile.readAsStringSync()); /// Returns the parsed [pubspecFile]. /// /// Caches for future use. Pubspec parsePubspec() => _parsedPubspec; /// Returns true if the package depends on Flutter. bool requiresFlutter() { final Pubspec pubspec = parsePubspec(); return pubspec.dependencies.containsKey('flutter'); } /// True if this appears to be a federated plugin package, according to /// repository conventions. bool get isFederated => directory.parent.basename != 'packages' && directory.basename.startsWith(directory.parent.basename); /// True if this appears to be the app-facing package of a federated plugin, /// according to repository conventions. bool get isAppFacing => directory.parent.basename != 'packages' && directory.basename == directory.parent.basename; /// True if this appears to be a platform interface package, according to /// repository conventions. bool get isPlatformInterface => directory.basename.endsWith('_platform_interface'); /// True if this appears to be a platform implementation package, according to /// repository conventions. bool get isPlatformImplementation => // Any part of a federated plugin that isn't the platform interface and // isn't the app-facing package should be an implementation package. isFederated && !isPlatformInterface && directory.basename != directory.parent.basename; /// Returns the Flutter example packages contained in the package, if any. Iterable getExamples() { final Directory exampleDirectory = directory.childDirectory('example'); if (!exampleDirectory.existsSync()) { return []; } if (isPackage(exampleDirectory)) { return [RepositoryPackage(exampleDirectory)]; } // Only look at the subdirectories of the example directory if the example // directory itself is not a Dart package, and only look one level below the // example directory for other Dart packages. return exampleDirectory .listSync() .where((FileSystemEntity entity) => isPackage(entity)) // isPackage guarantees that the cast to Directory is safe. .map((FileSystemEntity entity) => RepositoryPackage(entity as Directory)); } } ================================================ FILE: script/tool/lib/src/main.dart ================================================ // Copyright 2013 The Flutter 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/local.dart'; import 'analyze_command.dart'; import 'common/core.dart'; void main(List args) { print(''' *** WARNING *** This copy of the tooling is now only here as a shim for scripts in other repositories that have not yet been updated, and can only run 'analyze'. For full tooling in this repository, see the updated instructions: https://github.com/flutter/packages/blob/main/script/tool/README.md to switch to running the published version. '''); const FileSystem fileSystem = LocalFileSystem(); Directory packagesDir = fileSystem.currentDirectory.childDirectory('packages'); if (!packagesDir.existsSync()) { if (fileSystem.currentDirectory.basename == 'packages') { packagesDir = fileSystem.currentDirectory; } else { print('Error: Cannot find a "packages" sub-directory'); io.exit(1); } } final CommandRunner commandRunner = CommandRunner( 'dart pub global run flutter_plugin_tools', 'Productivity utils for hosting multiple plugins within one repository.') ..addCommand(AnalyzeCommand(packagesDir)); commandRunner.run(args).catchError((Object e) { final ToolExit toolExit = e as ToolExit; int exitCode = toolExit.exitCode; // This should never happen; this check is here to guarantee that a ToolExit // never accidentally has code 0 thus causing CI to pass. if (exitCode == 0) { assert(false); exitCode = 255; } io.exit(exitCode); }, test: (Object e) => e is ToolExit); } ================================================ FILE: script/tool/pubspec.yaml ================================================ 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.4+2 publish_to: none # See README.md dependencies: args: ^2.1.0 async: ^2.6.1 collection: ^1.15.0 colorize: ^3.0.0 file: ^6.1.0 # Pin git to 2.0.x until dart >=2.18 is legacy git: '>=2.0.0 <2.1.0' http: ^0.13.3 http_multi_server: ^3.0.1 meta: ^1.3.0 path: ^1.8.0 platform: ^3.0.0 pub_semver: ^2.0.0 pubspec_parse: ^1.0.0 quiver: ^3.0.1 test: ^1.17.3 uuid: ^3.0.4 yaml: ^3.1.0 yaml_edit: ^2.0.2 dev_dependencies: build_runner: ^2.0.3 matcher: ^0.12.10 mockito: ^5.0.7 environment: sdk: '>=2.12.0 <3.0.0' ================================================ FILE: script/tool_runner.sh ================================================ #!/bin/bash # Copyright 2013 The Flutter Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. set -e # WARNING! Do not remove this script, or change its behavior, unless you have # verified that it will not break the dart-lang analysis run of this # repository: https://github.com/dart-lang/sdk/blob/main/tools/bots/flutter/analyze_flutter_plugins.sh readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" readonly REPO_DIR="$(dirname "$SCRIPT_DIR")" # The tool expects to be run from the repo root. # PACKAGE_SHARDING is (optionally) set from Cirrus. See .cirrus.yml cd "$REPO_DIR" # Ensure that the tooling has been activated. .ci/scripts/prepare_tool.sh dart pub global run flutter_plugin_tools "$@" \ --packages-for-branch \ --log-timing \ $PACKAGE_SHARDING